chore: checkpoint before tool playback change

This commit is contained in:
JOJO 2025-12-14 14:43:54 +08:00
parent 053db95fee
commit 6d330b1388
3 changed files with 93 additions and 2 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -188,6 +188,8 @@ export class MonitorDirector implements MonitorDriver {
private destroyFns: Array<() => void> = [];
private appIcons = new Map<string, HTMLElement>();
private folderIcons = new Map<string, HTMLElement>();
// 用于控制编辑器动画全局加速(根据本次补丁的总体改动量动态调整)
private editorSpeedBoost = 1;
private pendingDesktopFolders = new Set<string>();
private fileIcons = new Map<string, HTMLElement>();
private browserResultMap = new Map<string, HTMLLIElement>();
@ -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('')) {

View File

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