桌面应用与多会话管理:让AI真正“住”进你的电脑 ——CogitoAgent开发实战(第十一篇)

桌面应用与多会话管理:让AI真正"住"进你的电脑

------CogitoAgent开发实战(第11篇)

📖 本文是专栏的第十一篇。前面十篇文章,我们一步步构建了一个完整的AI智能体------它能思考、能动手、能联网、能记忆、能执行代码。但它一直"住"在终端里,一个黑底白字的命令行窗口。这一篇,我们给AI盖一栋"房子"------一个真正的桌面应用,让它从"字符界面"走进"图形世界"。同时,我们还会给AI装上一个"档案室"------多会话管理系统,让它能同时处理多个独立的任务线索。


📌 从一个对比开始

先想象两个场景:

场景A:终端模式

你在命令行里启动AI,看着彩色文字一行行滚动。你和AI的对话像一条没有尽头的时间线------昨天聊的、今天聊的、全都混在一起。想切到另一个话题?得手动清空历史,或者忍受AI用旧话题的背景来回答新问题。

场景B:桌面模式

你打开一个毛玻璃质感的悬浮窗,AI的虚拟形象在窗口里静静等待。你点开"会话列表",看到三个独立的对话线索:"项目A设计"、"代码审查"、"日常记录"。点一下,就能切换到对应的对话上下文。每个会话都有自己的"记忆",互不干扰。

这就是CogitoAgent v2.3 带来的改变------从"终端里的聊天机器人"进化为"桌面上的智能体伴侣"。


一、为什么需要桌面应用?

1.1 终端的局限性

命令行界面的优势是轻量、高效、适合开发者。但它的局限性也很明显:

问题 说明
视觉单调 只有文字,没有图形界面,无法展示复杂信息
交互单一 只能输入文本,无法点击、拖拽、直观操作
缺少陪伴感 AI只是一个"程序",不是一个"伙伴"
会话混乱 所有对话挤在同一条时间线上,难以组织

1.2 桌面应用的目标

CogitoAgent的桌面模式不是简单的"网页包装",而是围绕三个核心目标设计的:

  1. 让AI可视:通过虚拟人物形象、界面布局、视觉反馈,让AI"活"起来
  2. 让交互更自然:支持点击、拖拽、多窗口,降低使用门槛
  3. 让会话可管理:引入多会话系统,让对话像文档一样可组织、可切换

二、整体架构:两条腿走路

CogitoAgent 的桌面方案采用 Electron + WebSocket + 独立Agent进程 的架构,而不是把Agent直接嵌入Electron主进程。

2.1 为什么是独立进程?

方案 优点 缺点
嵌入Electron 通信简单 Agent崩溃会拖垮整个界面,难以独立重启
独立进程 隔离性好,可独立重启 需要进程间通信(IPC/WebSocket)

