优化: ai-chat 对话流布局与轨迹信息密度改造

- 分组行去统计化,调用统计归并轨迹卡头部
- 相对时间改时间分隔线,跨日显绝对时间
- 思考过程单行 pill 化,答案卡加 accent 边
- 工具卡短路径展示与耗时提示统一
- 高风险工具名单启动时同步后端注册表
- 自动重试改琥珀细条,红卡与手动重试仅终态
- 轨迹卡头并时间线行,显轮次号与绝对时间
- 运行中摘要补当前工具名与失败计数
- 执行中工具卡加实时计时,耗时统一人话口径
- 重试残留清理改标记匹配,替代中文子串硬编码
This commit is contained in:
lxy
2026-10-01 19:35:20 +08:00
parent 0560137840
commit 66c6553cdf
14 changed files with 562 additions and 209 deletions
+4 -90
View File
@@ -1,6 +1,6 @@
<template>
<div class="ai-tool-calls" ref="rootEl">
<!-- 顶部操作栏:批量审批(全局收起已下沉到首个可折叠分组标题行,U-260618) -->
<!-- 顶部操作栏:批量审批(轨迹卡 toggle 已覆盖整体展开/收起,分组行仅单组导航) -->
<div v-if="pendingCount > 0" class="ai-tool-topbar">
<!-- 批量操作栏(存在待审批项时显示) -->
<div class="ai-batch-approve">
@@ -14,22 +14,12 @@
</div>
<template v-for="(group, gi) in groupedToolCalls" :key="group.name">
<!-- 分组标题:同名工具 >=2 个时显示(B 聚合卡统一:默认行「N 次调用 · 成功 x 失败 y」,
点开逐条;成功/失败与状态点同一套 isToolFailure 判定,文案走 ai.trace 同源 key) -->
<!-- 分组标题:同名工具 >=2 个时显示(仅分组导航语义:点开逐条。
调用统计收敛到执行轨迹卡头部一处,分组行不再重复计数) -->
<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-agg">{{ groupAggText(group) }}</span>
<span class="ai-tool-group-chevron" :class="{ 'is-open': !isGroupCollapsed(gi) }">▸</span>
<!-- 全部展开/收起(U-260618:从 topbar 下沉到首个可折叠分组标题行尾巴,与 run command 同行) -->
<div
v-if="gi === firstCollapsibleGi"
class="ai-tool-global-toggle"
@click.stop="toggleAllGroups"
>
<span class="ai-tool-global-toggle-icon">{{ allGroupsExpanded ? '▾' : '▸' }}</span>
<span>{{ allGroupsExpanded ? $t('aiTool.collapseAll') : $t('aiTool.expandAll') }}</span>
</div>
</div>
<ToolCard
v-for="tc in group.calls"
@@ -52,7 +42,6 @@ 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, turnSummaryText } from '@/composables/ai/useTurnTrace'
import type { AiToolCallInfo } from '@/api/types'
// useI18n needed for $t() in template (collapseAll / expandAll) + groupDisplayName(i18n key)
@@ -124,32 +113,11 @@ const groupedToolCalls = computed<ToolCallGroup[]>(() => {
return groups
})
/** 可折叠分组数(calls >= 2 的组),控制全局按钮显隐 */
const collapsibleGroupCount = computed(() =>
groupedToolCalls.value.filter(g => g.calls.length >= 2).length
)
/** 首个可折叠分组索引全部展开/收起按钮下沉到此分组标题行尾巴) */
const firstCollapsibleGi = computed(() => {
for (let i = 0; i < groupedToolCalls.value.length; i++) {
if (groupedToolCalls.value[i].calls.length >= 2) return i
}
return -1
})
/** 待审批工具数(status === 'pending_approval'),控制批量操作栏显隐 */
const pendingCount = computed(() =>
props.toolCalls.filter(tc => tc.status === 'pending_approval').length
)
/** 是否所有可折叠分组都已展开 */
const allGroupsExpanded = computed(() => {
if (collapsibleGroupCount.value === 0) return true
return groupedToolCalls.value.every((g, gi) =>
g.calls.length < 2 || !collapsedGroups.value.has(gi)
)
})
/**
* 默认收起补齐:达到多卡阈值(>=2)且用户未手动操作过的组自动收起。
* 响应式而非一次性初始化——流式场景第一张卡到达即触发旧一次性逻辑,
@@ -181,24 +149,6 @@ function toggleGroupCollapse(gi: number): void {
collapsedGroups.value = next
}
/** 全部展开 / 全部收起切换 */
function toggleAllGroups(): void {
const next = new Set<number>()
// 全部记 touched:批量操作针对当前可见组,流式新增组仍走默认收起,
// 但已有组不得被补齐逻辑收回(否则"全部展开"点完即被回退)
for (const g of groupedToolCalls.value) {
if (g.calls.length >= 2) userTouchedGroups.value.add(g.name)
}
if (allGroupsExpanded.value) {
// 当前全展开 → 全部收起
for (let i = 0; i < groupedToolCalls.value.length; i++) {
if (groupedToolCalls.value[i].calls.length >= 2) next.add(i)
}
}
// 当前非全展开 → 全部展开(next 保持空 Set)
collapsedGroups.value = next
}
/**
* 分组显示名:工具名 → i18n 友好标签(英文化,原硬编码中文)。
* 用 aiTool.toolGroup.* 子键查表;未命中的工具回退 _ → 空格风格。
@@ -228,11 +178,6 @@ function groupDisplayName(group: ToolCallGroup): string {
return group.name.replace(/_/g, ' ')
}
/** 分组聚合行文案:「N 次调用 · 成功 x 失败 y」(零值项省略;与单卡状态点同源 isToolFailure 判定) */
function groupAggText(group: ToolCallGroup): string {
return turnSummaryText(aggregateCalls(group.calls))
}
/**
* 单卡是否展开:
* - 分组已折叠 → false(整组隐藏)
@@ -414,28 +359,6 @@ function groupIcon(name: string): string {
}
.ai-btn--danger:hover { filter: brightness(1.1); }
/* -- 全部展开/收起从 topbar 下沉到分组标题行,移除独立按钮 padding/背景,
高度=文字行高,不撑高分组标题行;融入标题视觉,仅 hover 变色) -- */
.ai-tool-global-toggle {
display: flex;
align-items: center;
gap: 4px;
cursor: pointer;
user-select: none;
font-size: 11px;
color: var(--df-text-dim);
transition: color 0.15s var(--df-ease);
/* 推到分组标题行尾巴右对齐 */
margin-left: auto;
}
.ai-tool-global-toggle:hover {
color: var(--df-text-secondary);
}
.ai-tool-global-toggle-icon {
font-size: 10px;
transition: transform 0.15s var(--df-ease);
}
/* -- 分组标题 -- */
.ai-tool-group-header {
display: flex;
@@ -464,17 +387,8 @@ function groupIcon(name: string): string {
font-weight: 500;
color: var(--df-text-secondary);
}
/* 聚合行「N 次调用 · 成功 x 失败 y」:弱化等宽,与单卡耗时同视觉层级 */
.ai-tool-group-agg {
font-family: var(--df-font-mono);
font-size: 10px;
color: var(--df-text-dim);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ai-tool-group-chevron {
/* 移除 margin-left:auto,让全部展开/收起按钮(global-toggle)占据行尾右对齐 */
margin-left: auto;
font-size: 10px;
color: var(--df-text-dim);
transition: transform 0.15s var(--df-ease);