fix(chat): animate app logo while thinking
Build / build (push) Canceled after 0s
Playwright / e2e (push) Canceled after 0s

This commit is contained in:
2026-11-05 12:19:13 +08:00
parent e35a5626c6
commit 7ee844cd88
3 changed files with 40 additions and 4 deletions
@@ -0,0 +1,9 @@
---
date: 2026-11-05
pr: pending
feature: Animated application logo while thinking
impact: The thinking indicator uses the application logo with a gentle animation, disabled when reduced motion is requested. Chat behavior is unchanged.
---
The thinking indicator no longer uses the old character GIF. The new animation
is applied to the existing application logo without changing chat events.
@@ -21,7 +21,6 @@ import VirtualMessageList from "./VirtualMessageList.vue";
import MessageItem from "./MessageItem.vue";
import { groupRunMessages } from "./groupRunMessages";
import { LIVE_CHAT_MAX_LOADED_MESSAGES, useChatStore, type Message } from "@/stores/hermes/chat";
import thinkingImage from "@/assets/thinking.gif";
import { useToolTraceVisibility } from "@/composables/useToolTraceVisibility";
const chatStore = useChatStore();
@@ -527,7 +526,7 @@ defineExpose({
<div v-if="isThinkingIndicatorVisible" class="streaming-indicator">
<div class="thinking-status">
<img
:src="thinkingImage"
src="/app-logo.png"
alt=""
aria-hidden="true"
class="thinking-avatar"
@@ -1479,10 +1478,19 @@ defineExpose({
.thinking-avatar {
width: 40px;
height: 40px;
border-radius: $radius-md;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
filter: brightness(1.18) contrast(1.08) saturate(1.08);
animation: thinking-avatar-breathe 2.4s ease-in-out infinite;
}
@keyframes thinking-avatar-breathe {
0%, 100% { transform: translateY(0) scale(1); filter: drop-shadow(0 0 2px rgba(var(--accent-primary-rgb), 0.12)); }
50% { transform: translateY(-2px) scale(1.06); filter: drop-shadow(0 0 7px rgba(var(--accent-primary-rgb), 0.4)); }
}
@media (prefers-reduced-motion: reduce) {
.thinking-status .thinking-avatar, .thinking-status .thinking-status-label { animation: none; }
}
.thinking-status-copy {
+19
View File
@@ -138,6 +138,25 @@ test('groups assistant output across tool calls until the run ends', async ({ pa
await expect(page.locator('.run-end')).toHaveText('Run output ended')
})
test('animates the application logo while thinking and respects reduced motion', async ({ page }) => {
await authenticate(page, TEST_ACCESS_KEY, 'research')
await mockHermesApi(page)
await mockChatSocket(page)
await page.goto('/#/hermes/chat')
await sendChatMessage(page, 'think')
const { sessionId } = await waitForRunSessionId(page)
await page.evaluate((sid) => {
(window as any).__PW_CHAT_SOCKET__.latest.__trigger('run.started', { event: 'run.started', session_id: sid, run_id: 'thinking-logo' })
}, sessionId)
const avatar = page.locator('.thinking-avatar')
await expect(avatar).toBeVisible()
await expect(avatar).toHaveAttribute('src', '/app-logo.png')
await expect(avatar).toHaveCSS('animation-name', /^thinking-avatar-breathe/)
await page.emulateMedia({ reducedMotion: 'reduce' })
await expect(avatar).toHaveCSS('animation-name', 'none')
await expect(page.locator('.thinking-status-label')).toHaveCSS('animation-name', 'none')
})
test('opens the session model dialog and the session search from the chat header', async ({ page }) => {
await authenticate(page, TEST_ACCESS_KEY, 'research')
await mockHermesApi(page, { sessions: [sampleSession] })