1. Token 估算优化(核心改进):新增 token-estimator.ts 智能字符估算(中文 1.5 token/字、ASCII 0.25 token/字),替换三处旧的 length/2 粗略估算,中文场景准确度从 ~50% 提升到 ~90% 2. 工具确认超时改进:超时时间可配置(30s~600s)、超时 toast 通知、ConfirmationDialog 倒计时 UI(进度条 + 最后 10 秒红色脉冲动画)、SettingsModal 新增配置入口 3. 详情栏新增 Workspace 标签页:workspace:getInfo IPC + WorkspaceViewer 组件(文件预览、目录状态、在文件管理器打开)
172 lines
7.6 KiB
TypeScript
172 lines
7.6 KiB
TypeScript
/**
|
|
* MetonaAI Desktop — Preload 脚本
|
|
*
|
|
* 通过 contextBridge 安全地将 IPC 通道暴露给渲染进程。
|
|
* 渲染进程无 Node.js 访问权限,只能通过此桥接层通信。
|
|
*/
|
|
|
|
import { contextBridge, ipcRenderer } from 'electron';
|
|
import { electronAPI } from '@electron-toolkit/preload';
|
|
|
|
/**
|
|
* Metona API — 暴露给渲染进程的安全接口
|
|
*/
|
|
const metonaAPI = {
|
|
// ===== Agent 交互 =====
|
|
agent: {
|
|
/** 发送用户消息 */
|
|
sendMessage: (message: unknown, sessionId: string) => ipcRenderer.invoke('agent:sendMessage', message, sessionId),
|
|
/** 监听流式事件 */
|
|
onStreamEvent: (callback: (event: unknown) => void) => {
|
|
const listener = (_event: Electron.IpcRendererEvent, data: unknown) => callback(data);
|
|
ipcRenderer.on('agent:streamEvent', listener);
|
|
return () => ipcRenderer.removeListener('agent:streamEvent', listener);
|
|
},
|
|
/** 监听状态变化 */
|
|
onStateChange: (callback: (state: unknown) => void) => {
|
|
const listener = (_event: Electron.IpcRendererEvent, data: unknown) => callback(data);
|
|
ipcRenderer.on('agent:stateChange', listener);
|
|
return () => ipcRenderer.removeListener('agent:stateChange', listener);
|
|
},
|
|
/** 中断会话 */
|
|
abortSession: (sessionId: string) => ipcRenderer.invoke('agent:abortSession', sessionId),
|
|
/** 监听 Provider 切换通知 */
|
|
onProviderSwitched: (callback: (data: unknown) => void) => {
|
|
const listener = (_event: Electron.IpcRendererEvent, data: unknown) => callback(data);
|
|
ipcRenderer.on('agent:providerSwitched', listener);
|
|
return () => ipcRenderer.removeListener('agent:providerSwitched', listener);
|
|
},
|
|
},
|
|
|
|
// ===== 会话管理 =====
|
|
sessions: {
|
|
list: () => ipcRenderer.invoke('sessions:list'),
|
|
create: (title?: string) => ipcRenderer.invoke('sessions:create', title),
|
|
rename: (sessionId: string, title: string) => ipcRenderer.invoke('sessions:rename', sessionId, title),
|
|
delete: (sessionId: string) => ipcRenderer.invoke('sessions:delete', sessionId),
|
|
getMessages: (sessionId: string) => ipcRenderer.invoke('sessions:getMessages', sessionId),
|
|
pin: (sessionId: string, pinned: boolean) => ipcRenderer.invoke('sessions:pin', sessionId, pinned),
|
|
archive: (sessionId: string, archived: boolean) => ipcRenderer.invoke('sessions:archive', sessionId, archived),
|
|
deleteMessage: (messageId: string) => ipcRenderer.invoke('sessions:deleteMessage', messageId),
|
|
clearMessages: (sessionId: string) => ipcRenderer.invoke('sessions:clearMessages', sessionId),
|
|
saveTrace: (sessionId: string, data: unknown) => ipcRenderer.invoke('sessions:saveTrace', sessionId, data),
|
|
getTrace: (sessionId: string) => ipcRenderer.invoke('sessions:getTrace', sessionId),
|
|
},
|
|
|
|
// ===== MCP 管理 =====
|
|
mcp: {
|
|
listServers: () => ipcRenderer.invoke('mcp:listServers'),
|
|
addServer: (config: unknown) => ipcRenderer.invoke('mcp:addServer', config),
|
|
removeServer: (name: string) => ipcRenderer.invoke('mcp:removeServer', name),
|
|
toggleServer: (name: string, enabled: boolean) => ipcRenderer.invoke('mcp:toggleServer', name, enabled),
|
|
},
|
|
|
|
// ===== 记忆系统 =====
|
|
memory: {
|
|
search: (query: string, options?: unknown) => ipcRenderer.invoke('db:searchMemories', query, options),
|
|
listAll: (options?: { type?: string; limit?: number }) =>
|
|
ipcRenderer.invoke('memory:listAll', options),
|
|
delete: (type: string, id: string) => ipcRenderer.invoke('memory:delete', type, id),
|
|
},
|
|
|
|
// ===== v0.2.0: 任务管理 =====
|
|
tasks: {
|
|
list: (sessionId?: string) => ipcRenderer.invoke('tasks:list', sessionId),
|
|
create: (data: { sessionId: string; title: string; description?: string; priority?: string; parentId?: string }) =>
|
|
ipcRenderer.invoke('tasks:create', data),
|
|
update: (id: string, updates: { title?: string; description?: string; status?: string; priority?: string; assignedTo?: string }) =>
|
|
ipcRenderer.invoke('tasks:update', id, updates),
|
|
delete: (id: string) => ipcRenderer.invoke('tasks:delete', id),
|
|
},
|
|
|
|
// ===== v0.2.0: 审计日志 =====
|
|
audit: {
|
|
verifyChain: () => ipcRenderer.invoke('audit:verifyChain'),
|
|
query: (filters?: { sessionId?: string; eventType?: string; limit?: number }) =>
|
|
ipcRenderer.invoke('audit:query', filters),
|
|
},
|
|
|
|
// ===== v0.2.0: 工具确认请求 =====
|
|
tool: {
|
|
onConfirmationRequest: (callback: (request: unknown) => void) => {
|
|
const listener = (_event: Electron.IpcRendererEvent, data: unknown) => callback(data);
|
|
ipcRenderer.on('tool:confirmationRequest', listener);
|
|
return () => ipcRenderer.removeListener('tool:confirmationRequest', listener);
|
|
},
|
|
sendConfirmationResponse: (response: { toolCallId: string; approved: boolean; remember: boolean; autoExecute?: boolean }) =>
|
|
ipcRenderer.send('tool:confirmationResponse', response),
|
|
// v0.2.0: 持久化自动执行设置
|
|
setAutoExecute: (toolName: string, enabled: boolean) =>
|
|
ipcRenderer.invoke('tool:setAutoExecute', toolName, enabled),
|
|
getAutoExecuteList: () => ipcRenderer.invoke('tool:getAutoExecuteList'),
|
|
},
|
|
|
|
// ===== 配置 =====
|
|
config: {
|
|
get: (key: string) => ipcRenderer.invoke('config:get', key),
|
|
set: (key: string, value: unknown) => ipcRenderer.invoke('config:set', key, value),
|
|
},
|
|
|
|
// ===== 应用工具 =====
|
|
app: {
|
|
getVersion: () => ipcRenderer.invoke('app:getVersion'),
|
|
getAppDataPath: () => ipcRenderer.invoke('app:getAppDataPath'),
|
|
openExternal: (url: string) => ipcRenderer.invoke('app:openExternal', url),
|
|
showItemInFolder: (path: string) => ipcRenderer.invoke('app:showItemInFolder', path),
|
|
selectFolder: (defaultPath?: string) => ipcRenderer.invoke('app:selectFolder', defaultPath),
|
|
restart: () => ipcRenderer.invoke('app:restart'),
|
|
},
|
|
|
|
// ===== 工作空间管理 =====
|
|
workspace: {
|
|
check: (targetPath: string) => ipcRenderer.invoke('workspace:check', targetPath),
|
|
inheritFiles: (params: { targetPath: string; sourcePath: string; files: string[] }) =>
|
|
ipcRenderer.invoke('workspace:inheritFiles', params),
|
|
getInfo: () => ipcRenderer.invoke('workspace:getInfo'),
|
|
},
|
|
|
|
// ===== 工具管理 =====
|
|
tools: {
|
|
list: () => ipcRenderer.invoke('tools:list'),
|
|
toggle: (toolName: string, enabled: boolean) => ipcRenderer.invoke('tools:toggle', toolName, enabled),
|
|
},
|
|
|
|
// ===== 数据管理 =====
|
|
data: {
|
|
exportData: (sessionId?: string) => ipcRenderer.invoke('data:export', sessionId),
|
|
clearSessions: () => ipcRenderer.invoke('data:clearSessions'),
|
|
clearMemories: () => ipcRenderer.invoke('data:clearMemories'),
|
|
clearAuditLogs: () => ipcRenderer.invoke('data:clearAuditLogs'),
|
|
},
|
|
|
|
// ===== SearXNG =====
|
|
searxng: {
|
|
testConnection: (url: string, authKey: string, authType: string) =>
|
|
ipcRenderer.invoke('searxng:testConnection', url, authKey, authType),
|
|
},
|
|
|
|
// ===== Toast 通知桥接 =====
|
|
toast: {
|
|
onShow: (callback: (data: { type: string; message: string; options?: unknown }) => void) => {
|
|
const listener = (_event: Electron.IpcRendererEvent, data: { type: string; message: string; options?: unknown }) =>
|
|
callback(data);
|
|
ipcRenderer.on('toast:show', listener);
|
|
return () => ipcRenderer.removeListener('toast:show', listener);
|
|
},
|
|
},
|
|
};
|
|
|
|
// ===== 安全暴露到渲染进程 =====
|
|
if (process.contextIsolated) {
|
|
try {
|
|
contextBridge.exposeInMainWorld('electron', electronAPI);
|
|
contextBridge.exposeInMainWorld('metona', metonaAPI);
|
|
} catch (error) {
|
|
console.error('Failed to expose APIs:', error);
|
|
}
|
|
} else {
|
|
// 降级方案(不推荐)
|
|
(globalThis as unknown as Record<string, unknown>).electron = electronAPI;
|
|
(globalThis as unknown as Record<string, unknown>).metona = metonaAPI;
|
|
}
|