From c7aa5df81c087c94a5222b78311cdadfacd9c6b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B4=AB=E5=BD=B1233?= Date: Wed, 24 Jun 2026 11:40:18 +0800 Subject: [PATCH] =?UTF-8?q?v0.12.11:=20Plan=20Mode=20=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E6=A0=87=E5=87=86=E5=8C=96=20+=20=E5=9B=BE=E7=89=87=E9=99=84?= =?UTF-8?q?=E4=BB=B6=E6=84=9F=E7=9F=A5=20+=20=E5=BC=B9=E7=AA=97UI=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat: Plan Mode 强制输出格式模板(任务分析/执行计划/预期结果) feat: 支持无需工具步骤,纯分析推理任务不再强制工具调用 feat: 反幻觉铁律新增3条(已上传附件不查找、read_file不能看图、Plan不规划查找步骤) fix: 上传图片后消息文本明确提示AI图片已随消息提供 fix: Plan Mode确认弹窗UI优化(步骤卡片样式/宽度/滚动) style: extractPlanSteps 双策略解析,兼容多种模型输出格式 --- README.md | 6 +- package-lock.json | 4 +- package.json | 2 +- src/main/menu.ts | 2 +- src/renderer/components/input-area.ts | 65 +++++++++++++----- src/renderer/components/prompt-modal.ts | 20 ++++-- src/renderer/index.html | 2 +- src/renderer/public/AGENT.md | 3 +- src/renderer/services/agent-engine.ts | 91 +++++++++++++++++++------ 9 files changed, 143 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 9701e0d..af794ce 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@

- version + version electron typescript license @@ -256,7 +256,7 @@ npm start ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist ``` -产出:`release/Metona Ollama Setup v0.12.10.exe` +产出:`release/Metona Ollama Setup v0.12.11.exe` ## 🛠️ 常用命令 @@ -507,7 +507,7 @@ npm start ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist ``` -Output: `release/Metona Ollama Setup v0.12.10.exe` +Output: `release/Metona Ollama Setup v0.12.11.exe` ## 🛠️ Common Commands diff --git a/package-lock.json b/package-lock.json index 8590911..7cedc0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "metona-ollama-desktop", - "version": "0.12.10", + "version": "0.12.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "metona-ollama-desktop", - "version": "0.12.10", + "version": "0.12.11", "license": "MIT", "dependencies": { "ffmpeg-static": "^5.2.0", diff --git a/package.json b/package.json index 81a73dc..13d1d7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metona-ollama-desktop", - "version": "0.12.10", + "version": "0.12.11", "description": "Metona Ollama - TypeScript + Electron 桌面 AI 聊天客户端", "main": "dist/main/main.js", "author": "thzxx", diff --git a/src/main/menu.ts b/src/main/menu.ts index 756692c..efcc46a 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -101,7 +101,7 @@ export function createMenu(): void { dialog.showMessageBox(mainWindow!, { type: 'info', title: '关于 Metona Ollama', - message: 'Metona Ollama Desktop v0.12.10', + message: 'Metona Ollama Desktop v0.12.11', detail: 'TypeScript + Electron Ollama AI 聊天客户端\n\nhttps://gitee.com/thzxx/metona-ollama', icon: getIconPath() }); diff --git a/src/renderer/components/input-area.ts b/src/renderer/components/input-area.ts index 786a516..4600f21 100644 --- a/src/renderer/components/input-area.ts +++ b/src/renderer/components/input-area.ts @@ -582,14 +582,11 @@ async function handleRetry(): Promise { onPlanReady: async (plan: string, steps: string[]) => { try { const { showHtmlConfirm } = await import('./prompt-modal.js'); - const stepsHtml = steps.length > 0 - ? '

📋 执行步骤:
' + steps.map((s: string, i: number) => `  ${i + 1}. ${s}`).join('
') - : ''; - const html = `

