diff --git a/apps/web/src/components/settings/AiModelCard.tsx b/apps/web/src/components/settings/AiModelCard.tsx index 998a077c..504c0c60 100644 --- a/apps/web/src/components/settings/AiModelCard.tsx +++ b/apps/web/src/components/settings/AiModelCard.tsx @@ -32,12 +32,7 @@ import { } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Switch } from "@/components/ui/switch"; import { api } from "@/lib/api"; -import { - readAiStreamingPreference, - writeAiStreamingPreference, -} from "@/lib/ai-generation-preference"; import { cn } from "@/lib/utils"; export const AiModelCard = () => { @@ -55,7 +50,6 @@ export const AiModelCard = () => { const [baseUrl, setBaseUrl] = useState(providerDefaults["openai-compatible"].baseUrl); const [apiKey, setApiKey] = useState(""); const [initialModelId, setInitialModelId] = useState(providerDefaults["openai-compatible"].modelId); - const [streamingEnabled, setStreamingEnabled] = useState(readAiStreamingPreference); const refresh = () => queryClient.invalidateQueries({ queryKey: ["ai-settings"] }); const resetAddForm = (nextDisplayName = "") => { @@ -121,8 +115,6 @@ export const AiModelCard = () => { setShowAdd(true); }; const addDisabledReason = !encryptionConfigured ? t("aiModel.addDisabledReason") : undefined; - const showStreamingPreference = typeof window === "undefined" - || window.edgeeverDesktop?.isAvailable !== true; return ( @@ -186,23 +178,6 @@ export const AiModelCard = () => { - {showStreamingPreference ? ( -
-
-
{t("settings.aiStreamingTitle")}
-
{t("settings.aiStreamingDescription")}
-
- { - writeAiStreamingPreference(enabled); - setStreamingEnabled(enabled); - }} - aria-label={t("settings.aiStreamingAria")} - /> -
- ) : null} {!defaultModelAvailable ? ( diff --git a/apps/web/src/components/settings/ai-provider-options.test.mjs b/apps/web/src/components/settings/ai-provider-options.test.mjs index 2773e9b0..d27bd7e0 100644 --- a/apps/web/src/components/settings/ai-provider-options.test.mjs +++ b/apps/web/src/components/settings/ai-provider-options.test.mjs @@ -42,12 +42,7 @@ describe("AI settings cards tolerate missing text fields", () => { expect(modelCard).toContain("canAddProvider"); expect(modelCard).toContain("addDisabledReason"); expect(modelCard).toContain("savedCredentialsUnavailable"); - }); - - test("hides the AI streaming preference on the desktop client", () => { - const modelCard = readFileSync(new URL("./AiModelCard.tsx", import.meta.url), "utf8"); - expect(modelCard).toContain("window.edgeeverDesktop?.isAvailable !== true"); - expect(modelCard).toContain("showStreamingPreference"); - expect(modelCard).toContain("settings.aiStreamingTitle"); + expect(modelCard).not.toContain("aiStreamingTitle"); + expect(modelCard).not.toContain("readAiStreamingPreference"); }); }); diff --git a/apps/web/src/lib/ai-generation-preference.test.mjs b/apps/web/src/lib/ai-generation-preference.test.mjs deleted file mode 100644 index 6b36e59b..00000000 --- a/apps/web/src/lib/ai-generation-preference.test.mjs +++ /dev/null @@ -1,49 +0,0 @@ -import { afterEach, describe, expect, test } from "bun:test"; -import { - AI_STREAMING_STORAGE_KEY, - readAiStreamingPreference, - writeAiStreamingPreference, -} from "./ai-generation-preference.ts"; - -const originalWindow = globalThis.window; - -afterEach(() => { - globalThis.window = originalWindow; -}); - -const installLocalStorage = () => { - const values = new Map(); - globalThis.window = { - localStorage: { - getItem: (key) => values.get(key) ?? null, - setItem: (key, value) => values.set(key, String(value)), - }, - }; - return values; -}; - -describe("AI streaming preference", () => { - test("defaults to disabled and persists explicit changes", () => { - const values = installLocalStorage(); - expect(readAiStreamingPreference()).toBe(false); - - writeAiStreamingPreference(true); - expect(values.get(AI_STREAMING_STORAGE_KEY)).toBe("true"); - expect(readAiStreamingPreference()).toBe(true); - - writeAiStreamingPreference(false); - expect(values.get(AI_STREAMING_STORAGE_KEY)).toBe("false"); - expect(readAiStreamingPreference()).toBe(false); - }); - - test("fails closed when local storage is unavailable", () => { - globalThis.window = { - localStorage: { - getItem: () => { throw new Error("blocked"); }, - setItem: () => { throw new Error("blocked"); }, - }, - }; - expect(readAiStreamingPreference()).toBe(false); - expect(() => writeAiStreamingPreference(true)).not.toThrow(); - }); -}); diff --git a/apps/web/src/lib/ai-generation-preference.ts b/apps/web/src/lib/ai-generation-preference.ts deleted file mode 100644 index 42df7cab..00000000 --- a/apps/web/src/lib/ai-generation-preference.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const AI_STREAMING_STORAGE_KEY = "edgeever.aiStreamingEnabled"; - -export const readAiStreamingPreference = () => { - try { - return typeof window !== "undefined" - && window.localStorage.getItem(AI_STREAMING_STORAGE_KEY) === "true"; - } catch { - return false; - } -}; - -export const writeAiStreamingPreference = (enabled: boolean) => { - try { - window.localStorage.setItem(AI_STREAMING_STORAGE_KEY, enabled ? "true" : "false"); - } catch { - // Local storage can be unavailable in private or restricted browser contexts. - } -}; diff --git a/apps/web/src/lib/api.test.mjs b/apps/web/src/lib/api.test.mjs index b8f5c3f8..e7c03da4 100644 --- a/apps/web/src/lib/api.test.mjs +++ b/apps/web/src/lib/api.test.mjs @@ -392,7 +392,7 @@ describe("desktop instance setup", () => { } }); - test("sends the disabled-by-default AI streaming preference and honors opt-in", async () => { + test("does not opt the web proxy into AI streaming", async () => { const requestBodies = []; globalThis.fetch = async (url, init) => { if (String(url).includes("/api/v1/ai/direct-target")) { @@ -403,14 +403,13 @@ describe("desktop instance setup", () => { headers: { "Content-Type": "text/event-stream" }, }); }; - const payload = { action: "summarize", title: "Note", contentMarkdown: "Body" }; - storage.delete("edgeever.aiStreamingEnabled"); - await api.streamAiGeneration(payload, { onEvent: () => {} }); - storage.set("edgeever.aiStreamingEnabled", "true"); - await api.streamAiGeneration(payload, { onEvent: () => {} }); - await api.streamAiGeneration({ ...payload, stream: false }, { onEvent: () => {} }); + await api.streamAiGeneration( + { action: "summarize", title: "Note", contentMarkdown: "Body" }, + { onEvent: () => {} }, + ); - expect(requestBodies.map((body) => body.stream)).toEqual([false, true, false]); + expect(requestBodies).toHaveLength(1); + expect(requestBodies[0].stream).toBeUndefined(); }); }); diff --git a/apps/web/src/lib/api.ts b/apps/web/src/lib/api.ts index 1a7c11bf..f0aca4e7 100644 --- a/apps/web/src/lib/api.ts +++ b/apps/web/src/lib/api.ts @@ -5,7 +5,6 @@ import { } from "@edgeever/client"; import type { AuthSession } from "@edgeever/shared"; import { resolveInstanceUrlInput } from "@edgeever/shared"; -import { readAiStreamingPreference } from "./ai-generation-preference"; import { createClientUuid } from "./client-id"; export { ApiRequestError }; @@ -335,12 +334,4 @@ export const api = { desktopSessionRejected = false; return session; }, - - streamAiGeneration: ( - payload: Parameters[0], - options: Parameters[1], - ) => client.streamAiGeneration( - { ...payload, stream: payload.stream ?? readAiStreamingPreference() }, - options, - ), }; diff --git a/packages/shared/src/i18n/en-US.ts b/packages/shared/src/i18n/en-US.ts index 8d4e47f4..ed7fb52f 100644 --- a/packages/shared/src/i18n/en-US.ts +++ b/packages/shared/src/i18n/en-US.ts @@ -573,9 +573,6 @@ export const enUS = { imageCompressionTitle: "Compress note images", imageCompressionDescription: "Compress large images locally before upload to reduce storage use.", imageCompressionAria: "Compress note images", - aiStreamingTitle: "Stream AI responses", - aiStreamingDescription: "Turn on to watch AI content generation in real time; turn off for greater stability.", - aiStreamingAria: "Stream AI responses", aiTagPromptTitle: "AI tag suggestion instruction", aiTagPromptDescription: "Customize how AI derives tags from note content. This workspace setting is shared by every client.", aiTagPromptRestore: "Restore default instruction", diff --git a/packages/shared/src/i18n/zh-CN.ts b/packages/shared/src/i18n/zh-CN.ts index dd87c7f0..49b95e7e 100644 --- a/packages/shared/src/i18n/zh-CN.ts +++ b/packages/shared/src/i18n/zh-CN.ts @@ -573,9 +573,6 @@ export const zhCN = { imageCompressionTitle: "压缩笔记内图片", imageCompressionDescription: "上传大图时在本地压缩,节省资源占用。", imageCompressionAria: "是否压缩笔记内图片", - aiStreamingTitle: "流式返回 AI 结果", - aiStreamingDescription: "开启后实时展示 AI 内容生成过程;关闭可获得更高稳定性。", - aiStreamingAria: "是否流式返回 AI 结果", aiTagPromptTitle: "AI 标签建议指令", aiTagPromptDescription: "自定义 AI 如何根据笔记内容生成标签。此设置保存在工作区,各端共用。", aiTagPromptRestore: "恢复默认指令",