diff --git a/static/src/components/avatar/StatusAvatar.vue b/static/src/components/avatar/StatusAvatar.vue index 331d6926..16435f01 100644 --- a/static/src/components/avatar/StatusAvatar.vue +++ b/static/src/components/avatar/StatusAvatar.vue @@ -101,7 +101,6 @@ const props = withDefaults( } ); const emit = defineEmits<{ - (event: 'active-index', index: number): void; (event: 'poke'): void; }>(); @@ -314,18 +313,12 @@ function trackEyes() { trackRaf = requestAnimationFrame(trackEyes); } -// ---- 并行工具轮播 ---- -let carouselTimer: number | null = null; +// ---- 工具 face 切换 ---- let faceSwitchTimer: number | null = null; let blinkTimer: number | null = null; let nervousTimer: number | null = null; let isBlinking = false; -let carouselIdx = 0; const internalMode = ref(null); -function stopCarousel() { - if (carouselTimer) clearInterval(carouselTimer); - carouselTimer = null; -} function stopFaceSwitchTimer() { if (faceSwitchTimer) clearTimeout(faceSwitchTimer); faceSwitchTimer = null; @@ -375,7 +368,6 @@ function triggerNervous() { stopAutoBlink(); stopNervousTimer(); stopTracking(); - stopCarousel(); stopFaceSwitchTimer(); activeFaceKey.value = null; sharedVisible.value = true; @@ -424,20 +416,6 @@ function setIconFace(key: string, animated = true) { faceSwitchTimer = null; }, 160); } -function startCarousel(keys: string[]) { - stopCarousel(); - stopFaceSwitchTimer(); - carouselIdx = 0; - setIconFace(keys[0]); - emit('active-index', carouselIdx); - if (keys.length < 2) return; - carouselTimer = window.setInterval(() => { - carouselIdx = (carouselIdx + 1) % keys.length; - setIconFace(keys[carouselIdx]); - emit('active-index', carouselIdx); - }, 1450); -} - // ---- 状态应用 ---- function applyState() { if (props.mode !== 'idle' && internalMode.value) { @@ -449,10 +427,11 @@ function applyState() { if (mode === 'tool') { stopTracking(); const keys = (props.toolKeys || []).filter(Boolean); - startCarousel(keys.length ? keys : ['command']); + // 并行工具时固定显示第一个正在执行的工具; + // 完成一个后 toolKeys 变化,自动切到下一个 + setIconFace(keys.length ? keys[0] : 'command'); return; } - stopCarousel(); stopFaceSwitchTimer(); if (mode === 'think') { stopTracking(); @@ -493,7 +472,13 @@ function applyState() { } } -watch(() => [props.mode, props.toolKeys, props.tracking, internalMode.value], () => applyState(), { deep: true }); +// toolKeys 按内容 join 后再监听:avatarStatus computed 每次重算都产出新数组引用, +// 若直接监听数组,流式期间(intent 打字、状态刷新)applyState 会被高频触发, +// face 切换动画反复重启,表现为图标无规律抖动 +watch( + () => [props.mode, (props.toolKeys || []).join('\u001f'), props.tracking, internalMode.value], + () => applyState() +); onMounted(() => { // 初始化时直接定位,避免眼睛从 SVG 原点飞入 @@ -504,7 +489,6 @@ onMounted(() => { onBeforeUnmount(() => { document.removeEventListener('mousemove', onMouseMove); stopTracking(); - stopCarousel(); stopFaceSwitchTimer(); stopAutoBlink(); stopNervousTimer(); diff --git a/static/src/components/input/InputComposer.vue b/static/src/components/input/InputComposer.vue index d9e6e397..98b544bf 100644 --- a/static/src/components/input/InputComposer.vue +++ b/static/src/components/input/InputComposer.vue @@ -106,7 +106,7 @@