/** * 光湖知识库 · API 客户端 * Agent 和前端 UI 共用同一套接口。 */ const BASE = typeof window !== 'undefined' && window.location.protocol === 'file:' ? 'http://127.0.0.1:3890/api' : '/api'; async function request(path: string, options?: RequestInit): Promise { const res = await fetch(`${BASE}${path}`, { headers: { 'Content-Type': 'application/json' }, ...options, }); const data = await res.json(); if (!data.ok) throw new Error(data.error || '请求失败'); return data as T; } // ─── 类型(与 server 保持一致) ─── export interface DocMeta { id: string; title: string; slug: string; parentPath: string; createdAt: string; updatedAt: string; author: string; tags: string[]; } export interface DocContent { meta: DocMeta; body: string; } export interface DocTreeNode { name: string; path: string; type: 'folder' | 'document'; children?: DocTreeNode[]; } export interface VersionEntry { hash: string; shortHash: string; author: string; date: string; message: string; } export interface SearchResult { path: string; title: string; snippet: string; line: number; } export interface ModuleManifest { id: string; name: string; version: string; kind: 'native-application'; state: 'LIVE'; description: string; capabilities: string[]; dataPolicy: string; } export interface ChannelModuleState { id: string; installed: boolean; mounted: boolean; order: number; } export interface ChannelState { schema: 'hololake.personal-channel-state/v1'; channelId: string; revision: number; updatedAt: string; modules: ChannelModuleState[]; } export interface ChannelReceipt { id: string; after: ChannelState; reversible: true; } export interface DiffResult { additions: number; deletions: number; hunks: { oldStart: number; newStart: number; lines: string[] }[]; } // ─── API 方法 ─── export const api = { async getModules(): Promise { const data = await request<{ registry: ModuleManifest[] }>('/modules'); return data.registry; }, async getChannel(): Promise { const data = await request<{ channel: ChannelState }>('/channel'); return data.channel; }, async patchChannel(input: { operation: 'set_module_state'; moduleId: string; installed?: boolean; mounted?: boolean }): Promise<{ channel: ChannelState; receipt: ChannelReceipt }> { return request('/channel/patch', { method: 'POST', body: JSON.stringify(input) }); }, async undoChannel(receiptId: string): Promise<{ channel: ChannelState; receipt: ChannelReceipt }> { return request(`/channel/undo/${encodeURIComponent(receiptId)}`, { method: 'POST' }); }, /** 获取文档树 */ getTree: () => request<{ ok: true; tree: DocTreeNode[] }>('/tree').then(d => d.tree), /** 读取文档 */ getDoc: (path: string) => request<{ ok: true; doc: DocContent }>(`/docs/${path}`).then(d => d.doc), /** 创建文档 */ createDoc: (path: string, title: string, body: string, author = 'anonymous') => request<{ ok: true; doc: DocContent }>(`/docs/${path}`, { method: 'POST', body: JSON.stringify({ title, body, author }), }).then(d => d.doc), /** 更新文档 */ updateDoc: (path: string, title: string, body: string, author = 'anonymous') => request<{ ok: true; doc: DocContent }>(`/docs/${path}`, { method: 'PUT', body: JSON.stringify({ title, body, author }), }).then(d => d.doc), /** 删除文档 */ deleteDoc: (path: string) => request<{ ok: true }>(`/docs/${path}`, { method: 'DELETE' }), /** 移动文档 */ moveDoc: (oldPath: string, newPath: string) => request<{ ok: true }>('/move', { method: 'POST', body: JSON.stringify({ oldPath, newPath }), }), /** 获取版本历史 */ getHistory: (path: string, max = 50) => request<{ ok: true; history: VersionEntry[] }>(`/history/${path}?max=${max}`).then(d => d.history), /** 获取某版本内容 */ getDocAtVersion: (path: string, hash: string) => request<{ ok: true; content: string }>(`/version/${hash}/${path}`).then(d => d.content), /** 版本对比 */ diffVersions: (path: string, from: string, to: string) => request<{ ok: true; diff: DiffResult }>(`/diff/${path}?from=${from}&to=${to}`).then(d => d.diff), /** 搜索 */ search: (query: string) => request<{ ok: true; results: SearchResult[]; count: number }>(`/search?q=${encodeURIComponent(query)}`) .then(d => d.results), };