From a030ff954ba8650357b776ece4fafa82eb3e4349 Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Sat, 4 Apr 2026 15:37:38 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E6=98=BE=E7=A4=BA=E7=9A=84=E5=A4=9A=E4=B8=AA?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. read_file 工具支持三种模式显示 - read 模式:显示完整文件内容 - search 模式:显示搜索匹配项和行号 - extract 模式:显示提取的多个片段 2. edit_file 工具兼容新旧数据格式 - 支持 old_string/new_string 格式(当前后端使用) - 支持 replacements 数组格式(为未来扩展预留) 3. write_file 工具显示写入内容 - 显示完整的写入内容 - 使用绿色+号样式(与 edit_file 一致) - 支持显示追加模式标识 4. 极简模式工具图标匹配修复 - 修复所有工具图标都显示为 run_command 的问题 - 使用与堆叠块模式相同的图标获取逻辑 - 正确显示每个工具对应的图标 5. 新增样式支持 - 搜索匹配项样式 - 提取片段样式 - 移除彩色侧边框,保持简洁 Co-Authored-By: Claude Sonnet 4.5 --- static/src/components/chat/MinimalBlocks.vue | 28 ++- .../components/chat/actions/ToolAction.vue | 189 +++++++++++++++++- .../components/chat/actions/toolRenderers.ts | 125 +++++++++++- 3 files changed, 309 insertions(+), 33 deletions(-) diff --git a/static/src/components/chat/MinimalBlocks.vue b/static/src/components/chat/MinimalBlocks.vue index 7e12edb..8dd4c2f 100644 --- a/static/src/components/chat/MinimalBlocks.vue +++ b/static/src/components/chat/MinimalBlocks.vue @@ -34,9 +34,11 @@ class="step-item" >
- - - +
@@ -249,8 +251,14 @@ const toggleExpand = (groupId: string) => { } }; -const getStepIcon = (step: Step) => { - return step.type === 'thinking' ? getIconPath('brain') : getIconPath('terminal'); +const getStepIconStyle = (step: Step) => { + if (step.type === 'thinking') { + return props.iconStyle ? props.iconStyle('brain') : {}; + } else if (step.type === 'tool' && step.action.tool) { + const iconKey = props.getToolIcon ? props.getToolIcon(step.action.tool) : 'terminal'; + return props.iconStyle ? props.iconStyle(iconKey) : {}; + } + return props.iconStyle ? props.iconStyle('terminal') : {}; }; const getToolName = (action: Action) => { @@ -322,15 +330,6 @@ const escapeHtml = (text: string) => { return div.innerHTML; }; -const getIconPath = (name: string) => { - const icons: Record = { - brain: '', - terminal: '', - search: '' - }; - return icons[name] || icons.brain; -}; - watch(() => props.actions, () => { // 简化:不需要动态更新高度 }, { deep: true }); @@ -465,7 +464,6 @@ watch(() => props.actions, () => { } .step-icon { - color: var(--claude-text-secondary); flex-shrink: 0; } diff --git a/static/src/components/chat/actions/ToolAction.vue b/static/src/components/chat/actions/ToolAction.vue index c123aeb..7caaee7 100644 --- a/static/src/components/chat/actions/ToolAction.vue +++ b/static/src/components/chat/actions/ToolAction.vue @@ -244,27 +244,49 @@ function renderCreateFile(result: any, args: any): string { } function renderWriteFile(result: any, args: any): string { - const path = args.path || result.path || ''; + const path = args.file_path || args.path || result.path || ''; const status = result.success ? '✓ 已写入' : '✗ 失败'; + const content = args.content || ''; + const isAppend = args.append || false; let html = '
'; html += `
路径:${escapeHtml(path)}
`; html += `
状态:${status}
`; + if (isAppend) { + html += `
模式:追加
`; + } html += '
'; + // 显示写入的内容 + if (content) { + html += '
'; + const lines = content.split('\n'); + lines.forEach((line: string) => { + html += `
+ ${escapeHtml(line)}
`; + }); + html += '
'; + } + return html; } function renderEditFile(result: any, args: any): string { - const path = args.path || result.path || ''; + const path = args.file_path || args.path || result.path || ''; const status = result.success ? '✓ 已编辑' : '✗ 失败'; + + // 兼容两种数据格式: + // 1. 新格式:args.replacements 数组 + // 2. 旧格式:args.old_string 和 args.new_string const replacements = args.replacements || []; + let oldString = args.old_string || ''; + let newString = args.new_string || ''; let html = '
'; html += `
路径:${escapeHtml(path)}
`; html += `
状态:${status}
`; html += '
'; + // 如果有 replacements 数组,使用新格式 if (replacements.length > 0) { html += '
'; replacements.forEach((rep: any, idx: number) => { @@ -289,6 +311,30 @@ function renderEditFile(result: any, args: any): string { }); html += '
'; } + // 否则使用旧格式(old_string/new_string) + else if (oldString || newString) { + // 处理转义的 \n + oldString = oldString.replace(/\\n/g, '\n'); + newString = newString.replace(/\\n/g, '\n'); + + html += '
'; + + if (oldString) { + const oldLines = oldString.split('\n'); + oldLines.forEach((line: string) => { + html += `
- ${escapeHtml(line)}
`; + }); + } + + if (newString) { + const newLines = newString.split('\n'); + newLines.forEach((line: string) => { + html += `
+ ${escapeHtml(line)}
`; + }); + } + + html += '
'; + } return html; } @@ -324,7 +370,7 @@ function renderReadFile(result: any, args: any): string { const path = args.path || result.path || ''; const status = result.success ? '✓ 成功' : '✗ 失败'; const size = result.size || result.file_size || 0; - const content = result.content || result.text || ''; + const readType = result.type || 'read'; let html = '
'; html += `
路径:${escapeHtml(path)}
`; @@ -332,12 +378,69 @@ function renderReadFile(result: any, args: any): string { if (size > 0) { html += `
大小:${formatBytes(size)}
`; } + if (readType !== 'read') { + html += `
模式:${escapeHtml(readType)}
`; + } html += '
'; - if (content) { - html += '
'; - html += `
${escapeHtml(content)}
`; - html += '
'; + // 处理普通读取模式 + if (readType === 'read') { + const content = result.content || result.text || ''; + if (content) { + html += '
'; + html += `
${escapeHtml(content)}
`; + html += '
'; + } + } + // 处理搜索模式 + else if (readType === 'search') { + const matches = result.matches || []; + const query = result.query || args.query || ''; + + if (query) { + html += '
'; + html += `
搜索关键词:${escapeHtml(query)}
`; + html += `
匹配数量:${matches.length}
`; + html += '
'; + } + + if (matches.length > 0) { + html += '
'; + matches.forEach((match: any, idx: number) => { + if (idx > 0) html += '
'; + html += '
'; + html += `
行 ${match.line_number || '?'}
`; + html += `
${escapeHtml(match.snippet || match.content || '')}
`; + html += '
'; + }); + html += '
'; + } else { + html += '
未找到匹配内容
'; + } + } + // 处理提取模式 + else if (readType === 'extract') { + const segments = result.segments || []; + + if (segments.length > 0) { + html += '
'; + html += `
提取片段数:${segments.length}
`; + html += '
'; + + html += '
'; + segments.forEach((segment: any, idx: number) => { + if (idx > 0) html += '
'; + html += '
'; + const startLine = segment.start_line || segment.line_start || '?'; + const endLine = segment.end_line || segment.line_end || '?'; + html += `
行 ${startLine}-${endLine}
`; + html += `
${escapeHtml(segment.content || segment.text || '')}
`; + html += '
'; + }); + html += '
'; + } else { + html += '
未提取到内容
'; + } } return html; @@ -883,4 +986,76 @@ function renderEasterEgg(result: any, args: any): string { font-size: 14px; line-height: 1.6; } + +/* 搜索匹配项 */ +.search-match-separator { + text-align: center; + color: rgba(0, 0, 0, 0.3); + margin: 12px 0; + font-size: 16px; +} + +.search-match-item { + margin-bottom: 12px; + padding: 8px; + background: rgba(0, 0, 0, 0.02); + border-radius: 6px; +} + +:root[data-theme='dark'] .search-match-item { + background: rgba(255, 255, 255, 0.05); +} + +.search-match-line { + font-size: 11px; + color: rgba(0, 0, 0, 0.5); + margin-bottom: 4px; + font-weight: 600; +} + +:root[data-theme='dark'] .search-match-line { + color: rgba(255, 255, 255, 0.5); +} + +.search-match-item pre { + margin: 0; + font-size: 12px; + line-height: 1.5; +} + +/* 提取片段 */ +.segment-separator { + text-align: center; + color: rgba(0, 0, 0, 0.3); + margin: 12px 0; + font-size: 16px; +} + +.segment-item { + margin-bottom: 12px; + padding: 8px; + background: rgba(0, 0, 0, 0.02); + border-radius: 6px; +} + +:root[data-theme='dark'] .segment-item { + background: rgba(255, 255, 255, 0.05); +} + +.segment-range { + font-size: 11px; + color: rgba(0, 0, 0, 0.5); + margin-bottom: 4px; + font-weight: 600; +} + +:root[data-theme='dark'] .segment-range { + color: rgba(255, 255, 255, 0.5); +} + +.segment-item pre { + margin: 0; + font-size: 12px; + line-height: 1.5; +} diff --git a/static/src/components/chat/actions/toolRenderers.ts b/static/src/components/chat/actions/toolRenderers.ts index 9edaa18..7b9b62b 100644 --- a/static/src/components/chat/actions/toolRenderers.ts +++ b/static/src/components/chat/actions/toolRenderers.ts @@ -189,33 +189,79 @@ function renderCreateFile(result: any, args: any): string { } function renderWriteFile(result: any, args: any): string { - const path = args.path || result.path || ''; + const path = args.file_path || args.path || result.path || ''; const status = result.success ? '✓ 已写入' : '✗ 失败'; + const content = args.content || ''; + const isAppend = args.append || false; let html = '
'; html += `
路径:${escapeHtml(path)}
`; html += `
状态:${status}
`; + if (isAppend) { + html += `
模式:追加
`; + } html += '
'; + // 显示写入的内容 + if (content) { + html += '
'; + const lines = content.split('\n'); + lines.forEach((line: string) => { + html += `
+ ${escapeHtml(line)}
`; + }); + html += '
'; + } + return html; } function renderEditFile(result: any, args: any): string { const path = args.file_path || args.path || result.path || ''; const status = result.success ? '✓ 已编辑' : '✗ 失败'; + + // 兼容两种数据格式: + // 1. 新格式:args.replacements 数组 + // 2. 旧格式:args.old_string 和 args.new_string + const replacements = args.replacements || []; let oldString = args.old_string || ''; let newString = args.new_string || ''; - // 处理转义的 \n - oldString = oldString.replace(/\\n/g, '\n'); - newString = newString.replace(/\\n/g, '\n'); - let html = '
'; html += `
路径:${escapeHtml(path)}
`; html += `
状态:${status}
`; html += '
'; - if (oldString || newString) { + // 如果有 replacements 数组,使用新格式 + if (replacements.length > 0) { + html += '
'; + replacements.forEach((rep: any, idx: number) => { + if (idx > 0) html += '
'; + + const oldText = rep.old_text || rep.old || ''; + const newText = rep.new_text || rep.new || ''; + + if (oldText) { + const oldLines = oldText.split('\n'); + oldLines.forEach((line: string) => { + html += `
- ${escapeHtml(line)}
`; + }); + } + + if (newText) { + const newLines = newText.split('\n'); + newLines.forEach((line: string) => { + html += `
+ ${escapeHtml(line)}
`; + }); + } + }); + html += '
'; + } + // 否则使用旧格式(old_string/new_string) + else if (oldString || newString) { + // 处理转义的 \n + oldString = oldString.replace(/\\n/g, '\n'); + newString = newString.replace(/\\n/g, '\n'); + html += '
'; if (oldString) { @@ -269,7 +315,7 @@ function renderReadFile(result: any, args: any): string { const path = args.path || result.path || ''; const status = result.success ? '✓ 成功' : '✗ 失败'; const size = result.size || result.file_size || 0; - const content = result.content || result.text || ''; + const readType = result.type || 'read'; let html = '
'; html += `
路径:${escapeHtml(path)}
`; @@ -277,12 +323,69 @@ function renderReadFile(result: any, args: any): string { if (size > 0) { html += `
大小:${formatBytes(size)}
`; } + if (readType !== 'read') { + html += `
模式:${escapeHtml(readType)}
`; + } html += '
'; - if (content) { - html += '
'; - html += `
${escapeHtml(content)}
`; - html += '
'; + // 处理普通读取模式 + if (readType === 'read') { + const content = result.content || result.text || ''; + if (content) { + html += '
'; + html += `
${escapeHtml(content)}
`; + html += '
'; + } + } + // 处理搜索模式 + else if (readType === 'search') { + const matches = result.matches || []; + const query = result.query || args.query || ''; + + if (query) { + html += '
'; + html += `
搜索关键词:${escapeHtml(query)}
`; + html += `
匹配数量:${matches.length}
`; + html += '
'; + } + + if (matches.length > 0) { + html += '
'; + matches.forEach((match: any, idx: number) => { + if (idx > 0) html += '
'; + html += '
'; + html += `
行 ${match.line_number || '?'}
`; + html += `
${escapeHtml(match.snippet || match.content || '')}
`; + html += '
'; + }); + html += '
'; + } else { + html += '
未找到匹配内容
'; + } + } + // 处理提取模式 + else if (readType === 'extract') { + const segments = result.segments || []; + + if (segments.length > 0) { + html += '
'; + html += `
提取片段数:${segments.length}
`; + html += '
'; + + html += '
'; + segments.forEach((segment: any, idx: number) => { + if (idx > 0) html += '
'; + html += '
'; + const startLine = segment.start_line || segment.line_start || '?'; + const endLine = segment.end_line || segment.line_end || '?'; + html += `
行 ${startLine}-${endLine}
`; + html += `
${escapeHtml(segment.content || segment.text || '')}
`; + html += '
'; + }); + html += '
'; + } else { + html += '
未提取到内容
'; + } } return html;