Files
metona-ollama-desktop/src/renderer/index.html
T

685 lines
54 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="theme-color" content="#FAF7F2">
<meta name="description" content="Metona Ollama Desktop - TypeScript + Electron AI 聊天客户端">
<link rel="icon" href="/assets/icons/llama.ico" type="image/x-icon">
<title>Metona Ollama</title>
</head>
<body>
<div id="app">
<!-- ═══════════════ 日志面板(左侧)═══════════════ -->
<aside class="log-panel" id="logPanel">
<div class="log-panel-header">
<span class="log-panel-title">📋 执行日志</span>
<button class="btn btn-sm" id="btnExportLog" title="导出日志" style="padding:2px 8px;font-size:11px;opacity:0.7;">📥 导出</button>
</div>
<div class="log-panel-body" id="logPanelBody"></div>
</aside>
<!-- ═══════════════ 主内容区 ═══════════════ -->
<div class="main-wrap with-workspace" id="mainWrap">
<!-- ═══════════════ 顶部导航 ═══════════════ -->
<header class="app-header">
<div class="header-left">
<span class="logo">🦙</span>
<span class="app-title">Metona Ollama</span>
<span class="app-version">v0.11.0</span>
<button class="icon-btn help-btn" id="btnHelp" title="使用帮助">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>
<line x1="12" y1="17" x2="12.01" y2="17"/>
</svg>
</button>
</div>
<div class="header-right">
<div class="conn-status pending" id="connStatus" title="连接状态">
<span class="status-dot"></span>
<span class="status-label">未连接</span>
</div>
<button class="icon-btn" id="btnNewChat" title="新建会话">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
</svg>
</button>
<button class="icon-btn" id="btnMemory" title="Agent 记忆">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2a7 7 0 0 1 7 7c0 2.38-1.19 4.47-3 5.74V17a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-2.26C6.19 13.47 5 11.38 5 9a7 7 0 0 1 7-7z"/>
<line x1="9" y1="21" x2="15" y2="21"/>
</svg>
</button>
<button class="icon-btn" id="btnTools" title="工具面板">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
</svg>
</button>
<button class="icon-btn" id="btnTokenDashboard" title="Token 监控">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2"/><line x1="7" y1="13" x2="7" y2="17"/><line x1="11" y1="9" x2="11" y2="17"/><line x1="15" y1="5" x2="15" y2="17"/>
</svg>
</button>
<button class="icon-btn" id="btnHistory" title="历史记录">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>
</svg>
</button>
<button class="icon-btn" id="btnSettings" title="设置">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="3"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
</svg>
</button>
</div>
</header>
<!-- ═══════════════ 模型选择栏 ═══════════════ -->
<div class="model-bar">
<div class="model-bar-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="4" y="4" width="16" height="16" rx="2"/>
<path d="M9 9h6M9 12h6M9 15h4"/>
</svg>
</div>
<div class="model-select-wrap">
<select class="model-select" id="modelSelect">
<option value="">正在加载模型...</option>
</select>
<span class="model-select-arrow">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
</span>
</div>
<div class="model-badges" id="modelBadges">
<span class="model-badge think-badge" id="badgeThink" style="display:none;">🧠 Think</span>
<span class="model-badge vision-badge" id="badgeVision" style="display:none;">👁️ Vision</span>
<span class="model-badge tools-badge" id="badgeTools" style="display:none;">🔧 Tools</span>
</div>
</div>
<!-- ═══════════════ 聊天区域 ═══════════════ -->
<main class="chat-area" id="chatArea">
<div class="empty-state" id="emptyState">
<div class="empty-icon">
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="80" stroke="rgba(0,245,212,0.2)" stroke-width="2"/>
<circle cx="100" cy="100" r="60" stroke="rgba(123,47,247,0.2)" stroke-width="2"/>
<path d="M70 90c0-16.57 13.43-30 30-30s30 13.43 30 30" stroke="rgba(0,245,212,0.5)" stroke-width="2" stroke-linecap="round"/>
<circle cx="85" cy="100" r="5" fill="rgba(0,245,212,0.6)"/>
<circle cx="115" cy="100" r="5" fill="rgba(0,245,212,0.6)"/>
<path d="M88 120c4 4 20 4 24 0" stroke="rgba(0,245,212,0.4)" stroke-width="2" stroke-linecap="round"/>
</svg>
</div>
<h2>开始对话</h2>
<p>选择一个模型,输入消息开始聊天</p>
</div>
<div class="messages" id="messagesContainer" style="display:none;"></div>
<button class="scroll-to-bottom" id="scrollToBottom" style="display:none;" title="回到最新">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"/>
</svg>
<span>回到底部</span>
</button>
</main>
<!-- ═══════════════ 历史只读提示栏 ═══════════════ -->
<div class="history-view-bar" id="historyBar" style="display:none;">
<span>📖 正在查看历史记录(只读模式)</span>
<button class="btn btn-sm btn-outline" id="btnExitHistory">返回新会话</button>
</div>
<!-- ═══════════════ 输入区域 ═══════════════ -->
<div class="input-area" id="inputArea">
<div class="image-preview" id="imagePreview" style="display:none;"></div>
<div class="file-preview" id="filePreview" style="display:none;"></div>
<div class="input-row">
<button class="icon-btn attach-btn" id="btnAttachImg" title="上传图片">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/>
<polyline points="21 15 16 10 5 21"/>
</svg>
</button>
<button class="icon-btn attach-btn" id="btnAttachFile" title="上传文件(文本/代码)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<line x1="16" y1="13" x2="8" y2="13"/>
<line x1="16" y1="17" x2="8" y2="17"/>
<polyline points="10 9 9 9 8 9"/>
</svg>
</button>
<textarea class="chat-input" id="chatInput" placeholder="输入消息..." rows="1"></textarea>
<label class="think-toggle" id="thinkToggleWrap" title="Think 模式未可用">
<input type="checkbox" id="toggleThink" disabled>
<span class="think-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2a7 7 0 0 1 7 7c0 2.38-1.19 4.47-3 5.74V17a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-2.26C6.19 13.47 5 11.38 5 9a7 7 0 0 1 7-7z"/>
<line x1="9" y1="21" x2="15" y2="21"/>
</svg>
</span>
</label>
<button class="send-btn" id="btnSend">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/>
</svg>
</button>
</div>
</div>
</div><!-- /.main-wrap -->
<!-- ═══════════════ 工作空间面板(右侧,常驻显示,flex 子元素)═══════════════ -->
<aside class="workspace-panel" id="workspacePanel">
<div class="ws-header">
<span class="ws-title">🖥️ 工作空间</span>
<div class="ws-tabs">
<button class="ws-header-tab active" id="wsTabTerminal">💻 命令行</button>
<button class="ws-header-tab" id="wsTabTools">🔧 工具</button>
<button class="ws-header-tab" id="wsTabFiles">📁 文件</button>
</div>
</div>
<!-- 终端 Tab -->
<div class="ws-content" id="wsTerminalContent">
<div class="ws-term-output" id="wsTermOutput"></div>
<div class="ws-term-toolbar">
<span class="ws-term-hint" id="wsTermHint">等待 AI 执行命令...</span>
<div class="ws-term-toolbar-btns">
<button class="ws-toolbar-btn ws-stop-btn" id="wsStopBtn" title="终止当前命令" disabled>
<svg viewBox="0 0 24 24" fill="currentColor">
<rect x="6" y="6" width="12" height="12" rx="1"/>
</svg>
</button>
<button class="ws-toolbar-btn" id="wsClearBtn" title="清空">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="3 6 5 6 21 6"/>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
</svg>
</button>
</div>
</div>
</div>
<!-- 工具调用 Tab -->
<div class="ws-content" id="wsToolsContent" style="display:none;">
<div class="ws-tools-output" id="wsToolsOutput"></div>
<div class="ws-term-toolbar">
<span class="ws-tools-hint" id="wsToolsHint">等待工具调用...</span>
<div class="ws-term-toolbar-btns">
<button class="ws-toolbar-btn" id="wsToolsClearBtn" title="清空">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="3 6 5 6 21 6"/>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
</svg>
</button>
</div>
</div>
</div>
<!-- 文件 Tab -->
<div class="ws-content" id="wsFilesContent" style="display:none;">
<div class="ws-file-toolbar">
<button class="ws-toolbar-btn" id="wsFileUpBtn" title="上级目录">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"/>
</svg>
</button>
<span class="ws-file-path" id="wsFilePath">~</span>
<button class="ws-toolbar-btn" id="wsFileRefreshBtn" title="刷新">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="23 4 23 10 17 10"/>
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/>
</svg>
</button>
</div>
<div class="ws-file-list" id="wsFileList"></div>
<div class="ws-preview" id="wsPreview" style="display:none;">
<div class="ws-preview-header">
<span class="ws-preview-title" id="wsPreviewTitle"></span>
<button class="icon-btn" id="wsPreviewClose">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div class="ws-preview-content" id="wsPreviewContent"></div>
</div>
</div>
</aside>
<!-- ═══════════════ 设置模态框 ═══════════════ -->
<div class="modal-overlay" id="settingsModal" style="display:none;">
<div class="modal">
<div class="modal-header">
<h3>⚙️ 设置</h3>
<button class="icon-btn" id="btnCloseSettings">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div class="modal-body">
<div class="setting-group">
<label class="setting-label">Ollama 服务地址</label>
<input class="setting-input" id="inputServerUrl" type="url" placeholder="http://127.0.0.1:11434">
<div class="connection-info" id="connInfo">
<span class="status-badge" id="connBadge">检测中...</span>
<span class="server-version" id="serverVersion"></span>
</div>
<div class="cors-hint" id="corsHint" style="display:none;">
<p>⚠️ 连接失败,可能是 CORS 问题。请在启动 Ollama 时设置环境变量:</p>
<code>OLLAMA_ORIGINS="*" ollama serve</code>
</div>
</div>
<div class="setting-group">
<label class="setting-label">上下文长度(自动检测)</label>
<div style="display:flex;gap:8px;align-items:center;">
<span class="setting-input" id="displayNumCtx" style="margin-bottom:0;display:flex;align-items:center;background:var(--bg-layer);padding:9px 12px;">24576 tokens</span>
</div>
<p class="text-muted" style="margin-top:4px;font-size:11px;">选择模型时自动从模型元数据中获取实际上下文长度。</p>
<label class="setting-label" style="margin-top:12px;">温度(Temperature):<span id="tempValue">0.7</span></label>
<input type="range" id="inputTemperature" class="temp-slider" min="0" max="2" step="0.1" value="0.7">
<p class="text-muted" style="margin-top:4px;font-size:11px;">低值更精确稳定,高值更有创意多样(0 = 确定性,2 = 最大随机)</p>
<label class="setting-label" style="margin-top:12px;">Agent 最大轮数(maxTurns</label>
<div style="display:flex;gap:8px;align-items:center;">
<input class="setting-input" id="inputMaxTurns" type="number" min="1" max="500" step="1" value="85" style="margin-bottom:0;">
<span class="text-muted" style="white-space:nowrap;"></span>
</div>
<p class="text-muted" style="margin-top:4px;font-size:11px;">Agent Loop 最大工具调用轮数。默认 85 轮,复杂任务可能需要更多轮次。</p>
</div>
<div class="setting-group">
<label class="setting-label">⏱️ 超时设置(Timeout</label>
<p class="text-muted" style="font-size:11px;margin-bottom:10px;">调整各通信层超时时长。留空则使用默认值(HTTP:30s / 流式:300s / MCP:60s)。设为 0 可禁用超时。</p>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
<div>
<label style="font-size:12px;color:var(--text-secondary);" for="inputHttpTimeout">🌐 HTTP 请求超时(秒)</label>
<input class="setting-input" id="inputHttpTimeout" type="number" min="0" max="300" step="5" placeholder="30" style="margin-bottom:0;">
<p class="text-muted" style="font-size:10px;">web_search / web_fetch 等 HTTP 请求的超时</p>
</div>
<div>
<label style="font-size:12px;color:var(--text-secondary);" for="inputStreamTimeout">📡 流式调用超时(秒)</label>
<input class="setting-input" id="inputStreamTimeout" type="number" min="0" max="600" step="10" placeholder="300" style="margin-bottom:0;">
<p class="text-muted" style="font-size:10px;">Ollama 单轮流式响应的最大等待时间</p>
</div>
<div style="grid-column:1/-1;">
<label style="font-size:12px;color:var(--text-secondary);" for="inputMCPTimeout">🔌 MCP 请求超时(秒)</label>
<input class="setting-input" id="inputMCPTimeout" type="number" min="0" max="300" step="10" placeholder="60" style="margin-bottom:0;">
<p class="text-muted" style="font-size:10px;">MCP Server JSON-RPC 请求的超时(需重启 MCP 服务器生效)</p>
</div>
</div>
</div>
<div class="setting-group">
<label class="setting-label">运行中的模型</label>
<div class="running-models" id="runningModels">
<p class="text-muted">无运行中的模型</p>
</div>
</div>
<div class="setting-group">
<label class="setting-label">🖥️ 工作空间目录</label>
<div style="display:flex;gap:8px;align-items:center;">
<input class="setting-input" id="inputWorkspaceDir" type="text" placeholder="默认: 应用数据目录/workspace" style="margin-bottom:0;flex:1;" readonly>
<button class="btn btn-sm btn-outline" id="btnBrowseWorkspace">📂 浏览</button>
</div>
<p class="text-muted" style="margin-top:4px;font-size:11px;">工作空间用于存放 AI 执行命令时的工作文件。命令行默认在此目录执行。</p>
</div>
<div class="setting-group">
<label class="setting-label">
🧠 Agent 记忆系统
<label class="toggle-label" style="margin-left:auto;display:inline-flex;">
<input type="checkbox" id="toggleMemoryEnabled" checked>
<span class="toggle-slider"></span>
</label>
</label>
<p class="text-muted" style="font-size:11px;">启用后,AI 会自动从对话中提取关键信息(事实、偏好、规则)并跨会话记住。发送消息时自动检索相关记忆注入上下文。</p>
<div class="tool-list" style="margin-top:8px;">
<div class="tool-item"><span>📌 事实</span><span class="text-muted">项目信息、身份背景</span></div>
<div class="tool-item"><span>⚙️ 偏好</span><span class="text-muted">语言风格、技术栈偏好</span></div>
<div class="tool-item"><span>📏 规则</span><span class="text-muted">编码规范、输出格式要求</span></div>
</div>
</div>
<div class="setting-group">
<label class="setting-label">🧠 向量记忆引擎</label>
<p class="text-muted" style="font-size:11px;margin-bottom:8px;">选择嵌入模型以启用向量语义记忆。未选择时仅使用关键词匹配。</p>
<select class="setting-input" id="selectMemoryEmbedModel">
<option value="">未选择(仅关键词模式)</option>
</select>
<div id="memoryVectorStatus" class="text-muted" style="font-size:11px;margin-top:4px;"></div>
</div>
<div class="setting-group">
<label class="setting-label">🤖 子代理默认模型</label>
<p class="text-muted" style="font-size:11px;margin-bottom:8px;">子代理(spawn_task)使用的默认模型。留空则使用当前选中的模型。可为简单任务选择更快的模型。</p>
<select class="setting-input" id="selectSubAgentModel">
<option value="">跟随当前模型</option>
</select>
</div>
<div class="setting-group">
<label class="setting-label">显存管理</label>
<button class="btn btn-danger" id="btnReleaseVRAM">释放显存(卸载模型)</button>
</div>
<div class="setting-group">
<label class="setting-label">数据管理</label>
<div style="display:flex;gap:8px;flex-wrap:wrap;">
<button class="btn btn-outline" id="btnExportAll">📦 导出全部会话</button>
<button class="btn btn-outline" id="btnImportSessions">📥 导入会话</button>
<button class="btn btn-danger" id="btnClearAllHistory">清空所有历史</button>
</div>
<p class="text-muted" style="margin-top:6px;font-size:11px;">导出格式为 JSON,可备份后在其他设备导入恢复。</p>
</div>
<div class="setting-group">
<label class="setting-label">🔌 MCP 服务器</label>
<div style="display:flex;gap:8px;margin-bottom:8px;">
<input class="setting-input" id="inputMCPName" type="text" placeholder="名称" style="margin-bottom:0;width:100px;">
<input class="setting-input" id="inputMCPCommand" type="text" placeholder="命令 (如: npx)" style="margin-bottom:0;width:100px;">
<input class="setting-input" id="inputMCPArgs" type="text" placeholder="参数 (空格分隔)" style="margin-bottom:0;flex:1;">
<button class="btn btn-sm btn-outline" id="btnAddMCPServer"> 添加</button>
</div>
<div id="mcpServerList" style="font-size:12px;"></div>
</div>
</div>
</div>
</div>
<!-- ═══════════════ 历史记录模态框 ═══════════════ -->
<div class="modal-overlay" id="historyModal" style="display:none;">
<div class="modal">
<div class="modal-header">
<h3>🕐 历史记录</h3>
<button class="icon-btn" id="btnCloseHistory">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div class="modal-body">
<div class="history-search-wrap">
<input class="history-search-input" id="historySearchInput" type="text" placeholder="搜索会话标题或内容...">
<button class="history-search-clear" id="historySearchClear" style="display:none;"></button>
</div>
<div class="history-list" id="historyList"></div>
<div class="history-pagination" id="historyPagination"></div>
</div>
</div>
</div>
<!-- ═══════════════ 帮助模态框 ═══════════════ -->
<div class="modal-overlay" id="helpModal" style="display:none;">
<div class="modal">
<div class="modal-header">
<h3>📖 使用帮助</h3>
<button class="icon-btn" id="btnCloseHelp">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div class="modal-body">
<div class="help-section"><h4>🚀 快速开始</h4><ol><li>确保 Ollama 已启动(默认 <code>http://127.0.0.1:11434</code>,可在设置中修改)</li><li>顶部模型栏选择一个模型,右侧会显示模型能力徽章(🧠 Think / 👁️ Vision / 🔧 Tools</li><li>输入消息,按 <kbd>Enter</kbd> 发送,<kbd>Shift+Enter</kbd> 换行</li></ol></div>
<div class="help-section"><h4>💬 聊天功能</h4><ul><li><strong>流式回复</strong> — 实时打字效果,随时点 ■ 停止</li><li><strong>Think 推理</strong> — 下方 Think 按钮切换,让模型展示深度思考过程(需模型支持,如 Qwen3)</li><li><strong>上下文长度自动检测</strong> — 切换模型时自动从模型元数据获取实际支持的上下文长度(如 Qwen3.6 27B = 262,144 tokens),无需手动配置</li><li><strong>多模态</strong> — 上传图片,模型需支持 Vision 能力。图片自动压缩至合适分辨率,节省上下文</li><li><strong>文件分析</strong> — 支持 50+ 种文本/代码格式,单文件 ≤500KB,自动剥离注释并按上下文预算智能截断</li><li><strong>AI 回复顶部</strong> — 每条 AI 回复上方展示 📋 系统提示词折叠卡片,可点击查看实际发送给模型的完整上下文</li></ul></div>
<div class="help-section"><h4>🔧 Tool Calling(始终开启)</h4><ul><li>所有消息均通过 <strong>Agent Loop 自主调用本地工具</strong>,像一个本地 Agent,无普通聊天模式</li><li><strong>38 个工具</strong>,分为 9 类:<ul><li><strong>文件系统</strong>16 个):read_file / write_file / list_directory / search_files / create_directory / delete_file / move_file / copy_file / edit_file / get_file_info / tree / download_file / diff_files / replace_in_files / read_multiple_files / compress</li><li><strong>命令执行</strong>1 个):run_command(实时流式输出,支持自动/需确认/禁用三种模式,可配置超时)</li><li><strong>联网搜索</strong>2 个):web_search(4引擎并行+智能排序+时间过滤+摘要增强)/ web_fetch(反爬headers+UA自动切换+自动回退浏览器渲染)</li><li><strong>Git</strong>(1 个):git17 个子操作,push/pull/clone 内置60-120s超时保护)</li><li><strong>浏览器控制</strong>9 个):browser_open / browser_screenshot / browser_evaluate / browser_extract / browser_click / browser_type / browser_scroll / browser_wait / browser_close</li><li><strong>记忆 & 会话 & Skill</strong>8 个):memory_search / memory_add / memory_replace / memory_remove / session_list / session_read / skill_list / skill_view</li><li><strong>子代理</strong>1 个):spawn_task</li></ul></li><li>read_file 支持 binary模式读取/base64解码/字节分页/2000行默认+截断hint自动续读</li><li>write_file 支持 base64写二进制文件/mode(overwrite+append)合并append_file功能/10MB</li><li>edit_file 支持 use_regex 正则替换</li><li>search_files 支持正则表达式搜索(use_regex=true</li><li>browser_screenshot 支持全页面截图+元素截图</li><li>browser_extract 支持CSS选择器提取特定区域</li><li>new browser_wait 等待元素出现或定时等待</li><li><code>run_command</code> 支持三模式切换:自动/需确认/禁用</li><li>危险命令(<code>rm -rf</code><code>mkfs</code>、反弹 shell 等)和系统路径(<code>/etc</code><code>~/.ssh</code> 等)被自动拦截</li><li>工具调用以<strong>可视化卡片</strong>展示状态(pending → running → success/error),在工作空间🔧工具页签中显示</li><li>默认最大 <strong>85 轮</strong>工具调用循环,上下文使用率>80%时自动缩减到3轮</li><li>独立工具自动<strong>并行执行</strong>(16个只读工具加入并行白名单),有依赖关系的工具串行执行</li></ul></div>
<div class="help-section"><h4>🧠 Agent 记忆系统</h4><ul><li>设置中开启后,AI 从对话中<strong>自动提取关键信息</strong>(事实/偏好/规则),跨会话持续积累</li><li><strong>增量提取</strong>:长对话中每 20 轮自动触发轻量级记忆提取,不等到对话结束</li><li>对话结束时自动触发完整记忆提取</li><li>新对话时自动检索相关记忆注入上下文,让 AI "记住"你</li><li>点击顶部 🧠 按钮打开记忆面板:搜索、筛选、编辑、删除</li><li><strong>记忆容量上限 500 条</strong>,超限时自动清理低价值条目(规则类型受保护)</li><li><strong>向量语义搜索</strong>:在设置中选择嵌入模型后,支持语义级别记忆检索(更精准)</li><li>未选择嵌入模型时,使用关键词匹配检索记忆</li><li>AI 可通过 memory 工具主动管理记忆</li><li>记忆写入前自动安全扫描(prompt injection / 敏感信息 / 不可见字符检测)</li><li>记忆存储在本地 SQLite,不会上传到任何服务器</li></ul></div>
<div class="help-section"><h4>🤖 Agent Loop v0.11.0 增强</h4><ul><li><strong>智能上下文压缩</strong>:滑动窗口 + LLM结构化JSON摘要,超过50%上下文窗口自动触发</li><li><strong>流式调用超时保护</strong>:可配置超时(默认300s),Ollama 卡死不再永久阻塞</li><li><strong>HTTP/MCP 超时可配</strong>:设置面板可分别调整 HTTP(默认30s)和 MCP(默认60s)超时</li><li><strong>Final Answer 智能检测</strong>:多模式匹配(Final Answer/最终答案/最终回答/总结),避免漏检或误触</li><li><strong>Token 感知迭代预算</strong>:上下文使用率>80%时自动缩减剩余轮次到3轮</li><li><strong>工具缓存 TTL</strong>:搜索5分钟/网页10分钟/文件30分钟,过期自动刷新</li><li><strong>自动子任务拆解</strong>:检测"同时/分别/以及"等并行关键词,自动 spawn 子代理并行处理</li><li><strong>旧工具结果自动截断</strong>:超过10轮的工具结果自动截断到500字符,控制上下文膨胀</li></ul></div>
<div class="help-section"><h4>🔌 MCPModel Context Protocol</h4><ul><li>支持连接外部 MCP Server,动态扩展工具能力</li><li>设置面板可添加/启用/禁用/删除 MCP 服务器</li><li>MCP 工具以 <code>mcp_{server}_{tool}</code> 前缀注册,与内置工具统一调度</li><li>启动时自动连接已启用的 MCP 服务器</li></ul></div>
<div class="help-section"><h4>📋 自定义文件(SOUL.md / AGENT.md / USER.md</h4><ul><li>在工作空间目录创建以下文件即可自定义 AI 行为,修改后下一轮对话立即生效</li><li><strong>SOUL.md</strong> — AI 身份、性格、行为准则(<strong>永远不可被压缩</strong>,注入为最高优先级系统提示词)</li><li><strong>AGENT.md</strong> — 工具调用规则、链式调用模式、核心约束(内置精简默认版,可通过工作空间覆盖)</li><li><strong>USER.md</strong> — 用户画像:技术栈、偏好、习惯等个人信息,AI 在对话中自动参考</li><li>可在 AI 回复顶部的 📋 系统提示词卡片中查看实际注入的完整上下文</li><li>删除工作空间中的文件即可恢复为内置默认版本</li></ul></div>
<div class="help-section"><h4>🎯 技能自动生成</h4><ul><li>AI 完成包含 <strong>2+ 工具调用</strong>的复杂任务后,自动从执行轨迹中提取可复用技能</li><li><strong>语义匹配</strong>:配置嵌入模型后,根据用户消息的语义自动匹配最相关技能</li><li><strong>参数自优化</strong>:同一技能链多次执行后,自动合并高频参数提示,越用越精准</li><li><strong>技能衰减</strong>:30 天未使用的技能自动降权,90 天几乎忽略</li><li><strong>链合并</strong>:相同前缀(前 2 步相同)的多个技能自动合并为抽象技能</li><li>AI 可通过 <code>skill_list</code>(渐进式摘要)和 <code>skill_view</code>(完整工具链)查看技能</li><li>技能存储在本地 SQLite,不会上传到任何服务器</li></ul></div>
<div class="help-section"><h4>📊 Token 实时监控</h4><ul><li>点击顶部 📊 按钮打开 Token 监控仪表盘</li><li><strong>全局统计</strong> — 跨会话累计 Token 消耗,柱状图按会话展示趋势</li><li><strong>当前会话</strong> — 实时显示本轮对话的 Token 消耗,按轮次展示明细</li><li>每 2 秒自动刷新数据,支持输入/输出分色显示</li></ul></div>
<div class="help-section"><h4>🖥️ 布局说明</h4><ul><li><strong>左侧面板</strong> — 执行日志,实时显示应用运行日志(连接、模型加载、工具调用等)</li><li><strong>中间区域</strong> — 聊天消息,顶部 Header + 模型栏,底部输入框</li><li><strong>右侧面板</strong> — 工作空间(常驻显示),包含 3 个页签:<ul><li><strong>💻 命令行 Tab</strong> — 终端界面,实时流式输出,支持长时间运行(无超时),单一终端进程</li><li><strong>🔧 工具 Tab</strong> — 展示本轮对话的工具调用卡片,含状态统计(总数 ✅ 成功 ❌ 失败),AI 执行命令时自动切到此页签</li><li><strong>📁 文件 Tab</strong> — 浏览工作空间目录,点击文件预览内容(带行号),支持上级目录导航</li></ul></li><li>工作空间目录可在设置中修改</li></ul></div>
<div class="help-section"><h4>🕐 历史记录</h4><ul><li>所有会话自动保存到本地 SQLite</li><li>点击顶部 🕐 按钮查看、搜索、恢复历史会话</li><li>支持导出 JSON / <code>.metona</code> 加密备份</li></ul></div>
<div class="help-section"><h4>⚡ 快捷键 & 命令</h4><table class="help-shortcuts"><tr><td><kbd>Enter</kbd></td><td>发送消息</td></tr><tr><td><kbd>Shift + Enter</kbd></td><td>换行</td></tr><tr><td><kbd>Ctrl + N</kbd></td><td>新建会话</td></tr><tr><td><kbd>Ctrl + M</kbd></td><td>记忆管理</td></tr><tr><td><kbd>Esc</kbd></td><td>关闭弹窗</td></tr></table><p style="margin-top:8px;font-size:12px;color:var(--text-secondary);">输入框命令(直接输入后发送):</p><table class="help-shortcuts"><tr><td><code>/retry</code></td><td>重试上一轮回复</td></tr><tr><td><code>/undo</code></td><td>撤销最后一条用户消息及回复</td></tr><tr><td><code>/compress</code></td><td>用 AI 摘要压缩长对话上下文</td></tr></table></div>
</div>
</div>
</div>
<!-- ═══════════════ 工具面板模态框 ═══════════════ -->
<div class="modal-overlay" id="toolsModal" style="display:none;">
<div class="modal">
<div class="modal-header">
<h3>🔧 工具面板(38 个)</h3>
<button class="icon-btn" id="btnCloseTools">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div class="modal-body">
<p class="text-muted" style="font-size:12px;margin-bottom:12px;">AI 始终可在对话中自主调用以下工具(Tool Calling 永久开启)。高风险操作需用户确认。</p>
<div class="tools-grid">
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">📄</span><span class="tool-card-name">read_file</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">读取文件(文本/binary/base64)2000行默认,截断自动提示续读</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">✏️</span><span class="tool-card-name">write_file</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">写入/追加文件,支持base64二进制,最大10MB</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">📁</span><span class="tool-card-name">list_directory</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">列出目录内容(2000条上限),支持递归/偏移分页/扩展名过滤</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">🔍</span><span class="tool-card-name">search_files</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">按文件名或内容搜索,支持正则表达式</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">📂</span><span class="tool-card-name">create_directory</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">创建目录,自动创建父级</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">🗑️</span><span class="tool-card-name">delete_file</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">删除文件或目录,返回被删文件数和总大小</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">📦</span><span class="tool-card-name">move_file</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">移动或重命名文件/目录</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">📋</span><span class="tool-card-name">copy_file</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">复制文件或目录到新位置</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">🌐</span><span class="tool-card-name">web_fetch</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">抓取网页内容,反爬headers+UA切换+自动回退浏览器渲染</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">✂️</span><span class="tool-card-name">edit_file</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">查找替换(支持正则use_regex),全部或单次替换</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">💻</span><span class="tool-card-name">run_command</span><span class="tool-card-badge confirm" id="runCommandBadge">需确认</span></div>
<div class="tool-card-desc">执行 shell 命令,无超时限制</div>
<div style="margin-top:6px;display:flex;align-items:center;gap:8px;">
<label style="margin:0;font-size:11px;color:var(--text-secondary);">执行模式:</label>
<select id="selectRunCommandMode" style="font-size:11px;padding:2px 6px;background:var(--bg-card);color:var(--text-primary);border:1px solid var(--border-default);border-radius:4px;">
<option value="confirm">需确认</option>
<option value="auto">自动执行</option>
<option value="disabled">禁用</option>
</select>
</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon"></span><span class="tool-card-name">get_file_info</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">获取文件/目录详细信息:大小、日期、权限</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">🌳</span><span class="tool-card-name">tree</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">以树形结构展示目录内容</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">⬇️</span><span class="tool-card-name">download_file</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">从 URL 下载文件到本地</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">🔀</span><span class="tool-card-name">diff_files</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">对比两个文件,显示差异</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">🔄</span><span class="tool-card-name">replace_in_files</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">按 glob 模式批量查找替换多个文件</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">📚</span><span class="tool-card-name">read_multiple_files</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">并行读取最多50个文件,每文件10KB</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">🔖</span><span class="tool-card-name">git</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">Git 全操作(17个子命令:status/log/diff/add/commit/push等)</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">🗜️</span><span class="tool-card-name">compress</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">创建或解压 zip/tar.gz 归档</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">🔍</span><span class="tool-card-name">web_search</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">4引擎并行搜索(Bing+百度+DDG+Google),智能排序+时间过滤+摘要增强</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">🧠</span><span class="tool-card-name">memory_search</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">语义搜索 Agent 记忆,检索相关历史信息</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">💾</span><span class="tool-card-name">memory_add</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">添加记忆条目(事实/偏好/规则),跨会话积累</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">🔄</span><span class="tool-card-name">memory_replace</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">替换已有记忆内容(子串匹配 old_text,替换为 new_content</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">🗑️</span><span class="tool-card-name">memory_remove</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">删除记忆条目(子串匹配 old_text)</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">📋</span><span class="tool-card-name">session_list</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">列出历史会话,支持按标题搜索</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">📖</span><span class="tool-card-name">session_read</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">读取历史会话消息内容</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">🤖</span><span class="tool-card-name">spawn_task</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">生成子代理独立执行任务,完成后回传结果</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">📚</span><span class="tool-card-name">skill_list</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">列出所有自动生成的技能(名称、描述、成功率)</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">📖</span><span class="tool-card-name">skill_view</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">查看技能详情(完整工具链、参数提示、使用统计)</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">🌐</span><span class="tool-card-name">browser_open</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">打开URL,支持等待特定CSS元素出现(SPA页面)</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">📸</span><span class="tool-card-name">browser_screenshot</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">截图(视口/全页面/指定元素),返回base64 PNG</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon"></span><span class="tool-card-name">browser_evaluate</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">在浏览器页面中执行 JavaScript</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">📰</span><span class="tool-card-name">browser_extract</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">提取页面文本+链接,支持CSS选择器指定区域</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">👆</span><span class="tool-card-name">browser_click</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">点击元素,支持等待出现+自动滚动到视图</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">⌨️</span><span class="tool-card-name">browser_type</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">输入文本,支持追加模式(不先清空)+自动提交表单</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">↕️</span><span class="tool-card-name">browser_scroll</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">滚动页面(上/下/顶/底)或滚动到指定元素</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon"></span><span class="tool-card-name">browser_wait</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">等待指定元素出现或固定时间延迟</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon"></span><span class="tool-card-name">browser_close</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">关闭 Agent 浏览器并释放资源</div>
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════ Token 监控仪表盘 ═══════════════ -->
<div class="modal-overlay" id="tokenDashboardModal" style="display:none;">
<div class="modal" style="max-width:860px;width:860px;">
<div class="modal-header">
<h3>📊 Token 实时监控</h3>
<button class="icon-btn" id="btnCloseTokenDashboard">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div class="modal-body" style="max-height:75vh;overflow-y:auto;">
<div class="td-overview" id="tdOverview"></div>
<div class="td-view-toggle" id="tdViewToggle">
<button class="td-view-btn active" data-view="global">🌍 全局统计</button>
<button class="td-view-btn" data-view="session">💬 当前会话</button>
</div>
<div class="td-section">
<div class="td-section-title" id="tdChartTitle">📈 Token 消耗趋势 — 全局</div>
<div class="td-chart" id="tdChart"></div>
</div>
<div class="td-section">
<div class="td-section-title" id="tdTableTitle">📋 明细(按时间倒序)</div>
<div class="td-table-wrap">
<table class="td-table">
<thead>
<tr>
<th>轮次</th>
<th>角色</th>
<th>输入</th>
<th>输出</th>
<th>合计</th>
<th>耗时</th>
<th>时间</th>
</tr>
</thead>
<tbody id="tdTableBody"></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════ 图片预览 Lightbox ═══════════════ -->
<div class="lightbox-overlay" id="lightbox" style="display:none;">
<button class="lightbox-close" id="lightboxClose"></button>
<img class="lightbox-img" id="lightboxImg" src="" alt="预览">
</div>
<!-- ═══════════════ 工具调用确认对话框 ═══════════════ -->
<div class="modal-overlay" id="toolConfirmModal" style="display:none;">
<div class="modal" style="max-width:520px;">
<div class="modal-header">
<h3 id="toolConfirmTitle">⚠️ 确认操作</h3>
</div>
<div class="modal-body" id="toolConfirmBody">
<p>AI 请求执行一个需要确认的操作。</p>
</div>
<div class="modal-actions" style="padding:12px 20px 16px;">
<div style="flex:1;"></div>
<button class="btn btn-outline" id="toolCancelBtn">❌ 取消</button>
<button class="btn btn-primary" id="toolConfirmBtn">✅ 确认执行</button>
</div>
</div>
</div>
<!-- ═══════════════ Toast 通知容器 ═══════════════ -->
<div class="toast-container" id="toastContainer"></div>
<!-- ═══════════════ 应用入口 ════════════════ -->
<script type="module" src="./main.ts"></script>
</body>
</html>