fix(settings): sync the action language and AI personalize prompt

The server adds the general actionLanguage and the AI personalizePrompt
settings to the AI chat system prompt, but neither was in the server sync
whitelist, so both stayed on the device and AI chat ignored them: a French
action language still got English answers. Desktop now syncs both, and
mobile syncs actionLanguage, the only one of the two it has.

A value set before this change reaches the server the next time it is
changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DIYgod
2026-10-01 10:20:49 +08:00
parent b7df9ad01e
commit b0f5552661
4 changed files with 50 additions and 1 deletions
@@ -125,7 +125,8 @@ export const {
useSettingValue: useAISettingValue,
settingAtom: __aiSettingAtom,
} = createSettingAtom("ai", createDefaultSettings)
export const aiServerSyncWhiteListKeys = []
// The server adds the personalize prompt to the AI chat system prompt.
export const aiServerSyncWhiteListKeys: (keyof AISettings)[] = ["personalizePrompt"]
export const syncServerShortcuts = (
serverShortcuts: readonly ServerShortcutConfig[] | null | undefined,
@@ -84,6 +84,8 @@ export const generalServerSyncWhiteListKeys: (keyof GeneralSettings)[] = [
"sendAnonymousData",
"language",
"voice",
// The server answers AI chat in this language.
"actionLanguage",
]
export const enhancedGeneralSettingKeys = new Set<keyof GeneralSettings>([
@@ -3,6 +3,7 @@ import type { SyncAction } from "@follow/store/sync/types"
import { FollowAPIError } from "@follow-app/client-sdk"
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"
import { getAISettings, initializeDefaultAISettings, setAISetting } from "~/atoms/settings/ai"
import {
getGeneralSettings,
initializeDefaultGeneralSettings,
@@ -101,6 +102,7 @@ describe("desktop spotlight setting sync", () => {
initializeDefaultUISettings()
initializeDefaultGeneralSettings()
initializeDefaultAISettings()
initializeDefaultSpotlightSettings()
localStorage.clear()
})
@@ -113,6 +115,7 @@ describe("desktop spotlight setting sync", () => {
localStorage.clear()
initializeDefaultUISettings()
initializeDefaultGeneralSettings()
initializeDefaultAISettings()
initializeDefaultSpotlightSettings()
})
@@ -278,6 +281,47 @@ describe("desktop spotlight setting sync", () => {
)
})
test("the action language and the AI personalize prompt are sent to the server", async () => {
vi.useFakeTimers()
await settingSyncQueue.init()
setGeneralSetting("actionLanguage", "fr-FR")
setAISetting("personalizePrompt", "Call me Captain.")
// Local-only AI settings stay on the device.
setAISetting("showSplineButton", false)
await vi.advanceTimersByTimeAsync(1000)
await Promise.resolve()
expect(settingsUpdateMock).toHaveBeenCalledTimes(2)
expect(settingsUpdateMock).toHaveBeenCalledWith({ tab: "general", actionLanguage: "fr-FR" })
expect(settingsUpdateMock).toHaveBeenCalledWith({
tab: "ai",
personalizePrompt: "Call me Captain.",
})
})
test("a newer remote action language and personalize prompt replace the local ones", () => {
setGeneralSetting("actionLanguage", "ja")
setAISetting("personalizePrompt", "Keep answers short.")
settingSyncQueue.applyRemoteSettings({
code: 0,
settings: {
general: { actionLanguage: "fr-FR" },
ai: { personalizePrompt: "Call me Captain." },
},
updated: {
general: "2030-04-14T12:00:00.000Z",
ai: "2030-04-14T12:00:00.000Z",
},
})
expect(getGeneralSettings().actionLanguage).toBe("fr-FR")
expect(getAISettings().personalizePrompt).toBe("Call me Captain.")
})
test("replaceRemoteIfEmpty does not overwrite existing remote settings", async () => {
settingsGetMock.mockResolvedValue({
code: 0,
@@ -35,6 +35,8 @@ export const generalServerSyncWhiteListKeys: (keyof GeneralSettings)[] = [
"language",
"appLaunchOnStartup",
"voice",
// The server answers AI chat in this language.
"actionLanguage",
]
export function useActionLanguage() {