fix(web): remove the AI streaming toggle

Direct model calls still stream. Proxied web generation stays buffered so Cloudflare Free CPU limits are not left to a hidden setting.
This commit is contained in:
tianma-if committed 2026-09-11 19:13:42 +08:00
1 parent 2c3498c123
commit 778fc0a726
8 files changed
+9 -122

No files matched your search

@@ -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 (
<Collapsible open={expanded} onOpenChange={setExpanded} asChild>
@@ -186,23 +178,6 @@ export const AiModelCard = () => {
</Select>
</div>
</div>
{showStreamingPreference ? (
<div className="flex items-center justify-between gap-3 px-3.5 py-2.5">
<div className="min-w-0">
<div className={SETTINGS_ITEM_TITLE_CLASSNAME}>{t("settings.aiStreamingTitle")}</div>
<div className={SETTINGS_ITEM_DESCRIPTION_CLASSNAME}>{t("settings.aiStreamingDescription")}</div>
</div>
<Switch
className="shrink-0"
checked={streamingEnabled}
onCheckedChange={(enabled) => {
writeAiStreamingPreference(enabled);
setStreamingEnabled(enabled);
}}
aria-label={t("settings.aiStreamingAria")}
/>
</div>
) : null}
</div>
</section>
{!defaultModelAvailable ? (
@@ -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");
});
});
@@ -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();
});
});
@@ -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.
}
};
+7 -8
View File
@@ -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();
});
});
-9
View File
@@ -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<typeof client.streamAiGeneration>[0],
options: Parameters<typeof client.streamAiGeneration>[1],
) => client.streamAiGeneration(
{ ...payload, stream: payload.stream ?? readAiStreamingPreference() },
options,
),
};
-3
View File
@@ -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",
-3
View File
@@ -573,9 +573,6 @@ export const zhCN = {
imageCompressionTitle: "压缩笔记内图片",
imageCompressionDescription: "上传大图时在本地压缩,节省资源占用。",
imageCompressionAria: "是否压缩笔记内图片",
aiStreamingTitle: "流式返回 AI 结果",
aiStreamingDescription: "开启后实时展示 AI 内容生成过程;关闭可获得更高稳定性。",
aiStreamingAria: "是否流式返回 AI 结果",
aiTagPromptTitle: "AI 标签建议指令",
aiTagPromptDescription: "自定义 AI 如何根据笔记内容生成标签。此设置保存在工作区,各端共用。",
aiTagPromptRestore: "恢复默认指令",