返回 JoyAI-Echo
llm-params-storage.ts
根目录 / echo_longvideo / Director_Agent / webui / src / lib / llm-params-storage.ts
1 /**
2 * LLM sampling parameters stored in localStorage (browser-scoped, not session).
3 * Read at request time for WebSocket messages.
4 */
5 import type { GenerationLlmSettings } from "@/lib/api";
6
7 export const LLM_PARAMS_STORAGE_KEY = "nanobot.llm-params";
8
9 const EMPTY: GenerationLlmSettings = {
10 temperature: null,
11 top_p: null,
12 top_k: null,
13 };
14
15 function normalizeTemperature(value: unknown): number | null {
16 try {
17 const parsed = Number(value);
18 if (!Number.isFinite(parsed)) return null;
19 return parsed >= 0 && parsed < 2 ? parsed : null;
20 } catch {
21 return null;
22 }
23 }
24
25 function normalizeTopP(value: unknown): number | null {
26 try {
27 const parsed = Number(value);
28 if (!Number.isFinite(parsed)) return null;
29 return parsed >= 0 && parsed <= 1 ? parsed : null;
30 } catch {
31 return null;
32 }
33 }
34
35 function normalizeTopK(value: unknown): number | null {
36 try {
37 const parsed = Number.parseInt(String(value), 10);
38 if (!Number.isFinite(parsed)) return null;
39 return parsed >= 1 && parsed <= 64 ? parsed : null;
40 } catch {
41 return null;
42 }
43 }
44
45 function normalizeStored(raw: unknown): GenerationLlmSettings {
46 if (!raw || typeof raw !== "object") return { ...EMPTY };
47 const obj = raw as Record<string, unknown>;
48 return {
49 temperature:
50 obj.temperature == null || obj.temperature === ""
51 ? null
52 : normalizeTemperature(obj.temperature),
53 top_p:
54 obj.top_p == null || obj.top_p === "" ? null : normalizeTopP(obj.top_p),
55 top_k: obj.top_k == null || obj.top_k === "" ? null : normalizeTopK(obj.top_k),
56 };
57 }
58
59 /** Read validated params from localStorage; null fields mean gateway defaults. */
60 export function readLlmParams(): GenerationLlmSettings {
61 if (typeof window === "undefined") return { ...EMPTY };
62 try {
63 const raw = window.localStorage.getItem(LLM_PARAMS_STORAGE_KEY);
64 if (!raw) return { ...EMPTY };
65 return normalizeStored(JSON.parse(raw));
66 } catch {
67 return { ...EMPTY };
68 }
69 }
70
71 /** Merge partial settings and persist to localStorage. */
72 export function writeLlmParams(partial: Partial<GenerationLlmSettings>): GenerationLlmSettings {
73 const current = readLlmParams();
74 const next: GenerationLlmSettings = { ...current };
75
76 if ("temperature" in partial) {
77 next.temperature =
78 partial.temperature == null ? null : normalizeTemperature(partial.temperature);
79 }
80 if ("top_p" in partial) {
81 next.top_p = partial.top_p == null ? null : normalizeTopP(partial.top_p);
82 }
83 if ("top_k" in partial) {
84 next.top_k = partial.top_k == null ? null : normalizeTopK(partial.top_k);
85 }
86
87 if (typeof window !== "undefined") {
88 try {
89 window.localStorage.setItem(LLM_PARAMS_STORAGE_KEY, JSON.stringify(next));
90 } catch {
91 // best-effort persistence
92 }
93 }
94 return next;
95 }
96
97 function pickSetFields(
98 settings: GenerationLlmSettings,
99 ): Partial<GenerationLlmSettings> {
100 const out: Partial<GenerationLlmSettings> = {};
101 if (settings.temperature != null) out.temperature = settings.temperature;
102 if (settings.top_p != null) out.top_p = settings.top_p;
103 if (settings.top_k != null) out.top_k = settings.top_k;
104 return out;
105 }
106
107 /** Non-empty fields for HTTP X-Nanobot-Body. */
108 export function readLlmParamsForRequest(): Partial<GenerationLlmSettings> {
109 return pickSetFields(readLlmParams());
110 }
111
112 /** Non-empty fields for WebSocket message envelope. */
113 export function readLlmParamsForWire(): Partial<GenerationLlmSettings> {
114 return pickSetFields(readLlmParams());
115 }
116
116 lines TYPESCRIPT