优化: 对话执行轨迹聚合与工具卡交互

前端聚合当前轮次工具调用数,替代每轮清零的完成计数,进度文案改轮次+累计口径
完成聚合语义由执行轨迹卡承接,工具卡复读修复与默认折叠收敛
流式与工具事件配套调整,新增轮次轨迹收集 composable
This commit is contained in:
lxy
2026-10-01 02:41:05 +08:00
parent e7cd035c62
commit 04acc4297d
15 changed files with 482 additions and 95 deletions
+17 -32
View File
@@ -14,12 +14,12 @@
</div>
<template v-for="(group, gi) in groupedToolCalls" :key="group.name">
<!-- 分组标题:同名工具 >=2 个时显示 -->
<!-- 分组标题:同名工具 >=2 个时显示(B 聚合卡统一:默认行「N 次调用 · 成功 x 失败 y」,
点开逐条;成功/失败与状态点同一套 isToolFailure 判定,文案走 ai.trace 同源 key) -->
<div v-if="group.calls.length >= 2" class="ai-tool-group-header" @click="toggleGroupCollapse(gi)">
<span class="ai-tool-group-icon" v-html="groupIcon(group.name)"></span>
<span class="ai-tool-group-name">{{ groupDisplayName(group) }}</span>
<span class="ai-tool-group-count">{{ group.calls.length }}</span>
<span class="ai-tool-group-files">{{ groupFileSummary(group) }}</span>
<span class="ai-tool-group-agg">{{ groupAggText(group) }}</span>
<span class="ai-tool-group-chevron" :class="{ 'is-open': !isGroupCollapsed(gi) }">▸</span>
<!-- 全部展开/收起(U-260618:从 topbar 下沉到首个可折叠分组标题行尾巴,与 run command 同行) -->
<div
@@ -52,6 +52,7 @@ import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import ToolCard from './ToolCard.vue'
import { listen, type UnlistenFn } from '@tauri-apps/api/event'
import { aggregateCalls, statsTail } from '@/composables/ai/useTurnTrace'
import type { AiToolCallInfo } from '@/api/types'
// useI18n needed for $t() in template (collapseAll / expandAll) + groupDisplayName(i18n key)
@@ -227,24 +228,12 @@ function groupDisplayName(group: ToolCallGroup): string {
return group.name.replace(/_/g, ' ')
}
/** 从工具调用参数中提取路径摘要(取前 3 个文件名)。
* 供分组标题行展示,让用户一眼知道影响了哪些文件。*/
function groupFileSummary(group: ToolCallGroup): string {
const paths: string[] = []
for (const tc of group.calls) {
const args = tc.args as Record<string, unknown> | undefined
const p = args?.path
if (typeof p === 'string' && p.trim()) {
// 取最后一段文件名(或最后两级目录)
const parts = p.trim().split(/[/\\]/)
const short = parts.length >= 2
? parts.slice(-2).join('/')
: parts[parts.length - 1]
if (!paths.includes(short)) paths.push(short)
if (paths.length >= 3) break
}
}
return paths.length ? ' · ' + paths.join(' · ') : ''
/** 分组聚合行文案:「N 次调用 · 成功 x 失败 y」(零值项省略;与单卡状态点同源 isToolFailure 判定) */
function groupAggText(group: ToolCallGroup): string {
const s = aggregateCalls(group.calls)
const tail = statsTail(s)
const head = t('ai.trace.calls', { n: s.total })
return tail ? `${head} · ${tail}` : head
}
/**
@@ -478,18 +467,14 @@ function groupIcon(name: string): string {
font-weight: 500;
color: var(--df-text-secondary);
}
.ai-tool-group-count {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 16px;
height: 16px;
padding: 0 4px;
border-radius: 8px;
/* 聚合行「N 次调用 · 成功 x 失败 y」:弱化等宽,与单卡耗时同视觉层级 */
.ai-tool-group-agg {
font-family: var(--df-font-mono);
font-size: 10px;
font-weight: 600;
background: var(--df-accent-bg);
color: var(--df-accent);
color: var(--df-text-dim);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ai-tool-group-chevron {
/* 移除 margin-left:auto,让全部展开/收起按钮(global-toggle)占据行尾右对齐 */