CogitoAgent选择了独立进程方案:
#mermaid-svg-cOBZxlzBakGJ5gLC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cOBZxlzBakGJ5gLC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cOBZxlzBakGJ5gLC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cOBZxlzBakGJ5gLC .error-icon{fill:#552222;}#mermaid-svg-cOBZxlzBakGJ5gLC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cOBZxlzBakGJ5gLC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cOBZxlzBakGJ5gLC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cOBZxlzBakGJ5gLC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cOBZxlzBakGJ5gLC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cOBZxlzBakGJ5gLC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cOBZxlzBakGJ5gLC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cOBZxlzBakGJ5gLC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cOBZxlzBakGJ5gLC .marker.cross{stroke:#333333;}#mermaid-svg-cOBZxlzBakGJ5gLC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cOBZxlzBakGJ5gLC p{margin:0;}#mermaid-svg-cOBZxlzBakGJ5gLC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cOBZxlzBakGJ5gLC .cluster-label text{fill:#333;}#mermaid-svg-cOBZxlzBakGJ5gLC .cluster-label span{color:#333;}#mermaid-svg-cOBZxlzBakGJ5gLC .cluster-label span p{background-color:transparent;}#mermaid-svg-cOBZxlzBakGJ5gLC .label text,#mermaid-svg-cOBZxlzBakGJ5gLC span{fill:#333;color:#333;}#mermaid-svg-cOBZxlzBakGJ5gLC .node rect,#mermaid-svg-cOBZxlzBakGJ5gLC .node circle,#mermaid-svg-cOBZxlzBakGJ5gLC .node ellipse,#mermaid-svg-cOBZxlzBakGJ5gLC .node polygon,#mermaid-svg-cOBZxlzBakGJ5gLC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cOBZxlzBakGJ5gLC .rough-node .label text,#mermaid-svg-cOBZxlzBakGJ5gLC .node .label text,#mermaid-svg-cOBZxlzBakGJ5gLC .image-shape .label,#mermaid-svg-cOBZxlzBakGJ5gLC .icon-shape .label{text-anchor:middle;}#mermaid-svg-cOBZxlzBakGJ5gLC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cOBZxlzBakGJ5gLC .rough-node .label,#mermaid-svg-cOBZxlzBakGJ5gLC .node .label,#mermaid-svg-cOBZxlzBakGJ5gLC .image-shape .label,#mermaid-svg-cOBZxlzBakGJ5gLC .icon-shape .label{text-align:center;}#mermaid-svg-cOBZxlzBakGJ5gLC .node.clickable{cursor:pointer;}#mermaid-svg-cOBZxlzBakGJ5gLC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cOBZxlzBakGJ5gLC .arrowheadPath{fill:#333333;}#mermaid-svg-cOBZxlzBakGJ5gLC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cOBZxlzBakGJ5gLC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cOBZxlzBakGJ5gLC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cOBZxlzBakGJ5gLC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cOBZxlzBakGJ5gLC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cOBZxlzBakGJ5gLC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cOBZxlzBakGJ5gLC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cOBZxlzBakGJ5gLC .cluster text{fill:#333;}#mermaid-svg-cOBZxlzBakGJ5gLC .cluster span{color:#333;}#mermaid-svg-cOBZxlzBakGJ5gLC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cOBZxlzBakGJ5gLC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cOBZxlzBakGJ5gLC rect.text{fill:none;stroke-width:0;}#mermaid-svg-cOBZxlzBakGJ5gLC .icon-shape,#mermaid-svg-cOBZxlzBakGJ5gLC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cOBZxlzBakGJ5gLC .icon-shape p,#mermaid-svg-cOBZxlzBakGJ5gLC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cOBZxlzBakGJ5gLC .icon-shape .label rect,#mermaid-svg-cOBZxlzBakGJ5gLC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cOBZxlzBakGJ5gLC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cOBZxlzBakGJ5gLC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cOBZxlzBakGJ5gLC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Agent 独立进程
Electron 桌面应用
WebSocket
spawn 启动
main.js

主进程
preload.cjs

安全桥接
UI 渲染进程

桌面/仪表盘/配置向导
agent-bridge.js

WebSocket 桥接
ws-server.js

WebSocket 服务

(端口 9527)
agent/Agent.js

思考循环 + 工具执行
session.js

会话管理

2.2 进程分工

进程 职责
Electron主进程 窗口管理、系统托盘、启动Agent子进程、路由模式切换
Agent独立进程 思考循环、工具执行、会话管理、WebSocket服务
UI渲染进程 界面展示、用户交互、消息渲染

关键设计:Agent进程不依赖Electron,即使桌面窗口关闭,Agent也可以继续在后台运行(当然,当前版本设计为窗口关闭时Agent也随之退出)。


三、启动流程解析

3.1 入口:main.js

当用户执行 npm run electron:desktopnpm run electron:dashboard 时,package.json 脚本会调用 Electron,加载 electron/main.js

javascript 复制代码
// main.js 的核心启动流程
app.whenReady().then(async () => {
  // 1. 检查配置
  if (!isConfigured()) {
    createSetupWindow();   // 打开配置向导
  } else {
    launchMainApp();       // 直接启动主应用
  }
});

