style(frontend): '생각하는 중' 자리를 답변 말풍선과 같은 모양으로 — ThinkingBubble

세 군데(새 대화 세션 생성 중 / 스트리밍 첫 토큰 전 / 재진입 대기)가 각자 손으로 그린 테두리 카드라
실제 답변(Message: AI 배지 + CODEASSIST + 상자 없는 본문)과 달랐음. 컴포넌트 하나로 통일.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
lee-hyeon-cheol
2026-09-21 15:34:07 +09:00
co-authored by Claude Fable 5.1
parent 5998944f09
commit 833b834859
5 changed files with 30 additions and 25 deletions
@@ -0,0 +1,21 @@
import type { ReactNode } from "react"
/** 답변 자리(첫 토큰 전·재진입 대기·세션 생성 중). Message 의 assistant 모양과 똑같이 —
* AI 배지 + CODEASSIST 라벨, 본문은 상자 없이. 안에 시머 텍스트나 스트리밍 본문을 넣는다. */
export function ThinkingBubble({ children }: { children?: ReactNode }) {
return (
<div className="flex flex-col items-start gap-1">
<div className="flex w-full items-center gap-1.5">
<span className="bg-primary text-primary-foreground grid size-4 place-items-center rounded text-[8px] leading-none font-semibold">
AI
</span>
<span className="text-muted-foreground font-mono text-[9.5px] tracking-widest uppercase">
CODEASSIST
</span>
</div>
<div className="text-foreground w-full text-sm leading-[1.9]">
{children ?? <span className="shimmer-text text-sm"> </span>}
</div>
</div>
)
}
@@ -8,6 +8,7 @@ import { useCreateSession } from "../api/snap.api"
import { Hero } from "../components/Hero"
import { Composer } from "../components/Composer"
import { Message } from "../components/Message"
import { ThinkingBubble } from "../components/ThinkingBubble"
import { useEscapeKey } from "../hooks/useEscapeKey"
import type { SnapImageInput } from "../contract/types"
@@ -58,14 +59,7 @@ export default function NewChatPage() {
// 세션 만드는 동안 — SessionChatPage 첫 화면과 같은 모양(내 말풍선 + 생각 중)
<div className="mx-auto flex min-h-0 w-full max-w-[760px] flex-1 flex-col gap-4 overflow-y-auto px-5 py-4">
<Message role="user" content={pending} />
<div className="flex flex-col items-start gap-1">
<span className="text-muted-foreground font-mono text-[9.5px] tracking-widest uppercase">
CODEASSIST
</span>
<div className="border-border bg-card text-card-foreground w-full rounded-lg border px-3 py-2 text-sm leading-[1.9]">
<span className="shimmer-text text-sm"> </span>
</div>
</div>
<ThinkingBubble />
</div>
)}
<Composer
@@ -11,6 +11,7 @@ import type { SnapImageInput, SnapSession } from "../contract/types"
import { ChatHeader } from "../components/ChatHeader"
import { NavRail } from "../components/NavRail"
import { Message } from "../components/Message"
import { ThinkingBubble } from "../components/ThinkingBubble"
import { Composer } from "../components/Composer"
export default function SessionChatPage() {
@@ -164,11 +165,8 @@ export default function SessionChatPage() {
busy && i === messages.length - 1 && m.role === "assistant" && !m.frozen
if (isLiveLast) {
return (
<div key={m.id} className="flex flex-col items-start gap-1">
<span className="text-muted-foreground font-mono text-[9.5px] tracking-widest uppercase">
CODEASSIST
</span>
<div className="border-border bg-card text-card-foreground w-full rounded-lg border px-3 py-2 text-sm leading-[1.9]">
<div key={m.id}>
<ThinkingBubble>
{m.content === "" ? (
// 첫 토큰 오기 전 빈 시간 메움 — 시머 텍스트로 "생각 중" 신호
<span className="shimmer-text text-sm"> </span>
@@ -180,7 +178,7 @@ export default function SessionChatPage() {
onRevealEnd={() => useSnapChatStore.getState().setRevealing(false)}
/>
)}
</div>
</ThinkingBubble>
</div>
)
}
@@ -207,16 +205,7 @@ export default function SessionChatPage() {
/>
)
})}
{generatingRemotely && (
<div className="flex flex-col items-start gap-1">
<span className="text-muted-foreground font-mono text-[9.5px] tracking-widest uppercase">
CODEASSIST
</span>
<div className="border-border bg-card text-card-foreground w-full rounded-lg border px-3 py-2 text-sm leading-[1.9]">
<span className="shimmer-text text-sm"> </span>
</div>
</div>
)}
{generatingRemotely && <ThinkingBubble />}
</div>
</div>
<Composer