v0.11.9: 修复纯文本消息卡片丢失 + 子代理参数可配置
fix: 恢复纯文本消息的 else if 分支,修复 fed1e6d 重构后用户消息卡片不显示
fix: allVids 类型注解补充 base64 字段,消除 TS2322 编译错误
feat: 子代理最大轮次和超时接入设置面板(subAgentMaxLoops/subAgentTimeout)
chore: 版本号 0.11.7 → 0.11.9
This commit is contained in:
+1
-1
@@ -101,7 +101,7 @@ export function createMenu(): void {
|
||||
dialog.showMessageBox(mainWindow!, {
|
||||
type: 'info',
|
||||
title: '关于 Metona Ollama',
|
||||
message: 'Metona Ollama Desktop v0.11.7',
|
||||
message: 'Metona Ollama Desktop v0.11.9',
|
||||
detail: 'TypeScript + Electron Ollama AI 聊天客户端\n\nhttps://gitee.com/thzxx/metona-ollama',
|
||||
icon: getIconPath()
|
||||
});
|
||||
|
||||
@@ -1018,7 +1018,7 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio
|
||||
} else if (pendingImages.length > 1) {
|
||||
apiParts.push(`[${pendingImages.length} 张图片: ${pendingImages.map(img => img.name).join(', ')}]`);
|
||||
}
|
||||
let allVids: Array<{ name: string; count: number; dur: number; frames: Array<{ timestampSeconds: number; name: string }> }> = [];
|
||||
let allVids: Array<{ name: string; count: number; dur: number; frames: Array<{ timestampSeconds: number; name: string; base64: string }> }> = [];
|
||||
if (hasVideos) {
|
||||
if (pendingVideoMeta && pendingVideoFrames.length > 0) {
|
||||
allVids.push({ name: pendingVideoMeta.fileName, count: pendingVideoFrames.length, dur: pendingVideoMeta.duration, frames: pendingVideoFrames });
|
||||
@@ -1052,6 +1052,18 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio
|
||||
msg._fileContents = pendingFiles.map(f => ({ language: f.language, content: f.content }));
|
||||
}
|
||||
msgsToAdd.push(msg);
|
||||
} else if (text || pendingFiles.length > 0) {
|
||||
// 纯文本/纯文件消息(无图片、视频附件)
|
||||
const msg: ChatMessage = {
|
||||
role: 'user',
|
||||
content: text || '',
|
||||
timestamp: now
|
||||
};
|
||||
if (userFiles.length > 0) {
|
||||
msg.files = userFiles;
|
||||
msg._fileContents = pendingFiles.map(f => ({ language: f.language, content: f.content }));
|
||||
}
|
||||
msgsToAdd.push(msg);
|
||||
}
|
||||
|
||||
const isFirstMsg = currentSession.messages.length === 0;
|
||||
|
||||
@@ -471,6 +471,29 @@ document.querySelector('#selectSubAgentModel')?.addEventListener('change', async
|
||||
logSetting('子代理默认模型', val || '跟随当前模型');
|
||||
});
|
||||
|
||||
// 子代理最大轮次
|
||||
const saveSubAgentMaxLoops = debounce(async () => {
|
||||
const db = state.get<ChatDB | null>(KEYS.DB);
|
||||
const val = (document.querySelector('#inputSubAgentMaxLoops') as HTMLInputElement).value.trim();
|
||||
const maxLoops = val ? parseInt(val) : 10;
|
||||
state.set('subAgentMaxLoops', maxLoops);
|
||||
if (db) await db.saveSetting('subAgentMaxLoops', maxLoops);
|
||||
logSetting('子代理最大轮次', `${maxLoops} 轮`);
|
||||
}, 500);
|
||||
document.querySelector('#inputSubAgentMaxLoops')?.addEventListener('input', saveSubAgentMaxLoops);
|
||||
|
||||
// 子代理超时(秒)
|
||||
const saveSubAgentTimeout = debounce(async () => {
|
||||
const db = state.get<ChatDB | null>(KEYS.DB);
|
||||
const val = (document.querySelector('#inputSubAgentTimeout') as HTMLInputElement).value.trim();
|
||||
const sec = val ? parseInt(val) : -1; // -1=默认
|
||||
const ms = sec >= 0 ? sec * 1000 : 300_000;
|
||||
state.set('subAgentTimeout', ms);
|
||||
if (db) await db.saveSetting('subAgentTimeout', sec);
|
||||
logSetting('子代理超时', sec >= 0 ? (sec === 0 ? '已禁用' : `${sec}s`) : '默认(300s)');
|
||||
}, 500);
|
||||
document.querySelector('#inputSubAgentTimeout')?.addEventListener('input', saveSubAgentTimeout);
|
||||
|
||||
function updateMemoryVectorStatus(): void {
|
||||
const statusEl = document.querySelector('#memoryVectorStatus');
|
||||
if (!statusEl) return;
|
||||
|
||||
+12
-2
@@ -28,7 +28,7 @@
|
||||
<div class="header-left">
|
||||
<span class="logo">🦙</span>
|
||||
<span class="app-title">Metona Ollama</span>
|
||||
<span class="app-version">v0.11.7</span>
|
||||
<span class="app-version">v0.11.9</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"/>
|
||||
@@ -362,6 +362,16 @@
|
||||
<option value="">跟随当前模型</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="setting-group">
|
||||
<label class="setting-label">🤖 子代理最大轮次</label>
|
||||
<p class="text-muted" style="font-size:11px;margin-bottom:8px;">子代理最多执行多少轮工具调用。留空使用默认值(10轮)。</p>
|
||||
<input type="number" id="inputSubAgentMaxLoops" class="setting-input" placeholder="10" min="1" max="100" step="1" style="width:100px;">
|
||||
</div>
|
||||
<div class="setting-group">
|
||||
<label class="setting-label">🤖 子代理超时(秒)</label>
|
||||
<p class="text-muted" style="font-size:11px;margin-bottom:8px;">子代理最长运行时间,填 0 禁用超时。留空使用默认值(300秒)。</p>
|
||||
<input type="number" id="inputSubAgentTimeout" class="setting-input" placeholder="300" min="0" max="3600" step="30" style="width:100px;">
|
||||
</div>
|
||||
<div class="setting-group">
|
||||
<label class="setting-label">显存管理</label>
|
||||
<button class="btn btn-danger" id="btnReleaseVRAM">释放显存(卸载模型)</button>
|
||||
@@ -427,7 +437,7 @@
|
||||
<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> — 上传图片或视频(≤10MB),模型需支持 Vision。图片自动压缩,视频 1fps 提取帧序列带时序标注</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>44 个工具</strong>,分为 10 类:<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(支持 SearXNG 元搜索引擎 JSON API / 四引擎 HTML 解析,双模式可切换)/ web_fetch(反爬headers+UA自动切换+自动回退浏览器渲染)</li><li><strong>Git</strong>(1 个):git(17 个子操作,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><li><strong>系统工具</strong>(6 个):datetime / calculator / random / uuid / json_format / hash</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.7 增强</h4><ul><li><strong>🎬 视频上传</strong>:支持上传 .mp4/.avi/.mov/.mkv/.webm 等视频(≤10MB),自动 1fps 提取帧序列(带时间戳),多模态模型原生理解视频时序关系</li><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>🤖 Agent Loop v0.11.9 增强</h4><ul><li><strong>🎬 视频上传</strong>:支持上传 .mp4/.avi/.mov/.mkv/.webm 等视频(≤10MB),自动 1fps 提取帧序列(带时间戳),多模态模型原生理解视频时序关系</li><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>🔌 MCP(Model 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>🔍 SearXNG 元搜索引擎</h4><ul><li>点击顶部 🔍 按钮打开配置面板,可接入自部署的 SearXNG 实例</li><li><strong>JSON 模式</strong>:调用 SearXNG JSON API,聚合 70+ 引擎结果,结构化解析</li><li><strong>HTML 模式</strong>:获取原始搜索结果页面,交由 AI 自行分析提取信息</li><li>支持认证 Key(HTTP Header Authorization),保护私有实例</li><li>启用后替代内置四引擎方案;关闭即回退,无缝切换</li><li>所有参数(引擎、语言、安全搜索、时间范围等)均可独立配置</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>
|
||||
|
||||
@@ -401,6 +401,14 @@ async function init(): Promise<void> {
|
||||
(document.querySelector('#inputMCPTimeout') as HTMLInputElement).value = mcpTimeout >= 0 ? String(mcpTimeout) : '';
|
||||
logInit(`超时设置: HTTP=${httpTimeout>=0?httpTimeout:30}s 流式=${streamTimeout>=0?streamTimeout:300}s MCP=${mcpTimeout>=0?mcpTimeout:60}s`);
|
||||
|
||||
// ── 子代理参数 ──
|
||||
let subAgentMaxLoops = await db.getSetting<number>('subAgentMaxLoops', 10);
|
||||
let subAgentTimeout = await db.getSetting<number>('subAgentTimeout', -1); // -1=默认
|
||||
state.set('subAgentMaxLoops', subAgentMaxLoops);
|
||||
state.set('subAgentTimeout', subAgentTimeout >= 0 ? subAgentTimeout * 1000 : 300_000);
|
||||
(document.querySelector('#inputSubAgentMaxLoops') as HTMLInputElement).value = String(subAgentMaxLoops);
|
||||
(document.querySelector('#inputSubAgentTimeout') as HTMLInputElement).value = subAgentTimeout >= 0 ? String(subAgentTimeout) : '';
|
||||
|
||||
// ── Tool Calling 设置(永久开启,不可关闭)──
|
||||
state.set('toolCallingEnabled', true);
|
||||
const runCommandMode = await db.getSetting('runCommandMode', 'confirm') as 'auto' | 'confirm' | 'disabled';
|
||||
|
||||
@@ -48,8 +48,8 @@ export async function executeSubAgent(
|
||||
): Promise<ToolResult> {
|
||||
const api = state.get<OllamaAPI>(KEYS.API);
|
||||
const model = options.model || state.get<string>('_defaultModel', '');
|
||||
const maxLoops = options.maxLoops ?? SUB_AGENT_MAX_LOOPS;
|
||||
const timeout = options.timeout ?? SUB_AGENT_TIMEOUT;
|
||||
const maxLoops = options.maxLoops ?? state.get<number>('subAgentMaxLoops', SUB_AGENT_MAX_LOOPS);
|
||||
const timeout = options.timeout ?? state.get<number>('subAgentTimeout', SUB_AGENT_TIMEOUT);
|
||||
|
||||
if (!api || !model) {
|
||||
return { success: false, error: '未选择模型,无法执行子任务' };
|
||||
|
||||
Reference in New Issue
Block a user