import { useSyncExternalStore } from "react"; import { api, type CursorHarnessStatus, type LlmCall, type Model, type ModelInput, type Overview, type PluginRuntimeStatus, type PortSettings } from "../api"; import { applyTheme, isThemeId, type ThemeId } from "../theme/theme"; export type AppSnapshot = { models: Model[]; calls: LlmCall[]; overview: Overview; detailed: boolean; ports: PortSettings; busy: boolean; error: string | null; theme: ThemeId; cursorHarness: CursorHarnessStatus | null; cursorBusy: boolean; pluginRuntime: PluginRuntimeStatus | null; }; const savedTheme = (): ThemeId => { const saved = localStorage.getItem("cursor-byok.theme"); return isThemeId(saved) ? saved : "default-dark"; }; let snapshot: AppSnapshot = { models: [], calls: [], overview: { metrics: { llm_calls: 0, successful_calls: 0, failed_calls: 0, token_usage: 0, prompt_tokens: 0, input_tokens: 0, cache_read_tokens: 0, cache_write_tokens: 0, output_tokens: 0, }, token_usage_granularity: "day", token_usage_series: [], }, detailed: false, ports: { proxy_port: 0, service_port: 0 }, busy: false, error: null, theme: savedTheme(), cursorHarness: null, cursorBusy: false, pluginRuntime: null, }; const listeners = new Set<() => void>(); function update(patch: Partial) { snapshot = { ...snapshot, ...patch }; listeners.forEach((listener) => listener()); } async function perform(task: () => Promise) { update({ error: null }); try { await task(); } catch (cause) { update({ error: cause instanceof Error ? cause.message : String(cause) }); } } export const appStore = { subscribe(listener: () => void) { listeners.add(listener); return () => listeners.delete(listener); }, getSnapshot: () => snapshot, async refresh() { update({ busy: true, error: null }); try { const [models, calls, overview, settings, ports, cursorHarness, pluginRuntime] = await Promise.all([ api.models(), api.calls(), api.overview(), api.observability(), api.ports(), api.cursorHarness(), api.pluginRuntime(), ]); update({ models, calls, overview, detailed: settings.detailed, ports, cursorHarness, pluginRuntime }); } catch (cause) { update({ error: cause instanceof Error ? cause.message : String(cause) }); } finally { update({ busy: false }); } }, async deleteModel(modelHash: string) { await perform(async () => { await api.deleteModel(modelHash); await appStore.refresh(); }); }, async initializeCursorCa() { update({ cursorBusy: true, error: null }); try { const status = await api.initializeCursorCa(); update({ cursorHarness: status }); return status; } catch (cause) { update({ error: cause instanceof Error ? cause.message : String(cause) }); return null; } finally { update({ cursorBusy: false }); } }, async initializePluginRuntime() { update({ error: null }); try { const pluginRuntime = await api.initializePluginRuntime(); update({ pluginRuntime }); return pluginRuntime; } catch (cause) { update({ error: cause instanceof Error ? cause.message : String(cause) }); return null; } }, async refreshPluginRuntime() { try { const pluginRuntime = await api.pluginRuntime(); update({ pluginRuntime }); return pluginRuntime; } catch (cause) { update({ error: cause instanceof Error ? cause.message : String(cause) }); return null; } }, async cancelPluginRuntimeInitialization() { try { const pluginRuntime = await api.cancelPluginRuntimeInitialization(); update({ pluginRuntime }); return pluginRuntime; } catch (cause) { update({ error: cause instanceof Error ? cause.message : String(cause) }); return null; } }, async setCursorEnabled(enabled: boolean) { update({ cursorBusy: true, error: null }); try { update({ cursorHarness: await api.setCursorEnabled(enabled) }); } catch (cause) { update({ error: cause instanceof Error ? cause.message : String(cause) }); } finally { update({ cursorBusy: false }); } }, async createModels(models: ModelInput[]) { update({ cursorBusy: true, error: null }); try { const created = await api.createModels(models); await appStore.refresh(); return created; } catch (cause) { update({ error: cause instanceof Error ? cause.message : String(cause) }); return null; } finally { update({ cursorBusy: false }); } }, async importV0049Models() { update({ cursorBusy: true, error: null }); try { const result = await api.importV0049Models(); await appStore.refresh(); return result; } catch (cause) { update({ error: cause instanceof Error ? cause.message : String(cause) }); return null; } finally { update({ cursorBusy: false }); } }, async updateCursorModel(hash: string, model: ModelInput) { update({ cursorBusy: true, error: null }); try { const updated = await api.updateModel(hash, model); await appStore.refresh(); return updated; } catch (cause) { update({ error: cause instanceof Error ? cause.message : String(cause) }); return null; } finally { update({ cursorBusy: false }); } }, async reorderCursorModels(modelHashes: string[]) { const previous = snapshot.models; const byHash = new Map(previous.map((model) => [model.model_hash, model])); if (modelHashes.length !== previous.length || new Set(modelHashes).size !== previous.length) { update({ error: t("模型配置已发生变化,请刷新后重试") }); return false; } const reordered: Model[] = []; for (const [index, hash] of modelHashes.entries()) { const model = byHash.get(hash); if (!model) { update({ error: t("模型配置已发生变化,请刷新后重试") }); return false; } reordered.push({ ...model, sort_order: index + 1 }); } update({ models: reordered, cursorBusy: true, error: null }); try { update({ models: await api.reorderModels(modelHashes) }); return true; } catch (cause) { update({ models: previous, error: cause instanceof Error ? cause.message : String(cause), }); return false; } finally { update({ cursorBusy: false }); } }, async refreshCalls() { try { update({ calls: await api.calls() }); } catch (cause) { update({ error: cause instanceof Error ? cause.message : String(cause) }); } }, async openCallDetails(callId: string) { await perform(() => api.openCallDetails(callId)); }, async updateDetailed(detailed: boolean) { await perform(async () => update(await api.setObservability(detailed))); }, async updatePorts(ports: PortSettings) { try { update({ error: null }); update({ ports: await api.setPorts(ports) }); return true; } catch (cause) { update({ error: cause instanceof Error ? cause.message : String(cause) }); return false; } }, selectTheme(theme: ThemeId) { localStorage.setItem("cursor-byok.theme", theme); applyTheme(theme); update({ theme }); }, }; export function useAppStore() { return useSyncExternalStore(appStore.subscribe, appStore.getSnapshot); }