fix: 修复工具增强显示的多个问题
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 <noreply@anthropic.com>
This commit is contained in:
parent
6697da9581
commit
a030ff954b
@ -34,9 +34,11 @@
|
||||
class="step-item"
|
||||
>
|
||||
<div class="step-timeline">
|
||||
<svg class="step-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<g v-html="getStepIcon(step)"></g>
|
||||
</svg>
|
||||
<span
|
||||
class="step-icon icon icon-sm"
|
||||
:style="getStepIconStyle(step)"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<div class="step-line"></div>
|
||||
</div>
|
||||
<div class="step-content">
|
||||
@ -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<string, string> = {
|
||||
brain: '<path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"></path><path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"></path><path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"></path><path d="M17.599 6.5a3 3 0 0 0 .399-1.375"></path><path d="M6.003 5.125A3 3 0 0 0 6.401 6.5"></path><path d="M3.477 10.896a4 4 0 0 1 .585-.396"></path><path d="M19.938 10.5a4 4 0 0 1 .585.396"></path><path d="M6 18a4 4 0 0 1-1.967-.516"></path><path d="M19.967 17.484A4 4 0 0 1 18 18"></path>',
|
||||
terminal: '<polyline points="4 17 10 11 4 5"></polyline><line x1="12" y1="19" x2="20" y2="19"></line>',
|
||||
search: '<circle cx="11" cy="11" r="8"></circle><path d="m21 21-4.35-4.35"></path>'
|
||||
};
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -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 = '<div class="tool-result-meta">';
|
||||
html += `<div><strong>路径:</strong>${escapeHtml(path)}</div>`;
|
||||
html += `<div><strong>状态:</strong>${status}</div>`;
|
||||
if (isAppend) {
|
||||
html += `<div><strong>模式:</strong>追加</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
// 显示写入的内容
|
||||
if (content) {
|
||||
html += '<div class="tool-result-diff scrollable">';
|
||||
const lines = content.split('\n');
|
||||
lines.forEach((line: string) => {
|
||||
html += `<div class="diff-line diff-add">+ ${escapeHtml(line)}</div>`;
|
||||
});
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
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 = '<div class="tool-result-meta">';
|
||||
html += `<div><strong>路径:</strong>${escapeHtml(path)}</div>`;
|
||||
html += `<div><strong>状态:</strong>${status}</div>`;
|
||||
html += '</div>';
|
||||
|
||||
// 如果有 replacements 数组,使用新格式
|
||||
if (replacements.length > 0) {
|
||||
html += '<div class="tool-result-diff scrollable">';
|
||||
replacements.forEach((rep: any, idx: number) => {
|
||||
@ -289,6 +311,30 @@ function renderEditFile(result: any, args: any): string {
|
||||
});
|
||||
html += '</div>';
|
||||
}
|
||||
// 否则使用旧格式(old_string/new_string)
|
||||
else if (oldString || newString) {
|
||||
// 处理转义的 \n
|
||||
oldString = oldString.replace(/\\n/g, '\n');
|
||||
newString = newString.replace(/\\n/g, '\n');
|
||||
|
||||
html += '<div class="tool-result-diff scrollable">';
|
||||
|
||||
if (oldString) {
|
||||
const oldLines = oldString.split('\n');
|
||||
oldLines.forEach((line: string) => {
|
||||
html += `<div class="diff-line diff-remove">- ${escapeHtml(line)}</div>`;
|
||||
});
|
||||
}
|
||||
|
||||
if (newString) {
|
||||
const newLines = newString.split('\n');
|
||||
newLines.forEach((line: string) => {
|
||||
html += `<div class="diff-line diff-add">+ ${escapeHtml(line)}</div>`;
|
||||
});
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
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 = '<div class="tool-result-meta">';
|
||||
html += `<div><strong>路径:</strong>${escapeHtml(path)}</div>`;
|
||||
@ -332,12 +378,69 @@ function renderReadFile(result: any, args: any): string {
|
||||
if (size > 0) {
|
||||
html += `<div><strong>大小:</strong>${formatBytes(size)}</div>`;
|
||||
}
|
||||
if (readType !== 'read') {
|
||||
html += `<div><strong>模式:</strong>${escapeHtml(readType)}</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
if (content) {
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
html += `<pre>${escapeHtml(content)}</pre>`;
|
||||
html += '</div>';
|
||||
// 处理普通读取模式
|
||||
if (readType === 'read') {
|
||||
const content = result.content || result.text || '';
|
||||
if (content) {
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
html += `<pre>${escapeHtml(content)}</pre>`;
|
||||
html += '</div>';
|
||||
}
|
||||
}
|
||||
// 处理搜索模式
|
||||
else if (readType === 'search') {
|
||||
const matches = result.matches || [];
|
||||
const query = result.query || args.query || '';
|
||||
|
||||
if (query) {
|
||||
html += '<div class="tool-result-meta">';
|
||||
html += `<div><strong>搜索关键词:</strong>${escapeHtml(query)}</div>`;
|
||||
html += `<div><strong>匹配数量:</strong>${matches.length}</div>`;
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
if (matches.length > 0) {
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
matches.forEach((match: any, idx: number) => {
|
||||
if (idx > 0) html += '<div class="search-match-separator">⋯</div>';
|
||||
html += '<div class="search-match-item">';
|
||||
html += `<div class="search-match-line">行 ${match.line_number || '?'}</div>`;
|
||||
html += `<pre>${escapeHtml(match.snippet || match.content || '')}</pre>`;
|
||||
html += '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
} else {
|
||||
html += '<div class="tool-result-empty">未找到匹配内容</div>';
|
||||
}
|
||||
}
|
||||
// 处理提取模式
|
||||
else if (readType === 'extract') {
|
||||
const segments = result.segments || [];
|
||||
|
||||
if (segments.length > 0) {
|
||||
html += '<div class="tool-result-meta">';
|
||||
html += `<div><strong>提取片段数:</strong>${segments.length}</div>`;
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
segments.forEach((segment: any, idx: number) => {
|
||||
if (idx > 0) html += '<div class="segment-separator">⋯</div>';
|
||||
html += '<div class="segment-item">';
|
||||
const startLine = segment.start_line || segment.line_start || '?';
|
||||
const endLine = segment.end_line || segment.line_end || '?';
|
||||
html += `<div class="segment-range">行 ${startLine}-${endLine}</div>`;
|
||||
html += `<pre>${escapeHtml(segment.content || segment.text || '')}</pre>`;
|
||||
html += '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
} else {
|
||||
html += '<div class="tool-result-empty">未提取到内容</div>';
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -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 = '<div class="tool-result-meta">';
|
||||
html += `<div><strong>路径:</strong>${escapeHtml(path)}</div>`;
|
||||
html += `<div><strong>状态:</strong>${status}</div>`;
|
||||
if (isAppend) {
|
||||
html += `<div><strong>模式:</strong>追加</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
// 显示写入的内容
|
||||
if (content) {
|
||||
html += '<div class="tool-result-diff scrollable">';
|
||||
const lines = content.split('\n');
|
||||
lines.forEach((line: string) => {
|
||||
html += `<div class="diff-line diff-add">+ ${escapeHtml(line)}</div>`;
|
||||
});
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
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 = '<div class="tool-result-meta">';
|
||||
html += `<div><strong>路径:</strong>${escapeHtml(path)}</div>`;
|
||||
html += `<div><strong>状态:</strong>${status}</div>`;
|
||||
html += '</div>';
|
||||
|
||||
if (oldString || newString) {
|
||||
// 如果有 replacements 数组,使用新格式
|
||||
if (replacements.length > 0) {
|
||||
html += '<div class="tool-result-diff scrollable">';
|
||||
replacements.forEach((rep: any, idx: number) => {
|
||||
if (idx > 0) html += '<div class="diff-separator">⋮</div>';
|
||||
|
||||
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 += `<div class="diff-line diff-remove">- ${escapeHtml(line)}</div>`;
|
||||
});
|
||||
}
|
||||
|
||||
if (newText) {
|
||||
const newLines = newText.split('\n');
|
||||
newLines.forEach((line: string) => {
|
||||
html += `<div class="diff-line diff-add">+ ${escapeHtml(line)}</div>`;
|
||||
});
|
||||
}
|
||||
});
|
||||
html += '</div>';
|
||||
}
|
||||
// 否则使用旧格式(old_string/new_string)
|
||||
else if (oldString || newString) {
|
||||
// 处理转义的 \n
|
||||
oldString = oldString.replace(/\\n/g, '\n');
|
||||
newString = newString.replace(/\\n/g, '\n');
|
||||
|
||||
html += '<div class="tool-result-diff scrollable">';
|
||||
|
||||
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 = '<div class="tool-result-meta">';
|
||||
html += `<div><strong>路径:</strong>${escapeHtml(path)}</div>`;
|
||||
@ -277,12 +323,69 @@ function renderReadFile(result: any, args: any): string {
|
||||
if (size > 0) {
|
||||
html += `<div><strong>大小:</strong>${formatBytes(size)}</div>`;
|
||||
}
|
||||
if (readType !== 'read') {
|
||||
html += `<div><strong>模式:</strong>${escapeHtml(readType)}</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
if (content) {
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
html += `<pre>${escapeHtml(content)}</pre>`;
|
||||
html += '</div>';
|
||||
// 处理普通读取模式
|
||||
if (readType === 'read') {
|
||||
const content = result.content || result.text || '';
|
||||
if (content) {
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
html += `<pre>${escapeHtml(content)}</pre>`;
|
||||
html += '</div>';
|
||||
}
|
||||
}
|
||||
// 处理搜索模式
|
||||
else if (readType === 'search') {
|
||||
const matches = result.matches || [];
|
||||
const query = result.query || args.query || '';
|
||||
|
||||
if (query) {
|
||||
html += '<div class="tool-result-meta">';
|
||||
html += `<div><strong>搜索关键词:</strong>${escapeHtml(query)}</div>`;
|
||||
html += `<div><strong>匹配数量:</strong>${matches.length}</div>`;
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
if (matches.length > 0) {
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
matches.forEach((match: any, idx: number) => {
|
||||
if (idx > 0) html += '<div class="search-match-separator">⋯</div>';
|
||||
html += '<div class="search-match-item">';
|
||||
html += `<div class="search-match-line">行 ${match.line_number || '?'}</div>`;
|
||||
html += `<pre>${escapeHtml(match.snippet || match.content || '')}</pre>`;
|
||||
html += '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
} else {
|
||||
html += '<div class="tool-result-empty">未找到匹配内容</div>';
|
||||
}
|
||||
}
|
||||
// 处理提取模式
|
||||
else if (readType === 'extract') {
|
||||
const segments = result.segments || [];
|
||||
|
||||
if (segments.length > 0) {
|
||||
html += '<div class="tool-result-meta">';
|
||||
html += `<div><strong>提取片段数:</strong>${segments.length}</div>`;
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
segments.forEach((segment: any, idx: number) => {
|
||||
if (idx > 0) html += '<div class="segment-separator">⋯</div>';
|
||||
html += '<div class="segment-item">';
|
||||
const startLine = segment.start_line || segment.line_start || '?';
|
||||
const endLine = segment.end_line || segment.line_end || '?';
|
||||
html += `<div class="segment-range">行 ${startLine}-${endLine}</div>`;
|
||||
html += `<pre>${escapeHtml(segment.content || segment.text || '')}</pre>`;
|
||||
html += '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
} else {
|
||||
html += '<div class="tool-result-empty">未提取到内容</div>';
|
||||
}
|
||||
}
|
||||
|
||||
return html;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user