新增: 压缩摘要折叠进所属轮次轨迹卡

- 压缩摘要进轮次桶随轨迹卡收起(旧口径原样展开作废)
- 轨迹卡内渲染为摘要 pill 单行, 点击展开全文(复用思考展开态)
- 压缩摘要不竞选最终答复气泡, 过程记录语义
This commit is contained in:
lxy
2026-10-11 08:53:36 +08:00
parent b4b18e351b
commit c2809bd927
3 changed files with 32 additions and 4 deletions
+30 -4
View File
@@ -971,6 +971,11 @@ function thinkPlainText(m: AiMessage): string {
const src = m.reasoning_content || '' const src = m.reasoning_content || ''
return src.replace(/\s+/g, ' ').trim() return src.replace(/\s+/g, ' ').trim()
} }
/** 压缩摘要 pill 单行:纯文本空白归一截断(961 轮次记录形态,title 承载全文) */
function compactSnippet(content: string | undefined): string {
const txt = (content || '').replace(/\s+/g, ' ').trim()
return txt.length > 80 ? txt.slice(0, 80) + '…' : txt
}
/** pill 单行文本:纯文本截断 + 空轮回退文案(title 承载全文,此处只管单行) */ /** pill 单行文本:纯文本截断 + 空轮回退文案(title 承载全文,此处只管单行) */
function thinkSnippet(m: AiMessage): string { function thinkSnippet(m: AiMessage): string {
const txt = thinkPlainText(m) const txt = thinkPlainText(m)
@@ -1137,7 +1142,8 @@ function genBucketItems(
let fi = -1 let fi = -1
for (let i = msgs.length - 1; i >= 0; i--) { for (let i = msgs.length - 1; i >= 0; i--) {
const c = msgs[i].content const c = msgs[i].content
if (c && c.trim() && !isToolResultJson(c)) { fi = i; break } // compressed(压缩摘要)不竞选最终答复:摘要是过程记录,轨迹卡内 pill 呈现
if (c && c.trim() && !isToolResultJson(c) && msgs[i].status !== 'compressed') { fi = i; break }
} }
const finalMsg = fi >= 0 ? msgs[fi] : msgs[msgs.length - 1] const finalMsg = fi >= 0 ? msgs[fi] : msgs[msgs.length - 1]
// 过程消息 = finalMsg 之外全部(含其后尾气泡):异常收尾(错误/停止/流中断)时末轮气泡 // 过程消息 = finalMsg 之外全部(含其后尾气泡):异常收尾(错误/停止/流中断)时末轮气泡
@@ -1248,8 +1254,9 @@ const renderItems = computed<RenderItem[]>(() => {
items.push({ kind: 'msg', key: 'm-' + m.id, msg: m }) items.push({ kind: 'msg', key: 'm-' + m.id, msg: m })
continue continue
} }
// assistant 非错误非压缩进轮次桶(compressed 用户诉求不折叠,原样展开) // assistant 非错误进轮次桶——compressed(压缩摘要)也进桶(用户令 10-11:压缩摘要
if (m.role === 'assistant' && !m.isError && m.status !== 'compressed') { // 的动作作为轮次记录折叠,随轨迹卡收起;旧口径「原样展开」作废,961② 已定其归属当前轮)
if (m.role === 'assistant' && !m.isError) {
bucket.push(m) bucket.push(m)
} else { } else {
flushBucket() flushBucket()
@@ -1649,8 +1656,27 @@ defineExpose({
<!-- 展开态 rail 面板:每轮=思考单行 pill(点击展开全文)+工具卡,图标锚定左轨连线贯通 --> <!-- 展开态 rail 面板:每轮=思考单行 pill(点击展开全文)+工具卡,图标锚定左轨连线贯通 -->
<div v-if="item.expanded" class="ai-trace-body"> <div v-if="item.expanded" class="ai-trace-body">
<div v-for="m in item.traceMsgs" :key="m.id" class="ai-trace-round"> <div v-for="m in item.traceMsgs" :key="m.id" class="ai-trace-round">
<!-- 压缩摘要(用户令 10-11):轮次过程记录形态,pill 单行+点击展开全文,
复用思考 pill 展开态(expandedThinkIds/toggleThink 同交互语义零新状态) -->
<template v-if="m.status === 'compressed'">
<button
class="ai-think-pill"
:title="m.content"
@click="toggleThink(m.id)"
>
<span class="ai-think-ic">
<svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 11-9-9"/><path d="M21 3l-9 9"/></svg>
</span>
<span class="ai-think-text">{{ $t('ai.trace.compressed') }} · {{ compactSnippet(m.content) }}</span>
</button>
<div
v-if="expandedThinkIds.has(m.id) && m.content?.trim()"
class="ai-think-full ai-md"
v-html="renderMdDecorated(m.content)"
></div>
</template>
<!-- 空内容轮(只剩「过程记录」占位)不渲染思考 pill,降噪;工具卡照常 --> <!-- 空内容轮(只剩「过程记录」占位)不渲染思考 pill,降噪;工具卡照常 -->
<template v-if="hasThinkBody(m)"> <template v-else-if="hasThinkBody(m)">
<button <button
class="ai-think-pill" class="ai-think-pill"
:title="thinkFullText(m)" :title="thinkFullText(m)"
+1
View File
@@ -86,6 +86,7 @@ export default {
collapse: 'Collapse trace', collapse: 'Collapse trace',
roundN: 'Round {n}', roundN: 'Round {n}',
processOnly: 'Process log', processOnly: 'Process log',
compressed: 'Context summary',
running: 'Running', running: 'Running',
taskRef: 'Related task: {name}', taskRef: 'Related task: {name}',
stepDone: '✓ {n} calls done', stepDone: '✓ {n} calls done',
+1
View File
@@ -86,6 +86,7 @@ export default {
collapse: '收起轨迹', collapse: '收起轨迹',
roundN: '第 {n} 轮', roundN: '第 {n} 轮',
processOnly: '过程记录', processOnly: '过程记录',
compressed: '压缩摘要',
running: '进行中', running: '进行中',
taskRef: '关联任务:{name}', taskRef: '关联任务:{name}',
stepDone: '✓ {n} 次调用完成', stepDone: '✓ {n} 次调用完成',