Skip to content

Commit af66327

Browse files
10knamesmorewanger
andauthored
perf: tool rendering in conversation page (#7937)
* fix(dashboard): route conversation history tool messages through ToolCallCard When viewing conversation history, large tool outputs (e.g. a single git log --stat producing tens of KB) caused the browser renderer to freeze. Root cause: formattedMessages mapped every role (including tool / system / _checkpoint) into user/bot bubbles, and bot plain strings went through markstream-vue's MarkdownRender. Single 88KB tool messages plus 88-of-them adding up to ~349KB of synchronous markdown parsing was enough to block the main thread for 5+ seconds. This patch: - Indexes tool-role messages by tool_call_id - Filters formattedMessages to user/assistant only — tool, system and _checkpoint roles no longer render as standalone bubbles - Converts assistant.tool_calls (OpenAI shape, with tc.name/tc.arguments fallbacks) into the existing tool_call MessagePart, attaching the paired result so MessageList's ToolCallCard renders it (default collapsed, no longer feeds large strings into the markdown renderer) - Drops empty placeholder plain parts when an assistant message only carries tool_calls - Sets ts/finished_ts to 0 as a sentinel: ToolCallCard.toolCallDuration returns "" when startTime <= 0, suppressing a misleading "0ms" duration that would otherwise appear because conversation history has no real timing data Behavior change: tool results are now embedded in their assistant's ToolCallCard.result instead of appearing as separate bot bubbles. This matches the main chat UI's behavior. Fixes #7929 Refs #7372 #7456 * style(dashboard): use single scrollbar in conversation history preview ToolCallCard's result/args panes have their own max-height + overflow, which produced a nested scrollbar when nested inside the history preview's already-scrollable .conversation-messages-container. Override those constraints inside the preview only — the outer 500px-bounded container already provides scroll bounds, so a single scrollbar feels cleaner. The main chat UI is unaffected. --------- Co-authored-by: wanger <wanger@example.com>
1 parent 8098a92 commit af66327

1 file changed

Lines changed: 56 additions & 18 deletions

File tree

dashboard/src/views/ConversationPage.vue

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -539,28 +539,54 @@ export default {
539539
540540
// 将对话历史转换为 MessageList 组件期望的格式
541541
formattedMessages() {
542-
return this.conversationHistory.map(msg => {
543-
console.log('处理消息:', msg.role, msg.content);
544-
545-
// 将消息内容转换为 MessagePart[] 格式
546-
const messageParts = this.convertContentToMessageParts(msg.content);
547-
548-
if (msg.role === 'user') {
549-
return {
550-
content: {
551-
type: 'user',
552-
message: messageParts
553-
}
554-
};
555-
} else {
542+
// 按 tool_call_id 索引 tool 角色消息的执行结果
543+
const toolResultsById = {};
544+
for (const msg of this.conversationHistory) {
545+
if (msg.role === 'tool' && msg.tool_call_id) {
546+
toolResultsById[msg.tool_call_id] = msg.content;
547+
}
548+
}
549+
550+
return this.conversationHistory
551+
// tool / system 等非聊天角色不直接渲染为气泡,避免大文本走 markdown 路径卡死页面
552+
.filter(msg => msg.role === 'user' || msg.role === 'assistant')
553+
.map(msg => {
554+
console.log('处理消息:', msg.role, msg.content);
555+
556+
const messageParts = this.convertContentToMessageParts(msg.content)
557+
// 丢弃 convertContentToMessageParts 兜底插入的空 plain,避免 assistant 仅有工具调用时渲染空气泡
558+
.filter(part => part.type !== 'plain' || (part.text && part.text.trim()));
559+
560+
// 把 OpenAI 风格的 assistant.tool_calls 转成 MessageList 已支持的 tool_call part
561+
if (msg.role === 'assistant' && Array.isArray(msg.tool_calls) && msg.tool_calls.length) {
562+
const toolCalls = msg.tool_calls.map(tc => {
563+
const fn = tc.function || {};
564+
return {
565+
id: tc.id,
566+
name: fn.name || tc.name,
567+
args: fn.arguments ?? tc.arguments,
568+
result: toolResultsById[tc.id],
569+
// 历史回放无真实耗时数据:
570+
// ts: 0 → ToolCallCard.toolCallDuration 在 startTime<=0 时早退,跳过时长显示
571+
// finished_ts: 1 → MessageList.toolCallStatusText 视为已完成(避免误显示"运行中")
572+
ts: 0,
573+
finished_ts: 1,
574+
};
575+
});
576+
messageParts.push({ type: 'tool_call', tool_calls: toolCalls });
577+
}
578+
579+
const finalParts = messageParts.length
580+
? messageParts
581+
: [{ type: 'plain', text: '' }];
582+
556583
return {
557584
content: {
558-
type: 'bot',
559-
message: messageParts
585+
type: msg.role === 'user' ? 'user' : 'bot',
586+
message: finalParts,
560587
}
561588
};
562-
}
563-
});
589+
});
564590
}
565591
},
566592
@@ -1173,6 +1199,18 @@ export default {
11731199
background-color: #f9f9f9;
11741200
}
11751201
1202+
/* 让 ToolCallCard 内部的 args/result 自然展开,由外层容器统一滚动,避免双滚动条 */
1203+
.conversation-messages-container .detail-json,
1204+
.conversation-messages-container .detail-result {
1205+
max-height: none;
1206+
overflow: visible;
1207+
}
1208+
1209+
/* 历史回放无真实状态数据,隐藏 IPython 工具的"已完成"标签,与其它工具卡片保持一致 */
1210+
.conversation-messages-container .tool-call-inline-status {
1211+
display: none;
1212+
}
1213+
11761214
/* 暗色模式下的聊天消息容器 */
11771215
.v-theme--dark .conversation-messages-container {
11781216
background-color: #1e1e1e;

0 commit comments

Comments
 (0)