async function launchMainApp() {
  // 2. 清理端口(防止旧进程占用)
  killPortProcess(9527);
  
  // 3. 启动Agent子进程
  await startAgentProcess();
  
  // 4. 读取启动模式
  const mode = envConfig['COGITO_MODE'] || 'desktop';
  
  // 5. 创建对应模式的窗口
  if (mode === 'dashboard') {
    createDashboardWindow();
  } else {
    createMainWindow();
  }
}

3.2 关键细节:Agent启动等待

startAgentProcess 函数会等待Agent输出 WebSocket 服务已启动 后才返回,确保WebSocket服务就绪后再创建窗口:

javascript 复制代码
function startAgentProcess() {
  agentProcess = spawn('node', ['src/index.js'], { ... });
  
  return new Promise((resolve) => {
    agentProcess.stdout.on('data', (data) => {
      if (data.toString().includes('WebSocket 服务已启动')) {
        resolve();  // 收到这个日志才继续
      }
    });
  });
}

3.3 配置向导与主应用分离

首次运行时,会先打开一个独立的配置向导窗口(setup/),完成API配置、模型选择、工作区设置后才进入主应用。这个设计让首次配置流程清晰、专注,不会与主界面混杂。


四、双模式设计:桌面 vs 仪表盘

CogitoAgent 提供了两种桌面交互模式,满足不同使用场景。

4.1 桌面模式(Desktop Mode)

启动命令npm run electron:desktop

定位:轻量级悬浮窗,像"桌面宠物"一样常驻。

界面特点

  • 半透明毛玻璃效果,融入桌面背景
  • 左侧是虚拟人物视频(可拖拽移动)
  • 右侧是对话面板(可点击人物头像切换显示/隐藏)
  • 极简交互,专注于快速问答

适用场景

  • 日常快速问答
  • 作为桌面"伴侣"常驻
  • 资源占用少,适合长时间后台运行

核心文件

  • electron/desktop/index.html --- 界面
  • electron/desktop/renderer.js --- 逻辑
  • electron/desktop/style.css --- 毛玻璃样式

4.2 仪表盘模式(Dashboard Mode)

启动命令npm run electron:dashboard

定位:全功能工作台,像IDE一样集成所有工具。

界面特点

  • 左侧边栏:会话列表 + 快捷操作 + 人物形象
  • 主内容区:欢迎页 / 技能浏览 / 对话视图(三态切换)
  • 全窗口设计,最大化信息展示空间
  • 工具调用可视化(黄色卡片显示调用,绿色卡片显示结果)

适用场景

  • 复杂任务处理
  • 需要多会话切换的场景
  • 工具探索和管理

核心文件

  • electron/dashboard/index.html --- 界面
  • electron/dashboard/renderer.js --- 逻辑
  • electron/dashboard/style.css --- 布局样式

4.3 双模式切换

用户可以在两种模式间自由切换:

javascript 复制代码
// renderer.js
// 桌面模式点击"工作台模式"按钮
btnDashboard.addEventListener('click', () => {
  window.electronAPI.switchToDashboard();
});

// 仪表盘模式点击"桌宠模式"按钮
btnDesktop.addEventListener('click', () => {
  window.electronAPI.switchToDesktop();
});

主进程处理模式切换:

javascript 复制代码
ipcMain.on('switch-to-dashboard', () => {
  if (mainWindow) mainWindow.close();
  if (!dashboardWindow) createDashboardWindow();
});

五、WebSocket桥接:连接桌面与Agent

5.1 为什么需要桥接?

