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:
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.
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
),
|
||||
};
|
||||
@@ -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",
|
||||
|
||||
@@ -573,9 +573,6 @@ export const zhCN = {
|
||||
imageCompressionTitle: "压缩笔记内图片",
|
||||
imageCompressionDescription: "上传大图时在本地压缩,节省资源占用。",
|
||||
imageCompressionAria: "是否压缩笔记内图片",
|
||||
aiStreamingTitle: "流式返回 AI 结果",
|
||||
aiStreamingDescription: "开启后实时展示 AI 内容生成过程;关闭可获得更高稳定性。",
|
||||
aiStreamingAria: "是否流式返回 AI 结果",
|
||||
aiTagPromptTitle: "AI 标签建议指令",
|
||||
aiTagPromptDescription: "自定义 AI 如何根据笔记内容生成标签。此设置保存在工作区,各端共用。",
|
||||
aiTagPromptRestore: "恢复默认指令",
|
||||
|
||||
Reference in new issue
Block a user