From 6d330b1388f8e754a5224041d25b32262eca71b5 Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Sun, 14 Dec 2025 14:43:54 +0800 Subject: [PATCH] chore: checkpoint before tool playback change --- .DS_Store | Bin 10244 -> 24580 bytes .../chat/monitor/MonitorDirector.ts | 35 +++++++++- utils/tool_result_formatter.py | 60 ++++++++++++++++++ 3 files changed, 93 insertions(+), 2 deletions(-) diff --git a/.DS_Store b/.DS_Store index 73500b2cd2acc5241ea7699736f680cafc699534..4516de11bd5844ce9a03edbc5035618c08d34b62 100644 GIT binary patch literal 24580 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 VZR4{c&65B4S-WgsK1q}R{|A~AVXXiF delta 235 zcmZoUz}ONX!N9=4=v10w$iScgWO4v8h!)_PSSU78Pja$B42vX?&%gu3j6iXQr1Ii| zq@4UDAbf|DBs1hQ6fvYuo~P6>*}}y=9A+Er6 void> = []; private appIcons = new Map(); private folderIcons = new Map(); + // 用于控制编辑器动画全局加速(根据本次补丁的总体改动量动态调整) + private editorSpeedBoost = 1; private pendingDesktopFolders = new Set(); private fileIcons = new Map(); private browserResultMap = new Map(); @@ -3517,6 +3519,15 @@ export class MonitorDirector implements MonitorDriver { } const operations = this.buildEditorDiff(currentLines, targetLines); const mergedOperations = this.mergeEditorOperations(operations); + // 根据本次动画的总改动量动态提升速度:改动越大,动画越快 + const totalInsertedChars = mergedOperations.reduce((sum, op) => { + if (op.type === 'delete') { + return sum; + } + return sum + (op.text?.length || 0); + }, 0); + const totalOps = mergedOperations.length; + this.editorSpeedBoost = this.computeEditorSpeedBoost(totalInsertedChars, totalOps); editorDebug('animate:operations', { count: mergedOperations.length }); if (!mergedOperations.length) { if (!targetLines.length) { @@ -3548,6 +3559,8 @@ export class MonitorDirector implements MonitorDriver { this.editorScene.lines = targetLines.slice(); this.syncEditorIndices(); editorDebug('animate:done', { finalLines: this.editorScene.lines.length }); + // 重置全局加速 + this.editorSpeedBoost = 1; } private buildEditorDiff(before: string[], after: string[]): EditorOperation[] { @@ -3753,10 +3766,14 @@ export class MonitorDirector implements MonitorDriver { await sleep(40); return; } + // 行越长、整体改动越大 -> 动画越快 + const lineBoost = 1 + Math.min(normalized.length / 40, 6); + const speed = Math.max(1, lineBoost * this.editorSpeedBoost); + const interval = Math.max(4, Math.floor(EDITOR_TYPING_INTERVAL / speed)); for (const char of normalized.split('')) { target.textContent = `${target.textContent || ''}${char}`; this.adjustEditorScrollForLine(target); - await sleep(EDITOR_TYPING_INTERVAL); + await sleep(interval); } } @@ -3776,9 +3793,12 @@ export class MonitorDirector implements MonitorDriver { await sleep(60); return; } + const lineBoost = 1 + Math.min(current.length / 40, 6); + const speed = Math.max(1, lineBoost * this.editorSpeedBoost); + const interval = Math.max(3, Math.floor(EDITOR_ERASE_INTERVAL / speed)); for (let i = current.length - 1; i >= 0; i -= 1) { target.textContent = current.slice(0, i); - await sleep(EDITOR_ERASE_INTERVAL); + await sleep(interval); } target.textContent = ''; } @@ -3832,6 +3852,17 @@ export class MonitorDirector implements MonitorDriver { this.prepareEditorScene(lines); } + /** + * 根据本次补丁的总体字符量与操作数,返回动画加速倍数。 + * - 插入/替换字符越多,加速越明显 + * - 操作数量越多(分块多),也进一步加速 + */ + private computeEditorSpeedBoost(totalChars: number, totalOps: number): number { + const charBoost = Math.min(totalChars / 400, 4); // 0 ~ 4 + const opBoost = Math.min(totalOps / 120, 2); // 0 ~ 2 + return 1 + charBoost + opBoost; + } + private async typeSearchQuery(text: string) { this.elements.browserSearchText.textContent = ''; for (const char of text.split('')) { diff --git a/utils/tool_result_formatter.py b/utils/tool_result_formatter.py index 682d5cc..8bcb07e 100644 --- a/utils/tool_result_formatter.py +++ b/utils/tool_result_formatter.py @@ -97,6 +97,7 @@ def _format_write_file_diff(result_data: Dict[str, Any], raw_text: str) -> str: summary = result_data.get("summary") or result_data.get("message") completed = result_data.get("completed") or [] failed_blocks = result_data.get("failed") or [] + success_blocks = result_data.get("blocks") or [] lines = [f"[文件补丁] {path}"] if summary: lines.append(summary) @@ -126,6 +127,8 @@ def _format_write_file_diff(result_data: Dict[str, Any], raw_text: str) -> str: reason = item.get("reason") or item.get("error") or "未说明原因" hint = item.get("hint") block_patch = item.get("block_patch") or item.get("patch") + # 自动判别常见错误形态,便于快速定位问题 + diagnostics = _classify_diff_block_issue(item, result_data) if not block_patch: old_text = item.get("old_text") or "" new_text = item.get("new_text") or "" @@ -137,6 +140,8 @@ def _format_write_file_diff(result_data: Dict[str, Any], raw_text: str) -> str: if synthetic_lines: block_patch = "\n".join(synthetic_lines) detail_sections.append(f"- #{idx}: {reason}") + if diagnostics: + detail_sections.append(f" 错误类型: {diagnostics}") if hint: detail_sections.append(f" 提示: {hint}") if block_patch: @@ -149,6 +154,23 @@ def _format_write_file_diff(result_data: Dict[str, Any], raw_text: str) -> str: if detail_sections: lines.append("⚠️ 失败块详情:") lines.extend(detail_sections) + + # 对“成功块”做轻量体检:如果检测到潜在格式风险,给出风险提示(不影响 success 判定) + risk_sections: List[str] = [] + for item in success_blocks: + if not isinstance(item, dict): + continue + status = item.get("status") + idx = item.get("index") + if status != "success": + continue + diag = _classify_diff_block_issue(item, result_data) + if diag: + risk_sections.append(f"- #{idx}: {diag}") + if risk_sections: + lines.append("⚠️ 风险提示(补丁虽成功但格式可能有隐患):") + lines.extend(risk_sections) + if result_data.get("success") is False and result_data.get("error"): lines.append(f"⚠️ 错误: {result_data.get('error')}") formatted = "\n".join(line for line in lines if line) @@ -161,6 +183,44 @@ def _format_create_file(result_data: Dict[str, Any]) -> str: return result_data.get("message") or f"已创建空文件: {result_data.get('path', '未知路径')}" +def _classify_diff_block_issue(block: Dict[str, Any], result_data: Dict[str, Any]) -> str: + """ + 针对 write_file_diff 常见的“离谱/易错”用法做启发式判别,返回简短错误类型说明。 + 不改变后端逻辑,只用于提示。 + """ + patch_text = block.get("block_patch") or block.get("patch") or "" + lines = patch_text.splitlines() + plus = sum(1 for ln in lines if ln.startswith("+")) + minus = sum(1 for ln in lines if ln.startswith("-")) + context = sum(1 for ln in lines if ln.startswith(" ")) + total = len([ln for ln in lines if ln.strip() != ""]) + + reasons: List[str] = [] + # 1) 完全没加 + / - :最常见的“把目标当上下文” + if total > 0 and plus == 0 and minus == 0: + reasons.append("缺少 + / -,整块被当作上下文,无法定位到文件") + # 2) 全是 + 且没有上下文/删除:解析为“纯追加”,若目标非末尾插入会失败 + if plus > 0 and minus == 0 and context == 0: + reasons.append("仅包含 + 行,被视为追加块;若想中间插入/替换需提供上下文或 -") + # 3) 没有上下文或删除行却不是 append_only(多数是漏写空格前缀) + if block.get("append_only") and (context > 0 or minus > 0): + reasons.append("块被解析为追加模式,但混入了上下文/删除行,可能写法不一致") + # 4) 未找到匹配时,提示检查空格/缩进/全角半角/换行差异 + reason_text = (block.get("reason") or "").lower() + if "未找到匹配" in reason_text: + reasons.append("上下文未匹配:检查空格/缩进、全角半角、CRLF/LF、大小写是否与原文完全一致") + # 5) 空行未加 '+' 的典型情形: + # a) 有空白行但整块没有前缀(此前已由 #1 捕获),仍补充提示 + # b) 有空白行且块中存在 + / -,说明空行漏写前缀,易导致上下文匹配失败 + if any(ln == "" or ln.strip() == "" for ln in lines): + if plus == 0 and minus == 0: + reasons.append("空行未写 `+`,被当作上下文,建议空行写成单独一行 `+`") + else: + reasons.append("空行未写 `+`(或空格上下文),混入补丁时会被当作上下文,建议空行单独写成 `+`") + + return ";".join(reasons) + + def _format_delete_file(result_data: Dict[str, Any]) -> str: if not result_data.get("success"): return _format_failure("delete_file", result_data)