Electron渲染进程不能直接与Node.js子进程通信(出于安全隔离考虑)。因此需要一个桥接层
Agent.js ws-server.js agent-bridge.js preload.cjs UI渲染进程 Agent.js ws-server.js agent-bridge.js preload.cjs UI渲染进程 #mermaid-svg-KGSo4CVS8pOCFrci{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KGSo4CVS8pOCFrci .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KGSo4CVS8pOCFrci .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KGSo4CVS8pOCFrci .error-icon{fill:#552222;}#mermaid-svg-KGSo4CVS8pOCFrci .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KGSo4CVS8pOCFrci .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KGSo4CVS8pOCFrci .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KGSo4CVS8pOCFrci .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KGSo4CVS8pOCFrci .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KGSo4CVS8pOCFrci .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KGSo4CVS8pOCFrci .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KGSo4CVS8pOCFrci .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KGSo4CVS8pOCFrci .marker.cross{stroke:#333333;}#mermaid-svg-KGSo4CVS8pOCFrci svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KGSo4CVS8pOCFrci p{margin:0;}#mermaid-svg-KGSo4CVS8pOCFrci .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-KGSo4CVS8pOCFrci text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-KGSo4CVS8pOCFrci .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-KGSo4CVS8pOCFrci .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-KGSo4CVS8pOCFrci .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-KGSo4CVS8pOCFrci .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-KGSo4CVS8pOCFrci #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-KGSo4CVS8pOCFrci .sequenceNumber{fill:white;}#mermaid-svg-KGSo4CVS8pOCFrci #sequencenumber{fill:#333;}#mermaid-svg-KGSo4CVS8pOCFrci #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-KGSo4CVS8pOCFrci .messageText{fill:#333;stroke:none;}#mermaid-svg-KGSo4CVS8pOCFrci .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-KGSo4CVS8pOCFrci .labelText,#mermaid-svg-KGSo4CVS8pOCFrci .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-KGSo4CVS8pOCFrci .loopText,#mermaid-svg-KGSo4CVS8pOCFrci .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-KGSo4CVS8pOCFrci .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-KGSo4CVS8pOCFrci .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-KGSo4CVS8pOCFrci .noteText,#mermaid-svg-KGSo4CVS8pOCFrci .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-KGSo4CVS8pOCFrci .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-KGSo4CVS8pOCFrci .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-KGSo4CVS8pOCFrci .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-KGSo4CVS8pOCFrci .actorPopupMenu{position:absolute;}#mermaid-svg-KGSo4CVS8pOCFrci .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-KGSo4CVS8pOCFrci .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-KGSo4CVS8pOCFrci .actor-man circle,#mermaid-svg-KGSo4CVS8pOCFrci line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-KGSo4CVS8pOCFrci :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ipcRenderer.send('user-message') 转发IPC事件 ws.send(JSON) 解析并处理 广播回复 WebSocket消息 ipcRenderer.send('agent-reply') 渲染消息

5.2 桥接模块:agent-bridge.js

agent-bridge.js 是连接Electron和Agent的"翻译官":

javascript 复制代码
// 连接WebSocket
function connect() {
  ws = new WebSocket('ws://localhost:9527');
  
  ws.on('message', (raw) => {
    const msg = JSON.parse(raw);
    // 向所有已注册窗口转发消息
    connectedWindows.forEach(win => {
      win.webContents.send('agent-reply', msg);
    });
  });
}

// 处理来自渲染进程的消息
ipcMain.on('user-message', (_event, text) => {
  ws.send(JSON.stringify({ type: 'user-message', text }));
});

5.3 多窗口支持

agent-bridge.js 通过 connectedWindows Set 支持多个窗口同时连接到同一个Agent,适合未来扩展多窗口场景。


六、多会话管理:AI的"档案室"

6.1 什么是多会话?

在终端模式下,所有对话都在同一个上下文中。多会话系统允许用户创建多个独立的对话空间

会话 用途 状态
默认会话 日常对话 当前激活
项目A讨论 记录项目A的设计决策 已归档
代码审查 代码评审记录 活跃

每个会话拥有:

  • 独立的对话历史
  • 独立的上下文(不会被其他会话污染)
  • 独立的人设(可单独设置)
  • 持久化存储(关闭程序后不丢失)

6.2 会话数据结构

元数据(meta.json)

json 复制代码
{
  "sessions": [
    {
      "id": "sess_abc123",
      "name": "项目A讨论",
      "createdAt": "2024-06-20T10:00:00Z",
      "lastActiveAt": "2024-06-20T15:30:00Z",
      "messageCount": 45
    }
  ],
  "activeId": "sess_abc123"
}

