From 37189dafa8cf5d0774569ed81ecc47e91a56ca77 Mon Sep 17 00:00:00 2001 From: thzxx Date: Thu, 18 Jun 2026 11:48:35 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E8=A7=86=E9=A2=91=E6=8F=90?= =?UTF-8?q?=E5=8F=96=E6=97=A5=E5=BF=97=E5=8E=9F=E5=9C=B0=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=EF=BC=8C=E5=A4=8D=E7=94=A8=20logStreamProgress=20=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 resetVideoProgress/updateVideoProgress/finishVideoProgress - 日志面板中视频提取只占一条,实时更新帧数和状态 - 主进程只通过 video:progress IPC 推送原始帧数,不写日志 --- src/main/ipc.ts | 4 --- src/renderer/components/input-area.ts | 19 +++++++-------- src/renderer/services/log-service.ts | 35 ++++++++++++++++++++++++++- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 4086c04..68f4fbe 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -544,9 +544,6 @@ function extractVideoFrames(filePath: string, maxFrames: number, maxWidth: numbe try { await fs.promises.mkdir(tmpDir, { recursive: true }); - const fileName = path.basename(filePath); - sendLog('info', `🎬 开始提取视频帧: 1fps`, fileName); - // ffmpeg 提取帧:1fps,同时捕获 stderr 解析进度和视频信息 const outPattern = path.join(tmpDir, 'frame_%04d.jpg'); let ffmpegStderr = ''; @@ -618,7 +615,6 @@ function extractVideoFrames(filePath: string, maxFrames: number, maxWidth: numbe await fs.promises.rm(tmpDir, { recursive: true, force: true }); - sendLog('success', `🎬 视频帧提取完成: 1fps`, `${filePath} → ${frames.length} 帧 / ${videoInfo.duration.toFixed(1)}s (${videoInfo.width}x${videoInfo.height})`); resolve({ success: true, frames, videoInfo }); } catch (err) { diff --git a/src/renderer/components/input-area.ts b/src/renderer/components/input-area.ts index 2f1ddf5..0b90f67 100644 --- a/src/renderer/components/input-area.ts +++ b/src/renderer/components/input-area.ts @@ -19,7 +19,7 @@ import { runAgentLoop } from '../services/agent-engine.js'; import { estimateTokens } from '../services/context-manager.js'; import { searchMemories, buildMemoryContext, markMemoryUsed, extractMemoriesFromConversation, isMemoryEnabled } from '../services/memory-manager.js'; import { showToolConfirm } from './tool-confirm-modal.js'; -import { logInfo, logStream, logError, logSuccess, logWarn } from '../services/log-service.js'; +import { logInfo, logStream, logError, logSuccess, logWarn, resetVideoProgress, updateVideoProgress, finishVideoProgress } from '../services/log-service.js'; import type { ChatSession, ChatMessage, OllamaStreamChunk, FileContent, ChatFile, ToolCallRecord } from '../types.js'; let chatInputEl: HTMLTextAreaElement; @@ -252,13 +252,15 @@ async function handleVideoPath(filePath: string): Promise { const name = filePath.split(/[/\\]/).pop() || filePath; - // 先显示提取中的预览卡片 + // 先显示提取中的预览卡片 + 日志进度条目 pendingVideoMeta = { fileName: name, duration: 0, width: 0, height: 0 }; renderVideoPreview(); + resetVideoProgress(name); - // 监听提取进度,实时更新卡片 + // 监听提取进度:实时更新日志条目 + 视频芯片 let progressEl: HTMLElement | null = null; bridge.video.onProgress(({ current }) => { + updateVideoProgress(current); if (!progressEl) progressEl = videoPreviewEl.querySelector('.video-meta'); if (progressEl) progressEl.textContent = `提取中 · ${current}帧`; }); @@ -271,25 +273,22 @@ async function handleVideoPath(filePath: string): Promise { pendingVideoFrames = []; pendingVideoMeta = null; renderVideoPreview(); - appendSystemMessage(`❌ 视频帧提取失败: ${result.error}`); + logError('视频帧提取失败', result.error || ''); return; } pendingVideoFrames = result.frames!; pendingVideoMeta = { fileName: name, ...result.videoInfo! }; - logInfo(`视频帧提取完成: ${name}`, - `${pendingVideoFrames.length} 帧 (1fps, ${pendingVideoMeta.duration.toFixed(0)}s)`); - appendSystemMessage(`✅ ${name}: 已提取 ${pendingVideoFrames.length} 帧 (1fps 采样)`); - + // 完成:日志条目原地更新为 success(不新增) + finishVideoProgress(name, pendingVideoFrames.length, pendingVideoMeta.duration); renderVideoPreview(); } catch (err) { bridge.video.removeProgressListener(); pendingVideoFrames = []; pendingVideoMeta = null; renderVideoPreview(); - logError('视频帧提取异常', (err as Error).message); - appendSystemMessage(`❌ 视频处理失败: ${(err as Error).message}`); + logError('视频帧提取失败', (err as Error).message); } } diff --git a/src/renderer/services/log-service.ts b/src/renderer/services/log-service.ts index 0589e91..72a1df9 100644 --- a/src/renderer/services/log-service.ts +++ b/src/renderer/services/log-service.ts @@ -187,7 +187,6 @@ export function resetStreamProgress(): void { /** 更新**最新**一条流式进度日志(原地更新消息,不改变 DOM 位置和时间戳) */ export function logStreamProgress(msg: string): void { - // 找最后一条进度日志(当前轮) const all = logBodyEl?.querySelectorAll?.('.log-stream-progress'); if (all && all.length > 0) { const latest = all[all.length - 1] as HTMLElement; @@ -196,6 +195,40 @@ export function logStreamProgress(msg: string): void { } } +/** 视频提取进度:创建初始条目 */ +export function resetVideoProgress(fileName: string): void { + if (!logBodyEl) return; + const entry = document.createElement('div'); + entry.className = 'log-entry log-info log-video-progress'; + entry.innerHTML = `${formatTime(Date.now())}🎬开始提取视频帧: ${escapeHtml(fileName)}
0 帧
`; + logBodyEl.appendChild(entry); +} + +/** 更新视频提取进度(原地更新,不新增条目) */ +export function updateVideoProgress(current: number): void { + const all = logBodyEl?.querySelectorAll?.('.log-video-progress'); + if (all && all.length > 0) { + const latest = all[all.length - 1] as HTMLElement; + const detail = latest.querySelector('.log-detail'); + if (detail) detail.textContent = `${current} 帧`; + } +} + +/** 视频提取完成:将进度条目原地更新为完成状态 */ +export function finishVideoProgress(fileName: string, frameCount: number, duration: number): void { + const all = logBodyEl?.querySelectorAll?.('.log-video-progress'); + if (all && all.length > 0) { + const latest = all[all.length - 1] as HTMLElement; + latest.className = 'log-entry log-success'; + const icon = latest.querySelector('.log-icon'); + const msg = latest.querySelector('.log-msg'); + const detail = latest.querySelector('.log-detail'); + if (icon) icon.textContent = '✅'; + if (msg) msg.textContent = `视频帧提取完成: ${escapeHtml(fileName)}`; + if (detail) detail.textContent = `${frameCount} 帧 / ${duration.toFixed(1)}s (1fps)`; + } +} + export function logAgentLoop(iteration: number, maxLoops: number): void { addLog('info', `Loop #${iteration}/${maxLoops}`); } export function logModelResponse(contentLen: number, toolCalls: number): void { addLog('stream', `响应: ${contentLen}字, ${toolCalls}工具`); } export function logConnection(status: string, detail?: string): void { addLog(status === 'connected' ? 'success' : 'warn', `连接: ${status}`, detail); }