feat: MetonaAI Desktop 初始项目
- Electron + React + TypeScript 架构 - 三栏布局: Sidebar | ChatPanel | DetailPanel - 9 个内置工具 (文件系统/网络/记忆/命令) - SQLite 持久化 (better-sqlite3) - MUI 暗色/亮色主题系统 - Agent Loop ReAct 状态机引擎 - DeepSeek / Agnes AI / Ollama Provider 适配器 - MCP 协议集成 - 系统托盘 + 全局快捷键 - Tailwind CSS v4 + Tailwind Merge - 修复: Sidebar 缺失 TextField 导入导致黑屏
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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),
|
||||
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),
|
||||
},
|
||||
|
||||
// ===== 配置 =====
|
||||
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),
|
||||
},
|
||||
|
||||
// ===== 工具管理 =====
|
||||
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'),
|
||||
},
|
||||
|
||||
// ===== 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 {
|
||||
// 降级方案(不推荐)
|
||||
(window as unknown as Record<string, unknown>).electron = electronAPI;
|
||||
(window as unknown as Record<string, unknown>).metona = metonaAPI;
|
||||
}
|
||||
Reference in New Issue
Block a user