hololake-system-architecture/product-source/hololake-clean-desktop/src/runtime.ts

293 lines
8.6 KiB
TypeScript

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<SystemSnapshot> {
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<Channel> {
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<MutationReceipt> {
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<KnowledgeDocument> {
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<MutationReceipt> {
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<MutationReceipt> {
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<MutationReceipt> {
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<MutationReceipt | null> {
if (!tauri()) throw new Error("文件夹导入请在已安装的 HoloLake 中使用");
return invoke("import_knowledge_folder");
}
export async function exportDocument(path: string): Promise<string | null> {
if (!tauri()) throw new Error("下载请在已安装的 HoloLake 中使用");
return invoke("export_knowledge_document", { path });
}
export async function registerBridge(
displayName: string,
): Promise<ExternalAiBridge> {
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<RuntimeOverview> {
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<RealtimeInvitation> {
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<PublicPersona> {
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<RuntimeOverview["persona"]> {
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<AgentReceipt> {
return invoke("approve_agent_proposal", { proposalId });
}
export async function rejectProposal(
proposalId: string,
): Promise<AgentProposal> {
return invoke("reject_agent_proposal", { proposalId });
}