优化: 工具卡复读修复与默认折叠收敛
This commit is contained in:
@@ -32,12 +32,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<ToolCard
|
||||
v-for="(tc, ci) in group.calls"
|
||||
v-for="tc in group.calls"
|
||||
:key="tc.id"
|
||||
:tc="tc"
|
||||
:isExpanded="isCardExpanded(tc, gi, ci)"
|
||||
:isExpanded="isCardExpanded(tc, gi)"
|
||||
:isContentExpanded="expandedTools.has(tc.id)"
|
||||
@toggle="() => toggleCardExpand(tc, gi, ci)"
|
||||
@toggle="() => toggleCardExpand(tc, gi)"
|
||||
@expand-content="toggleExpand"
|
||||
@approve="(e) => emit('approve', e)"
|
||||
@install-obscura="() => emit('install-obscura')"
|
||||
@@ -86,13 +86,16 @@ const expandedCards = ref(new Set<string>())
|
||||
// userExpandedCards 是"用户明确点开"的持久标记,collapseInactive 跳过这些卡。
|
||||
// 语义:用户读过的卡不强制折叠,符合"跟随阅读位置"的增强目标。
|
||||
const userExpandedCards = ref(new Set<string>())
|
||||
// 用户主动折叠记忆(修复首卡 ci===0 兜底 return true 导致无法折叠)。
|
||||
// isCardExpanded 最优先判断,覆盖默认首卡展开 + userExpandedCards 记忆。
|
||||
// 用户主动折叠记忆(与 userExpandedCards 对偶,记录显式收起过的卡)。
|
||||
// isCardExpanded 最优先判断,覆盖用户展开记忆。
|
||||
const userCollapsedCards = ref(new Set<string>())
|
||||
// 内容级展开(read_file "展开全部")
|
||||
const expandedTools = ref(new Set<string>())
|
||||
// 分组折叠状态(按分组索引);多卡片分组默认收起
|
||||
const collapsedGroups = ref<Set<number>>(new Set())
|
||||
// 用户手动操作过折叠的组(按组名记忆)。流式期间组卡片数增长会反复触发默认收起补齐,
|
||||
// 用户手动展开过的组不再被自动收回——名字比索引稳(分组只追加不重排,但语义上名字更直白)。
|
||||
const userTouchedGroups = ref(new Set<string>())
|
||||
|
||||
interface ToolCallGroup {
|
||||
name: string
|
||||
@@ -146,26 +149,20 @@ const allGroupsExpanded = computed(() => {
|
||||
)
|
||||
})
|
||||
|
||||
/** 初始化默认折叠:多卡片分组(>=2)默认收起,首条不参与 group-hidden 但跟随折叠态 */
|
||||
function initDefaultCollapse(): void {
|
||||
const set = new Set<number>()
|
||||
for (let i = 0; i < groupedToolCalls.value.length; i++) {
|
||||
if (groupedToolCalls.value[i].calls.length >= 2) {
|
||||
set.add(i)
|
||||
}
|
||||
}
|
||||
collapsedGroups.value = set
|
||||
}
|
||||
|
||||
/** 首次有数据时执行默认折叠初始化(watch 防重复,修复异步加载竞态 BUG-B) */
|
||||
let initialized = false
|
||||
/**
|
||||
* 默认收起补齐:达到多卡阈值(>=2)且用户未手动操作过的组自动收起。
|
||||
* 响应式而非一次性初始化——流式场景第一张卡到达即触发旧一次性逻辑,
|
||||
* 此后组从 1 卡长到 N 卡不再折叠,批量读取的 N 张单行卡全展开刷屏;
|
||||
* 签名(组名:卡数)变化即幂等补齐,新达到阈值的组收起,已 touched 的组不动。
|
||||
*/
|
||||
watch(
|
||||
() => props.toolCalls.length,
|
||||
(len) => {
|
||||
if (len > 0 && !initialized) {
|
||||
initialized = true
|
||||
initDefaultCollapse()
|
||||
}
|
||||
() => groupedToolCalls.value.map(g => `${g.name}:${g.calls.length}`).join('|'),
|
||||
() => {
|
||||
const next = new Set(collapsedGroups.value)
|
||||
groupedToolCalls.value.forEach((g, gi) => {
|
||||
if (g.calls.length >= 2 && !userTouchedGroups.value.has(g.name)) next.add(gi)
|
||||
})
|
||||
collapsedGroups.value = next
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
@@ -175,6 +172,8 @@ function isGroupCollapsed(gi: number): boolean {
|
||||
}
|
||||
|
||||
function toggleGroupCollapse(gi: number): void {
|
||||
// 手动操作记 touched:此后该组不被默认收起补齐自动收回
|
||||
userTouchedGroups.value.add(groupedToolCalls.value[gi]?.name ?? String(gi))
|
||||
const next = new Set(collapsedGroups.value)
|
||||
if (next.has(gi)) next.delete(gi)
|
||||
else next.add(gi)
|
||||
@@ -184,6 +183,11 @@ function toggleGroupCollapse(gi: number): void {
|
||||
/** 全部展开 / 全部收起切换 */
|
||||
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++) {
|
||||
@@ -245,31 +249,31 @@ function groupFileSummary(group: ToolCallGroup): string {
|
||||
|
||||
/**
|
||||
* 单卡是否展开:
|
||||
* - 分组未折叠 → 用户手动 expandedCards 或首卡默认展开
|
||||
* - 分组已折叠 → false(整组隐藏)
|
||||
* - 多卡片分组 + 未手动操作过 → 仅首卡(ci===0)展开
|
||||
* - 未手动操作过 → 默认折叠(U-260629):completed 卡一律单行 header(工具名+目标+结果摘要),
|
||||
* 杜绝"AI 一轮多工具各铺开一整卡"刷屏;点 header 才展开。
|
||||
* running/pending/rejected/write_file 不受此值影响——ToolCard 内部
|
||||
* v-show="isExpanded || shouldKeepOpen(tc)" 强制展开 body。
|
||||
* - userExpandedCards(用户主动展开记忆)优先,即使父级 collapseInactive
|
||||
* 清了 expandedCards,记忆态仍保持展开(用户读过的卡不强制折叠)。
|
||||
*/
|
||||
function isCardExpanded(tc: AiToolCallInfo, gi: number, ci: number): boolean {
|
||||
function isCardExpanded(tc: AiToolCallInfo, gi: number): boolean {
|
||||
if (isGroupCollapsed(gi)) return false
|
||||
// 用户主动折叠最优先(覆盖默认首卡 ci===0 兜底展开 + 用户展开记忆)
|
||||
// 用户主动折叠最优先(覆盖用户展开记忆)
|
||||
if (userCollapsedCards.value.has(tc.id)) return false
|
||||
// 用户主动展开的卡片记忆态优先(不被自动收起影响)
|
||||
if (userExpandedCards.value.has(tc.id)) return true
|
||||
// 用户手动切换过该卡(当前态)
|
||||
if (expandedCards.value.has(tc.id)) return true
|
||||
// 多卡片分组:仅首卡默认展开
|
||||
if (groupedToolCalls.value[gi]?.calls.length >= 2) return ci === 0
|
||||
// 单卡组:默认展开
|
||||
return true
|
||||
// 兜底:默认折叠(单行 header 已有 toolDisplayName 路径/pattern/host + toolResultSummary 摘要)
|
||||
return false
|
||||
}
|
||||
|
||||
function toggleCardExpand(tc: AiToolCallInfo, gi: number, ci: number) {
|
||||
function toggleCardExpand(tc: AiToolCallInfo, gi: number) {
|
||||
const id = tc.id
|
||||
// 用 isCardExpanded 判当前真实态(含默认首卡展开),而非 expandedCards(默认首卡不在其中,
|
||||
// 否则首卡点 toggle 会误判"当前折叠"反向展开)
|
||||
const currentlyExpanded = isCardExpanded(tc, gi, ci)
|
||||
// 用 isCardExpanded 判当前真实态(含用户操作记忆),而非 expandedCards(记忆态不在其中,
|
||||
// 否则已展开卡点 toggle 会误判"当前折叠"反向展开)
|
||||
const currentlyExpanded = isCardExpanded(tc, gi)
|
||||
if (currentlyExpanded) {
|
||||
// 展开 → 折叠
|
||||
expandedCards.value.delete(id)
|
||||
@@ -316,7 +320,12 @@ function scrollToFirstPending(): void {
|
||||
const card = root.querySelector<HTMLElement>('.ai-tool-card--pending_approval')
|
||||
if (!card) return
|
||||
if (card.offsetParent === null) {
|
||||
// 处于折叠分组内 → 展开后下一帧重查
|
||||
// 处于折叠分组内 → 展开后下一帧重查。
|
||||
// 展开组记 touched:审批卡可见性优先于默认收起,否则流式新卡一触发补齐,
|
||||
// 刚展开的含 pending 卡的组又被收回,BUG-260624-02(审批不可见)复发。
|
||||
groupedToolCalls.value.forEach(g => {
|
||||
if (g.calls.length >= 2) userTouchedGroups.value.add(g.name)
|
||||
})
|
||||
collapsedGroups.value = new Set()
|
||||
nextTick(() => {
|
||||
root.querySelector<HTMLElement>('.ai-tool-card--pending_approval')
|
||||
|
||||
Reference in New Issue
Block a user