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;