import { invoke } from "@tauri-apps/api/core"; import type { AgentProposal, AgentReceipt, Channel, ExternalAiBridge, KnowledgeDocument, MutationReceipt, PublicPersona, RealtimeInvitation, RuntimeOverview, SystemSnapshot, TimelineEvent, } from "./types"; const tauri = () => "__TAURI_INTERNALS__" in window; const now = () => new Date().toISOString(); const key = "hololake.clean.browser-state.v1"; const runtimeKey = "hololake.clean.browser-runtime.v1"; type BrowserState = { channel: Channel | null; timeline: TimelineEvent[]; documents: KnowledgeDocument[]; bridges: ExternalAiBridge[]; }; const empty: BrowserState = { channel: null, timeline: [], documents: [], bridges: [], }; const load = (): BrowserState => JSON.parse(localStorage.getItem(key) ?? JSON.stringify(empty)); const save = (state: BrowserState) => localStorage.setItem(key, JSON.stringify(state)); const loadRuntime = (): { personas: PublicPersona[] } => JSON.parse(localStorage.getItem(runtimeKey) ?? '{"personas":[]}'); const saveRuntime = (state: { personas: PublicPersona[] }) => localStorage.setItem(runtimeKey, JSON.stringify(state)); const event = ( sourceKind: TimelineEvent["sourceKind"], title: string, content: string, state: TimelineEvent["state"] = "SUCCEEDED", ): TimelineEvent => ({ eventId: crypto.randomUUID(), sourceKind, title, content, occurredAt: now(), state, }); export async function snapshot(): Promise { if (tauri()) return invoke("system_snapshot"); const s = load(); return { appVersion: "1.1.0-browser", channel: s.channel, timeline: s.timeline, documents: s.documents.map(({ path, title, updatedAt, sha256 }) => ({ path, title, updatedAt, sha256, })), moduleRegistryVersion: "1", publicUpdateEndpoint: "https://guanghulab.com/hololake/releases/latest.json", publicDistributionState: "SIGNED_UPDATE_FEED_READY_NO_RELEASE", externalAiBridges: s.bridges, }; } export async function createChannel(name: string): Promise { if (tauri()) return invoke("create_channel", { name }); const s = load(); const channel: Channel = { channelId: `HL-CH-${crypto.randomUUID().slice(0, 8).toUpperCase()}`, name, createdAt: now(), privateGitPath: "浏览器预览不写入磁盘", publicDistributionState: "SIGNED_UPDATE_FEED_READY_NO_RELEASE", }; s.channel = channel; s.timeline.unshift( event("SYSTEM_RECEIPT", "频道已建立", `${channel.channelId} 已登记。`), ); save(s); return channel; } export async function submitMessage(content: string): Promise { if (tauri()) return invoke("submit_user_message", { content }); const s = load(); const e = event("USER_MESSAGE", "用户语言", content, "RECORDED"); s.timeline.unshift( e, event( "SYSTEM_RECEIPT", "系统回执", "用户语言已分流保存;等待人格体接入,不伪造人格回应。", "WAITING", ), ); save(s); return { receiptId: crypto.randomUUID(), state: "WAITING_PERSONA", message: "用户语言已记录", gitCommit: null, event: e, }; } export async function readDocument(path: string): Promise { if (tauri()) return invoke("read_knowledge_document", { path }); const doc = load().documents.find((d) => d.path === path); if (!doc) throw new Error("文档不存在"); return doc; } export async function createDocument(title: string): Promise { if (tauri()) return invoke("create_knowledge_document", { title }); const s = load(); const path = `${title.trim().replace(/[\\/:*?\"<>|]/g, "-")}.md`; const d = { path, title: title.trim(), body: `# ${title.trim()}\n\n`, updatedAt: now(), sha256: "browser-preview", }; s.documents.unshift(d); const e = event("AGENT_ACTION", "Agent 执行", `已创建 ${path}`); s.timeline.unshift(e); save(s); return { receiptId: crypto.randomUUID(), state: "SUCCEEDED", message: "文档已创建", gitCommit: "browser-preview", event: e, }; } export async function saveDocument( path: string, body: string, ): Promise { if (tauri()) return invoke("save_knowledge_document", { path, body }); const s = load(); const d = s.documents.find((x) => x.path === path); if (!d) throw new Error("文档不存在"); d.body = body; d.updatedAt = now(); const e = event( "SYSTEM_RECEIPT", "系统回执", `${path} 已保存到私人 Git 预览层。`, ); s.timeline.unshift(e); save(s); return { receiptId: crypto.randomUUID(), state: "SUCCEEDED", message: "保存完成", gitCommit: "browser-preview", event: e, }; } export async function deleteDocument(path: string): Promise { if (tauri()) return invoke("delete_knowledge_document", { path }); const s = load(); s.documents = s.documents.filter((x) => x.path !== path); const e = event("SYSTEM_RECEIPT", "系统回执", `${path} 已移入回收站。`); s.timeline.unshift(e); save(s); return { receiptId: crypto.randomUUID(), state: "SUCCEEDED", message: "已移入回收站", gitCommit: "browser-preview", event: e, }; } export async function importFolder(): Promise { if (!tauri()) throw new Error("文件夹导入请在已安装的 HoloLake 中使用"); return invoke("import_knowledge_folder"); } export async function exportDocument(path: string): Promise { if (!tauri()) throw new Error("下载请在已安装的 HoloLake 中使用"); return invoke("export_knowledge_document", { path }); } export async function registerBridge( displayName: string, ): Promise { if (tauri()) return invoke("register_external_ai_bridge", { displayName }); const s = load(); const b = { bridgeId: `HL-AI-${crypto.randomUUID().slice(0, 8).toUpperCase()}`, displayName, inboxPath: "浏览器预览", outboxPath: "浏览器预览", state: "LOCAL_EXPRESSION_ONLY", }; s.bridges.push(b); save(s); return b; } export async function runtimeOverview(): Promise { if (tauri()) return invoke("get_runtime_overview"); const preview = loadRuntime(); return { realtime: { schema: "preview", state: "LISTENING", protocol: "GLP_LOCAL_REALTIME/1", endpoint: "tcp://127.0.0.1:39281", connectedClients: 0, loopbackOnly: true, transportGrantsAuthority: false, }, persona: { schema: "preview", state: preview.personas.length ? "TRIAL_PERSONA_READY" : "READY_NO_PERSONA", activePersonaId: preview.personas[0]?.personaId ?? null, personas: preview.personas, verifiedExistingPersonaCount: 0, }, proposals: [], }; } export async function realtimeInvitation(): Promise { if (tauri()) return invoke("get_realtime_invitation"); return { schema: "preview", protocol: "GLP_LOCAL_REALTIME/1", endpoint: "tcp://127.0.0.1:39281", token: "browser-preview", descriptorPath: "browser-preview", connectorCommand: "仅已安装 App 可生成接入命令", warning: "令牌不证明人格身份。", }; } export async function registerPersona( displayName: string, ): Promise { if (tauri()) return invoke("register_public_persona", { displayName }); const persona = { personaId: `HL-PERSONA-${crypto.randomUUID().slice(0, 10).toUpperCase()}`, displayName, state: "LOCAL_TRIAL_UNVERIFIED_EXTERNAL_HOST_REQUIRED", createdAt: now(), reversibleUntilUnixMs: Date.now() + 30 * 86400000, }; const preview = loadRuntime(); preview.personas.push(persona); saveRuntime(preview); return persona; } export async function deleteTrialPersona( personaId: string, ): Promise { if (tauri()) return invoke("delete_trial_persona", { personaId, exactConfirmation: `删除试用人格 ${personaId}`, }); const preview = loadRuntime(); preview.personas = preview.personas.filter( (persona) => persona.personaId !== personaId, ); saveRuntime(preview); return { schema: "preview", state: preview.personas.length ? "TRIAL_PERSONA_READY" : "READY_NO_PERSONA", activePersonaId: preview.personas[0]?.personaId ?? null, personas: preview.personas, verifiedExistingPersonaCount: 0, }; } export async function approveProposal( proposalId: string, ): Promise { return invoke("approve_agent_proposal", { proposalId }); } export async function rejectProposal( proposalId: string, ): Promise { return invoke("reject_agent_proposal", { proposalId }); }