chore: 版本号 0.14.2 → 0.14.3

fix: AI 回复消息卡片重复渲染 — onDone 去掉 updateLastAssistantMessage 避免双重 DOM
fix: 用户消息卡片只显示用户文字,代码生成的附件标记不显示
fix: onDone assistantMsg 补充 model 字段
This commit is contained in:
thzxx
2026-06-28 19:52:53 +08:00
parent 4c0405dc95
commit 3ddd493847
7 changed files with 20 additions and 33 deletions
+3 -3
View File
@@ -14,7 +14,7 @@
</p> </p>
<p align="center"> <p align="center">
<img src="https://img.shields.io/badge/version-v0.14.2-E8734A?style=flat-square" alt="version"> <img src="https://img.shields.io/badge/version-v0.14.3-E8734A?style=flat-square" alt="version">
<img src="https://img.shields.io/badge/electron-33+-47848F?style=flat-square&logo=electron" alt="electron"> <img src="https://img.shields.io/badge/electron-33+-47848F?style=flat-square&logo=electron" alt="electron">
<img src="https://img.shields.io/badge/typescript-5.7+-3178C6?style=flat-square&logo=typescript" alt="typescript"> <img src="https://img.shields.io/badge/typescript-5.7+-3178C6?style=flat-square&logo=typescript" alt="typescript">
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="license"> <img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="license">
@@ -253,7 +253,7 @@ npm start
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist
``` ```
产出:`release/Metona Ollama Setup v0.14.2.exe` 产出:`release/Metona Ollama Setup v0.14.3.exe`
## 🛠️ 常用命令 ## 🛠️ 常用命令
@@ -501,7 +501,7 @@ npm start
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist
``` ```
Output: `release/Metona Ollama Setup v0.14.2.exe` Output: `release/Metona Ollama Setup v0.14.3.exe`
## 🛠️ Common Commands ## 🛠️ Common Commands
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "metona-ollama-desktop", "name": "metona-ollama-desktop",
"version": "0.14.2", "version": "0.14.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "metona-ollama-desktop", "name": "metona-ollama-desktop",
"version": "0.14.2", "version": "0.14.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ffmpeg-static": "^5.2.0", "ffmpeg-static": "^5.2.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "metona-ollama-desktop", "name": "metona-ollama-desktop",
"version": "0.14.2", "version": "0.14.3",
"description": "Metona Ollama - TypeScript + Electron 桌面 AI 聊天客户端", "description": "Metona Ollama - TypeScript + Electron 桌面 AI 聊天客户端",
"main": "dist/main/main.js", "main": "dist/main/main.js",
"author": "thzxx", "author": "thzxx",
+1 -1
View File
@@ -101,7 +101,7 @@ export function createMenu(): void {
dialog.showMessageBox(mainWindow!, { dialog.showMessageBox(mainWindow!, {
type: 'info', type: 'info',
title: '关于 Metona Ollama', title: '关于 Metona Ollama',
message: 'Metona Ollama Desktop v0.14.2', message: 'Metona Ollama Desktop v0.14.3',
detail: 'TypeScript + Electron Ollama AI 聊天客户端\n\nhttps://gitee.com/thzxx/metona-ollama', detail: 'TypeScript + Electron Ollama AI 聊天客户端\n\nhttps://gitee.com/thzxx/metona-ollama',
icon: getIconPath() icon: getIconPath()
}); });
+10 -25
View File
@@ -588,7 +588,7 @@ async function handleRetry(): Promise<void> {
if (retryIterations > 0) { if (retryIterations > 0) {
if (finalContent) { if (finalContent) {
const lastMsg: ChatMessage = { const lastMsg: ChatMessage = {
role: 'assistant', content: finalContent, timestamp: Date.now(), role: 'assistant', content: finalContent, model: getSelectedModel(), timestamp: Date.now(),
...(retryThinkContent && { think: retryThinkContent }), ...(retryThinkContent && { think: retryThinkContent }),
...(toolRecords?.length && { toolCalls: toolRecords }), ...(toolRecords?.length && { toolCalls: toolRecords }),
...(loopStats?.eval_count && { eval_count: loopStats.eval_count }), ...(loopStats?.eval_count && { eval_count: loopStats.eval_count }),
@@ -599,10 +599,9 @@ async function handleRetry(): Promise<void> {
...s, messages: [...s.messages, lastMsg], updatedAt: Date.now() ...s, messages: [...s.messages, lastMsg], updatedAt: Date.now()
})); }));
} }
updateLastAssistantMessage(finalContent, retryThinkContent || null, loopStats || null, getSelectedModel(), Date.now(), toolRecords?.length ? toolRecords : undefined);
} else { } else {
const assistantMsg: ChatMessage = { const assistantMsg: ChatMessage = {
role: 'assistant', content: finalContent || '', timestamp: Date.now(), role: 'assistant', content: finalContent || '', model: getSelectedModel(), timestamp: Date.now(),
...(retryThinkContent && { think: retryThinkContent }), ...(retryThinkContent && { think: retryThinkContent }),
...(toolRecords?.length && { toolCalls: toolRecords }), ...(toolRecords?.length && { toolCalls: toolRecords }),
...(loopStats?.eval_count && { eval_count: loopStats.eval_count }), ...(loopStats?.eval_count && { eval_count: loopStats.eval_count }),
@@ -612,8 +611,9 @@ async function handleRetry(): Promise<void> {
state.update(KEYS.CURRENT_SESSION, (s: any) => ({ state.update(KEYS.CURRENT_SESSION, (s: any) => ({
...s, messages: [...s.messages, assistantMsg], updatedAt: Date.now() ...s, messages: [...s.messages, assistantMsg], updatedAt: Date.now()
})); }));
updateLastAssistantMessage(finalContent, retryThinkContent || null, loopStats || null, getSelectedModel(), Date.now(), toolRecords?.length ? toolRecords : undefined);
} }
// renderMessages 统一渲染 session 数据,先清掉流式 placeholder 避免 DOM 重复
clearMessagesDOM();
renderMessages(); renderMessages();
// ── 保存 Plan Mode 最终进度到会话(供下一轮 Loop 注入)── // ── 保存 Plan Mode 最终进度到会话(供下一轮 Loop 注入)──
const lastPlanStatus = state.get<string>('_lastPlanStatus', ''); const lastPlanStatus = state.get<string>('_lastPlanStatus', '');
@@ -1135,26 +1135,10 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio
const hasAttachments = pendingImages.length > 0 || hasVideos || userFiles.length > 0; const hasAttachments = pendingImages.length > 0 || hasVideos || userFiles.length > 0;
if (hasAttachments) { if (hasAttachments) {
// 聊天卡片显示用的简短描述 // ── 聊天卡片:只显示用户自己的文字 + 附件视觉预览(缩略图/文件卡片/视频帧),不含代码生成的文本标记 ──
const displayParts: string[] = []; const displayContent = text || '';
if (text) displayParts.push(text);
if (pendingVideoMeta && pendingVideoFrames.length > 0) {
displayParts.push(`[视频: ${pendingVideoMeta.fileName}, ${pendingVideoFrames.length}帧, 1fps]`);
}
for (const v of completedVideos) {
displayParts.push(`[视频: ${v.fileName}, ${v.frames.length}帧, 1fps]`);
}
if (pendingImages.length === 1) {
displayParts.push(`[图片: ${pendingImages[0].name}]`);
} else if (pendingImages.length > 1) {
displayParts.push(`[${pendingImages.length} 张图片]`);
}
for (const f of userFiles) {
displayParts.push(`[文件: ${f.name}]`);
}
const displayContent = displayParts.join('\n');
// Ollama API 用完整描述(文字 + 文件 + 视频帧序列 + 图片提示) // ── Ollama API 用完整描述(文字 + 文件内容 + 视频帧序列 + 图片提示)──
const apiParts: string[] = []; const apiParts: string[] = [];
if (text) apiParts.push(text); if (text) apiParts.push(text);
if (userFiles.length > 0) { if (userFiles.length > 0) {
@@ -1164,9 +1148,9 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio
logInfo('📄 文件内容已注入', `${fileParts.length} 段, 共 ${fileContents.reduce((s, f) => s + f.content.length, 0)} 字符`); logInfo('📄 文件内容已注入', `${fileParts.length} 段, 共 ${fileContents.reduce((s, f) => s + f.content.length, 0)} 字符`);
} }
if (pendingImages.length === 1) { if (pendingImages.length === 1) {
apiParts.push(`[已上传图片: ${pendingImages[0].name} — 此图片已随消息提供,你可以直接分析,无需去磁盘查找]`); apiParts.push(`[已上传图片: ${pendingImages[0].name}]`);
} else if (pendingImages.length > 1) { } 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 }> }> = []; let allVids: Array<{ name: string; count: number; dur: number; frames: Array<{ timestampSeconds: number; name: string; base64: string }> }> = [];
if (hasVideos) { if (hasVideos) {
@@ -1190,6 +1174,7 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio
role: 'user', role: 'user',
content: displayContent, content: displayContent,
images: [...pendingImages.map(img => img.base64)], images: [...pendingImages.map(img => img.base64)],
_apiContent: apiContentForModel, // 给模型的完整内容,不显示给用户
timestamp: now timestamp: now
}; };
if (allVids.length > 0) { if (allVids.length > 0) {
+1 -1
View File
@@ -28,7 +28,7 @@
<div class="header-left"> <div class="header-left">
<span class="logo">🦙</span> <span class="logo">🦙</span>
<span class="app-title">Metona Ollama</span> <span class="app-title">Metona Ollama</span>
<span class="app-version">v0.14.2</span> <span class="app-version">v0.14.3</span>
<button class="icon-btn help-btn" id="btnHelp" title="使用帮助"> <button class="icon-btn help-btn" id="btnHelp" title="使用帮助">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <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"/> <circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>
+2
View File
@@ -122,6 +122,8 @@ export interface ChatMessage {
compressed?: boolean; compressed?: boolean;
/** 实际发送给模型的完整系统提示词(调试用) */ /** 实际发送给模型的完整系统提示词(调试用) */
_systemPrompt?: string; _systemPrompt?: string;
/** 实际发送给模型的用户消息内容(含文件/图片/视频帧描述,不显示给用户) */
_apiContent?: string;
} }
export interface ChatSession { export interface ChatSession {