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 @@
-
+
@@ -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 @@