// Persisted LLM settings — base URL, context budget, response cap. // Reactive across tabs (storage event) and within the same tab (custom event). import { useEffect, useSyncExternalStore } from "react" export type LLMSettings = { baseURL: string contextTokens: number responseBudget: number } export const DEFAULT_SETTINGS: LLMSettings = { baseURL: "http://localhost:1234/v1", contextTokens: 9000, responseBudget: 512, } const STORAGE_KEY = "crema.llm.settings" const CHANGE_EVENT = "comfy:llm-settings-change" function readFromStorage(): LLMSettings { if (typeof window === "undefined") return DEFAULT_SETTINGS try { const raw = localStorage.getItem(STORAGE_KEY) if (!raw) return DEFAULT_SETTINGS const parsed = JSON.parse(raw) as Partial return { baseURL: typeof parsed.baseURL === "string" ? parsed.baseURL : DEFAULT_SETTINGS.baseURL, contextTokens: Number.isFinite(parsed.contextTokens) && (parsed.contextTokens as number) > 0 ? (parsed.contextTokens as number) : DEFAULT_SETTINGS.contextTokens, responseBudget: Number.isFinite(parsed.responseBudget) && (parsed.responseBudget as number) > 0 ? (parsed.responseBudget as number) : DEFAULT_SETTINGS.responseBudget, } } catch { return DEFAULT_SETTINGS } } export function loadLLMSettings(): LLMSettings { return readFromStorage() } export function saveLLMSettings(next: LLMSettings) { if (typeof window === "undefined") return localStorage.setItem(STORAGE_KEY, JSON.stringify(next)) window.dispatchEvent(new CustomEvent(CHANGE_EVENT)) } export function resetLLMSettings() { saveLLMSettings(DEFAULT_SETTINGS) } let cached: LLMSettings | null = null function subscribe(cb: () => void): () => void { const onChange = () => { cached = null cb() } window.addEventListener(CHANGE_EVENT, onChange) window.addEventListener("storage", (e) => { if (e.key === STORAGE_KEY) onChange() }) return () => { window.removeEventListener(CHANGE_EVENT, onChange) } } function getSnapshot(): LLMSettings { if (!cached) cached = readFromStorage() return cached } function getServerSnapshot(): LLMSettings { return DEFAULT_SETTINGS } export function useLLMSettings(): LLMSettings { // useSyncExternalStore avoids hydration flicker and stays reactive. const value = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) // Re-read after mount to pick up localStorage on first client render. useEffect(() => { cached = null }, []) return value }