fix: 修复大数据导致应用崩溃 + 图片上传冗余工具调用 + 预存类型错误
## 崩溃修复 (renderer OOM) view_image 返回的 base64 dataUrl(~6.7MB/张)经 dataUrl 白名单跳过截断后, 在 IPC/store/DOM 链路被反复放大,累积撑爆渲染进程堆导致 OOM 崩溃退出。 - 新增 src/lib/tool-result-display.ts: toDisplayResult() 在显示边界剥离 dataUrl - ToolResultBlock/TraceStep 渲染前套用,store 原始 result 不变(LLM 看图链路完整) - data:export 剥离 dataUrl/preview + 每会话 2000 条消息上限 + truncated 标记 ## 图片上传修复 用户上传图片并告知 AI 后,AI 仍调 view_image 去磁盘读取。 根因: 附件提示 "do NOT search in workspace" 与 view_image "Read an image file" 语义冲突,AI 认为"读取"≠"搜索"。强化提示明确禁止调用 view_image/read_file 读取已上传图片。 ## 预存类型修复 - ConfirmationDialog: MUI v9 移除 disableEscapeKeyDown 顶层 prop,迁移到 onClose reason 拦截 - ContextMenu/Sidebar: TokenUsage 对象字面量补齐 lastInputTokens/lastCompressedSaved ## 依赖升级 - @metona-team/metona-toast 0.1.2 → 0.2.1 (TypeScript 重构,修复 0.1.2 版本号打包 bug) ## 其他 - 删除仓库根目录误生成的 nul 垃圾文件 - bump version to 0.3.22
This commit is contained in:
@@ -311,7 +311,6 @@ export function ConfirmationDialog(): React.JSX.Element | null {
|
||||
}}
|
||||
maxWidth="md"
|
||||
fullWidth
|
||||
disableEscapeKeyDown={confirmAutoExecute}
|
||||
slotProps={{
|
||||
paper: {
|
||||
sx: { bgcolor: 'background.paper' },
|
||||
@@ -588,13 +587,17 @@ export function ConfirmationDialog(): React.JSX.Element | null {
|
||||
</Dialog>
|
||||
|
||||
{/* #40 修复: autoExecute 二次确认 Dialog — 避免误点击导致永久自动执行 */}
|
||||
{/* 审查修复: disableEscapeKeyDown + onKeyDown stopPropagation 防止 ESC 事件穿透到外层 Dialog */}
|
||||
{/* 审查修复: MUI v9 移除了 disableEscapeKeyDown 顶层 prop,改为在 onClose 中按 reason 拦截 escapeKeyDown;
|
||||
onKeyDown stopPropagation 阻止 ESC 事件穿透到外层 Dialog */}
|
||||
<Dialog
|
||||
open={confirmAutoExecute}
|
||||
onClose={() => setConfirmAutoExecute(false)}
|
||||
onClose={(_, reason) => {
|
||||
// 禁用 ESC 关闭(仅允许点击遮罩/按钮关闭),与原 disableEscapeKeyDown 行为一致
|
||||
if (reason === 'escapeKeyDown') return;
|
||||
setConfirmAutoExecute(false);
|
||||
}}
|
||||
maxWidth="xs"
|
||||
fullWidth
|
||||
disableEscapeKeyDown
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: 14 }}>确认永久自动执行?</DialogTitle>
|
||||
|
||||
@@ -284,7 +284,7 @@ export function createContextMenuItems(type: ContextMenuType, data?: unknown): C
|
||||
currentSessionId: null,
|
||||
messages: [],
|
||||
traceSteps: [],
|
||||
tokenUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
||||
tokenUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0, lastInputTokens: 0, lastCompressedSaved: 0 },
|
||||
currentRunId: null,
|
||||
currentIteration: 0,
|
||||
});
|
||||
|
||||
@@ -6,12 +6,16 @@ import { Box, Typography, Stack } from '@mui/material';
|
||||
import { FileText } from 'lucide-react';
|
||||
import type { ToolCallInfo } from '@renderer/stores/agent-store';
|
||||
import { formatDuration } from '@renderer/lib/formatters';
|
||||
import { toDisplayResult } from '@renderer/lib/tool-result-display';
|
||||
|
||||
interface ToolResultBlockProps { toolCall: ToolCallInfo; }
|
||||
|
||||
export function ToolResultBlock({ toolCall }: ToolResultBlockProps): React.JSX.Element {
|
||||
if (toolCall.status !== 'success' || toolCall.result == null) return <></>;
|
||||
const resultStr = typeof toolCall.result === 'string' ? toolCall.result : JSON.stringify(toolCall.result, null, 2);
|
||||
// 渲染前剥离 dataUrl 等超大 base64 字段,避免 ~6.7MB/张的 dataUrl 进 DOM 导致渲染进程 OOM
|
||||
// store 内原始 result 不变(LLM 看图能力不受影响)
|
||||
const displayResult = toDisplayResult(toolCall.result);
|
||||
const resultStr = typeof displayResult === 'string' ? displayResult : JSON.stringify(displayResult, null, 2);
|
||||
|
||||
return (
|
||||
<Box sx={{ borderRadius: 1.5, border: '1px solid', borderColor: 'divider', borderLeft: '3px solid', borderLeftColor: 'info.main', bgcolor: 'secondary.main', px: 1.5, py: 1, mb: 1, animation: 'fadeInUp 300ms ease-out' }}>
|
||||
|
||||
@@ -135,7 +135,7 @@ function SessionItem({ session, isActive, isAgentActive, onClick }: { session: S
|
||||
currentSessionId: null,
|
||||
messages: [],
|
||||
traceSteps: [],
|
||||
tokenUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
||||
tokenUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0, lastInputTokens: 0, lastCompressedSaved: 0 },
|
||||
currentRunId: null,
|
||||
currentIteration: 0,
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Box, Typography, IconButton, Collapse, Stack } from '@mui/material';
|
||||
import { ChevronDown, ChevronRight, CircleDot, CheckCircle, Loader2, Clock, XCircle, Ban } from 'lucide-react';
|
||||
import { TRACE_STATE_COLORS, TRACE_STATE_LABELS } from '@renderer/lib/constants';
|
||||
import { formatDuration, formatTokens } from '@renderer/lib/formatters';
|
||||
import { toDisplayResult } from '@renderer/lib/tool-result-display';
|
||||
import type { ToolCallInfo } from '@renderer/stores/agent-store';
|
||||
import type { TraceStep as TraceStepType } from '@renderer/stores/agent-store';
|
||||
|
||||
@@ -118,10 +119,11 @@ function ToolCallRow({ tc }: { tc: ToolCallInfo }): React.JSX.Element {
|
||||
const statusColor = TOOL_STATUS_COLORS[tc.status] ?? '#8b8fa7';
|
||||
const statusLabel = TOOL_STATUS_LABELS[tc.status] ?? tc.status;
|
||||
|
||||
// 结果摘要(不截断,限制高度 + 滚动)
|
||||
// 结果摘要(限制高度 + 滚动)。渲染前剥离 dataUrl 等超大 base64,避免 ~6.7MB/张进 DOM 导致 OOM
|
||||
const hasResult = tc.result != null;
|
||||
const displayResult = toDisplayResult(tc.result);
|
||||
const resultStr = hasResult
|
||||
? (typeof tc.result === 'string' ? tc.result : JSON.stringify(tc.result, null, 2))
|
||||
? (typeof displayResult === 'string' ? displayResult : JSON.stringify(displayResult, null, 2))
|
||||
: '';
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user