${safeMarkdown(plan.slice(0, 1500))}${plan.length > 1500 ? '
…' : ''}${stepsHtml}
`; - return showHtmlConfirm(html, '📋 Plan Mode — 确认执行计划', true); + const html = buildPlanConfirmHtml(plan, steps); + return showHtmlConfirm(html, '📋 执行计划确认', true); } catch (err) { logWarn('Plan Mode: 弹窗加载失败,自动批准', (err as Error).message); - return true; // 加载失败时自动批准,不阻断执行 + return true; } }, onDone: async (finalContent, toolRecords, loopStats) => { @@ -866,10 +863,47 @@ function stripComments(content: string, language: string): string { } /** - * 构建文件内容 Parts,带 token 预算感知 - * - 小文件直接嵌入,大文件按预算截断 - * - 代码文件自动剥离注释 - */ + * 构建文件内容 Parts,带 token 预算感知 + * - 小文件直接嵌入,大文件按预算截断 + * - 代码文件自动剥离注释 + */ + +/** 构建 Plan Mode 确认弹窗 HTML */ +function buildPlanConfirmHtml(plan: string, steps: string[]): string { + const renderedPlan = safeMarkdown(plan.slice(0, 2000)); + const truncated = plan.length > 2000; + + const stepsHtml = steps.length > 0 + ? `
+
+ 📌 执行步骤(共 ${steps.length} 步) +
+
+ ${steps.map((s, i) => ` +
+ ${i + 1} + ${escapeHtml(s)} +
+ `).join('')} +
+
` + : ''; + + return ` +
+
+ 🤖 AI 执行计划 + 批准后 AI 将按步骤依次执行,每完成一步自动标记进度 +
+
+ ${renderedPlan} + ${truncated ? '
…(计划内容已截断)
' : ''} +
+ ${stepsHtml} +
+ `; +} + function buildFileContentParts(fileContents: Array<{ language: string; content: string }>): string[] { const numCtx = state.get(KEYS.NUM_CTX, 24576); const fileBudget = Math.floor(numCtx * FILE_TOKEN_BUDGET_RATIO); @@ -1056,9 +1090,9 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio logInfo('📄 文件内容已注入', `${fileParts.length} 段, 共 ${fileContents.reduce((s, f) => s + f.content.length, 0)} 字符`); } if (pendingImages.length === 1) { - apiParts.push(`[图片: ${pendingImages[0].name}]`); + apiParts.push(`[已上传图片: ${pendingImages[0].name} — 此图片已随消息提供,你可以直接分析,无需去磁盘查找]`); } else if (pendingImages.length > 1) { - apiParts.push(`[${pendingImages.length} 张图片: ${pendingImages.map(img => img.name).join(', ')}]`); + 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; base64: string }> }> = []; if (hasVideos) { @@ -1272,11 +1306,8 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio onPlanReady: async (plan: string, steps: string[]) => { try { const { showHtmlConfirm } = await import('./prompt-modal.js'); - const stepsHtml = steps.length > 0 - ? '

📋 执行步骤:
' + steps.map((s, i) => `  ${i + 1}. ${s}`).join('
') - : ''; - const html = `
${safeMarkdown(plan.slice(0, 1500))}${plan.length > 1500 ? '
…' : ''}${stepsHtml}
`; - const approved = await showHtmlConfirm(html, '📋 Plan Mode — 确认执行计划', true); + const html = buildPlanConfirmHtml(plan, steps); + const approved = await showHtmlConfirm(html, '📋 执行计划确认', true); if (approved) { logInfo('Plan Mode: 用户批准计划'); } diff --git a/src/renderer/components/prompt-modal.ts b/src/renderer/components/prompt-modal.ts index d1d1ae8..613aaca 100644 --- a/src/renderer/components/prompt-modal.ts +++ b/src/renderer/components/prompt-modal.ts @@ -25,11 +25,13 @@ function ensureModal(): void { - - `; @@ -131,12 +133,16 @@ export function showHtmlConfirm(html: string, title = '确认', wide = false): P return new Promise((resolve) => { resolveFn = (val) => resolve(val !== null); titleEl!.textContent = title; - bodyEl!.innerHTML = `
${html}
`; + bodyEl!.innerHTML = html; inputEl = null; overlayEl!.style.display = ''; // 宽屏模式:Plan Mode 确认框需要更大空间 const modalEl = overlayEl!.querySelector('.modal') as HTMLElement; - if (modalEl) modalEl.style.maxWidth = wide ? '700px' : ''; + if (modalEl) { + modalEl.style.maxWidth = wide ? '640px' : '420px'; + modalEl.style.maxHeight = wide ? '85vh' : ''; + } + okBtn!.textContent = wide ? '✅ 批准执行' : '确定'; okBtn!.focus(); }); } diff --git a/src/renderer/index.html b/src/renderer/index.html index 61b0914..b72379c 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -28,7 +28,7 @@
Metona Ollama - v0.12.10 + v0.12.11