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