优化: P1-a 长内容折叠(AI文本气泡+多冲突+错误堆栈)

- MessageList: assistant文本气泡>1500字符折叠(max-height 400px+渐变遮罩+展开按钮),流式中不折叠避免delta重排抖动(M1)

- ConflictResolver: 多冲突(>=3)默认折叠,点击展开diff(M6)

- HelpRequiredCard: context堆栈max-height 200px滚动+等宽字体(M7)
This commit is contained in:
lxy
2026-08-02 12:24:20 +08:00
parent 7f0edced01
commit 9e85cadb3f
3 changed files with 164 additions and 26 deletions
+70 -1
View File
@@ -548,6 +548,31 @@ function toggleSegment(key: string): void {
expandedSegmentIds.value = next
}
// M1:assistant 纯文本气泡超长折叠,避免单条 AI 回复撑爆对话区。
// 仅对「已完成(非流式)」assistant 文本生效;流式中的消息不折叠(delta 重排抖动)。
// 阈值 1500 字符:约 markdown 渲染后一屏(400px@13px/1.6≈35行)的源文本量,超此即判长。
// 折叠 max-height 同步 400px(见 CSS .ai-msg-bubble--collapsed,两处一致)。
const LONG_MSG_THRESHOLD = 1500
/// 展开的长消息 id 集合(点击底部按钮切换);默认折叠
const expandedLongIds = ref<Set<string>>(new Set())
function toggleLong(id: string): void {
const next = new Set(expandedLongIds.value)
if (next.has(id)) next.delete(id)
else next.add(id)
expandedLongIds.value = next
}
function isLongMsg(msg: AiMessage): boolean {
return msg.role === 'assistant' && !!msg.content && msg.content.length > LONG_MSG_THRESHOLD
}
/// 是否折叠展示该 assistant 文本气泡:长消息 + 未手动展开 + 非流式(当前未在流式生成)。
function shouldCollapseLong(msg: AiMessage): boolean {
if (!isLongMsg(msg)) return false
if (expandedLongIds.value.has(msg.id)) return false
// 流式约束:正在生成的当前消息不折叠(避免 delta 重排抖动),参考 L92 同款 streaming 判定。
if (isLastAi(msg) && store.state.streaming) return false
return true
}
type RenderItem =
| { kind: 'msg'; key: string; msg: AiMessage }
| { kind: 'sep'; key: string; seg: MessageSegment & { key: string } }
@@ -683,7 +708,7 @@ defineExpose({
<div
v-if="item.msg.content || (isLastAi(item.msg) && store.state.streaming && store.state.currentText)"
class="ai-msg-bubble ai-msg-bubble--ai ai-md"
:class="{ 'ai-msg-bubble--error': item.msg.isError }"
:class="{ 'ai-msg-bubble--error': item.msg.isError, 'ai-msg-bubble--collapsed': shouldCollapseLong(item.msg) }"
:key="'md-' + item.msg.id + '-' + (isLastAi(item.msg) ? _mdRenderKey : 0)"
>
<!-- 流式:分块渲染(已完成块key稳定→DOM不重建) -->
@@ -693,6 +718,12 @@ defineExpose({
</template>
<!-- 完成/历史:整段v-html(不变) -->
<div v-else v-html="renderMd(item.msg.content)"></div>
<!-- M1:超长 assistant 文本折叠后的底部展开按钮(仅非流式 + 长消息显示) -->
<button
v-if="isLongMsg(item.msg) && !(isLastAi(item.msg) && store.state.streaming)"
class="ai-msg-bubble-toggle"
@click.stop="toggleLong(item.msg.id)"
>{{ expandedLongIds.has(item.msg.id) ? $t('aiChat.collapse') : $t('aiChat.expand') }}</button>
<button
v-if="item.msg.content"
class="ai-copy-btn ai-copy-btn--ai"
@@ -895,6 +926,44 @@ defineExpose({
.ai-msg-bubble--ai.ai-md {
overflow-x: auto;
}
/* M1:超长 assistant 文本折叠——max-height 截断 + 底部渐变遮罩 + 展开按钮。
背景 var(--df-bg-card) 对齐 .ai-msg-bubble--ai(L876):渐变淡出到气泡背景,
视觉上内容「沉入」气泡而非被硬切。overflow:hidden 覆盖 .ai-md 的 overflow-x:auto。 */
.ai-msg-bubble--collapsed {
max-height: 400px;
overflow: hidden;
position: relative;
}
.ai-msg-bubble--collapsed::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 48px;
background: linear-gradient(transparent, var(--df-bg-card));
pointer-events: none; /* 让点击穿透到下方展开按钮 */
}
/* 展开按钮:气泡底部居中,置于渐变遮罩之上 */
.ai-msg-bubble-toggle {
position: absolute;
bottom: 6px;
left: 50%;
transform: translateX(-50%);
z-index: 1;
padding: 2px 12px;
font-size: 11px;
line-height: 1.4;
color: var(--df-accent);
background: var(--df-bg-card);
border: 0.5px solid var(--df-border);
border-radius: var(--df-radius);
cursor: pointer;
user-select: none;
}
.ai-msg-bubble-toggle:hover {
background: var(--df-bg-card-hover);
}
/* ── Input Augmentation: 用户气泡内 mention chip(圆角徽标,复用 ChatInput .ai-skill-chip-name 风格) ──
用户气泡背景 = --df-accent(主题强调色,如深色蓝/紫),chip 用半透明白底+深字 保证对比度;