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,737 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>MetonaAI Desktop — UI/UX 设计集成方案 | v1.0</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg:#0f1117; --bg-card:#1a1d27; --bg-code:#12141c; --bg-nav:#141620;
|
||||
--text:#e1e4ed; --text-dim:#8b8fa7; --accent:#6366f1; --accent2:#818cf8;
|
||||
--green:#34d399; --orange:#fb923c; --red:#f87171; --amber:#fbbf24;
|
||||
--cyan:#22d3ee; --purple:#a855f7; --pink:#f472b6; --border:#2a2d3a;
|
||||
}
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif;background:var(--bg);color:var(--text);line-height:1.7;display:flex;scroll-behavior:smooth}
|
||||
.sidebar{position:fixed;top:0;left:0;width:300px;height:100vh;background:var(--bg-nav);border-right:1px solid var(--border);overflow-y:auto;z-index:100;padding:24px 0}
|
||||
.sidebar-logo{padding:0 20px 20px;border-bottom:1px solid var(--border);margin-bottom:16px}
|
||||
.sidebar-logo h2{font-size:19px;background:linear-gradient(135deg,var(--accent),var(--accent2),var(--purple));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.sidebar-section{padding:8px 20px;font-size:10.5px;text-transform:uppercase;letter-spacing:1.2px;color:var(--text-dim);font-weight:600}
|
||||
.sidebar a{display:flex;align-items:center;gap:10px;padding:7px 20px;color:var(--text-dim);text-decoration:none;font-size:13px;transition:all .15s}
|
||||
.sidebar a:hover,.sidebar a.active{color:var(--text);background:rgba(99,102,241,.06)}
|
||||
.sidebar a.active{border-right:2px solid var(--accent)}
|
||||
.main{margin-left:300px;flex:1;min-height:100vh}
|
||||
.hero{background:linear-gradient(135deg,rgba(99,102,241,.07),rgba(244,114,182,.05));border-bottom:1px solid var(--border);padding:56px 60px 48px}
|
||||
.hero h1{font-size:30px;font-weight:800;background:linear-gradient(135deg,var(--accent),var(--purple),var(--pink));-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:10px}
|
||||
.hero p{color:var(--text-dim);font-size:15px;max-width:740px}
|
||||
.hero-meta{display:flex;gap:24px;margin-top:18px;flex-wrap:wrap}
|
||||
.hero-meta span{font-size:13px;color:var(--text-dim);display:flex;align-items:center;gap:6px}
|
||||
.content{padding:40px 60px 100px;max-width:1080px}
|
||||
.api-section{margin-bottom:60px;scroll-margin-top:20px}
|
||||
.api-section h2{font-size:22px;font-weight:700;margin-bottom:16px;display:flex;align-items:center;gap:10px}
|
||||
.api-section h3{font-size:16px;font-weight:600;margin:32px 0 12px;color:var(--accent2)}
|
||||
.api-section .desc{color:var(--text-dim);font-size:14.5px;margin-bottom:16px}
|
||||
.api-section .desc code{color:var(--cyan);background:var(--bg-code);padding:2px 6px;border-radius:4px;font-size:13px}
|
||||
table.spec{width:100%;border-collapse:collapse;margin-bottom:24px;font-size:13.5px}
|
||||
table.spec th{text-align:left;padding:10px 14px;background:var(--bg-card);border-bottom:1px solid var(--border);color:var(--text-dim);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:.8px}
|
||||
table.spec td{padding:10px 14px;border-bottom:1px solid rgba(42,45,58,.5);vertical-align:top;transition:background .15s}
|
||||
table.spec tr:hover td{background:rgba(99,102,241,.03)}
|
||||
table.spec tr:last-child td{border-bottom:none}
|
||||
.f1{font-family:'SF Mono',monospace;color:var(--cyan);font-weight:600;font-size:13px}
|
||||
.f2{font-family:'SF Mono',monospace;color:var(--accent2);font-size:11.5px}
|
||||
.f3{color:var(--red);font-size:11px;font-weight:600}
|
||||
.code-block{background:var(--bg-code);border:1px solid var(--border);border-radius:10px;padding:20px;overflow-x:auto;margin-bottom:24px;transition:border-color .2s}
|
||||
.code-block:hover{border-color:rgba(99,102,241,.3)}
|
||||
.code-block pre{margin:0;font-family:'SF Mono','Fira Code',monospace;font-size:13px;line-height:1.6;color:var(--text)}
|
||||
.hl-kw{color:var(--purple)}.hl-str{color:var(--green)}.hl-num{color:var(--orange)}
|
||||
.hl-cm{color:var(--text-dim);font-style:italic}.hl-fn{color:var(--accent)}.hl-prop{color:var(--cyan)}
|
||||
.note-box{background:rgba(99,102,241,.05);border-left:3px solid var(--accent);border-radius:0 8px 8px 0;padding:14px 18px;margin-bottom:24px;font-size:13.5px;color:var(--text-dim);transition:border-color .2s}
|
||||
.note-box:hover{border-left-color:var(--accent2)}
|
||||
.note-box strong{color:var(--accent)}
|
||||
.note-box code{color:var(--cyan);background:var(--bg-code);padding:1px 5px;border-radius:3px;font-size:12.5px}
|
||||
.warn-box{background:rgba(251,191,36,.05);border-left:3px solid var(--amber);border-radius:0 8px 8px 0;padding:14px 18px;margin-bottom:24px;font-size:13.5px;color:var(--text-dim);transition:border-color .2s}
|
||||
.warn-box:hover{border-left-color:var(--orange)}
|
||||
.arch-diagram{background:var(--bg-card);border:1px solid var(--border);border-radius:10px;padding:24px;margin:20px 0 30px;overflow-x:auto;transition:border-color .2s}
|
||||
.arch-diagram:hover{border-color:rgba(99,102,241,.3)}
|
||||
.arch-diagram pre{font-family:'SF Mono',monospace;font-size:12px;line-height:1.55;color:var(--text);margin:0}
|
||||
.node{display:inline-block;padding:6px 14px;border-radius:8px;margin:3px;font-size:12px;font-family:'SF Mono',monospace}
|
||||
.node-core{background:rgba(99,102,241,.15);border:1px solid rgba(99,102,241,.3);color:var(--accent2)}
|
||||
.node-data{background:rgba(34,211,238,.1);border:1px solid rgba(34,211,238,.2);color:var(--cyan)}
|
||||
.node-ui{background:rgba(168,85,247,.12);border:1px solid rgba(168,85,247,.25);color:var(--purple)}
|
||||
.node-tool{background:rgba(244,114,182,.1);border:1px solid rgba(244,114,182,.2);color:var(--pink)}
|
||||
.section-divider{border:none;height:1px;background:var(--border);margin:56px 0}
|
||||
::-webkit-scrollbar{width:6px;height:6px}
|
||||
::-webkit-scrollbar-track{background:transparent}
|
||||
::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
|
||||
@media(max-width:900px){.sidebar{display:none}.main{margin-left:0}.hero,.content{padding:30px 24px}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-logo"><h2>🎨 MetonaAI Desktop UI/UX</h2></div>
|
||||
<div class="sidebar-section">研究综述</div>
|
||||
<a href="#trends">📊 2026 UI/UX 趋势</a>
|
||||
<a href="#patterns">🧩 Agent 设计模式</a>
|
||||
<a href="#references">🔗 参考项目</a>
|
||||
<div class="sidebar-section">MetonaToast</div>
|
||||
<a href="#metona-toast">📢 项目分析</a>
|
||||
<a href="#toast-integration">⚙️ 集成方案</a>
|
||||
<div class="sidebar-section">UI 设计</div>
|
||||
<a href="#layout">🏗️ 整体布局</a>
|
||||
<a href="#chat-panel">💬 聊天面板</a>
|
||||
<a href="#trace-viewer">🔍 Trace Viewer</a>
|
||||
<a href="#tool-call">🔧 工具调用 UI</a>
|
||||
<a href="#settings">⚙️ 设置面板</a>
|
||||
<div class="sidebar-section">组件系统</div>
|
||||
<a href="#component-tree">🌲 组件树</a>
|
||||
<a href="#theme-system">🎨 主题系统</a>
|
||||
<a href="#animation">✨ 动效规范</a>
|
||||
</nav>
|
||||
|
||||
<div class="main">
|
||||
|
||||
<div class="hero">
|
||||
<h1>MetonaAI Desktop — UI/UX 设计集成方案</h1>
|
||||
<p>综合 2026 年 AI Agent 交互设计前沿趋势 + MetonaToast 组件库研究成果,产出 MetonaAI Desktop 完整的用户界面设计方案,确保"可见思维、可控行动、可信结果"。</p>
|
||||
<div class="hero-meta">
|
||||
<span>📊 研究来源: <strong>Fuselab Creative / Ant Design X / A2UI / Hermes Agent</strong></span>
|
||||
<span>📦 集成组件: <strong>MetonaToast v2.0.0</strong></span>
|
||||
</div>
|
||||
<div class="note-box" style="margin-top:16px">
|
||||
<strong>📋 文档层级:</strong>本文档是 <strong>用户界面与交互设计的权威定义</strong>,与《构建指南》第九章(React 前端)对应。冲突时以本文档为准。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
|
||||
<!-- ===== 行业趋势 ===== -->
|
||||
<section class="api-section" id="trends">
|
||||
<h2>📊 2026 AI Agent UI/UX 趋势综述</h2>
|
||||
<p class="desc">通过分析 Fuselab Creative、UX Tigers、Ant Design X、Google A2UI、The Skins Factory 等权威来源,提炼出 6 大核心趋势:</p>
|
||||
|
||||
<table class="spec">
|
||||
<tr><th>#</th><th>趋势</th><th>核心要求</th><th>Metona 对应</th></tr>
|
||||
<tr>
|
||||
<td class="f1">1</td>
|
||||
<td><strong>可解释 AI(Explainable AI)</strong></td>
|
||||
<td>用户必须看到 Agent 在<strong>想什么</strong>、<strong>做什么</strong>、<strong>为什么</strong></td>
|
||||
<td class="f2">TraceViewer 实时展示 Thought → ToolCall → Observation 全链路</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="f1">2</td>
|
||||
<td><strong>Agentic UX / RICH 范式</strong></td>
|
||||
<td>角色(Role) + 意图(Intention) + 会话(Conversation) + 混合界面(Hybrid UI)</td>
|
||||
<td class="f2">SOUL.md 定义角色 + Chat 对话 + Tool Panel 混合 UI</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="f1">3</td>
|
||||
<td><strong>透明与可控</strong></td>
|
||||
<td>每步提供 override 控制、用户可随时中断/修正</td>
|
||||
<td class="f2">Abort 按钮、工具调用前确认弹窗、参数可编辑</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="f1">4</td>
|
||||
<td><strong>A2UI — AI 说出 UI</strong></td>
|
||||
<td>Agent 以声明式 JSON 描述 UI 意图,客户端原生渲染</td>
|
||||
<td class="f2">预留 A2UI 接口,Agent 可动态生成卡片/表格/图表</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="f1">5</td>
|
||||
<td><strong>多模态混合界面</strong></td>
|
||||
<td>文本 + 代码 + 图表 + 图片 + 3D 混合呈现</td>
|
||||
<td class="f2">MessageItem 支持 Markdown + 代码高亮 + 图片 + Mermaid 图表</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="f1">6</td>
|
||||
<td><strong>优雅错误恢复</strong></td>
|
||||
<td>错误不是终点,是引导用户修正的入口</td>
|
||||
<td class="f2">MetonaToast error/warning 通知 + 错误上下文面板</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="note-box">
|
||||
<strong>💡 Fuselab Creative 核心论断:</strong>"Agent UX 与传统 UI 的根本区别在于 — Agent 界面必须传达系统正在做什么、解释其推理逻辑、在每一步提供覆盖控制、并从错误中优雅恢复。设计挑战是在保持用户信任的同时允许系统自主操作。"
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="section-divider">
|
||||
|
||||
<!-- ===== 设计模式 ===== -->
|
||||
<section class="api-section" id="patterns">
|
||||
<h2>🧩 Agent 交互设计模式</h2>
|
||||
|
||||
<h3>Ant Design X — RICH 范式</h3>
|
||||
<table class="spec">
|
||||
<tr><th>维度</th><th>含义</th><th>Metona 实现</th></tr>
|
||||
<tr><td class="f1">Role(角色)</td><td>Agent 的身份、性格、能力边界</td><td class="f2">SOUL.md → System Prompt RoleDefinition</td></tr>
|
||||
<tr><td class="f1">Intention(意图)</td><td>理解用户目标,主动规划</td><td class="f2">ReAct Loop → Thought 思考过程实时展示</td></tr>
|
||||
<tr><td class="f1">Conversation(会话)</td><td>多轮对话 + 历史上下文</td><td class="f2">ChatPanel + Session 管理 + Memory 检索</td></tr>
|
||||
<tr><td class="f1">Hybrid UI(混合界面)</td><td>对话 + 传统 UI 混合</td><td class="f2">Chat + Tool Panel + TraceViewer + Settings 同屏</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>Agent UI 五大核心面板</h3>
|
||||
<div class="arch-diagram">
|
||||
<pre>
|
||||
<span class="node node-core">1. 对话面板</span> → <span class="node node-data">2. 思维面板</span> → <span class="node node-tool">3. 工具面板</span> → <span class="node node-ui">4. 工作区面板</span> → <span class="node node-core">5. 状态栏</span>
|
||||
|
||||
核心交互链路: 用户输入 → Agent思考 → 工具执行 → 结果展示 → 反馈通知
|
||||
│ │ │ │
|
||||
实时流式 折叠/展开 参数+耗时 Markdown渲染
|
||||
│ │ │ │
|
||||
MetonaToast TraceViewer ToolCallCard MessageItem</pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="section-divider">
|
||||
|
||||
<!-- ===== MetonaToast 分析 ===== -->
|
||||
<section class="api-section" id="metona-toast">
|
||||
<h2>📢 MetonaToast 项目分析</h2>
|
||||
<p class="desc">
|
||||
<code>metona-toast</code> v2.0.0 — 你的自研通知组件库,已发布到 npm。
|
||||
纯 JavaScript,零依赖,gzip <10KB,设计精致。
|
||||
</p>
|
||||
|
||||
<table class="spec">
|
||||
<tr><th>特性</th><th>详情</th><th>Metona Desktop 用途</th></tr>
|
||||
<tr><td class="f1">80+ 图标</td><td class="f2">success/error/warning/info/loading 及扩展</td><td>工具结果通知、LLM 错误提示、操作确认</td></tr>
|
||||
<tr><td class="f1">11 种动画</td><td class="f2">slide/fade/scale/bounce/flip/rotate/zoom/slideUp/Down/Left/Right</td><td>用 <strong>bounce</strong> 表示重要通知,<strong>fade</strong> 表示信息</td></tr>
|
||||
<tr><td class="f1">主题系统</td><td class="f2">light/dark/auto/warm + registerTheme()</td><td>与 Electron 暗色模式无缝同步,auto 跟随系统</td></tr>
|
||||
<tr><td class="f1">国际化</td><td class="f2">zh-CN / en-US + addTranslations()</td><td>根据用户系统语言自动切换</td></tr>
|
||||
<tr><td class="f1">插件系统</td><td class="f2">keyboard(ESC关闭)/persistence(localStorage)/accessibility</td><td>keyboard 插件让用户 ESC 关闭所有通知</td></tr>
|
||||
<tr><td class="f1">可拖拽关闭</td><td class="f2">Pointer Events 拖拽,旋转+透明反馈</td><td>自然手势关闭,符合现代交互习惯</td></tr>
|
||||
<tr><td class="f1">毛玻璃效果</td><td class="f2">backdrop-filter: blur(14px) saturate(180%)</td><td>与 Electron 窗口背景融合,有层次感</td></tr>
|
||||
<tr><td class="f1">链式调用</td><td class="f2">loading()→success() / loading()→error()</td><td>工具执行中显示 loading,完成后转为 success/error</td></tr>
|
||||
<tr><td class="f1">confirm/prompt</td><td class="f2">对话框式确认和输入</td><td>高风险工具调用前的用户确认</td></tr>
|
||||
<tr><td class="f1">progress/countdown</td><td class="f2">进度条和倒计时</td><td>长时间操作(模型下载、文件处理)</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="api-section" id="toast-integration">
|
||||
<h2>⚙️ MetonaToast 集成方案</h2>
|
||||
|
||||
<h3>安装与配置</h3>
|
||||
<div class="code-block">
|
||||
<pre><span class="hl-cm">// electron/main.ts — 主进程不直接使用(Node 环境无 DOM)</span>
|
||||
<span class="hl-cm">// src/main.tsx — React 渲染进程入口</span>
|
||||
<span class="hl-kw">import</span> MeToast <span class="hl-kw">from</span> <span class="hl-str">'metona-toast'</span>;
|
||||
|
||||
<span class="hl-cm">// 全局配置(应用启动时执行一次)</span>
|
||||
MeToast.<span class="hl-fn">configure</span>({
|
||||
<span class="hl-prop">position</span>: <span class="hl-str">'top-right'</span>,
|
||||
<span class="hl-prop">duration</span>: <span class="hl-num">4000</span>,
|
||||
<span class="hl-prop">max</span>: <span class="hl-num">6</span>,
|
||||
<span class="hl-prop">theme</span>: <span class="hl-str">'auto'</span>, <span class="hl-cm">// 跟随系统暗色模式</span>
|
||||
<span class="hl-prop">animation</span>: <span class="hl-str">'slide'</span>,
|
||||
<span class="hl-prop">pauseOnHover</span>: <span class="hl-kw">true</span>,
|
||||
<span class="hl-prop">closeOnClick</span>: <span class="hl-kw">true</span>,
|
||||
<span class="hl-prop">showProgress</span>: <span class="hl-kw">true</span>,
|
||||
<span class="hl-prop">draggable</span>: <span class="hl-kw">true</span>,
|
||||
<span class="hl-prop">locale</span>: <span class="hl-str">'zh-CN'</span>,
|
||||
});
|
||||
|
||||
<span class="hl-cm">// 安装插件</span>
|
||||
MeToast.<span class="hl-fn">use</span>(<span class="hl-str">'keyboard'</span>); <span class="hl-cm">// ESC 关闭所有</span>
|
||||
MeToast.<span class="hl-fn">use</span>(<span class="hl-str">'persistence'</span>); <span class="hl-cm">// 配置持久化</span>
|
||||
MeToast.<span class="hl-fn">use</span>(<span class="hl-str">'accessibility'</span>); <span class="hl-cm">// 屏幕阅读器</span></pre>
|
||||
</div>
|
||||
|
||||
<h3>场景映射表</h3>
|
||||
<table class="spec">
|
||||
<tr><th>Metona 场景</th><th>Toast 调用</th><th>动画</th></tr>
|
||||
<tr><td>工具执行成功</td><td class="f1"><code>MeToast.success('文件读取完成')</code></td><td class="f2">slide</td></tr>
|
||||
<tr><td>工具执行失败</td><td class="f1"><code>MeToast.error('网络请求超时')</code></td><td class="f2">bounce</td></tr>
|
||||
<tr><td>权限校验被拒</td><td class="f1"><code>MeToast.warning('需要权限确认')</code></td><td class="f2">scale</td></tr>
|
||||
<tr><td>LLM 开始推理</td><td class="f1"><code>MeToast.info('正在思考...', {duration:0})</code></td><td class="f2">fade</td></tr>
|
||||
<tr><td>Session 保存</td><td class="f1"><code>MeToast.success('会话已保存')</code></td><td class="f2">slide</td></tr>
|
||||
<tr><td>MCP Server 连接</td><td class="f1"><code>MeToast.success('MCP 已连接', {title:'filesystem'})</code></td><td class="f2">slideUp</td></tr>
|
||||
<tr><td>上下文压缩</td><td class="f1"><code>MeToast.info('上下文已压缩', {duration:2000})</code></td><td class="f2">fade</td></tr>
|
||||
<tr><td>模型下载进度</td><td class="f1"><code>MeToast.progress('下载中...').setProgress(60)</code></td><td class="f2">slide</td></tr>
|
||||
<tr><td>高风险操作确认</td><td class="f1"><code>MeToast.confirm('确定删除文件?')</code></td><td class="f2">scale</td></tr>
|
||||
</table>
|
||||
|
||||
<div class="note-box">
|
||||
<strong>💡 主进程通知桥接:</strong>Electron 主进程(Node.js 环境)无法直接调用 DOM Toast。通过 IPC 发送
|
||||
<code>mainWindow.webContents.send('toast:show', { type:'success', message:'...' })</code>,
|
||||
渲染进程监听并调用 <code>MeToast[event.type](event.message)</code>。
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="section-divider">
|
||||
|
||||
<!-- ===== 整体布局 ===== -->
|
||||
<section class="api-section" id="layout">
|
||||
<h2>🏗️ 整体布局设计</h2>
|
||||
<p class="desc">参考 Claude Code / Cursor / Hermes Agent 桌面版的布局设计,采用经典的 IDE 式三栏布局。</p>
|
||||
|
||||
<div class="arch-diagram">
|
||||
<pre>
|
||||
<span class="hl-cm">┌──────────────────────────────────────────────────────────────┐</span>
|
||||
<span class="hl-cm">│ Header Bar [🔮 MetonaAI Desktop] [会话] [设置] [— □ ✕] │</span>
|
||||
<span class="hl-cm">├──────────┬────────────────────────────────┬──────────────────┤</span>
|
||||
<span class="hl-cm">│ │ │ │</span>
|
||||
<span class="hl-cm">│ Sidebar │ Main Chat Area │ Detail Panel │</span>
|
||||
<span class="hl-cm">│ │ │ (可折叠) │</span>
|
||||
<span class="hl-cm">│ ┌──────┐ │ ┌──────────────────────────┐ │ ┌──────────────┐│</span>
|
||||
<span class="hl-cm">│ │ 会话 │ │ │ [Agent] 💭 思考过程... │ │ │ Trace Viewer ││</span>
|
||||
<span class="hl-cm">│ │ 列表 │ │ │ [Agent] 🔧 read_file │ │ │ ││</span>
|
||||
<span class="hl-cm">│ │ │ │ │ [Agent] 📄 文件内容 │ │ │ #1 THINKING ││</span>
|
||||
<span class="hl-cm">│ │ + 新建│ │ │ [Agent] 结论: ... │ │ │ #2 PARSING ││</span>
|
||||
<span class="hl-cm">│ │ │ │ │ │ │ │ #3 EXECUTING ││</span>
|
||||
<span class="hl-cm">│ │ ──── │ │ │ │ │ │ #4 OBSERVING ││</span>
|
||||
<span class="hl-cm">│ │ 工具 │ │ │ │ │ │ #5 REFLECTING ││</span>
|
||||
<span class="hl-cm">│ │ 管理 │ │ │ │ │ │ #6 COMPRESSING ││</span>
|
||||
<span class="hl-cm">│ │ 工具 │ │ ├──────────────────────────┤ │ ├──────────────┤│</span>
|
||||
<span class="hl-cm">│ │ 管理 │ │ │ [输入框..............] [发送] │ │ │ Token 用量 ││</span>
|
||||
<span class="hl-cm">│ │ │ │ │ [📎文件] [🔧工具] [⚙️] │ │ │ 输入: 1240 ││</span>
|
||||
<span class="hl-cm">│ │ ──── │ │ └──────────────────────────┘ │ │ 输出: 350 ││</span>
|
||||
<span class="hl-cm">│ │ 记忆 │ │ │ └──────────────┘│</span>
|
||||
<span class="hl-cm">│ │ 搜索 │ │ │ │</span>
|
||||
<span class="hl-cm">│ └──────┘ │ │ │</span>
|
||||
<span class="hl-cm">├──────────┴────────────────────────────────┴──────────────────┤</span>
|
||||
<span class="hl-cm">│ Status Bar 🟢 Agent Ready | deepseek-v4-pro | Tokens: 1.2K │</span>
|
||||
<span class="hl-cm">└──────────────────────────────────────────────────────────────┘</span>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<table class="spec">
|
||||
<tr><th>区域</th><th>宽度</th><th>内容</th></tr>
|
||||
<tr><td class="f1">Header Bar</td><td class="f2">全宽</td><td>Logo + 窗口控制 + 快捷操作</td></tr>
|
||||
<tr><td class="f1">Sidebar (可折叠)</td><td class="f2">260px</td><td>会话列表、工具管理、记忆搜索、MCP 管理</td></tr>
|
||||
<tr><td class="f1">Main Chat Area</td><td class="f2">弹性</td><td>消息流 + 输入框 + 流式渲染</td></tr>
|
||||
<tr><td class="f1">Detail Panel (可折叠)</td><td class="f2">320px</td><td>TraceViewer + Token 用量 + 工具状态</td></tr>
|
||||
<tr><td class="f1">Status Bar</td><td class="f2">全宽</td><td>Agent 状态、Provider 信息、Token 统计</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>布局模式</h3>
|
||||
<p class="desc">为适应不同场景和屏幕尺寸,提供三种布局模式:</p>
|
||||
<table class="spec">
|
||||
<tr><th>模式</th><th>Sidebar</th><th>DetailPanel</th><th>Chat 区域</th><th>触发方式</th></tr>
|
||||
<tr><td class="f1">默认模式</td><td class="f2">展开 260px</td><td class="f2">展开 320px</td><td class="f2">弹性(约 1340px @1920)</td><td class="f2">默认状态</td></tr>
|
||||
<tr><td class="f1">紧凑模式</td><td class="f2">折叠 56px 图标条</td><td class="f2">折叠 48px 图标条</td><td class="f2">弹性(约 1816px @1920)</td><td class="f2"><code>Cmd/Ctrl+B</code> + <code>Cmd/Ctrl+J</code></td></tr>
|
||||
<tr><td class="f1">专注模式</td><td class="f2">隐藏</td><td class="f2">隐藏</td><td class="f2">全宽(约 1920px)</td><td class="f2"><code>Cmd/Ctrl+Shift+F</code></td></tr>
|
||||
</table>
|
||||
<div class="note-box">
|
||||
<strong>💡 布局设计原则:</strong>Sidebar 和 DetailPanel 均为<strong>可折叠抽屉式</strong>,默认展开完整面板,折叠后只保留图标条(悬停可预览,点击展开为浮层)。Chat 区域在两侧折叠时获得最大宽度,适合宽屏代码展示和 Markdown 渲染。
|
||||
</div>
|
||||
|
||||
<h3>消息布局:全宽卡片流</h3>
|
||||
<p class="desc">放弃传统聊天应用的左右分栏气泡布局,采用<strong>全宽卡片流</strong>布局(参考 Claude / ChatGPT)。Agent 的核心交互是任务执行而非聊天,工具调用结果、代码块、表格都需要全宽展示。</p>
|
||||
<table class="spec">
|
||||
<tr><th>消息类型</th><th>布局</th><th>对齐</th><th>宽度</th></tr>
|
||||
<tr><td class="f1">User Message</td><td class="f2">浅色背景卡片 + 用户头像</td><td class="f2">左对齐</td><td class="f2">全宽(max-width: 768px 居中)</td></tr>
|
||||
<tr><td class="f1">Agent Thought</td><td class="f2">缩进灰色虚线框</td><td class="f2">左对齐</td><td class="f2">全宽(可折叠)</td></tr>
|
||||
<tr><td class="f1">Agent ToolCall</td><td class="f2">缩进彩色卡片 + 状态图标</td><td class="f2">左对齐</td><td class="f2">全宽</td></tr>
|
||||
<tr><td class="f1">Agent Answer</td><td class="f2">无背景或极浅背景 + Agent 头像</td><td class="f2">左对齐</td><td class="f2">全宽 Markdown 渲染</td></tr>
|
||||
</table>
|
||||
<div class="note-box">
|
||||
<strong>💡 为什么不用气泡布局:</strong>传统左右分栏气泡将用户消息右对齐,导致 Agent 的长回复(含代码块、表格、图表)被压缩到一半宽度。全宽卡片流让所有内容都能利用完整宽度,阅读体验显著提升。用户消息虽然全宽,但通过 <code>max-width: 768px</code> 居中限制可读性。
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== 聊天面板 ===== -->
|
||||
<section class="api-section" id="chat-panel">
|
||||
<h2>💬 聊天面板设计</h2>
|
||||
|
||||
<h3>消息类型矩阵</h3>
|
||||
<table class="spec">
|
||||
<tr><th>消息角色</th><th>视觉样式</th><th>内容渲染</th><th>交互</th></tr>
|
||||
<tr>
|
||||
<td class="f1">User Message</td>
|
||||
<td class="f2">全宽 / 浅色背景卡片 / 用户头像 / 左对齐</td>
|
||||
<td>纯文本 + 附件缩略图</td>
|
||||
<td>双击编辑 / 右键菜单</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="f1">Agent Thought</td>
|
||||
<td class="f2">左对齐 / 虚线边框 / 💭 图标 / 可折叠</td>
|
||||
<td>纯文本(等宽字体)</td>
|
||||
<td>默认折叠,点击展开</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="f1">Agent Tool Call</td>
|
||||
<td class="f2">左对齐 / 紫色边框 / 🔧 图标</td>
|
||||
<td>工具名 + JSON 参数 + 状态图标</td>
|
||||
<td>悬停显示参数详情、点击跳转 Trace</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="f1">Agent Tool Result</td>
|
||||
<td class="f2">左对齐 / 青色边框 / 📄 图标</td>
|
||||
<td>结果摘要 + 文件路径/行数/耗时</td>
|
||||
<td>点击打开完整结果</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="f1">Agent Answer</td>
|
||||
<td class="f2">左对齐 / 无背景 / Agent 头像</td>
|
||||
<td>Markdown + 代码高亮 + 图表</td>
|
||||
<td>代码块一键复制、链接可点击</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="f1">System Notification</td>
|
||||
<td class="f2">居中 / 灰色 / 小字</td>
|
||||
<td>时间戳 / 状态变化</td>
|
||||
<td>不可交互</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>流式渲染策略</h3>
|
||||
<p class="desc">
|
||||
使用 <code>requestAnimationFrame</code> + 节流(每 16ms 一次)平滑更新 DOM。
|
||||
Thought 内容进入时显示 <code>💭 思考中...</code> 加载动画,收到 <code>thinking_end</code> 后折叠。
|
||||
工具调用使用 <code>ToolCallCard</code> 骨架屏先行占位,结果到达时填充。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- ===== Trace Viewer ===== -->
|
||||
<section class="api-section" id="trace-viewer">
|
||||
<h2>🔍 Trace Viewer 设计</h2>
|
||||
<p class="desc">置于右侧 Detail Panel,以时间轴方式展示每次 ReAct 迭代的完整过程。用户可展开每一步查看细节。</p>
|
||||
|
||||
<div class="arch-diagram">
|
||||
<pre>
|
||||
<span class="hl-cm">┌─ Trace Viewer ──────────────────────────────────┐</span>
|
||||
<span class="hl-cm">│ Session: 数据分析任务 迭代: 2/20 │</span>
|
||||
<span class="hl-cm">│─────────────────────────────────────────────────│</span>
|
||||
<span class="hl-cm">│ │</span>
|
||||
<span class="hl-cm">│ ▼ #1 THINKING 1.2s 120 tok │</span>
|
||||
<span class="hl-cm">│ 💭 "需要先读取目标文件..." │</span>
|
||||
<span class="hl-cm">│ │</span>
|
||||
<span class="hl-cm">│ ▶ #2 PARSING 0.1s │</span>
|
||||
<span class="hl-cm">│ │</span>
|
||||
<span class="hl-cm">│ ▼ #3 EXECUTING 0.3s │</span>
|
||||
<span class="hl-cm">│ 🔧 read_file: /data.csv ✓ 5ms │</span>
|
||||
<span class="hl-cm">│ 📄 1000 rows, 3 cols │</span>
|
||||
<span class="hl-cm">│ │</span>
|
||||
<span class="hl-cm">│ ▶ #4 OBSERVING 0.1s │</span>
|
||||
<span class="hl-cm">│ │</span>
|
||||
<span class="hl-cm">│ ▶ #5 REFLECTING 0.2s │</span>
|
||||
<span class="hl-cm">│ 💭 "数据结构清晰,可以进行下一步分析..." │</span>
|
||||
<span class="hl-cm">│ │</span>
|
||||
<span class="hl-cm">│ ▶ #6 COMPRESSING 0.1s │</span>
|
||||
<span class="hl-cm">│ 📦 上下文已压缩 (3.2K → 1.8K) │</span>
|
||||
<span class="hl-cm">│ │</span>
|
||||
<span class="hl-cm">│ ○ #7 THINKING (当前) │</span>
|
||||
<span class="hl-cm">│ ⏳ 正在调用 LLM... │</span>
|
||||
<span class="hl-cm">│ │</span>
|
||||
<span class="hl-cm">│─────────────────────────────────────────────────│</span>
|
||||
<span class="hl-cm">│ Token: 入 1.2K | 出 0.4K | 总计 1.6K │</span>
|
||||
<span class="hl-cm">│ 耗时: 2.3s | Provider: DeepSeek v4-pro │</span>
|
||||
<span class="hl-cm">└─────────────────────────────────────────────────┘</span>
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== 工具调用 UI ===== -->
|
||||
<section class="api-section" id="tool-call">
|
||||
<h2>🔧 工具调用 UI</h2>
|
||||
|
||||
<h3>ToolCallCard 状态机</h3>
|
||||
<table class="spec">
|
||||
<tr><th>状态</th><th>图标</th><th>颜色</th><th>行为</th></tr>
|
||||
<tr><td class="f1">pending</td><td class="f2">⏳ 旋转</td><td class="f2" style="color:var(--amber)">#fbbf24</td><td>骨架屏占位,等待工具执行</td></tr>
|
||||
<tr><td class="f1">executing</td><td class="f2">🔧 脉冲</td><td class="f2" style="color:var(--purple)">#a855f7</td><td>显示参数摘要</td></tr>
|
||||
<tr><td class="f1">success</td><td class="f2">✅</td><td class="f2" style="color:var(--green)">#34d399</td><td>显示结果摘要 + 执行耗时</td></tr>
|
||||
<tr><td class="f1">error</td><td class="f2">❌</td><td class="f2" style="color:var(--red)">#f87171</td><td>显示错误信息 + 重试按钮</td></tr>
|
||||
<tr><td class="f1">blocked</td><td class="f2">🚫</td><td class="f2" style="color:var(--orange)">#fb923c</td><td>显示拒绝原因 + 手动授权按钮</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>高风险操作确认流程</h3>
|
||||
<div class="code-block">
|
||||
<pre><span class="hl-cm"># 用户在聊天面板看到:</span>
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 🔧 write_file │
|
||||
│ 目标: /home/user/config.yaml │
|
||||
│ 风险: ⚠️ MEDIUM — 将覆盖已有文件 │
|
||||
│ │
|
||||
│ [查看差异] [✅ 确认执行] [❌ 取消] │
|
||||
└─────────────────────────────────────────┘
|
||||
|
||||
<span class="hl-cm"># 用户点击「确认执行」后:</span>
|
||||
MeToast.<span class="hl-fn">success</span>({ <span class="hl-prop">title</span>: <span class="hl-str">'已授权'</span>, <span class="hl-prop">message</span>: <span class="hl-str">'write_file 正在执行...'</span> });
|
||||
|
||||
<span class="hl-cm"># 用户点击「取消」后:</span>
|
||||
MeToast.<span class="hl-fn">warning</span>(<span class="hl-str">'操作已取消'</span>);</pre>
|
||||
</div>
|
||||
|
||||
<h3>确认流程优化:信任会话</h3>
|
||||
<p class="desc">为避免密集操作场景(如批量重构代码)下逐次确认打断流程,引入<strong>信任会话</strong>机制:</p>
|
||||
<table class="spec">
|
||||
<tr><th>模式</th><th>行为</th><th>触发方式</th></tr>
|
||||
<tr><td class="f1">默认模式</td><td class="f2">MEDIUM+ 操作逐次确认</td><td class="f2">默认</td></tr>
|
||||
<tr><td class="f1">信任会话</td><td class="f2">MEDIUM 操作不逐次确认,会话结束时生成操作摘要供审查</td><td class="f2">设置中开启</td></tr>
|
||||
<tr><td class="f1">同类免确认</td><td class="f2">同一会话内同类操作(如 write_file)首次确认后不再确认</td><td class="f2">确认弹窗勾选「本次会话不再确认同类操作」</td></tr>
|
||||
<tr><td class="f1">批量确认</td><td class="f2">连续多个同类操作合并为一个确认弹窗</td><td class="f2">Agent 连续发起 ≥3 个同类操作时自动触发</td></tr>
|
||||
</table>
|
||||
<div class="warn-box">
|
||||
<strong>⚠ HIGH 级别操作始终需要确认</strong>,不受信任会话影响。CRITICAL 级别需要双人复核(未来计划)。
|
||||
</div>
|
||||
<div class="note-box">
|
||||
<strong>💡 确认弹窗增强:</strong>增加「查看命令」展开区域,显示完整的工具调用参数(JSON 格式),让用户做出知情决策。
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== 设置面板 ===== -->
|
||||
<section class="api-section" id="settings">
|
||||
<h2>⚙️ 设置面板</h2>
|
||||
<p class="desc">分 Tab 展示,配置变更即时持久化到 SQLite 数据库。</p>
|
||||
|
||||
<table class="spec">
|
||||
<tr><th>Tab</th><th>内容</th></tr>
|
||||
<tr><td class="f1">LLM 配置</td><td class="f2">Provider 选择、API Key、模型名称、参数滑块(temperature/maxTokens/contextWindow)</td></tr>
|
||||
<tr><td class="f1">Agent 配置</td><td class="f2">最大迭代次数、超时、Thinking 开关、反思模式、压缩阈值</td></tr>
|
||||
<tr><td class="f1">工具管理</td><td class="f2">9 个基础工具开关、风险级别配置、路径白名单、命令黑名单</td></tr>
|
||||
<tr><td class="f1">MCP 服务</td><td class="f2">Server 列表、添加/删除/启停、连接状态指示灯</td></tr>
|
||||
<tr><td class="f1">外观</td><td class="f2">主题(light/dark/auto)、字体大小、消息密度、动画开关</td></tr>
|
||||
<tr><td class="f1">日志与数据</td><td class="f2">日志级别、数据库位置、数据导出/清理、使用统计</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<hr class="section-divider">
|
||||
|
||||
<!-- ===== 组件树 ===== -->
|
||||
<section class="api-section" id="component-tree">
|
||||
<h2>🌲 组件树</h2>
|
||||
|
||||
<div class="arch-diagram">
|
||||
<pre>
|
||||
<span class="hl-cm">AppLayout</span>
|
||||
├── <span class="hl-box">Header</span> (Logo + SessionSelector + SettingsButton)
|
||||
├── <span class="hl-box">Sidebar</span>
|
||||
│ ├── SessionList (会话列表 + 新建/搜索/分组/置顶)
|
||||
│ ├── ToolManager (工具状态 + MCP 管理)
|
||||
│ └── MemorySearch (记忆检索面板)
|
||||
├── <span class="hl-box">ChatPanel</span>
|
||||
│ ├── MessageList (虚拟滚动 · 全宽卡片流)
|
||||
│ │ ├── MessageItem (User/Assistant/Tool/System)
|
||||
│ │ ├── ThoughtBlock (可折叠思考内容)
|
||||
│ │ ├── ToolCallCard (工具调用卡片)
|
||||
│ │ └── ToolResultBlock (工具结果块)
|
||||
│ ├── StreamingIndicator (流式加载)
|
||||
│ └── ChatInput (输入框 + 附件 + 工具选择 + 上下文指示器)
|
||||
├── <span class="hl-box">DetailPanel</span> (右侧,可折叠为图标条)
|
||||
│ ├── TraceViewer (ReAct 迭代追踪)
|
||||
│ ├── TokenUsage (Token 统计图表)
|
||||
│ └── AgentMonitor (Agent 状态指示)
|
||||
├── <span class="hl-box">StatusBar</span> (状态栏)
|
||||
├── <span class="hl-box">ToastContainer</span> (MetonaToast 通知层)
|
||||
├── <span class="hl-box">OnboardingWizard</span> (首次使用引导)
|
||||
└── <span class="hl-box">CommandPalette</span> (Cmd+K 快速搜索)
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== 首次使用引导 ===== -->
|
||||
<section class="api-section" id="onboarding">
|
||||
<h2>👋 首次使用引导(Onboarding)</h2>
|
||||
<p class="desc">首次启动应用时自动展示引导向导,帮助用户完成初始配置。向导完成后不再显示(可通过设置重新触发)。</p>
|
||||
|
||||
<h3>引导流程</h3>
|
||||
<div class="flow-step" style="display:flex;gap:14px;margin-bottom:14px;align-items:flex-start"><span style="min-width:28px;height:28px;background:var(--accent);color:#000;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;flex-shrink:0">1</span><span style="font-size:14px"><strong>欢迎页</strong>:Metona 是什么、能做什么(3 句话 + 动图展示)</span></div>
|
||||
<div class="flow-step" style="display:flex;gap:14px;margin-bottom:14px;align-items:flex-start"><span style="min-width:28px;height:28px;background:var(--accent);color:#000;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;flex-shrink:0">2</span><span style="font-size:14px"><strong>配置 LLM</strong>:选择 Provider(DeepSeek/Agnes/Ollama)→ 输入 API Key 或 Ollama 地址 → 测试连接</span></div>
|
||||
<div class="flow-step" style="display:flex;gap:14px;margin-bottom:14px;align-items:flex-start"><span style="min-width:28px;height:28px;background:var(--accent);color:#000;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;flex-shrink:0">3</span><span style="font-size:14px"><strong>自定义 Agent</strong>:选择预设角色或自定义 SOUL.md → 填写 USERS.md(技术栈、偏好)</span></div>
|
||||
<div class="flow-step" style="display:flex;gap:14px;margin-bottom:14px;align-items:flex-start"><span style="min-width:28px;height:28px;background:var(--accent);color:#000;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;flex-shrink:0">4</span><span style="font-size:14px"><strong>工作空间</strong>:确认默认路径 <code>~/MetonaWorkspaces/default/</code> 或选择自定义路径</span></div>
|
||||
<div class="flow-step" style="display:flex;gap:14px;margin-bottom:14px;align-items:flex-start"><span style="min-width:28px;height:28px;background:var(--accent);color:#000;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;flex-shrink:0">5</span><span style="font-size:14px"><strong>开始使用</strong>:展示一个示例对话,让用户感受 Agent 能力</span></div>
|
||||
|
||||
<div class="note-box">
|
||||
<strong>💡 设计要点:</strong>每一步都可以「跳过」,不强制完成。跳过配置 LLM 的用户会在首次发送消息时提示配置。向导状态记录在 <code>app_config</code> 表中(<code>onboarding.completed = true/false</code>)。
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== 快捷键 ===== -->
|
||||
<section class="api-section" id="shortcuts">
|
||||
<h2>⌨️ 快捷键体系</h2>
|
||||
<p class="desc">快捷键是桌面 Agent 应用的核心生产力工具。所有快捷键支持 macOS(Cmd)和 Windows/Linux(Ctrl)。</p>
|
||||
|
||||
<table class="spec">
|
||||
<tr><th>快捷键</th><th>功能</th><th>分类</th></tr>
|
||||
<tr><td class="f1"><code>Cmd/Ctrl + N</code></td><td>新建会话</td><td class="f2">会话</td></tr>
|
||||
<tr><td class="f1"><code>Cmd/Ctrl + K</code></td><td>快速搜索(会话/记忆/文件)</td><td class="f2">全局</td></tr>
|
||||
<tr><td class="f1"><code>Cmd/Ctrl + Enter</code></td><td>发送消息</td><td class="f2">输入</td></tr>
|
||||
<tr><td class="f1"><code>Cmd/Ctrl + Shift + Enter</code></td><td>换行(多行输入)</td><td class="f2">输入</td></tr>
|
||||
<tr><td class="f1"><code>Cmd/Ctrl + .</code></td><td>中断 Agent 执行</td><td class="f2">Agent</td></tr>
|
||||
<tr><td class="f1"><code>Cmd/Ctrl + Shift + F</code></td><td>专注模式(隐藏 Sidebar + DetailPanel)</td><td class="f2">布局</td></tr>
|
||||
<tr><td class="f1"><code>Cmd/Ctrl + B</code></td><td>折叠/展开 Sidebar</td><td class="f2">布局</td></tr>
|
||||
<tr><td class="f1"><code>Cmd/Ctrl + J</code></td><td>折叠/展开 DetailPanel</td><td class="f2">布局</td></tr>
|
||||
<tr><td class="f1"><code>Cmd/Ctrl + ,</code></td><td>打开设置</td><td class="f2">全局</td></tr>
|
||||
<tr><td class="f1"><code>Cmd/Ctrl + [ / ]</code></td><td>历史会话切换(上一个/下一个)</td><td class="f2">会话</td></tr>
|
||||
<tr><td class="f1"><code>Esc</code></td><td>关闭弹窗/取消焦点</td><td class="f2">全局</td></tr>
|
||||
<tr><td class="f1"><code>Cmd/Ctrl + L</code></td><td>聚焦输入框</td><td class="f2">输入</td></tr>
|
||||
<tr><td class="f1"><code>Cmd/Ctrl + Shift + C</code></td><td>复制最后一条 Agent 回复</td><td class="f2">编辑</td></tr>
|
||||
<tr><td class="f1"><code>Cmd/Ctrl + D</code></td><td>切换暗色/亮色主题</td><td class="f2">外观</td></tr>
|
||||
</table>
|
||||
|
||||
<div class="note-box">
|
||||
<strong>💡 输入框智能特性:</strong>
|
||||
<br>• <strong>多行自适应</strong>:输入框默认单行,输入多行时自动扩展(最大 6 行后滚动)
|
||||
<br>• <strong>文件拖拽</strong>:拖拽文件到输入框自动添加为附件,显示文件名 + 大小
|
||||
<br>• <strong>图片粘贴</strong>:粘贴剪贴板图片自动转为 base64 附件
|
||||
<br>• <strong><code>/</code> 命令</strong>:输入 <code>/</code> 弹出命令菜单(<code>/tool</code> 选择工具、<code>/memory</code> 搜索记忆、<code>/clear</code> 清空会话、<code>/export</code> 导出会话)
|
||||
<br>• <strong><code>@</code> 提及</strong>:输入 <code>@</code> 弹出文件/会话提及菜单,快速引用上下文
|
||||
<br>• <strong>上下文指示器</strong>:输入框下方显示当前上下文使用率(如 "Context: 12K / 128K (9%)"),接近上限时变色警告
|
||||
<br>• <strong>草稿自动保存</strong>:未发送的输入内容自动保存,切换会话后恢复
|
||||
</div>
|
||||
|
||||
<h3>右键菜单设计</h3>
|
||||
<table class="spec">
|
||||
<tr><th>对象</th><th>菜单项</th></tr>
|
||||
<tr><td class="f1">消息</td><td class="f2">复制 · 引用回复 · 编辑(仅用户消息) · 删除 · 重新生成(仅 Agent 消息)</td></tr>
|
||||
<tr><td class="f1">工具调用卡片</td><td class="f2">查看参数 · 查看完整结果 · 复制结果 · 重新执行</td></tr>
|
||||
<tr><td class="f1">会话项</td><td class="f2">重命名 · 置顶 · 归档 · 删除 · 导出</td></tr>
|
||||
<tr><td class="f1">代码块</td><td class="f2">复制代码 · 在编辑器中打开</td></tr>
|
||||
<tr><td class="f1">Trace 步骤</td><td class="f2">复制 Thought · 复制工具参数 · 导出步骤详情</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- ===== 主题系统 ===== -->
|
||||
<section class="api-section" id="theme-system">
|
||||
<h2>🎨 主题系统</h2>
|
||||
<p class="desc">双主题引擎:CSS Variables(应用级)+ MetonaToast Theme API(通知级),统一 token 体系。</p>
|
||||
|
||||
<h3>CSS 变量 Token 体系</h3>
|
||||
<div class="code-block">
|
||||
<pre><span class="hl-cm">/* 亮色主题 */</span>
|
||||
:root[data-theme=<span class="hl-str">"light"</span>] {
|
||||
--bg-primary: <span class="hl-str">#ffffff</span>;
|
||||
--bg-secondary: <span class="hl-str">#f8fafc</span>;
|
||||
--bg-tertiary: <span class="hl-str">#f1f5f9</span>;
|
||||
--text-primary: <span class="hl-str">#0f172a</span>;
|
||||
--text-secondary: <span class="hl-str">#334155</span>; <span class="hl-cm">/* 对比度 ~8:1,满足 WCAG AA */</span>
|
||||
--border-color: <span class="hl-str">#e2e8f0</span>;
|
||||
--accent: <span class="hl-str">#6366f1</span>;
|
||||
}
|
||||
|
||||
<span class="hl-cm">/* 暗色主题 */</span>
|
||||
:root[data-theme=<span class="hl-str">"dark"</span>] {
|
||||
--bg-primary: <span class="hl-str">#0f1117</span>;
|
||||
--bg-secondary: <span class="hl-str">#1a1d27</span>;
|
||||
--bg-tertiary: <span class="hl-str">#252836</span>;
|
||||
--text-primary: <span class="hl-str">#e1e4ed</span>;
|
||||
--text-secondary: <span class="hl-str">#64748b</span>; <span class="hl-cm">/* 对比度 ~4.6:1,满足 WCAG AA */</span>
|
||||
--border-color: <span class="hl-str">#2a2d3a</span>;
|
||||
--accent: <span class="hl-str">#818cf8</span>;
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<div class="note-box">
|
||||
<strong>💡 主题同步:</strong>Electron 的 <code>nativeTheme.themeSource</code> 控制 Chromium 的 <code>prefers-color-scheme</code>。
|
||||
MetonaToast 的 <code>theme:'auto'</code> 自动跟随。应用级 CSS 通过 <code>matchMedia('(prefers-color-scheme: dark)')</code> 监听切换。
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== 动效 ===== -->
|
||||
<section class="api-section" id="animation">
|
||||
<h2>✨ 动效规范</h2>
|
||||
|
||||
<table class="spec">
|
||||
<tr><th>场景</th><th>动效</th><th>时长</th><th>缓动</th></tr>
|
||||
<tr><td class="f1">消息进入</td><td class="f2">从下方淡入 + 上移 10px</td><td>300ms</td><td class="f2">ease-out</td></tr>
|
||||
<tr><td class="f1">流式文本</td><td class="f2">逐字出现(CSS typing 模拟)</td><td>—</td><td class="f2">—</td></tr>
|
||||
<tr><td class="f1">Thought 展开</td><td class="f2">max-height 过渡 + 淡入</td><td>250ms</td><td class="f2">ease</td></tr>
|
||||
<tr><td class="f1">Tool 状态切换</td><td class="f2">图标旋转/脉冲 + 边框颜色渐变</td><td>400ms</td><td class="f2">ease-in-out</td></tr>
|
||||
<tr><td class="f1">Sidebar 折叠</td><td class="f2">width 过渡</td><td>300ms</td><td class="f2">cubic-bezier(0.4,0,0.2,1)</td></tr>
|
||||
<tr><td class="f1">Toast 通知</td><td class="f2">11 种(MetonaToast)</td><td>400-650ms</td><td class="f2">cubic-bezier (库内)</td></tr>
|
||||
<tr><td class="f1">加载骨架屏</td><td class="f2">shimmer 光泽扫过</td><td>1.5s loop</td><td class="f2">linear infinite</td></tr>
|
||||
<tr><td class="f1">悬停反馈</td><td class="f2">scale(1.02) + shadow 提升</td><td>150ms</td><td class="f2">ease-out</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>性能保护机制</h3>
|
||||
<p class="desc">动效在低性能设备和用户偏好下自动降级:</p>
|
||||
<table class="spec">
|
||||
<tr><th>条件</th><th>降级策略</th></tr>
|
||||
<tr><td class="f1"><code>prefers-reduced-motion: reduce</code></td><td class="f2">所有动效即时切换(duration: 0ms),流式文本改为闪烁光标</td></tr>
|
||||
<tr><td class="f1">FPS < 30(检测到掉帧)</td><td class="f2">骨架屏 shimmer 改为静态灰色背景,悬停 scale 改为仅 shadow</td></tr>
|
||||
<tr><td class="f1">设置中关闭「动画效果」</td><td class="f2">等同于 <code>prefers-reduced-motion</code>,所有动效禁用</td></tr>
|
||||
</table>
|
||||
<div class="note-box">
|
||||
<strong>💡 流式文本优化:</strong>放弃逐字 DOM 更新方案(性能差),改为 <code>caret-color</code> 闪烁光标 + CSS <code>animation</code> 实现打字机效果。文本内容通过 <code>requestAnimationFrame</code> 批量插入(每帧一次),避免高频 DOM 操作。
|
||||
</div>
|
||||
<div class="note-box">
|
||||
<strong>💡 设置面板增加「动画效果」开关:</strong>三档选项——<code>开启</code>(全动效)/ <code>关闭</code>(禁用所有动效)/ <code>自动跟随系统</code>(默认,读取 <code>prefers-reduced-motion</code>)。配置存储在 <code>app_config</code> 表中(<code>ui.animationMode = 'auto' | 'on' | 'off'</code>)。
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== 错误恢复 ===== -->
|
||||
<section class="api-section" id="error-recovery">
|
||||
<h2>🚧 错误恢复体验</h2>
|
||||
<p class="desc">错误不是终点,是引导用户修正的入口。不同错误类型采用不同的交互策略:</p>
|
||||
|
||||
<table class="spec">
|
||||
<tr><th>错误类型</th><th>UI 展示</th><th>恢复策略</th><th>用户操作</th></tr>
|
||||
<tr><td class="f1">工具执行错误<br>(如文件不存在)</td><td class="f2">内联在聊天流中<br>红色边框卡片</td><td class="f2">Agent 自动分析错误 → 决定重试或换策略</td><td class="f2">通常无需操作;如需用户输入,Agent 在聊天中内联提问</td></tr>
|
||||
<tr><td class="f1">LLM 调用错误<br>(如 API 超时)</td><td class="f2">错误卡片 + 操作按钮</td><td class="f2">等待用户决策</td><td class="f2"><code>[重试]</code> / <code>[切换 Provider]</code> / <code>[查看详情]</code></td></tr>
|
||||
<tr><td class="f1">解析错误<br>(LLM 输出不符合格式)</td><td class="f2">用户无感知<br>(静默重试)</td><td class="f2">静默重试(最多 3 次),超出后显示提示</td><td class="f2">超出重试后显示「模型输出异常」+ <code>[重试]</code></td></tr>
|
||||
<tr><td class="f1">权限拒绝<br>(用户取消操作)</td><td class="f2">灰色卡片</td><td class="f2">Agent 收到取消信号,调整策略</td><td class="f2"><code>[修改权限设置]</code> 链接</td></tr>
|
||||
<tr><td class="f1">网络连接错误</td><td class="f2">StatusBar 红色指示灯 + Toast</td><td class="f2">自动检测网络恢复后提示用户</td><td class="f2"><code>[重试]</code> / <code>[离线模式]</code>(仅 Ollama 可用)</td></tr>
|
||||
</table>
|
||||
<div class="note-box">
|
||||
<strong>💡 所有错误在 TraceViewer 中留痕:</strong>每个错误作为一个独立 Trace 步骤,记录完整的错误信息、堆栈、重试历史。用户可以事后展开查看,不影响正常对话流。
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== 会话管理 ===== -->
|
||||
<section class="api-section" id="session-management">
|
||||
<h2>📁 会话管理</h2>
|
||||
<p class="desc">Sidebar 会话列表支持搜索、分组、置顶、归档,适应长期使用场景。</p>
|
||||
|
||||
<h3>会话列表功能</h3>
|
||||
<table class="spec">
|
||||
<tr><th>功能</th><th>交互方式</th><th>说明</th></tr>
|
||||
<tr><td class="f1">搜索</td><td class="f2">会话列表顶部搜索框</td><td class="f2">按标题和消息内容模糊搜索(<code>fuse.js</code>)</td></tr>
|
||||
<tr><td class="f1">置顶</td><td class="f2">右键菜单 → 置顶</td><td class="f2">置顶会话固定在列表顶部,显示 📌 图标</td></tr>
|
||||
<tr><td class="f1">分组</td><td class="f2">自动按工作空间分组</td><td class="f2">每个工作空间一个折叠组,可展开/收起</td></tr>
|
||||
<tr><td class="f1">归档</td><td class="f2">右键菜单 → 归档</td><td class="f2">归档的会话移入「归档」折叠区,不占主列表空间,可恢复</td></tr>
|
||||
<tr><td class="f1">重命名</td><td class="f2">双击会话标题 / 右键菜单</td><td class="f2">内联编辑,回车保存</td></tr>
|
||||
<tr><td class="f1">导出</td><td class="f2">右键菜单 → 导出</td><td class="f2">导出为 Markdown / JSON</td></tr>
|
||||
<tr><td class="f1">删除</td><td class="f2">右键菜单 → 删除</td><td class="f2">移入回收站(可恢复 30 天)</td></tr>
|
||||
</table>
|
||||
<div class="note-box">
|
||||
<strong>💡 会话项信息:</strong>每个会话项显示——会话标题(自动生成或用户重命名)、最后活跃时间(如「3 分钟前」)、消息数、Agent 状态指示灯(进行中显示绿色脉冲)。
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== 窗口管理 ===== -->
|
||||
<section class="api-section" id="window-management">
|
||||
<h2>🖥️ 窗口管理</h2>
|
||||
<p class="desc">Electron 桌面应用的窗口管理习惯设计:</p>
|
||||
|
||||
<table class="spec">
|
||||
<tr><th>功能</th><th>行为</th><th>配置项</th></tr>
|
||||
<tr><td class="f1">关闭窗口</td><td class="f2">最小化到系统托盘(默认),不退出应用</td><td class="f2"><code>app.closeToTray = true</code>(可关闭)</td></tr>
|
||||
<tr><td class="f1">托盘图标</td><td class="f2">显示 Agent 状态:空闲(白色)/ 思考中(蓝色脉冲)/ 执行中(绿色旋转)</td><td class="f2">—</td></tr>
|
||||
<tr><td class="f1">托盘菜单</td><td class="f2">新建会话 · 显示窗口 · 退出</td><td class="f2">—</td></tr>
|
||||
<tr><td class="f1">多窗口</td><td class="f2">每个工作空间可独立开窗口,窗口标题显示工作空间名称</td><td class="f2"><code>app.multiWindow = true</code></td></tr>
|
||||
<tr><td class="f1">全局快捷键</td><td class="f2"><code>Cmd/Ctrl+Shift+M</code> 从任意应用切换到 Metona 窗口</td><td class="f2">可自定义快捷键</td></tr>
|
||||
<tr><td class="f1">系统通知</td><td class="f2">Agent 完成长时间任务时发送系统通知(点击跳转到对应会话)</td><td class="f2"><code>app.notifications = true</code></td></tr>
|
||||
<tr><td class="f1">开机自启</td><td class="f2">系统启动时自动启动 Metona(最小化到托盘)</td><td class="f2"><code>app.autoStart = false</code></td></tr>
|
||||
</table>
|
||||
<div class="note-box">
|
||||
<strong>💡 托盘图标实现:</strong>使用 <code>electron Tray</code> API,动态切换图标以反映 Agent 状态。托盘图标右键菜单通过 <code>Menu.buildFromTemplate()</code> 构建。全局快捷键使用 <code>globalShortcut.register()</code>。
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div style="text-align:center;padding:40px 0 20px;color:var(--text-dim);font-size:13px;border-top:1px solid var(--border)">
|
||||
<p>🎨 MetonaAI Desktop UI/UX 设计集成方案</p>
|
||||
<p>研究来源: <strong style="color:var(--accent)">Fuselab Creative · Ant Design X · Google A2UI · Hermes Agent · Siyu's Newsletter</strong></p>
|
||||
<p style="margin-top:6px">集成组件: <strong style="color:var(--purple)">MetonaToast v2.0.0</strong> · 版本 <strong style="color:var(--accent2)">v1.0.0</strong> · 2026-06-26</p>
|
||||
</div>
|
||||
|
||||
<!-- Back to Top Button -->
|
||||
<button id="backToTop" onclick="window.scrollTo({top:0,behavior:'smooth'})" style="
|
||||
position:fixed;bottom:30px;right:30px;width:44px;height:44px;border-radius:50%;
|
||||
background:var(--accent);color:#fff;border:none;cursor:pointer;font-size:20px;
|
||||
display:none;align-items:center;justify-content:center;
|
||||
box-shadow:0 4px 12px rgba(99,102,241,.3);transition:transform .2s,opacity .2s;z-index:1000
|
||||
">↑</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll('.sidebar a[href^="#"]').forEach(a=>{a.addEventListener('click',e=>{e.preventDefault();const t=document.querySelector(a.getAttribute('href'));t&&t.scrollIntoView({behavior:'smooth',block:'start'})})});
|
||||
const S=document.querySelectorAll('.api-section'),L=document.querySelectorAll('.sidebar a[href^="#"]'),B=document.getElementById('backToTop');
|
||||
window.addEventListener('scroll',()=>{let c='';S.forEach(s=>{if(window.scrollY>=s.offsetTop-100)c=s.id});L.forEach(a=>{a.classList.toggle('active',a.getAttribute('href')==='#'+c)});if(B)B.style.display=window.scrollY>300?'flex':'none'});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,998 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>MetonaAI-Desktop 架构与交互设计文档 | v1.0</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0f1117;
|
||||
--bg-card: #1a1d27;
|
||||
--bg-code: #12141c;
|
||||
--bg-nav: #141620;
|
||||
--text: #e1e4ed;
|
||||
--text-dim: #8b8fa7;
|
||||
--accent: #06b6d4;
|
||||
--accent2: #22d3ee;
|
||||
--green: #34d399;
|
||||
--orange: #fb923c;
|
||||
--red: #f87171;
|
||||
--amber: #fbbf24;
|
||||
--purple: #a855f7;
|
||||
--border: #2a2d3a;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
background: var(--bg); color: var(--text); line-height: 1.7; display: flex;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
.sidebar {
|
||||
position: fixed; top: 0; left: 0; width: 300px; height: 100vh;
|
||||
background: var(--bg-nav); border-right: 1px solid var(--border);
|
||||
overflow-y: auto; z-index: 100; padding: 24px 0;
|
||||
}
|
||||
.sidebar-logo { padding: 0 20px 20px; border-bottom: 1px solid var(--border); margin-bottom: 16px; }
|
||||
.sidebar-logo h2 {
|
||||
font-size: 19px;
|
||||
background: linear-gradient(135deg, var(--accent), var(--accent2));
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
||||
}
|
||||
.sidebar-section {
|
||||
padding: 8px 20px; font-size: 10.5px; text-transform: uppercase;
|
||||
letter-spacing: 1.2px; color: var(--text-dim); font-weight: 600;
|
||||
}
|
||||
.sidebar a {
|
||||
display: flex; align-items: center; gap: 10px; padding: 7px 20px;
|
||||
color: var(--text-dim); text-decoration: none; font-size: 13px; transition: all 0.15s;
|
||||
}
|
||||
.sidebar a:hover, .sidebar a.active { color: var(--text); background: rgba(6,182,212,0.06); }
|
||||
.sidebar a.active { border-right: 2px solid var(--accent); }
|
||||
|
||||
.main { margin-left: 300px; flex: 1; min-height: 100vh; }
|
||||
.hero {
|
||||
background: linear-gradient(135deg, rgba(6,182,212,0.07), rgba(34,211,238,0.05));
|
||||
border-bottom: 1px solid var(--border); padding: 56px 60px 48px;
|
||||
}
|
||||
.hero h1 {
|
||||
font-size: 32px; font-weight: 800;
|
||||
background: linear-gradient(135deg, var(--accent), var(--accent2));
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 10px;
|
||||
}
|
||||
.hero p { color: var(--text-dim); font-size: 15px; max-width: 740px; }
|
||||
.hero-meta { display: flex; gap: 24px; margin-top: 18px; flex-wrap: wrap; }
|
||||
.hero-meta span { font-size: 13px; color: var(--text-dim); display: flex; align-items: center; gap: 6px; }
|
||||
.hero-meta .dot { width: 6px; height: 6px; border-radius: 50%; }
|
||||
.dot-cyan { background: var(--accent); } .dot-green { background: var(--green); } .dot-amber { background: var(--amber); }
|
||||
|
||||
.content { padding: 40px 60px 100px; max-width: 1080px; }
|
||||
.api-section { margin-bottom: 60px; scroll-margin-top: 20px; }
|
||||
.api-section h2 { font-size: 22px; font-weight: 700; margin-bottom: 8px; display: flex; align-items: center; gap: 10px; }
|
||||
.api-section h3 { font-size: 17px; font-weight: 600; margin: 36px 0 12px; color: var(--accent); }
|
||||
.api-section h4 { font-size: 14.5px; font-weight: 600; margin: 24px 0 8px; color: var(--accent2); }
|
||||
.api-section .desc { color: var(--text-dim); font-size: 14.5px; margin-bottom: 20px; }
|
||||
.api-section .desc code { color: var(--cyan); background: var(--bg-code); padding: 2px 6px; border-radius: 4px; font-size: 13px; }
|
||||
|
||||
table.spec { width: 100%; border-collapse: collapse; margin-bottom: 24px; font-size: 13.5px; }
|
||||
table.spec th { text-align: left; padding: 10px 14px; background: var(--bg-card); border-bottom: 1px solid var(--border); color: var(--text-dim); font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: 0.8px; }
|
||||
table.spec td { padding: 10px 14px; border-bottom: 1px solid rgba(42,45,58,0.5); vertical-align: top; transition: background 0.15s; }
|
||||
table.spec tr:hover td { background: rgba(6,182,212,0.03); }
|
||||
table.spec tr:last-child td { border-bottom: none; }
|
||||
.f-name { font-family: 'SF Mono','Fira Code',monospace; color: #22d3ee; font-weight: 600; font-size: 13px; }
|
||||
.f-type { font-family: 'SF Mono','Fira Code',monospace; color: var(--accent2); font-size: 11.5px; }
|
||||
.f-req { color: var(--red); font-size: 11px; font-weight: 600; }
|
||||
.f-opt { color: var(--text-dim); font-size: 11px; }
|
||||
.f-risk { font-weight: 700; }
|
||||
.risk-safe { color: var(--green); } .risk-medium { color: var(--amber); } .risk-high { color: var(--red); }
|
||||
|
||||
.code-block { background: var(--bg-code); border: 1px solid var(--border); border-radius: 10px; padding: 20px; overflow-x: auto; margin-bottom: 24px; transition: border-color 0.2s; }
|
||||
.code-block:hover { border-color: rgba(6,182,212,0.3); }
|
||||
.code-block pre { margin: 0; font-family: 'SF Mono','Fira Code','Cascadia Code',monospace; font-size: 13px; line-height: 1.6; color: var(--text); }
|
||||
.hl-kw { color: var(--purple); } .hl-str { color: var(--green); } .hl-num { color: var(--orange); }
|
||||
.hl-cm { color: var(--text-dim); font-style: italic; } .hl-fn { color: var(--accent); } .hl-prop { color: #22d3ee; }
|
||||
.hl-type { color: var(--amber); } .hl-box { color: var(--accent); font-weight: 700; }
|
||||
|
||||
.note-box { background: rgba(6,182,212,0.05); border-left: 3px solid var(--accent); border-radius: 0 8px 8px 0; padding: 14px 18px; margin-bottom: 24px; font-size: 13.5px; color: var(--text-dim); transition: border-color 0.2s; }
|
||||
.note-box:hover { border-left-color: var(--accent2); }
|
||||
.note-box strong { color: var(--accent); }
|
||||
.note-box code { color: var(--cyan); background: var(--bg-code); padding: 1px 5px; border-radius: 3px; font-size: 12.5px; }
|
||||
.warn-box { background: rgba(251,191,36,0.05); border-left: 3px solid var(--amber); border-radius: 0 8px 8px 0; padding: 14px 18px; margin-bottom: 24px; font-size: 13.5px; color: var(--text-dim); transition: border-color 0.2s; }
|
||||
.warn-box:hover { border-left-color: var(--orange); }
|
||||
.warn-box strong { color: var(--amber); }
|
||||
|
||||
.arch-diagram { background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px; padding: 28px; margin: 20px 0 30px; overflow-x: auto; transition: border-color 0.2s; }
|
||||
.arch-diagram:hover { border-color: rgba(6,182,212,0.3); }
|
||||
.arch-diagram pre { font-family: 'SF Mono','Fira Code',monospace; font-size: 12.5px; line-height: 1.55; color: var(--text); margin: 0; }
|
||||
|
||||
.section-divider { border: none; height: 1px; background: var(--border); margin: 56px 0; }
|
||||
|
||||
.flow-step { display: flex; gap: 14px; margin-bottom: 14px; align-items: flex-start; transition: transform 0.15s; }
|
||||
.flow-step:hover { transform: translateX(4px); }
|
||||
.flow-num { min-width: 28px; height: 28px; background: var(--accent); color: #000; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 13px; flex-shrink: 0; transition: transform 0.2s; }
|
||||
.flow-step:hover .flow-num { transform: scale(1.1); }
|
||||
.flow-text { padding-top: 2px; font-size: 14px; }
|
||||
.flow-text code { color: var(--cyan); background: var(--bg-code); padding: 1px 5px; border-radius: 3px; font-size: 12.5px; }
|
||||
|
||||
.file-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px; padding: 20px 24px; margin-bottom: 16px; transition: border-color 0.2s, transform 0.2s; }
|
||||
.file-card:hover { border-color: rgba(6,182,212,0.3); transform: translateY(-2px); }
|
||||
.file-card h4 { color: var(--accent); font-size: 15px; margin-bottom: 6px; display: flex; align-items: center; gap: 10px; }
|
||||
.file-card .path { font-family: 'SF Mono',monospace; font-size: 12px; color: var(--amber); margin-bottom: 10px; }
|
||||
.file-card p { font-size: 13.5px; color: var(--text-dim); }
|
||||
|
||||
.tree { font-family: 'SF Mono','Fira Code',monospace; font-size: 12.5px; line-height: 1.7; color: var(--text-dim); }
|
||||
|
||||
::-webkit-scrollbar { width: 6px; height: 6px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }
|
||||
|
||||
@media (max-width: 900px) { .sidebar { display: none; } .main { margin-left: 0; } .hero,.content { padding: 30px 24px; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-logo">
|
||||
<h2>🏗️ MetonaAI-Desktop</h2>
|
||||
</div>
|
||||
<div class="sidebar-section">架构</div>
|
||||
<a href="#overview">📋 总览</a>
|
||||
<a href="#arch">🏗️ 系统架构</a>
|
||||
<a href="#workspace">📁 工作空间</a>
|
||||
<a href="#db-config">💾 数据库配置</a>
|
||||
|
||||
<div class="sidebar-section">9 个基础工具</div>
|
||||
<a href="#tools-overview">📊 工具总表</a>
|
||||
<a href="#tool-filesystem">📄 文件系统工具</a>
|
||||
<a href="#tool-web">🌐 网络搜索与抓取</a>
|
||||
<a href="#tool-memory">🧠 记忆工具</a>
|
||||
<a href="#tool-command">⚒️ 命令工具</a>
|
||||
|
||||
<div class="sidebar-section">4 个磁盘文件</div>
|
||||
<a href="#disk-files">💾 文件总览</a>
|
||||
<a href="#file-soul">✨ SOUL.md</a>
|
||||
<a href="#file-agents">📋 AGENTS.md</a>
|
||||
<a href="#file-memory">🧩 MEMORY.md</a>
|
||||
<a href="#file-users">👤 USERS.md</a>
|
||||
|
||||
<div class="sidebar-section">可追踪 & 日志</div>
|
||||
<a href="#trace">🔍 全链路透明</a>
|
||||
<a href="#logging">📝 日志设计</a>
|
||||
<a href="#interaction">🔄 交互流程</a>
|
||||
</nav>
|
||||
|
||||
<div class="main">
|
||||
|
||||
<div class="hero">
|
||||
<h1>MetonaAI-Desktop 架构与交互设计</h1>
|
||||
<p>基于「生产级通用 AI Agent 桌面应用构建指南」+「Metona 内部 IR 标准」,定义完整的系统架构、9 个基础工具、4 个用户级磁盘文件、工作空间机制、数据库配置规范及全链路可追踪日志体系。</p>
|
||||
<div class="hero-meta">
|
||||
<span><span class="dot dot-cyan"></span> 版本: <strong>v1.0.0</strong></span>
|
||||
<span><span class="dot dot-green"></span> 技术栈: <strong>React + Electron + SQLite</strong></span>
|
||||
<span><span class="dot dot-amber"></span> 日期: <strong>2026-06-26</strong></span>
|
||||
</div>
|
||||
<div class="note-box" style="margin-top:16px">
|
||||
<strong>📋 文档层级:</strong>本文档是 <strong>工作空间、9 个基础工具、4 个磁盘文件、数据库配置的权威定义</strong>,与《构建指南》第三、五、六章对应。冲突时以本文档为准。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
|
||||
<!-- ====== 总览 ====== -->
|
||||
<section class="api-section" id="overview">
|
||||
<h2>📋 设计总览</h2>
|
||||
<p class="desc">
|
||||
MetonaAI-Desktop 是一个运行在用户本地桌面上的通用 AI Agent 应用。它以<strong>工作空间(Workspace)</strong>为基本组织单元,
|
||||
通过 <strong>4 个 Markdown 磁盘文件</strong> 定义 Agent 的灵魂、行为、记忆和用户画像,
|
||||
提供 <strong>9 个基础工具</strong> 赋予 Agent 操作文件系统、网络、记忆和命令行的能力。
|
||||
全链路操作<strong>透明可追踪</strong>,所有决策过程、工具调用、LLM 推理记录在本地 SQLite 日志中。
|
||||
</p>
|
||||
|
||||
<div class="arch-diagram">
|
||||
<pre>
|
||||
<span class="hl-box">┌──────────────────────────────────────────────────────────┐</span>
|
||||
<span class="hl-box">│ MetonaAI-Desktop │</span>
|
||||
<span class="hl-box">│ │</span>
|
||||
<span class="hl-box">│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │</span>
|
||||
<span class="hl-box">│ │ SOUL.md │ │ AGENTS.md│ │ MEMORY.md│ │ USERS.md│ │</span> ← 用户磁盘文件
|
||||
<span class="hl-box">│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬────┘ │</span>
|
||||
<span class="hl-box">│ │ │ │ │ │</span>
|
||||
<span class="hl-box">│ ┌────▼─────────────▼─────────────▼─────────────▼────┐ │</span>
|
||||
<span class="hl-box">│ │ Agent Engine (ReAct Loop) │ │</span>
|
||||
<span class="hl-box">│ │ INIT → THINKING → PARSING → EXECUTING → OBSERVING → REFLECTING → COMPRESSING → TERMINATED │ │</span>
|
||||
<span class="hl-box">│ └────┬──────────────────────────────────────────────┘ │</span>
|
||||
<span class="hl-box">│ │ │</span>
|
||||
<span class="hl-box">│ ┌────▼──────────────────────────────────────────────┐ │</span>
|
||||
<span class="hl-box">│ │ 9 Base Tools (统一 IR) │ │</span>
|
||||
<span class="hl-box">│ │ read_file | write_file | list_dir | search_files │ │</span>
|
||||
<span class="hl-box">│ │ web_search | web_extract │ │</span>
|
||||
<span class="hl-box">│ │ memory_store | memory_search │ │</span>
|
||||
<span class="hl-box">│ │ run_command │ │</span>
|
||||
<span class="hl-box">│ └────┬──────────────────────────────────────────────┘ │</span>
|
||||
<span class="hl-box">│ │ │</span>
|
||||
<span class="hl-box">│ ┌────▼──────────────────────────────────────────────┐ │</span>
|
||||
<span class="hl-box">│ │ Trace & Audit Logger (全链路 SQLite) │ │</span>
|
||||
<span class="hl-box">│ └───────────────────────────────────────────────────┘ │</span>
|
||||
<span class="hl-box">└──────────────────────────────────────────────────────────┘</span>
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="section-divider">
|
||||
|
||||
<!-- ====== 系统架构 ====== -->
|
||||
<section class="api-section" id="arch">
|
||||
<h2>🏗️ 系统架构</h2>
|
||||
|
||||
<h3>进程架构</h3>
|
||||
<table class="spec">
|
||||
<tr><th>进程</th><th>运行时</th><th>职责</th></tr>
|
||||
<tr><td class="f-name">Main Process</td><td class="f-type">Node.js</td><td>Agent 引擎、工具调度、数据库、MCP 管理、配置加载</td></tr>
|
||||
<tr><td class="f-name">Preload Script</td><td class="f-type">沙箱 Node</td><td>通过 contextBridge 安全暴露 API 给渲染进程</td></tr>
|
||||
<tr><td class="f-name">Renderer</td><td class="f-type">Chromium</td><td>React UI:聊天界面、Agent 监控、设置面板、Trace Viewer</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>四层 Harness 架构</h3>
|
||||
<table class="spec">
|
||||
<tr><th>层级</th><th>名称</th><th>核心模块</th><th>使用的 IR 类型</th></tr>
|
||||
<tr><td>L1</td><td class="f-name">推理与编排层</td><td>ReAct Loop 状态机、Plan Mode 执行器、SubAgent 编排器</td><td class="f-type">MetonaRequest / MetonaResponse / MetonaStreamEvent</td></tr>
|
||||
<tr><td>L2</td><td class="f-name">上下文与记忆层</td><td>Context Builder、MemorySystem(SQLite)</td><td class="f-type">MetonaContext / MetonaMemoryItem</td></tr>
|
||||
<tr><td>L3</td><td class="f-name">工具与安全执行层</td><td>Tool Registry、Sandbox Manager、Policy Engine、MCP Adapter</td><td class="f-type">MetonaToolDef / MetonaToolCall / MetonaToolResult</td></tr>
|
||||
<tr><td>L4</td><td class="f-name">支撑与基础架构层</td><td>Config Manager、Logging System、OTel Tracing、Error Boundary</td><td class="f-type">MetonaError / 内置类型</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<hr class="section-divider">
|
||||
|
||||
<!-- ====== 工作空间 ====== -->
|
||||
<section class="api-section" id="workspace">
|
||||
<h2>📁 工作空间(Workspace)</h2>
|
||||
<p class="desc">
|
||||
工作空间是 Metona 的组织核心。每个工作空间是一个<strong>本地磁盘目录</strong>,包含该上下文的全部文件。
|
||||
Agent 启动时加载工作空间下的配置/状态文件,所有工具操作默认限制在工作空间内。
|
||||
</p>
|
||||
|
||||
<h3>默认工作空间</h3>
|
||||
<div class="note-box">
|
||||
<strong>📍 默认路径:</strong><code>~/MetonaWorkspaces/default/</code>
|
||||
<br>首次启动时自动创建。用户可在设置界面修改默认路径或为不同项目创建独立工作空间。
|
||||
</div>
|
||||
|
||||
<h3>自定义工作空间</h3>
|
||||
<p class="desc">
|
||||
用户可通过以下方式选择自定义工作空间目录:
|
||||
</p>
|
||||
<ul style="margin-bottom:20px; padding-left:20px; color:var(--text-dim); font-size:14px;">
|
||||
<li><strong>启动时选择</strong>:应用启动界面的"选择工作空间"按钮</li>
|
||||
<li><strong>菜单切换</strong>:菜单栏 → 文件 → 打开/创建工作空间</li>
|
||||
<li><strong>拖拽导入</strong>:将文件夹拖入应用窗口</li>
|
||||
<li><strong>命令行参数</strong>:<code>metona --workspace /path/to/dir</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>工作空间目录结构</h3>
|
||||
<div class="code-block">
|
||||
<pre><span class="hl-cm"># ~/MetonaWorkspaces/my-project/</span>
|
||||
<span class="hl-tree">├──</span> <span class="hl-box">SOUL.md</span> <span class="hl-cm"># [必需] AI 灵魂定义 — 角色、性格、核心价值观(用户自定义)</span>
|
||||
<span class="hl-tree">├──</span> <span class="hl-box">AGENTS.md</span> <span class="hl-cm"># [必需] AI 行为定义 — 规则、边界、工作流(用户自定义)</span>
|
||||
<span class="hl-tree">├──</span> <span class="hl-box">MEMORY.md</span> <span class="hl-cm"># [必需] AI 持久记忆 — 跨会话保留的知识(Agent 维护 + 用户编辑)</span>
|
||||
<span class="hl-tree">├──</span> <span class="hl-box">USERS.md</span> <span class="hl-cm"># [必需] 用户画像 — 背景、技能、偏好(用户自定义)</span>
|
||||
<span class="hl-tree">├──</span> logs/ <span class="hl-cm"># [自动创建] 会话日志(每次对话一个 .jsonl)</span>
|
||||
<span class="hl-tree">├──</span> traces/ <span class="hl-cm"># [自动创建] 执行追踪(每次 ReAct 迭代一条 trace)</span>
|
||||
<span class="hl-tree">├──</span> .metona/ <span class="hl-cm"># [自动创建] Metona 内部目录</span>
|
||||
<span class="hl-tree">│ └──</span> agent.db <span class="hl-cm"># SQLite 数据库(配置、记忆、审计日志、会话记录)</span>
|
||||
<span class="hl-tree">└──</span> src/ <span class="hl-cm"># [可选] 用户项目文件(Agent 可读写)</span></pre>
|
||||
</div>
|
||||
|
||||
<h3>必需文件说明</h3>
|
||||
<table class="spec">
|
||||
<tr><th>文件</th><th>状态</th><th>缺失时处理</th><th>说明</th></tr>
|
||||
<tr><td class="f-name">SOUL.md</td><td class="f-req">必需</td><td>自动创建空文件,Agent 以通用模式运行</td><td>定义 Agent 身份和价值观</td></tr>
|
||||
<tr><td class="f-name">AGENTS.md</td><td class="f-req">必需</td><td>自动创建空文件,使用内置最小安全规则</td><td>定义 Agent 行为规则</td></tr>
|
||||
<tr><td class="f-name">MEMORY.md</td><td class="f-req">必需</td><td>自动创建带元数据头的规范文件</td><td>跨会话记忆(有严格格式要求)</td></tr>
|
||||
<tr><td class="f-name">USERS.md</td><td class="f-req">必需</td><td>自动创建空文件,Agent 以通用模式运行</td><td>用户画像信息</td></tr>
|
||||
</table>
|
||||
|
||||
<div class="note-box">
|
||||
<strong>✅ 自动创建策略:</strong>打开工作空间时,Metona 会校验 4 个必需文件是否存在。
|
||||
<br>任何文件缺失都会<strong>自动创建</strong>,不会阻止启动。创建后提示用户编辑自定义内容。
|
||||
<br><code>MEMORY.md</code> 创建时会自动包含符合格式规范的元数据头。
|
||||
</div>
|
||||
|
||||
<h3>工作空间生命周期</h3>
|
||||
<div class="flow-step"><span class="flow-num">1</span><span class="flow-text">用户选择/创建工作空间目录(或使用默认路径 <code>~/MetonaWorkspaces/default/</code>)</span></div>
|
||||
<div class="flow-step"><span class="flow-num">2</span><span class="flow-text">校验必需文件,缺失则自动创建(<code>MEMORY.md</code> 带元数据头)</span></div>
|
||||
<div class="flow-step"><span class="flow-num">3</span><span class="flow-text">加载 4 个磁盘文件,构建 System Prompt(空文件不影响启动)</span></div>
|
||||
<div class="flow-step"><span class="flow-num">4</span><span class="flow-text">连接 <code>.metona/agent.db</code>,加载配置、恢复历史会话</span></div>
|
||||
<div class="flow-step"><span class="flow-num">5</span><span class="flow-text">Agent 就绪,开始对话。所有工具操作默认以工作空间为根</span></div>
|
||||
<div class="flow-step"><span class="flow-num">6</span><span class="flow-text">会话结束后,<code>MEMORY.md</code>(更新时间戳)和 <code>.metona/agent.db</code> 自动更新</span></div>
|
||||
</section>
|
||||
|
||||
<hr class="section-divider">
|
||||
|
||||
<!-- ====== 数据库配置 ====== -->
|
||||
<section class="api-section" id="db-config">
|
||||
<h2>💾 数据库配置:.metona/agent.db</h2>
|
||||
<p class="desc">
|
||||
所有运行时配置存储在工作空间的 SQLite 数据库中(<code>.metona/agent.db</code>),而非外部配置文件。
|
||||
这确保了配置与工作空间的强绑定,支持事务性更新和版本迁移。
|
||||
</p>
|
||||
|
||||
<div class="note-box">
|
||||
<strong>💡 设计决策:</strong>采用数据库存储配置而非 YAML/JSON 文件,原因:
|
||||
<br>1. 配置与工作空间数据原子性一致
|
||||
<br>2. 支持并发访问和事务保护
|
||||
<br>3. 统一备份和迁移策略
|
||||
<br>4. 避免文件格式解析错误
|
||||
</div>
|
||||
|
||||
<h3>配置表结构</h3>
|
||||
<div class="code-block">
|
||||
<pre><span class="hl-cm">-- .metona/agent.db > app_config</span>
|
||||
<span class="hl-kw">CREATE TABLE</span> app_config (
|
||||
<span class="hl-prop">key</span> TEXT <span class="hl-kw">PRIMARY KEY</span>,
|
||||
<span class="hl-prop">value</span> TEXT <span class="hl-kw">NOT NULL</span>, <span class="hl-cm">-- JSON 格式值</span>
|
||||
<span class="hl-prop">category</span> TEXT <span class="hl-kw">NOT NULL</span>, <span class="hl-cm">-- llm | agent | tools | security | logging | mcp</span>
|
||||
<span class="hl-prop">updated_at</span> TEXT <span class="hl-kw">DEFAULT</span> (datetime(<span class="hl-str">'now'</span>))
|
||||
);
|
||||
|
||||
<span class="hl-cm">-- 配置分类索引</span>
|
||||
<span class="hl-kw">CREATE INDEX</span> idx_config_category <span class="hl-kw">ON</span> app_config(category);</pre>
|
||||
</div>
|
||||
|
||||
<h3>配置项一览</h3>
|
||||
<table class="spec">
|
||||
<tr><th>分类</th><th>键</th><th>类型</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td class="f-type" rowspan="5">llm</td><td class="f-name">provider</td><td>string</td><td>"deepseek"</td><td>LLM 提供商</td></tr>
|
||||
<tr><td class="f-name">model</td><td>string</td><td>"deepseek-v4-pro"</td><td>模型名称</td></tr>
|
||||
<tr><td class="f-name">apiKey</td><td>string</td><td>""</td><td>API 密钥(加密存储)</td></tr>
|
||||
<tr><td class="f-name">baseURL</td><td>string</td><td>""</td><td>API 基础 URL</td></tr>
|
||||
<tr><td class="f-name">params</td><td>JSON</td><td>{temperature:0, maxTokens:8192}</td><td>生成参数</td></tr>
|
||||
<tr><td class="f-name">fallbackProvider</td><td>string</td><td>""</td><td>备选 LLM 提供商(故障转移)</td></tr>
|
||||
<tr><td class="f-name">fallbackModel</td><td>string</td><td>""</td><td>备选模型名称</td></tr>
|
||||
<tr><td class="f-type" rowspan="4">agent</td><td class="f-name">maxIterations</td><td>number</td><td>20</td><td>最大迭代次数</td></tr>
|
||||
<tr><td class="f-name">totalTimeoutMs</td><td>number</td><td>600000</td><td>总超时(毫秒)</td></tr>
|
||||
<tr><td class="f-name">enableThinking</td><td>boolean</td><td>true</td><td>启用思考模式</td></tr>
|
||||
<tr><td class="f-name">thinkingEffort</td><td>string</td><td>"high"</td><td>思考强度: low/medium/high/max</td></tr>
|
||||
<tr><td class="f-type" rowspan="3">tools</td><td class="f-name">filesystem.enabled</td><td>boolean</td><td>true</td><td>文件系统工具开关</td></tr>
|
||||
<tr><td class="f-name">web.enabled</td><td>boolean</td><td>true</td><td>网络工具开关</td></tr>
|
||||
<tr><td class="f-name">command.enabled</td><td>boolean</td><td>true</td><td>命令工具开关</td></tr>
|
||||
<tr><td class="f-type" rowspan="3">security</td><td class="f-name">requireWriteConfirmation</td><td>boolean</td><td>true</td><td>写操作需确认</td></tr>
|
||||
<tr><td class="f-name">maxFileWriteSizeKB</td><td>number</td><td>1024</td><td>最大写入文件大小</td></tr>
|
||||
<tr><td class="f-name">promptInjectionDefense</td><td>boolean</td><td>true</td><td>注入防护开关</td></tr>
|
||||
<tr><td class="f-type" rowspan="3">logging</td><td class="f-name">level</td><td>string</td><td>"info"</td><td>日志级别</td></tr>
|
||||
<tr><td class="f-name">auditEnabled</td><td>boolean</td><td>true</td><td>审计日志开关</td></tr>
|
||||
<tr><td class="f-name">traceEnabled</td><td>boolean</td><td>true</td><td>追踪日志开关</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>MCP Server 配置表</h3>
|
||||
<div class="code-block">
|
||||
<pre><span class="hl-cm">-- .metona/agent.db > mcp_servers</span>
|
||||
<span class="hl-kw">CREATE TABLE</span> mcp_servers (
|
||||
<span class="hl-prop">id</span> TEXT <span class="hl-kw">PRIMARY KEY</span>,
|
||||
<span class="hl-prop">name</span> TEXT <span class="hl-kw">NOT NULL UNIQUE</span>,
|
||||
<span class="hl-prop">transport</span> TEXT <span class="hl-kw">CHECK</span>(transport <span class="hl-kw">IN</span> (<span class="hl-str">'stdio'</span>, <span class="hl-str">'sse'</span>)),
|
||||
<span class="hl-prop">command</span> TEXT, <span class="hl-cm">-- stdio 模式的命令</span>
|
||||
<span class="hl-prop">args</span> TEXT, <span class="hl-cm">-- JSON 数组格式的参数</span>
|
||||
<span class="hl-prop">url</span> TEXT, <span class="hl-cm">-- SSE 模式的 URL</span>
|
||||
<span class="hl-prop">enabled</span> BOOLEAN <span class="hl-kw">DEFAULT</span> TRUE,
|
||||
<span class="hl-prop">created_at</span> TEXT <span class="hl-kw">DEFAULT</span> (datetime(<span class="hl-str">'now'</span>)),
|
||||
<span class="hl-prop">updated_at</span> TEXT <span class="hl-kw">DEFAULT</span> (datetime(<span class="hl-str">'now'</span>))
|
||||
);</pre>
|
||||
</div>
|
||||
|
||||
<div class="note-box">
|
||||
<strong>🔧 配置管理:</strong>用户通过设置界面修改配置,变更即时生效并持久化到数据库。
|
||||
首次创建工作空间时,系统自动插入所有配置项的默认值。
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="section-divider">
|
||||
|
||||
<!-- ====== 9 个基础工具 ====== -->
|
||||
<section class="api-section" id="tools-overview">
|
||||
<h2>📊 9 个基础工具 — 总表</h2>
|
||||
<p class="desc">所有工具使用 <strong>Metona IR 的 MetonaToolDef / MetonaToolCall / MetonaToolResult</strong> 结构。内置在 Tool Registry 中,Adaper 为 LLM 生成 JSON Schema 格式的描述。</p>
|
||||
|
||||
<table class="spec">
|
||||
<tr><th>#</th><th>工具名</th><th>分类</th><th>风险</th><th>需确认</th><th>核心功能</th></tr>
|
||||
<tr><td class="f-name">1</td><td class="f-name">read_file</td><td class="f-type">filesystem</td><td class="f-risk risk-safe">SAFE</td><td>否</td><td>读取文件内容,支持分页</td></tr>
|
||||
<tr><td class="f-name">2</td><td class="f-name">write_file</td><td class="f-type">filesystem</td><td class="f-risk risk-medium">MEDIUM</td><td>是(可配)</td><td>写入/覆盖/追加文件内容</td></tr>
|
||||
<tr><td class="f-name">3</td><td class="f-name">list_directory</td><td class="f-type">filesystem</td><td class="f-risk risk-safe">SAFE</td><td>否</td><td>列出目录内容</td></tr>
|
||||
<tr><td class="f-name">4</td><td class="f-name">search_files</td><td class="f-type">filesystem</td><td class="f-risk risk-safe">SAFE</td><td>否</td><td>按模式搜索文件(名称/内容)</td></tr>
|
||||
<tr><td class="f-name">5</td><td class="f-name">web_search</td><td class="f-type">network</td><td class="f-risk risk-medium">LOW</td><td>否</td><td>网络搜索,返回结果列表</td></tr>
|
||||
<tr><td class="f-name">6</td><td class="f-name">web_extract</td><td class="f-type">network</td><td class="f-risk risk-medium">LOW</td><td>否</td><td>抓取网页内容转 Markdown</td></tr>
|
||||
<tr><td class="f-name">7</td><td class="f-name">memory_store</td><td class="f-type">database</td><td class="f-risk risk-medium">MEDIUM</td><td>否</td><td>存储一条记忆到 SQLite</td></tr>
|
||||
<tr><td class="f-name">8</td><td class="f-name">memory_search</td><td class="f-type">database</td><td class="f-risk risk-safe">SAFE</td><td>否</td><td>检索记忆(关键词匹配)</td></tr>
|
||||
<tr><td class="f-name">9</td><td class="f-name">run_command</td><td class="f-type">code_execution</td><td class="f-risk risk-high">HIGH</td><td>是</td><td>执行 Shell 命令,沙箱限制</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- --- 文件系统工具 --- -->
|
||||
<section class="api-section" id="tool-filesystem">
|
||||
<h2>📄 类别一:文件系统工具(4个)</h2>
|
||||
|
||||
<h3>1. read_file</h3>
|
||||
<p class="desc">读取文件完整内容。支持行偏移和行数限制,自动检测二进制文件。文件超过 100K 字符时返回截断提示。</p>
|
||||
<table class="spec">
|
||||
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
|
||||
<tr><td class="f-name">file_path</td><td class="f-type">string</td><td class="f-req">必填</td><td>文件路径(相对于工作空间)</td></tr>
|
||||
<tr><td class="f-name">offset</td><td class="f-type">number</td><td class="f-opt">可选</td><td>起始行号(1-indexed,默认 1)</td></tr>
|
||||
<tr><td class="f-name">limit</td><td class="f-type">number</td><td class="f-opt">可选</td><td>最大行数(默认 500,最大 2000)</td></tr>
|
||||
</table>
|
||||
<div class="note-box"><strong>💡</strong> 返回格式:<code>{ content, total_lines, truncated, file_size }</code>。truncated=true 时须提示用户指定 offset 继续读取。</div>
|
||||
|
||||
<h3>2. write_file</h3>
|
||||
<p class="desc">写入内容到文件。默认覆盖模式,支持追加。写操作前校验路径白名单,默认需用户确认。</p>
|
||||
<table class="spec">
|
||||
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
|
||||
<tr><td class="f-name">file_path</td><td class="f-type">string</td><td class="f-req">必填</td><td>目标文件路径</td></tr>
|
||||
<tr><td class="f-name">content</td><td class="f-type">string</td><td class="f-req">必填</td><td>写入内容</td></tr>
|
||||
<tr><td class="f-name">mode</td><td class="f-type">string</td><td class="f-opt">可选</td><td>"overwrite"(默认)/ "append"</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>3. list_directory</h3>
|
||||
<p class="desc">列出目录内容,支持递归深度控制和 glob 过滤。</p>
|
||||
<table class="spec">
|
||||
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
|
||||
<tr><td class="f-name">dir_path</td><td class="f-type">string</td><td class="f-opt">可选</td><td>目录路径(默认工作空间根)</td></tr>
|
||||
<tr><td class="f-name">depth</td><td class="f-type">number</td><td class="f-opt">可选</td><td>递归深度(默认 1,最大 5)</td></tr>
|
||||
<tr><td class="f-name">glob</td><td class="f-type">string</td><td class="f-opt">可选</td><td>文件名过滤 (如 "*.ts")</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>4. search_files</h3>
|
||||
<p class="desc">在目录中按正则/glob 搜索文件内容或文件名。底层使用 ripgrep。</p>
|
||||
<table class="spec">
|
||||
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
|
||||
<tr><td class="f-name">pattern</td><td class="f-type">string</td><td class="f-req">必填</td><td>搜索正则或 glob 模式</td></tr>
|
||||
<tr><td class="f-name">target</td><td class="f-type">string</td><td class="f-opt">可选</td><td>"content"(默认)/ "files"</td></tr>
|
||||
<tr><td class="f-name">path</td><td class="f-type">string</td><td class="f-opt">可选</td><td>搜索目录(默认工作空间根)</td></tr>
|
||||
<tr><td class="f-name">file_glob</td><td class="f-type">string</td><td class="f-opt">可选</td><td>限定文件名(如 "*.py")</td></tr>
|
||||
<tr><td class="f-name">limit</td><td class="f-type">number</td><td class="f-opt">可选</td><td>最大结果数(默认 50)</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- --- 网络工具 --- -->
|
||||
<section class="api-section" id="tool-web">
|
||||
<h2>🌐 类别二:网络搜索与抓取(2个)</h2>
|
||||
|
||||
<h3>5. web_search</h3>
|
||||
<p class="desc">执行网络搜索,返回标题、摘要和 URL。支持搜索运算符(site:、filetype: 等)。</p>
|
||||
<table class="spec">
|
||||
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
|
||||
<tr><td class="f-name">query</td><td class="f-type">string</td><td class="f-req">必填</td><td>搜索关键词(支持 site:domain filetype:pdf 等)</td></tr>
|
||||
<tr><td class="f-name">limit</td><td class="f-type">number</td><td class="f-opt">可选</td><td>结果数(默认 5,最大 100)</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>6. web_extract</h3>
|
||||
<p class="desc">抓取网页内容并转换为 Markdown。支持 HTML 页面和 PDF 链接。超过 5000 字符自动摘要。</p>
|
||||
<table class="spec">
|
||||
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
|
||||
<tr><td class="f-name">urls</td><td class="f-type">string[]</td><td class="f-req">必填</td><td>待抓取的 URL 列表(最多 5 个)</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- --- 记忆工具 --- -->
|
||||
<section class="api-section" id="tool-memory">
|
||||
<h2>🧠 类别三:记忆工具(2个)</h2>
|
||||
|
||||
<h3>7. memory_store</h3>
|
||||
<p class="desc">将一条内容存入持久记忆。写入 SQLite,支持关键词检索。Agent 可在对话中保存重要信息。</p>
|
||||
<table class="spec">
|
||||
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
|
||||
<tr><td class="f-name">content</td><td class="f-type">string</td><td class="f-req">必填</td><td>记忆内容</td></tr>
|
||||
<tr><td class="f-name">type</td><td class="f-type">string</td><td class="f-req">必填</td><td>"episodic"(情节)/ "semantic"(语义)/ "working"(工作)</td></tr>
|
||||
<tr><td class="f-name">importance</td><td class="f-type">number</td><td class="f-opt">可选</td><td>重要程度 0-1(默认 0.5)</td></tr>
|
||||
<tr><td class="f-name">source</td><td class="f-type">string</td><td class="f-opt">可选</td><td>来源标识(默认 "agent")</td></tr>
|
||||
<tr><td class="f-name">tags</td><td class="f-type">string[]</td><td class="f-opt">可选</td><td>标签列表</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>8. memory_search</h3>
|
||||
<p class="desc">检索记忆库:关键词精确匹配,返回相关性排序结果。</p>
|
||||
<table class="spec">
|
||||
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
|
||||
<tr><td class="f-name">query</td><td class="f-type">string</td><td class="f-req">必填</td><td>搜索关键词或语义查询</td></tr>
|
||||
<tr><td class="f-name">type</td><td class="f-type">string</td><td class="f-opt">可选</td><td>过滤记忆类型</td></tr>
|
||||
<tr><td class="f-name">topK</td><td class="f-type">number</td><td class="f-opt">可选</td><td>返回结果数(默认 5)</td></tr>
|
||||
<tr><td class="f-name">threshold</td><td class="f-type">number</td><td class="f-opt">可选</td><td>相似度阈值(默认 0.7)</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- --- 命令工具 --- -->
|
||||
<section class="api-section" id="tool-command">
|
||||
<h2>⚒️ 类别四:命令工具(1个)</h2>
|
||||
|
||||
<h3>9. run_command</h3>
|
||||
<p class="desc">在沙箱环境中执行 Shell 命令。命令在工作空间目录下运行,有超时限制和输出截断。高危命令需用户确认。</p>
|
||||
<table class="spec">
|
||||
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
|
||||
<tr><td class="f-name">command</td><td class="f-type">string</td><td class="f-req">必填</td><td>Shell 命令</td></tr>
|
||||
<tr><td class="f-name">workdir</td><td class="f-type">string</td><td class="f-opt">可选</td><td>执行目录(默认工作空间根)</td></tr>
|
||||
<tr><td class="f-name">timeout</td><td class="f-type">number</td><td class="f-opt">可选</td><td>超时毫秒(默认 120000)</td></tr>
|
||||
</table>
|
||||
|
||||
<div class="warn-box">
|
||||
<strong>⚠ 安全规则(命令解析 + 模式匹配):</strong>使用 <code>shell-quote</code> 库解析命令为 token 数组,再对每个 token 做模式匹配。<strong>不使用</strong>简单字符串匹配(易被绕过)。
|
||||
<br><br><strong>硬阻止列表(绝对禁止执行):</strong>
|
||||
<ul style="margin-top:8px;padding-left:20px;">
|
||||
<li><code>rm</code> + 包含 <code>/</code> 的路径参数(阻止删除根/系统目录)</li>
|
||||
<li><code>sudo</code> / <code>su</code> / <code>doas</code>(提权命令)</li>
|
||||
<li><code>shutdown</code> / <code>reboot</code> / <code>halt</code> / <code>poweroff</code></li>
|
||||
<li><code>curl ... | sh</code> / <code>curl ... | bash</code> / <code>wget ... | sh</code>(远程执行)</li>
|
||||
<li><code>dd</code> + <code>of=/dev/</code>(写设备文件)</li>
|
||||
<li><code>mkfs</code> / <code>fdisk</code>(格式化磁盘)</li>
|
||||
<li><code>chmod 777</code> / <code>chown</code> 到非当前用户</li>
|
||||
</ul>
|
||||
<strong>需确认列表(用户显式确认后执行):</strong>
|
||||
<ul style="margin-top:8px;padding-left:20px;">
|
||||
<li><code>eval</code> / <code>exec</code>(动态执行)</li>
|
||||
<li>修改系统配置文件的命令</li>
|
||||
<li>安装/卸载软件的命令(<code>apt</code> / <code>brew</code> / <code>npm install -g</code>)</li>
|
||||
<li>网络请求类命令(<code>curl</code> / <code>wget</code> 不含管道)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="note-box">
|
||||
<strong>🔧 实现要求:</strong>在 <code>SandboxManager</code> 中实现 <code>validateCommand(command: string): {allowed: boolean; reason?: string}</code> 方法。使用 <code>shell-quote</code>(npm 包)解析命令,检查每个 token。安全规则配置存储在 <code>app_config</code> 表中(<code>security.commandBlocklist</code> / <code>security.commandConfirmList</code>),用户可在设置界面自定义。
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="section-divider">
|
||||
|
||||
<!-- ====== 4 个用户级磁盘文件 ====== -->
|
||||
<section class="api-section" id="disk-files">
|
||||
<h2>💾 4 个用户级磁盘文件</h2>
|
||||
<p class="desc">
|
||||
4 个 <code>.md</code> 文件位于工作空间根目录,是工作空间的<strong>必需文件</strong>。
|
||||
其中 <code>SOUL.md</code>、<code>AGENTS.md</code>、<code>USERS.md</code> 完全由用户自定义,<code>MEMORY.md</code> 由 Agent 维护但用户可编辑。
|
||||
</p>
|
||||
|
||||
<table class="spec">
|
||||
<tr><th>文件</th><th>必需</th><th>注入阶段</th><th>作用</th><th>内容来源</th><th>缺失时处理</th></tr>
|
||||
<tr><td class="f-name">SOUL.md</td><td class="f-req">是</td><td class="f-type">静态区(优先)</td><td>定义 Agent 身份、性格、核心价值观</td><td>用户自定义</td><td>自动创建空文件</td></tr>
|
||||
<tr><td class="f-name">AGENTS.md</td><td class="f-req">是</td><td class="f-type">静态区</td><td>定义行为规则、边界、工作流</td><td>用户自定义</td><td>自动创建空文件</td></tr>
|
||||
<tr><td class="f-name">MEMORY.md</td><td class="f-req">是</td><td class="f-type">动态区</td><td>跨会话持久记忆</td><td>Agent 维护 + 用户可编辑</td><td>自动创建带元数据头的规范文件</td></tr>
|
||||
<tr><td class="f-name">USERS.md</td><td class="f-req">是</td><td class="f-type">静态区</td><td>用户画像:背景、技能、偏好</td><td>用户自定义</td><td>自动创建空文件</td></tr>
|
||||
</table>
|
||||
|
||||
<div class="note-box">
|
||||
<strong>✅ 自动创建策略:</strong>所有必需文件缺失时都会自动创建,不会阻止启动。
|
||||
<br>• <code>SOUL.md</code>、<code>AGENTS.md</code>、<code>USERS.md</code>:创建空文件,提示用户编辑
|
||||
<br>• <code>MEMORY.md</code>:创建带完整元数据头的规范文件(格式版本、创建时间、工作空间路径)
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="api-section" id="file-soul">
|
||||
<h2>✨ SOUL.md — AI 灵魂定义</h2>
|
||||
<p class="desc">定义 Agent 的身份、性格和核心价值观。加载后注入 System Prompt 的最高优先级静态区。<strong>此文件完全由用户自定义,Metona 不提供默认内容。</strong></p>
|
||||
|
||||
<div class="file-card">
|
||||
<h4>✨ SOUL.md</h4>
|
||||
<div class="path">~/MetonaWorkspaces/my-project/SOUL.md</div>
|
||||
<p>用户自定义文件,定义 Agent 的灵魂</p>
|
||||
</div>
|
||||
|
||||
<div class="note-box">
|
||||
<strong>📝 用户自定义:</strong>SOUL.md 的内容完全由用户决定。Metona 不会预设任何角色、性格或价值观。
|
||||
<br>用户可以定义任何类型的 Agent:编程助手、写作伙伴、学习导师、虚拟角色等。
|
||||
</div>
|
||||
|
||||
<h4>推荐结构(仅供参考)</h4>
|
||||
<div class="code-block">
|
||||
<pre><span class="hl-cm"># SOUL.md — 用户自定义 Agent 灵魂</span>
|
||||
|
||||
<span class="hl-cm">## 身份</span>
|
||||
<span class="hl-cm"># 定义 Agent 是谁:名称、角色、核心特征</span>
|
||||
|
||||
<span class="hl-cm">## 性格与语气</span>
|
||||
<span class="hl-cm"># 定义 Agent 如何与用户交流:风格、语气、态度</span>
|
||||
|
||||
<span class="hl-cm">## 核心价值观</span>
|
||||
<span class="hl-cm"># 定义 Agent 的行为准则和底线</span></pre>
|
||||
</div>
|
||||
|
||||
<h4>SOUL.md 作用域</h4>
|
||||
<table class="spec">
|
||||
<tr><th>对象</th><th>影响</th></tr>
|
||||
<tr><td>LLM 推理</td><td>全部轮次注入,决定回复语气、风格和价值观</td></tr>
|
||||
<tr><td>工具调用</td><td>影响安全决策和行为边界</td></tr>
|
||||
<tr><td>记忆存储</td><td>影响哪些信息被认为值得记忆</td></tr>
|
||||
<tr><td>错误处理</td><td>决定错误回复的风格和态度</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="api-section" id="file-agents">
|
||||
<h2>📋 AGENTS.md — AI 行为定义</h2>
|
||||
<p class="desc">定义 Agent 的行为规则、边界、工作流程和工具使用权限。<strong>此文件完全由用户自定义</strong>,Metona 仅提供内置最小安全规则作为兜底。</p>
|
||||
|
||||
<div class="file-card">
|
||||
<h4>📋 AGENTS.md</h4>
|
||||
<div class="path">~/MetonaWorkspaces/my-project/AGENTS.md</div>
|
||||
<p>用户自定义文件,定义 Agent 行为边界</p>
|
||||
</div>
|
||||
|
||||
<div class="note-box">
|
||||
<strong>📝 用户自定义:</strong>AGENTS.md 的内容完全由用户决定。Metona 不预设行为规则。
|
||||
<br>用户可以定义任意复杂度的规则体系,从简单的行为准则到详细的多层规则架构。
|
||||
</div>
|
||||
|
||||
<h4>推荐结构(仅供参考)</h4>
|
||||
<div class="code-block">
|
||||
<pre><span class="hl-cm"># AGENTS.md — 用户自定义行为规则</span>
|
||||
|
||||
<span class="hl-cm">## 行为准则</span>
|
||||
<span class="hl-cm"># 定义 Agent 必须遵守的规则</span>
|
||||
|
||||
<span class="hl-cm">## 工具使用规范</span>
|
||||
<span class="hl-cm"># 定义哪些工具可用、何时需要确认</span>
|
||||
|
||||
<span class="hl-cm">## 安全边界</span>
|
||||
<span class="hl-cm"># 定义 Agent 的行为底线</span>
|
||||
|
||||
<span class="hl-cm">## 工作流程</span>
|
||||
<span class="hl-cm"># 定义 Agent 的推理和执行流程</span></pre>
|
||||
</div>
|
||||
|
||||
<h4>内置最小安全规则(兜底)</h4>
|
||||
<div class="warn-box">
|
||||
<strong>🛡 无论 AGENTS.md 如何定义,以下规则始终生效:</strong>
|
||||
<ul style="margin-top:8px; padding-left:20px;">
|
||||
<li>不执行明确违法的操作</li>
|
||||
<li>不泄露用户隐私数据</li>
|
||||
<li>不可逆操作前必须确认</li>
|
||||
<li>工具调用失败必须如实报告</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h4>AGENTS.md 作用域</h4>
|
||||
<table class="spec">
|
||||
<tr><th>对象</th><th>影响</th></tr>
|
||||
<tr><td>Agent 决策</td><td>所有行为受用户定义的规则约束</td></tr>
|
||||
<tr><td>工具权限</td><td>定义哪些工具可用、需要确认、被禁用</td></tr>
|
||||
<tr><td>输出验证</td><td>根据用户规则验证输出合规性</td></tr>
|
||||
<tr><td>工作流</td><td>引导 Agent 的推理和执行流程</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="api-section" id="file-memory">
|
||||
<h2>🧩 MEMORY.md — AI 记忆文件</h2>
|
||||
<p class="desc">跨会话持久记忆。Agent 启动时读取注入上下文,会话结束后自动追加新记忆。<strong>此文件有严格的格式规范,Agent 写入时必须遵循,用户编辑时也应遵守。</strong></p>
|
||||
|
||||
<div class="file-card">
|
||||
<h4>🧩 MEMORY.md</h4>
|
||||
<div class="path">~/MetonaWorkspaces/my-project/MEMORY.md</div>
|
||||
<p>Agent 维护 + 用户可编辑的记忆文件</p>
|
||||
</div>
|
||||
|
||||
<h4>格式规范</h4>
|
||||
<div class="warn-box">
|
||||
<strong>📋 强制格式:</strong>MEMORY.md 必须遵循以下结构,否则 Agent 写入时会自动修正格式。
|
||||
</div>
|
||||
|
||||
<h4>创建时的初始模板(自动填充)</h4>
|
||||
<p class="desc">当 MEMORY.md 不存在时,Agent 自动创建以下带元数据头的规范文件:</p>
|
||||
|
||||
<div class="code-block">
|
||||
<pre><span class="hl-cm"># MEMORY.md — AI 持久记忆</span>
|
||||
<span class="hl-cm">#</span>
|
||||
<span class="hl-cm"># 格式版本: 1.0</span>
|
||||
<span class="hl-cm"># 创建时间: 2026-06-25T12:00:00Z</span>
|
||||
<span class="hl-cm"># 最后更新: 2026-06-25T12:00:00Z</span>
|
||||
<span class="hl-cm"># 工作空间: /home/user/MetonaWorkspaces/my-project</span>
|
||||
<span class="hl-cm">#</span>
|
||||
<span class="hl-cm"># 此文件由 Metona Agent 自动维护,用户可手动编辑。</span>
|
||||
<span class="hl-cm"># 格式规范详见文档,Agent 写入时会自动校验格式。</span>
|
||||
|
||||
<span class="hl-cm">## 用户偏好</span>
|
||||
<span class="hl-cm"># 格式: - [类别] 内容描述</span>
|
||||
<span class="hl-cm"># 示例: - [沟通风格] 用户喜欢简洁的回答</span>
|
||||
|
||||
<span class="hl-cm">## 项目上下文</span>
|
||||
<span class="hl-cm"># 格式: - [项目名] 关键信息</span>
|
||||
<span class="hl-cm"># 示例: - [MyApp] 技术栈: React + TypeScript</span>
|
||||
|
||||
<span class="hl-cm">## 重要决策</span>
|
||||
<span class="hl-cm"># 格式: - YYYY-MM-DD: 决策内容</span>
|
||||
<span class="hl-cm"># 示例: - 2026-06-25: 选择 sql.js 作为数据库方案</span>
|
||||
|
||||
<span class="hl-cm">## 待办事项</span>
|
||||
<span class="hl-cm"># 格式: - [状态] 任务描述 (状态: pending/done/cancelled)</span>
|
||||
<span class="hl-cm"># 示例: - [pending] 实现用户登录功能</span>
|
||||
|
||||
<span class="hl-cm">## 已知问题</span>
|
||||
<span class="hl-cm"># 格式: - 问题描述 | 影响范围 | 解决方案</span>
|
||||
<span class="hl-cm"># 示例: - 首次加载慢 | 启动 | 预加载优化</span></pre>
|
||||
</div>
|
||||
|
||||
<h4>完整示例(有内容时)</h4>
|
||||
<div class="code-block">
|
||||
<pre><span class="hl-cm"># MEMORY.md — AI 持久记忆</span>
|
||||
<span class="hl-cm">#</span>
|
||||
<span class="hl-cm"># 格式版本: 1.0</span>
|
||||
<span class="hl-cm"># 创建时间: 2026-06-25T12:00:00Z</span>
|
||||
<span class="hl-cm"># 最后更新: 2026-06-25T15:30:00Z</span>
|
||||
<span class="hl-cm"># 工作空间: /home/user/MetonaWorkspaces/my-project</span>
|
||||
|
||||
<span class="hl-cm">## 用户偏好</span>
|
||||
- [沟通风格] 用户喜欢简洁的回答,不需要过度解释
|
||||
- [代码风格] 代码块使用 TypeScript 语法高亮
|
||||
- [工具偏好] 项目使用 pnpm 而非 npm
|
||||
|
||||
<span class="hl-cm">## 项目上下文</span>
|
||||
- [MetonaAI-Desktop] 技术栈: React 18 + Electron 28 + TypeScript 5.x
|
||||
- [MetonaAI-Desktop] 构建工具: Vite + electron-builder
|
||||
|
||||
<span class="hl-cm">## 重要决策</span>
|
||||
- 2026-06-20: 选择 sql.js 作为 SQLite 实现
|
||||
- 2026-06-22: 决定采用四层 Harness 架构
|
||||
|
||||
<span class="hl-cm">## 待办事项</span>
|
||||
- [pending] 实现 MCP Server 动态加载
|
||||
- [done] 完成 Agent Loop 状态机
|
||||
|
||||
<span class="hl-cm">## 已知问题</span>
|
||||
- Windows 下 electron-builder 签名需要证书 | 部署 | 使用代码签名证书</pre>
|
||||
</div>
|
||||
|
||||
<h4>格式校验规则</h4>
|
||||
<table class="spec">
|
||||
<tr><th>规则</th><th>说明</th><th>违反处理</th></tr>
|
||||
<tr><td class="f-name">元数据头</td><td>必须包含 <code># 格式版本</code>、<code># 创建时间</code>、<code># 最后更新</code>、<code># 工作空间</code></td><td>自动补充缺失的元数据</td></tr>
|
||||
<tr><td class="f-name">分区结构</td><td>必须包含 <code>## 用户偏好</code>、<code>## 项目上下文</code>、<code>## 重要决策</code> 三个分区</td><td>自动创建缺失分区</td></tr>
|
||||
<tr><td class="f-name">条目前缀</td><td>每个条目必须以 <code>- </code> 开头,后跟 <code>[类别/标签]</code></td><td>自动添加默认标签</td></tr>
|
||||
<tr><td class="f-name">日期格式</td><td>决策条目必须使用 <code>YYYY-MM-DD</code> 格式</td><td>自动格式化为 ISO 日期</td></tr>
|
||||
<tr><td class="f-name">状态标记</td><td>待办事项必须包含 <code>[pending/done/cancelled]</code> 状态</td><td>默认标记为 <code>[pending]</code></td></tr>
|
||||
<tr><td class="f-name">时间戳更新</td><td>每次写入时自动更新 <code># 最后更新</code> 时间戳</td><td>自动更新</td></tr>
|
||||
</table>
|
||||
|
||||
<h4>MEMORY.md 生命周期</h4>
|
||||
<div class="flow-step"><span class="flow-num">1</span><span class="flow-text"><strong>首次创建</strong>:文件不存在时自动创建,包含完整元数据头(格式版本、创建时间、工作空间路径)</span></div>
|
||||
<div class="flow-step"><span class="flow-num">2</span><span class="flow-text"><strong>启动读取</strong>:Agent 初始化时解析 MEMORY.md,校验格式,注入 System Prompt 动态区</span></div>
|
||||
<div class="flow-step"><span class="flow-num">3</span><span class="flow-text"><strong>会话中使用</strong>:Agent 可通过 <code>memory_search</code> 检索 MEMORY.md 内容</span></div>
|
||||
<div class="flow-step"><span class="flow-num">4</span><span class="flow-text"><strong>会话结束后</strong>:Agent 自动分析本次会话,按格式规范追加新记忆条目,更新时间戳</span></div>
|
||||
<div class="flow-step"><span class="flow-num">5</span><span class="flow-text"><strong>格式校验</strong>:每次写入前校验格式,不合规内容自动修正</span></div>
|
||||
<div class="flow-step"><span class="flow-num">6</span><span class="flow-text"><strong>用户编辑</strong>:用户可随时编辑,Agent 下次启动时重新校验格式</span></div>
|
||||
|
||||
<div class="note-box">
|
||||
<strong>💡 格式保护:</strong>Agent 写入 MEMORY.md 时会严格遵循格式规范。
|
||||
如果用户手动编辑导致格式不合规,Agent 会在下次启动时提示并尝试自动修正,不会丢失已有内容。
|
||||
</div>
|
||||
|
||||
<h4>MEMORY.md 与 SQLite 记忆系统的关系</h4>
|
||||
<p class="desc">MEMORY.md 磁盘文件与 SQLite 数据库中的记忆表是<strong>互补关系</strong>,各有明确职责:</p>
|
||||
<table class="spec">
|
||||
<tr><th>维度</th><th>MEMORY.md(磁盘文件)</th><th>SQLite memories(数据库)</th></tr>
|
||||
<tr><td class="f-name">定位</td><td>用户可读可编辑的跨会话记忆摘要</td><td>结构化记忆存储,支持检索/评分/过期</td></tr>
|
||||
<tr><td class="f-name">格式</td><td>Markdown,有严格格式规范</td><td>结构化表(episodic_memories / semantic_memories / working_memories)</td></tr>
|
||||
<tr><td class="f-name">谁写入</td><td>Agent 会话结束后追加 + 用户手动编辑</td><td>Agent 运行时通过 memory_store 工具写入</td></tr>
|
||||
<tr><td class="f-name">谁读取</td><td>Agent 启动时解析,注入 System Prompt</td><td>Agent 运行时通过 memory_search 检索</td></tr>
|
||||
<tr><td class="f-name">检索方式</td><td>全量注入上下文(不检索)</td><td>关键词/语义检索,按相关性排序</td></tr>
|
||||
</table>
|
||||
<div class="note-box">
|
||||
<strong>🔄 同步策略:</strong>
|
||||
<br><strong>Agent 启动时</strong>:读取 MEMORY.md → 解析 → 注入 System Prompt 动态区(不写入 SQLite)
|
||||
<br><strong>Agent 运行时</strong>:memory_store / memory_search 操作 SQLite(不读写 MEMORY.md)
|
||||
<br><strong>会话结束后</strong>:Agent 从 SQLite 提取本次会话的重要记忆 → 追加到 MEMORY.md(按格式规范)
|
||||
<br><strong>用户编辑后</strong>:下次启动时 Agent 重新解析 MEMORY.md,不回写 SQLite
|
||||
<br><strong>Source of Truth</strong>:MEMORY.md 是用户可见的“记忆摘要”,SQLite 是 Agent 运行时的“记忆工作区”。两者不强制实时同步,通过启动读取 + 会话结束追加实现单向流动。
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="api-section" id="file-users">
|
||||
<h2>👤 USERS.md — 用户信息画像</h2>
|
||||
<p class="desc">定义用户的背景、技能、偏好和当前目标。Agent 据此调整回答深度、技术栈偏向和交互风格。<strong>此文件完全由用户自定义。</strong></p>
|
||||
|
||||
<div class="file-card">
|
||||
<h4>👤 USERS.md</h4>
|
||||
<div class="path">~/MetonaWorkspaces/my-project/USERS.md</div>
|
||||
<p>用户自定义文件,描述用户画像</p>
|
||||
</div>
|
||||
|
||||
<div class="note-box">
|
||||
<strong>📝 用户自定义:</strong>USERS.md 的内容完全由用户决定。Metona 不预设任何用户信息。
|
||||
<br>用户可以描述自己的背景、技能、偏好、目标等,帮助 Agent 更好地理解和服务用户。
|
||||
</div>
|
||||
|
||||
<h4>推荐结构(仅供参考)</h4>
|
||||
<div class="code-block">
|
||||
<pre><span class="hl-cm"># USERS.md — 用户自定义画像</span>
|
||||
|
||||
<span class="hl-cm">## 基本信息</span>
|
||||
<span class="hl-cm"># 称呼、角色、经验等</span>
|
||||
|
||||
<span class="hl-cm">## 技术栈</span>
|
||||
<span class="hl-cm"># 熟悉的技术、工具、框架</span>
|
||||
|
||||
<span class="hl-cm">## 偏好</span>
|
||||
<span class="hl-cm"># 工具偏好、沟通风格、工作习惯</span>
|
||||
|
||||
<span class="hl-cm">## 当前目标</span>
|
||||
<span class="hl-cm"># 正在做什么、想要达成什么</span></pre>
|
||||
</div>
|
||||
|
||||
<h4>USERS.md 作用域</h4>
|
||||
<table class="spec">
|
||||
<tr><th>对象</th><th>影响</th></tr>
|
||||
<tr><td>技术回答</td><td>根据用户技术栈调整回答深度和示例</td></tr>
|
||||
<tr><td>工具选择</td><td>根据用户偏好选择工具和命令</td></tr>
|
||||
<tr><td>安全策略</td><td>根据用户角色调整权限级别</td></tr>
|
||||
<tr><td>语气风格</td><td>匹配用户的沟通习惯和偏好</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<hr class="section-divider">
|
||||
|
||||
<!-- ====== 全链路透明 ====== -->
|
||||
<section class="api-section" id="trace">
|
||||
<h2>🔍 全链路透明可追踪</h2>
|
||||
<p class="desc">
|
||||
用户可在任意时刻<strong>完整回溯</strong> Agent 的每一步决策过程。所有数据分为三个可见层级:
|
||||
</p>
|
||||
|
||||
<h3>三层可见性</h3>
|
||||
<table class="spec">
|
||||
<tr><th>层级</th><th>名称</th><th>存储位置</th><th>可见内容</th><th>用户访问方式</th></tr>
|
||||
<tr>
|
||||
<td class="f-name">L0</td>
|
||||
<td>UI 实时展示</td>
|
||||
<td class="f-type">内存</td>
|
||||
<td>Thought 过程、ToolCall 参数/结果、最终答案</td>
|
||||
<td>聊天界面 / TraceViewer 面板</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="f-name">L1</td>
|
||||
<td>会话日志</td>
|
||||
<td class="f-type">logs/session_{id}.jsonl</td>
|
||||
<td>每轮 ReAct 迭代的完整状态、LLM 原始输入/输出、工具调用详情</td>
|
||||
<td>直接打开 .jsonl 或内置日志查看器</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="f-name">L2</td>
|
||||
<td>审计数据库</td>
|
||||
<td class="f-type">.metona/agent.db</td>
|
||||
<td>结构化审计记录:谁(actor)、做了什么(target)、结果(outcome)、耗时</td>
|
||||
<td>SQLite 浏览器 / 内置控制台</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>会话日志格式 (.jsonl)</h3>
|
||||
<div class="code-block">
|
||||
<pre><span class="hl-cm"># logs/session_s_abc_20260625T120000Z.jsonl</span>
|
||||
{"seq":0,"ts":"2026-06-25T12:00:00.000Z","event":"session_start","sessionId":"s_abc","workspace":"/home/user/my-project"}
|
||||
{"seq":1,"ts":"2026-06-25T12:00:01.000Z","event":"context_built","sessionId":"s_abc","tokens":1240,"ratio":0.01}
|
||||
{"seq":2,"ts":"2026-06-25T12:00:01.500Z","event":"iteration_start","sessionId":"s_abc","iteration":1}
|
||||
{"seq":3,"ts":"2026-06-25T12:00:02.100Z","event":"llm_request","sessionId":"s_abc","iteration":1,"provider":"deepseek","model":"deepseek-v4-pro","messages":[...]}
|
||||
{"seq":4,"ts":"2026-06-25T12:00:03.800Z","event":"llm_response","sessionId":"s_abc","iteration":1,"content":"Thought: 需要读取文件...","finishReason":"tool_calls","usage":{"inputTokens":1240,"outputTokens":85,"totalTokens":1325}}
|
||||
{"seq":5,"ts":"2026-06-25T12:00:03.810Z","event":"tool_call","sessionId":"s_abc","iteration":1,"tool":"read_file","args":{"file_path":"data.csv"}}
|
||||
{"seq":6,"ts":"2026-06-25T12:00:03.820Z","event":"tool_result","sessionId":"s_abc","iteration":1,"tool":"read_file","success":true,"durationMs":5,"result":"..."}
|
||||
{"seq":7,"ts":"2026-06-25T12:00:04.500Z","event":"iteration_end","sessionId":"s_abc","iteration":1,"durationMs":3000}
|
||||
{"seq":8,"ts":"2026-06-25T12:00:10.000Z","event":"session_end","sessionId":"s_abc","totalIterations":3,"totalTokens":5430,"totalDurationMs":10000}</pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ====== 日志设计 ====== -->
|
||||
<section class="api-section" id="logging">
|
||||
<h2>📝 日志设计</h2>
|
||||
<p class="desc">四层日志体系,覆盖从系统级到业务级的全部可观测需求。</p>
|
||||
|
||||
<h3>日志分层</h3>
|
||||
<table class="spec">
|
||||
<tr><th>层级</th><th>日志类型</th><th>存储</th><th>内容</th></tr>
|
||||
<tr><td class="f-name">SYS</td><td>系统日志</td><td class="f-type">electron-log 文件</td><td>进程启动/退出、崩溃堆栈、内存/CPU 异常、更新事件</td></tr>
|
||||
<tr><td class="f-name">AGENT</td><td>Agent 引擎日志</td><td class="f-type">logs/agent.log</td><td>状态转换、迭代计数、超时、压缩触发、错误恢复</td></tr>
|
||||
<tr><td class="f-name">TOOL</td><td>工具执行日志</td><td class="f-type">.metona/agent.db (audit_logs)</td><td>每次工具调用的参数、结果、耗时、权限校验</td></tr>
|
||||
<tr><td class="f-name">TRACE</td><td>全链路追踪</td><td class="f-type">logs/session_*.jsonl</td><td>完整会话记录(见上文),可导出分析</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>数据库审计表结构</h3>
|
||||
<div class="code-block">
|
||||
<pre><span class="hl-cm">-- .metona/agent.db > audit_logs</span>
|
||||
<span class="hl-kw">CREATE TABLE</span> audit_logs (
|
||||
<span class="hl-prop">id</span> INTEGER <span class="hl-kw">PRIMARY KEY AUTOINCREMENT</span>,
|
||||
<span class="hl-prop">session_id</span> TEXT <span class="hl-kw">NOT NULL</span>, <span class="hl-cm">-- 会话 ID</span>
|
||||
<span class="hl-prop">iteration</span> INTEGER, <span class="hl-cm">-- ReAct 迭代轮次</span>
|
||||
<span class="hl-prop">event_type</span> TEXT <span class="hl-kw">NOT NULL</span>, <span class="hl-cm">-- tool_call | permission_check | error | llm_request | llm_response</span>
|
||||
<span class="hl-prop">actor</span> TEXT <span class="hl-kw">NOT NULL</span>, <span class="hl-cm">-- 'agent' | 'user' | 'system'</span>
|
||||
<span class="hl-prop">target</span> TEXT <span class="hl-kw">NOT NULL</span>, <span class="hl-cm">-- 操作对象 (工具名 / 模块名)</span>
|
||||
<span class="hl-prop">details</span> TEXT, <span class="hl-cm">-- JSON 格式详细信息</span>
|
||||
<span class="hl-prop">outcome</span> TEXT, <span class="hl-cm">-- 'success' | 'denied' | 'error'</span>
|
||||
<span class="hl-prop">duration_ms</span> INTEGER, <span class="hl-cm">-- 耗时</span>
|
||||
<span class="hl-prop">created_at</span> TEXT <span class="hl-kw">DEFAULT</span> (datetime(<span class="hl-str">'now'</span>))
|
||||
);</pre>
|
||||
</div>
|
||||
|
||||
<h3>日志级别</h3>
|
||||
<table class="spec">
|
||||
<tr><th>级别</th><th>含义</th><th>示例</th></tr>
|
||||
<tr><td class="f-name">DEBUG</td><td>开发调试细节</td><td>State transition: THINKING → PARSING</td></tr>
|
||||
<tr><td class="f-name">INFO</td><td>正常业务流程</td><td>MCP server 'filesystem' connected with 8 tools</td></tr>
|
||||
<tr><td class="f-name">WARN</td><td>非预期但可恢复</td><td>Context compression triggered at iteration 15</td></tr>
|
||||
<tr><td class="f-name">ERROR</td><td>需要关注的错误</td><td>Tool 'web_search' failed: network timeout</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<hr class="section-divider">
|
||||
|
||||
<!-- ====== 交互流程 ====== -->
|
||||
<section class="api-section" id="interaction">
|
||||
<h2>🔄 完整交互流程</h2>
|
||||
<p class="desc">从用户启动应用到一次完整对话结束的端到端流程。</p>
|
||||
|
||||
<h3>启动流程</h3>
|
||||
<div class="flow-step"><span class="flow-num">1</span><span class="flow-text">Electron Main Process 启动 → 初始化日志系统</span></div>
|
||||
<div class="flow-step"><span class="flow-num">2</span><span class="flow-text">选择/创建工作空间 → 校验必需文件(缺失则自动创建)</span></div>
|
||||
<div class="flow-step"><span class="flow-num">3</span><span class="flow-text">连接 SQLite(.metona/agent.db)→ 执行 schema 迁移 → 加载配置</span></div>
|
||||
<div class="flow-step"><span class="flow-num">4</span><span class="flow-text">初始化 Provider Adapter(根据数据库配置选择)</span></div>
|
||||
<div class="flow-step"><span class="flow-num">5</span><span class="flow-text">加载 4 个磁盘文件,构建 System Prompt(空文件不影响启动)</span></div>
|
||||
<div class="flow-step"><span class="flow-num">6</span><span class="flow-text">连接启用的 MCP Servers → 动态加载 MCP 工具</span></div>
|
||||
<div class="flow-step"><span class="flow-num">7</span><span class="flow-text">启动 React UI(Chromium Renderer),Agent 就绪</span></div>
|
||||
<div class="flow-step"><span class="flow-num">8</span><span class="flow-text">(可选)提示用户编辑 SOUL.md / AGENTS.md / USERS.md 以自定义 Agent</span></div>
|
||||
|
||||
<h3>对话流程(一次 ReAct 迭代)</h3>
|
||||
<div class="flow-step"><span class="flow-num">1</span><span class="flow-text">用户在 ChatInput 输入消息 → IPC <code>agent:sendMessage</code> 发送 MetonaRequest</span></div>
|
||||
<div class="flow-step"><span class="flow-num">2</span><span class="flow-text">Context Builder 组装 MetonaContext:System Prompt + 历史 + 记忆 + 工具列表</span></div>
|
||||
<div class="flow-step"><span class="flow-num">3</span><span class="flow-text">Provider Adapter 将 MetonaContext → 外部 API 格式 → 发送 LLM 请求</span></div>
|
||||
<div class="flow-step"><span class="flow-num">4</span><span class="flow-text">流式接收响应 → 转换为 MetonaStreamEvent → 实时推送 UI</span></div>
|
||||
<div class="flow-step"><span class="flow-num">5</span><span class="flow-text">Parser 解析 LLM 输出 → 提取 Thought / ToolCall 或 FinalAnswer</span></div>
|
||||
<div class="flow-step"><span class="flow-num">6</span><span class="flow-text">(如有 ToolCall)Policy Engine 校验权限 → 执行工具 → 收集 MetonaToolResult</span></div>
|
||||
<div class="flow-step"><span class="flow-num">7</span><span class="flow-text">Observation 注入上下文 → 写入审计日志 → 进入下一轮迭代或输出最终答案</span></div>
|
||||
<div class="flow-step"><span class="flow-num">8</span><span class="flow-text">会话结束 → 更新 MEMORY.md + SQLite → 生成 Session Report</span></div>
|
||||
|
||||
<h3>IPC 通道总览</h3>
|
||||
<p class="desc">以下为核心 Agent 交互通道。完整 IPC 通道列表(含会话管理、MCP 管理、应用工具等)见<strong>《构建指南》第八章 IPC 架构</strong>,以构建指南为权威定义。</p>
|
||||
<table class="spec">
|
||||
<tr><th>通道</th><th>方向</th><th>数据类型</th><th>用途</th></tr>
|
||||
<tr><td class="f-name">agent:sendMessage</td><td>Renderer → Main</td><td class="f-type">MetonaRequest</td><td>发送用户消息</td></tr>
|
||||
<tr><td class="f-name">agent:streamEvent</td><td>Main → Renderer</td><td class="f-type">MetonaStreamEvent</td><td>流式推送 LLM 输出</td></tr>
|
||||
<tr><td class="f-name">agent:stateChange</td><td>Main → Renderer</td><td class="f-type">AgentLoopState</td><td>状态机状态变化</td></tr>
|
||||
<tr><td class="f-name">agent:abortSession</td><td>Renderer → Main</td><td class="f-type">{sessionId}</td><td>用户中断会话</td></tr>
|
||||
<tr><td class="f-name">agent:providerSwitched</td><td>Main → Renderer</td><td class="f-type">{from, to, reason}</td><td>故障转移通知</td></tr>
|
||||
<tr><td class="f-name">db:searchMemories</td><td>Renderer → Main</td><td class="f-type">MemorySearchOptions</td><td>UI 查询记忆</td></tr>
|
||||
<tr><td class="f-name">config:get / config:set</td><td>双向</td><td class="f-type">{key, value}</td><td>读写配置</td></tr>
|
||||
</table>
|
||||
<div class="note-box">
|
||||
<strong>💡 完整 IPC 通道分组:</strong>构建指南第八章定义了 4 组 IPC 通道:<br>
|
||||
• <strong>Agent 交互</strong>(6 个):上表所列<br>
|
||||
• <strong>会话管理</strong>(6 个):<code>sessions:list / create / rename / delete / getMessages / pin</code><br>
|
||||
• <strong>MCP 管理</strong>(4 个):<code>mcp:listServers / addServer / removeServer / toggleServer</code><br>
|
||||
• <strong>应用工具</strong>(4 个):<code>app:getVersion / getAppDataPath / openExternal / showItemInFolder</code><br>
|
||||
所有 IPC 通道均通过 Preload <code>contextBridge</code> 安全暴露,渲染进程无 Node.js 访问权限。
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div style="text-align:center; padding: 40px 0 20px; color: var(--text-dim); font-size: 13px; border-top: 1px solid var(--border);">
|
||||
<p>🏗️ MetonaAI-Desktop 架构与交互设计文档</p>
|
||||
<p>基于: <strong style="color:var(--accent)">生产级通用 AI Agent 构建指南</strong> + <strong style="color:var(--accent)">Metona 内部 IR 标准</strong></p>
|
||||
<p style="margin-top:6px;">版本 <strong style="color:var(--accent)">v1.1.0</strong> · 2026-06-26</p>
|
||||
</div>
|
||||
|
||||
<!-- Back to Top Button -->
|
||||
<button id="backToTop" onclick="window.scrollTo({top:0,behavior:'smooth'})" style="
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
color: #000;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 12px rgba(6,182,212,0.3);
|
||||
transition: transform 0.2s, opacity 0.2s;
|
||||
z-index: 1000;
|
||||
">↑</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll('.sidebar a[href^="#"]').forEach(a=>{a.addEventListener('click',e=>{e.preventDefault();const t=document.querySelector(a.getAttribute('href'));t&&t.scrollIntoView({behavior:'smooth',block:'start'})})});
|
||||
const S=document.querySelectorAll('.api-section'),L=document.querySelectorAll('.sidebar a[href^="#"]'),B=document.getElementById('backToTop');
|
||||
window.addEventListener('scroll',()=>{let c='';S.forEach(s=>{if(window.scrollY>=s.offsetTop-100)c=s.id});L.forEach(a=>{a.classList.toggle('active',a.getAttribute('href')==='#'+c)});if(B)B.style.display=window.scrollY>300?'flex':'none'});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user