会话文件(sess_abc123.json)

json 复制代码
[
  { "role": "system", "content": "...系统提示词..." },
  { "role": "user", "content": "我们开始讨论项目A" },
  { "role": "assistant", "content": "好的,我已经记录了..." }
]

6.3 会话管理命令

在CLI模式下,用户可以通过以下命令管理会话:

命令 功能
/sessions 列出所有会话
/new 创建新会话
/switch <id> 切换到指定会话
/delete <id> 删除会话
/rename <name> 重命名当前会话

6.4 会话切换流程

javascript 复制代码
function switchSession(sessionId) {
  // 1. 归档当前会话
  archiveCurrentSession();
  saveSession(currentSessionId, conversationHistory);
  
  // 2. 切换
  currentSessionId = sessionId;
  meta.activeId = sessionId;
  
  // 3. 加载新会话
  const messages = loadSession(sessionId);
  conversationHistory = messages.length > 0 
    ? messages 
    : [{ role: 'system', content: buildSystemPrompt() }];
  
  // 4. 更新元数据
  saveMeta(meta);
}

6.5 会话压缩与归档

长时间对话会导致上下文膨胀,CogitoAgent采用自动压缩 + 归档策略:

  1. 触发条件:对话轮数超过150轮 或 Token估算超过100K
  2. 压缩策略
    • 保留最近10轮对话
    • 将更早的对话生成摘要
    • 完整历史归档到文件
  3. 归档保留:每个会话最多保留3个归档文件
javascript 复制代码
function compressHistory() {
  const nonSystemMessages = conversationHistory.filter(m => m.role !== 'system');
  if (nonSystemMessages.length <= 10) return;
  
  // 归档旧对话
  const toArchive = nonSystemMessages.slice(0, -10);
  archiveConversation(toArchive);
  
  // 保留最近10轮
  const recentMessages = nonSystemMessages.slice(-10);
  const summary = generateSummary(recentMessages);
  
  // 重建历史
  conversationHistory = [
    { role: 'system', content: buildSystemPrompt() },
    { role: 'user', content: `[上下文摘要] ${summary}` },
    ...recentMessages
  ];
}

七、工具调用可视化

7.1 为什么需要可视化?

在终端模式下,工具调用显示为灰色小框。在桌面模式下,我们可以做得更好:

终端显示 桌面显示
灰色小框 彩色卡片(黄色=调用,绿色=成功,红色=失败)
纯文本 可折叠展开
无法交互 点击展开/收起详情

7.2 工具卡片的实现

Dashboard模式下的工具调用通过 MessageRenderer 渲染为卡片:

javascript 复制代码
// message-renderer.js
addToolCall(toolName, args) {
  const card = document.createElement('div');
  card.className = 'tool-result-card tool-result-collapsed';
  
  // 头部:工具名称 + 状态
  const header = `
    <div class="tool-result-header">
      <div class="tool-icon">✓</div>
      <div class="tool-name">${toolName}</div>
      <div class="tool-status success">成功</div>
    </div>
  `;
  
  // 内容:参数和结果(可折叠)
  const content = `
    <div class="tool-result-content">
      <pre>${JSON.stringify(data, null, 2)}</pre>
    </div>
  `;
  
  // 点击切换折叠状态
  card.addEventListener('click', () => {
    card.classList.toggle('tool-result-collapsed');
    card.classList.toggle('tool-result-expanded');
  });
}

7.3 流式渲染优化

Dashboard模式使用双缓冲 + requestAnimationFrame 节流优化流式输出:

javascript 复制代码
// 流式渲染
_streamBuffer = '';
_streamPending = false;

_appendToStream(text) {
  this._streamBuffer = text;
  if (!this._streamPending) {
    this._streamPending = true;
    requestAnimationFrame(() => this._flushStream(false));
  }
}

_flushStream(isFinal) {
  this._streamPending = false;
  // 用 textContent 更新,不重建DOM
  this._streamTextNode.textContent = this._streamBuffer;
}

