mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 03:55:09 +08:00
feat(chat): first-token pending placeholder in the assistant bubble
This commit is contained in:
parent
fefb3b25b8
commit
c31a216cbd
@ -35,6 +35,15 @@
|
|||||||
paths, so plan-mode progress is never buried in a collapsed panel. -->
|
paths, so plan-mode progress is never buried in a collapsed panel. -->
|
||||||
<PlanStepsPanel v-if="planMeta" :plan="planMeta" :is-generating="isGenerating" />
|
<PlanStepsPanel v-if="planMeta" :plan="planMeta" :is-generating="isGenerating" />
|
||||||
|
|
||||||
|
<!-- 首包前占位:从发送到第一个 delta 之间(上下文准备 + LLM 首 token,
|
||||||
|
可达数秒)气泡完全空白会读作"卡死"。占位动效让气泡从第一帧起就是
|
||||||
|
活的;任何真实内容(thinking/tool/content/phase 面板)出现后自动让位。 -->
|
||||||
|
<div v-if="showPendingPlaceholder" class="pending-placeholder">
|
||||||
|
<span class="pending-placeholder__dots"><i /><i /><i /></span>
|
||||||
|
<span class="pending-placeholder__text">{{ $t('chat.pendingReply') }}</span>
|
||||||
|
<span v-if="phaseElapsed" class="pending-placeholder__elapsed">{{ phaseElapsed }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ===== 分段式渲染模式(Claude Code 风格)===== -->
|
<!-- ===== 分段式渲染模式(Claude Code 风格)===== -->
|
||||||
<template v-if="useSegmentedView">
|
<template v-if="useSegmentedView">
|
||||||
<div class="segments-view">
|
<div class="segments-view">
|
||||||
@ -1433,6 +1442,24 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First-token wait state: the assistant bubble exists (placeholder created
|
||||||
|
* synchronously on send) but nothing renderable has arrived — no segments, no
|
||||||
|
* thinking, no content, no phase-driven execution panel. Without this the
|
||||||
|
* bubble sits blank for the whole context-prep + LLM-prefill window (multiple
|
||||||
|
* seconds) and reads as a hang; the placeholder yields automatically the
|
||||||
|
* moment any real block renders.
|
||||||
|
*/
|
||||||
|
const showPendingPlaceholder = computed(() =>
|
||||||
|
role.value === 'assistant'
|
||||||
|
&& isGenerating.value
|
||||||
|
&& segments.value.length === 0
|
||||||
|
&& !thinkingContent.value
|
||||||
|
&& !displayContent.value
|
||||||
|
&& !showExecutionPanel.value
|
||||||
|
&& !planMeta.value
|
||||||
|
)
|
||||||
|
|
||||||
const phaseElapsed = computed(() => {
|
const phaseElapsed = computed(() => {
|
||||||
// Only meaningful while waiting on the model with nothing else to show.
|
// Only meaningful while waiting on the model with nothing else to show.
|
||||||
if (!isGenerating.value || toolCallsMeta.value.length) return ''
|
if (!isGenerating.value || toolCallsMeta.value.length) return ''
|
||||||
@ -1565,6 +1592,48 @@ watch(isGenerating, (generating) => {
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pending-placeholder {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
align-self: flex-start;
|
||||||
|
padding: 8px 14px;
|
||||||
|
margin: 4px 0;
|
||||||
|
font-family: var(--mc-font-body, sans-serif);
|
||||||
|
font-size: var(--mc-text-sm, 13px);
|
||||||
|
color: var(--mc-text-secondary, #665245);
|
||||||
|
background: var(--mc-bg-muted, #f1e8df);
|
||||||
|
border-radius: var(--mc-radius-md, 12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending-placeholder__dots {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending-placeholder__dots i {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: var(--mc-radius-full, 9999px);
|
||||||
|
background: var(--mc-primary, #d96d46);
|
||||||
|
opacity: 0.35;
|
||||||
|
animation: pending-dot-pulse 1.2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending-placeholder__dots i:nth-child(2) { animation-delay: 0.2s; }
|
||||||
|
.pending-placeholder__dots i:nth-child(3) { animation-delay: 0.4s; }
|
||||||
|
|
||||||
|
@keyframes pending-dot-pulse {
|
||||||
|
0%, 60%, 100% { opacity: 0.35; transform: translateY(0); }
|
||||||
|
30% { opacity: 1; transform: translateY(-3px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending-placeholder__elapsed {
|
||||||
|
font-size: var(--mc-text-xs, 11px);
|
||||||
|
color: var(--mc-text-tertiary, #9b7d6c);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
.superseded-toggle {
|
.superseded-toggle {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@ -531,7 +531,8 @@ export default {
|
|||||||
// Per-iteration grouping
|
// Per-iteration grouping
|
||||||
iterationEmpty: 'Iteration {index} interrupted (no output)',
|
iterationEmpty: 'Iteration {index} interrupted (no output)',
|
||||||
contentRepetitionWarning: 'Repetitive content detected near the end (model artifact)',
|
contentRepetitionWarning: 'Repetitive content detected near the end (model artifact)',
|
||||||
supersededPreviewCollapsed: 'Model preview replaced by the actual tool result',
|
supersededPreviewCollapsed: 'Collapsed: content drafted before tool execution (may not match actual results)',
|
||||||
|
pendingReply: 'Preparing a reply…',
|
||||||
expand: 'Expand',
|
expand: 'Expand',
|
||||||
// INCOMPLETE truncation card (finishReason=incomplete)
|
// INCOMPLETE truncation card (finishReason=incomplete)
|
||||||
incompleteTitle: 'Answer auto-truncated after repeated output was detected',
|
incompleteTitle: 'Answer auto-truncated after repeated output was detected',
|
||||||
|
|||||||
@ -531,7 +531,8 @@ export default {
|
|||||||
// 按轮次分组渲染
|
// 按轮次分组渲染
|
||||||
iterationEmpty: '第 {index} 轮被中断(无输出)',
|
iterationEmpty: '第 {index} 轮被中断(无输出)',
|
||||||
contentRepetitionWarning: '检测到内容尾部重复(疑似模型输出 artifact)',
|
contentRepetitionWarning: '检测到内容尾部重复(疑似模型输出 artifact)',
|
||||||
supersededPreviewCollapsed: '过程预演已被实际工具结果替换',
|
supersededPreviewCollapsed: '已折叠:模型在工具执行前预写的内容(可能与实际结果不符)',
|
||||||
|
pendingReply: '正在准备回复…',
|
||||||
expand: '展开',
|
expand: '展开',
|
||||||
// INCOMPLETE 截断卡片(finishReason=incomplete)
|
// INCOMPLETE 截断卡片(finishReason=incomplete)
|
||||||
incompleteTitle: '回答因检测到重复输出已被自动截断',
|
incompleteTitle: '回答因检测到重复输出已被自动截断',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user