diff --git a/static/src/App.vue b/static/src/App.vue index c5bcfbc..fe4fd4d 100644 --- a/static/src/App.vue +++ b/static/src/App.vue @@ -222,7 +222,6 @@ v-show="chatDisplayMode === 'chat'" ref="messagesArea" :messages="messages" - :avatar-status="avatarStatus" :icon-style="iconStyle" :expanded-blocks="expandedBlocks" :render-markdown="renderMarkdown" @@ -319,6 +318,7 @@ :goal-mode-armed="goalModeArmed" :goal-running="goalRunning" :goal-progress="goalProgress" + :avatar-status="showStatusAvatar && messages.length > 0 ? avatarStatus : null" @restore-user-question="restoreUserQuestionDialog" @update:input-message="inputSetMessage" @input-change="handleInputChange" diff --git a/static/src/app/computed.ts b/static/src/app/computed.ts index 8f54107..55fde2e 100644 --- a/static/src/app/computed.ts +++ b/static/src/app/computed.ts @@ -357,21 +357,21 @@ export const computed = { } if (runningTools.length > 0) { const keys = runningTools.map((a) => toolFaceKey(a?.tool?.name)); - const toolTexts = runningTools.map((a) => { - const tool = a?.tool; + const getFinalIntentText = (tool: any) => { if (!tool) return ''; - const status = getToolStatusText(tool, { intentEnabled }); - const desc = getToolDescription(tool); - return desc ? `${status} · ${desc}` : status; + const full = tool.intent_full || ''; + const rendered = tool.intent_rendered || ''; + // 只在 intent 打字效果完成后才显示完整文案,避免头像上出现逐字动画 + return full && rendered === full ? full : ''; + }; + const toolTexts = runningTools.map((a) => { + return intentEnabled ? getFinalIntentText(a?.tool) : ''; }); let text = toolTexts[0] || ''; if (runningTools.length > 1) { - text = toolTexts[0] || `并行调用 ${runningTools.length} 个工具...`; + text = toolTexts[0] || ''; } else if (!text) { - const first = runningTools[0]?.tool; - const status = getToolStatusText(first, { intentEnabled }); - const desc = getToolDescription(first); - text = desc ? `${status} · ${desc}` : status; + text = intentEnabled ? getFinalIntentText(runningTools[0]?.tool) : ''; } return { mode: 'tool', toolKeys: keys, toolTexts, text, tracking: false }; } diff --git a/static/src/components/avatar/StatusAvatar.vue b/static/src/components/avatar/StatusAvatar.vue index 817c426..eb6f321 100644 --- a/static/src/components/avatar/StatusAvatar.vue +++ b/static/src/components/avatar/StatusAvatar.vue @@ -8,6 +8,13 @@ aria-hidden="true" @click="handleAvatarClick" > + + + - - + + @@ -73,12 +90,14 @@ const props = withDefaults( toolKeys?: string[]; tracking?: boolean; size?: number; + hexBackground?: boolean; }>(), { mode: 'idle', toolKeys: () => [], tracking: false, - size: 30 + size: 30, + hexBackground: false } ); const emit = defineEmits<{ @@ -228,14 +247,22 @@ function applyWorkTransform() { eyeRightRef.value.style.transform = `translate(${WORK_ORIGIN.x}px, ${WORK_ORIGIN.y}px) rotate(${WORK_ROTATION_RIGHT}deg)`; } } -function applyIdleTransform() { +function applyIdleTransform(animate = true) { if (eyeLeftRef.value) { - eyeLeftRef.value.style.transition = DEFAULT_EYE_TRANSITION; + if (!animate) eyeLeftRef.value.style.transition = 'none'; eyeLeftRef.value.style.transform = `translate(${EYE_LEFT_ORIGIN.x}px, ${EYE_LEFT_ORIGIN.y}px)`; + if (!animate) { + void eyeLeftRef.value.offsetWidth; + eyeLeftRef.value.style.transition = ''; + } } if (eyeRightRef.value) { - eyeRightRef.value.style.transition = DEFAULT_EYE_TRANSITION; + if (!animate) eyeRightRef.value.style.transition = 'none'; eyeRightRef.value.style.transform = `translate(${EYE_RIGHT_ORIGIN.x}px, ${EYE_RIGHT_ORIGIN.y}px)`; + if (!animate) { + void eyeRightRef.value.offsetWidth; + eyeRightRef.value.style.transition = ''; + } } } function applyNervousTransform() { @@ -361,7 +388,7 @@ function triggerNervous() { nervousTimer = window.setTimeout(() => { if (internalMode.value === 'nervous') { internalMode.value = null; - applyState(); + // 由 watcher 统一调用 applyState,避免与显式调用叠加导致动画被重置 } }, 500); } @@ -469,7 +496,8 @@ function applyState() { watch(() => [props.mode, props.toolKeys, props.tracking, internalMode.value], () => applyState(), { deep: true }); onMounted(() => { - applyIdleTransform(); + // 初始化时直接定位,避免眼睛从 SVG 原点飞入 + applyIdleTransform(false); document.addEventListener('mousemove', onMouseMove); applyState(); }); @@ -494,6 +522,11 @@ onBeforeUnmount(() => { flex-shrink: 0; } +.sa-bg { + fill: var(--chat-surface-color); + stroke: none; +} + .sa-frame { fill: none; stroke: currentColor; diff --git a/static/src/components/chat/ChatArea.vue b/static/src/components/chat/ChatArea.vue index f3a5efb..4f22b2c 100644 --- a/static/src/components/chat/ChatArea.vue +++ b/static/src/components/chat/ChatArea.vue @@ -112,6 +112,27 @@ assistantWorkLabel(index) }} +
+
+
+ + {{ letter }} + +
+
+