效果:文字逐字出现,流畅无闪烁,性能优于直接DOM操作。


八、配置向导:首次体验的设计

8.1 向导流程

首次运行时,CogitoAgent会打开一个独立的配置向导窗口(setup/),引导用户完成5步配置:

步骤 配置项 说明
1 API Base URL https://api.openai.com/v1
2 API Key 用户的API密钥
3 模型名称 如 gpt-4o
4 工作区路径 AI可访问的目录
5 人设选择 从13种人设中选择

8.2 预设按钮

向导界面提供了常用服务的预设按钮,一键填入:

html 复制代码
<div class="preset-btns">
  <button data-preset="https://api.openai.com/v1">OpenAI</button>
  <button data-preset="https://api.moark.com/v1">Moark</button>
  <button data-preset="https://api.deepseek.com/v1">DeepSeek</button>
  <button data-preset="https://openrouter.ai/api/v1">OpenRouter</button>
</div>

8.3 配置存储

配置完成后,系统会:

  1. 将敏感信息写入 .env 文件
  2. 将非敏感配置写入 config.json
  3. 将人设文件复制为 persona.md

这种双文件存储策略兼顾了安全性和可读性。


九、共享组件库的设计

9.1 为什么需要共享库?

Desktop 和 Dashboard 两种模式有很多重复的UI逻辑------消息渲染、工具卡片、人物形象管理。如果各自实现,会导致代码重复和维护困难。

9.2 共享组件

electron/shared/ 目录下包含了公共组件:

文件 功能
common.css 消息气泡、Markdown、工具卡片等公共样式
utils.js Markdown渲染、HTML净化、工具调用过滤
message-renderer.js 消息渲染引擎(支持两种布局模式)
persona-manager.js 人物形象管理(视频/图片切换)

9.3 使用方式

javascript 复制代码
// desktop/renderer.js 和 dashboard/renderer.js 共用
MessageRenderer.init({
  messagesEl,
  layout: 'desktop',  // 或 'dashboard'
  showAvatar: true,   // dashboard显示头像,desktop不显示
});

PersonaManager.init({
  containerEl: characterEl,
  defaultMediaPath: '../shared/video.mp4',
});

十、设计原则与启发

10.1 渐进增强

CogitoAgent的桌面方案遵循"渐进增强"原则:

  • 终端模式:核心功能完整可用
  • 桌面模式:增加视觉和交互增强
  • 仪表盘模式:增加会话管理和工具可视化

用户可以从终端开始,逐步升级到桌面模式,不需要一次性学习所有功能。

10.2 关注点分离

关注点 实现位置
AI推理 Agent独立进程
界面交互 Electron渲染进程
进程通信 WebSocket + IPC
会话管理 session.js(Agent侧)
数据持久化 文件系统

10.3 善用现有生态

  • Electron:成熟的桌面应用框架
  • WebSocket:标准的双向通信协议
  • V8 Isolateisolated-vm 提供进程级隔离
  • CSS毛玻璃backdrop-filter: blur() 实现视觉效果

十一、小结

这一篇我们看了CogitoAgent的桌面应用与多会话管理:

模块 核心实现
桌面架构 Electron + 独立Agent进程 + WebSocket桥接
双模式 桌面模式(轻量悬浮窗)+ 仪表盘模式(全功能工作台)
进程通信 agent-bridge.js 桥接Electron IPC和WebSocket
多会话管理 独立会话文件 + 自动压缩归档 + CLI命令管理
工具可视化 彩色卡片 + 折叠展开 + 实时流式渲染
共享组件 公共样式、渲染器、人物形象管理
配置向导 5步引导 + 预设按钮 + 双文件存储

核心设计原则

  1. 独立进程隔离风险,WebSocket桥接通信
  2. 双模式满足不同使用场景
  3. 多会话让对话可组织、可管理
  4. 共享组件避免代码重复
  5. 渐进增强降低使用门槛

开源仓库


如果这篇文章对你有帮助,欢迎 ⭐Star 支持一下开源项目!

👉 Gitee | GitHub 👈