style(frontend): '생각하는 중' 자리를 답변 말풍선과 같은 모양으로 — ThinkingBubble
세 군데(새 대화 세션 생성 중 / 스트리밍 첫 토큰 전 / 재진입 대기)가 각자 손으로 그린 테두리 카드라 실제 답변(Message: AI 배지 + CODEASSIST + 상자 없는 본문)과 달랐음. 컴포넌트 하나로 통일. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
5998944f09
commit
833b834859
@@ -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 { Hero } from "../components/Hero"
|
||||||
import { Composer } from "../components/Composer"
|
import { Composer } from "../components/Composer"
|
||||||
import { Message } from "../components/Message"
|
import { Message } from "../components/Message"
|
||||||
|
import { ThinkingBubble } from "../components/ThinkingBubble"
|
||||||
import { useEscapeKey } from "../hooks/useEscapeKey"
|
import { useEscapeKey } from "../hooks/useEscapeKey"
|
||||||
import type { SnapImageInput } from "../contract/types"
|
import type { SnapImageInput } from "../contract/types"
|
||||||
|
|
||||||
@@ -58,14 +59,7 @@ export default function NewChatPage() {
|
|||||||
// 세션 만드는 동안 — SessionChatPage 첫 화면과 같은 모양(내 말풍선 + 생각 중)
|
// 세션 만드는 동안 — 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">
|
<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} />
|
<Message role="user" content={pending} />
|
||||||
<div className="flex flex-col items-start gap-1">
|
<ThinkingBubble />
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Composer
|
<Composer
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type { SnapImageInput, SnapSession } from "../contract/types"
|
|||||||
import { ChatHeader } from "../components/ChatHeader"
|
import { ChatHeader } from "../components/ChatHeader"
|
||||||
import { NavRail } from "../components/NavRail"
|
import { NavRail } from "../components/NavRail"
|
||||||
import { Message } from "../components/Message"
|
import { Message } from "../components/Message"
|
||||||
|
import { ThinkingBubble } from "../components/ThinkingBubble"
|
||||||
import { Composer } from "../components/Composer"
|
import { Composer } from "../components/Composer"
|
||||||
|
|
||||||
export default function SessionChatPage() {
|
export default function SessionChatPage() {
|
||||||
@@ -164,11 +165,8 @@ export default function SessionChatPage() {
|
|||||||
busy && i === messages.length - 1 && m.role === "assistant" && !m.frozen
|
busy && i === messages.length - 1 && m.role === "assistant" && !m.frozen
|
||||||
if (isLiveLast) {
|
if (isLiveLast) {
|
||||||
return (
|
return (
|
||||||
<div key={m.id} className="flex flex-col items-start gap-1">
|
<div key={m.id}>
|
||||||
<span className="text-muted-foreground font-mono text-[9.5px] tracking-widest uppercase">
|
<ThinkingBubble>
|
||||||
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]">
|
|
||||||
{m.content === "" ? (
|
{m.content === "" ? (
|
||||||
// 첫 토큰 오기 전 빈 시간 메움 — 시머 텍스트로 "생각 중" 신호
|
// 첫 토큰 오기 전 빈 시간 메움 — 시머 텍스트로 "생각 중" 신호
|
||||||
<span className="shimmer-text text-sm">생각하는 중…</span>
|
<span className="shimmer-text text-sm">생각하는 중…</span>
|
||||||
@@ -180,7 +178,7 @@ export default function SessionChatPage() {
|
|||||||
onRevealEnd={() => useSnapChatStore.getState().setRevealing(false)}
|
onRevealEnd={() => useSnapChatStore.getState().setRevealing(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</ThinkingBubble>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -207,16 +205,7 @@ export default function SessionChatPage() {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
{generatingRemotely && (
|
{generatingRemotely && <ThinkingBubble />}
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Composer
|
<Composer
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"root":["./src/app.tsx","./src/main.tsx","./src/routes.tsx","./src/vite-env.d.ts","./src/config/env.ts","./src/config/routes.ts","./src/features/admin/api/admin.api.ts","./src/features/admin/pages/adminstatspage.tsx","./src/features/auth/index.ts","./src/features/auth/api/auth.api.ts","./src/features/auth/components/entraloginbutton.tsx","./src/features/auth/components/entraloginsection.tsx","./src/features/auth/components/loginform.tsx","./src/features/auth/components/sessionexpirydialog.tsx","./src/features/auth/hooks/useentraenabled.ts","./src/features/auth/hooks/useentralogin.ts","./src/features/auth/hooks/uselogin.ts","./src/features/auth/hooks/uselogout.ts","./src/features/auth/hooks/useme.ts","./src/features/auth/hooks/useslidingrefresh.ts","./src/features/auth/pages/loginpage.tsx","./src/features/auth/schemas/index.ts","./src/features/auth/store/authstore.ts","./src/features/auth/store/sessionexpirystore.ts","./src/features/auth/utils/saferedirectpath.ts","./src/features/snap/api/snap.api.ts","./src/features/snap/api/snap.stream.ts","./src/features/snap/components/chatheader.tsx","./src/features/snap/components/clipboardhistory.tsx","./src/features/snap/components/codeblock.tsx","./src/features/snap/components/composer.tsx","./src/features/snap/components/hero.tsx","./src/features/snap/components/message.tsx","./src/features/snap/components/navrail.tsx","./src/features/snap/components/searchhitcard.tsx","./src/features/snap/components/sessioncard.tsx","./src/features/snap/components/sessionsearch.tsx","./src/features/snap/components/snaplayout.tsx","./src/features/snap/components/snapusercontrols.tsx","./src/features/snap/components/abaphljs.ts","./src/features/snap/contract/types.ts","./src/features/snap/hooks/useescapekey.ts","./src/features/snap/hooks/usesnapchat.ts","./src/features/snap/lib/format.ts","./src/features/snap/pages/newchatpage.tsx","./src/features/snap/pages/sessionchatpage.tsx","./src/features/snap/pages/sessionlistpage.tsx","./src/features/snap/store/snapchatstore.ts","./src/features/snippets/types.ts","./src/features/snippets/api/snippets.api.ts","./src/features/snippets/components/categorychips.tsx","./src/features/snippets/components/editdialog.tsx","./src/features/snippets/components/previewpane.tsx","./src/features/snippets/components/snippetrow.tsx","./src/features/snippets/core/ranking.ts","./src/features/snippets/core/search.ts","./src/features/snippets/hooks/usesnippets.ts","./src/features/snippets/pages/snippetpalettepage.tsx","./src/lib/api/client.ts","./src/lib/api/errors.ts","./src/lib/auth/msal.ts","./src/lib/auth/tokenprovider.ts","./src/lib/bridge/bridgenavigate.ts","./src/lib/bridge/snippetbridge.ts","./src/lib/bridge/transport.ts","./src/lib/bridge/webviewbridge.ts","./src/lib/hooks/usedebounce.ts","./src/lib/query/queryclient.ts","./src/lib/streaming/stoppednotice.tsx","./src/lib/streaming/streamingtext.tsx","./src/lib/streaming/abort.ts","./src/lib/streaming/index.ts","./src/lib/streaming/sse.ts","./src/lib/streaming/streamllm.ts","./src/lib/streaming/usesmoothedtext.ts","./src/lib/streaming/usestreamsession.ts","./src/lib/utils/cn.ts","./src/lib/utils/randomid.ts","./src/lib/utils/relativetime.ts","./src/lib/utils/sleep.ts","./src/shared/components/centerspinner.tsx","./src/shared/components/desktopwindowframe.tsx","./src/shared/components/errorboundary.tsx","./src/shared/components/kbd.tsx","./src/shared/components/layout.tsx","./src/shared/components/logomark.tsx","./src/shared/components/paletteshell.tsx","./src/shared/components/pastetargetbadge.tsx","./src/shared/components/protectedroute.tsx","./src/shared/components/themetoggle.tsx","./src/shared/hooks/usepastetarget.ts","./src/shared/store/themestore.ts","./src/shared/theme/derive.ts","./src/shared/theme/palettes.ts","./src/shared/theme/usetheme.ts","./src/shared/ui/alert-dialog.tsx","./src/shared/ui/alert.tsx","./src/shared/ui/button.tsx","./src/shared/ui/dialog.tsx","./src/shared/ui/dropdown-menu.tsx","./src/shared/ui/input.tsx","./src/shared/ui/label.tsx","./src/shared/ui/separator.tsx","./src/shared/ui/skeleton.tsx","./src/shared/ui/textarea.tsx","./src/shared/ui/tooltip.tsx","./src/types/api.ts"],"version":"5.9.3"}
|
{"root":["./src/app.tsx","./src/main.tsx","./src/routes.tsx","./src/vite-env.d.ts","./src/config/env.ts","./src/config/routes.ts","./src/features/admin/api/admin.api.ts","./src/features/admin/pages/adminstatspage.tsx","./src/features/auth/index.ts","./src/features/auth/api/auth.api.ts","./src/features/auth/components/entraloginbutton.tsx","./src/features/auth/components/entraloginsection.tsx","./src/features/auth/components/loginform.tsx","./src/features/auth/components/sessionexpirydialog.tsx","./src/features/auth/hooks/useentraenabled.ts","./src/features/auth/hooks/useentralogin.ts","./src/features/auth/hooks/uselogin.ts","./src/features/auth/hooks/uselogout.ts","./src/features/auth/hooks/useme.ts","./src/features/auth/hooks/useslidingrefresh.ts","./src/features/auth/pages/loginpage.tsx","./src/features/auth/schemas/index.ts","./src/features/auth/store/authstore.ts","./src/features/auth/store/sessionexpirystore.ts","./src/features/auth/utils/saferedirectpath.ts","./src/features/snap/api/snap.api.ts","./src/features/snap/api/snap.stream.ts","./src/features/snap/components/chatheader.tsx","./src/features/snap/components/clipboardhistory.tsx","./src/features/snap/components/codeblock.tsx","./src/features/snap/components/composer.tsx","./src/features/snap/components/hero.tsx","./src/features/snap/components/message.tsx","./src/features/snap/components/navrail.tsx","./src/features/snap/components/searchhitcard.tsx","./src/features/snap/components/sessioncard.tsx","./src/features/snap/components/sessionsearch.tsx","./src/features/snap/components/snaplayout.tsx","./src/features/snap/components/snapusercontrols.tsx","./src/features/snap/components/thinkingbubble.tsx","./src/features/snap/components/abaphljs.ts","./src/features/snap/contract/types.ts","./src/features/snap/hooks/useescapekey.ts","./src/features/snap/hooks/usesnapchat.ts","./src/features/snap/lib/format.ts","./src/features/snap/pages/newchatpage.tsx","./src/features/snap/pages/sessionchatpage.tsx","./src/features/snap/pages/sessionlistpage.tsx","./src/features/snap/store/snapchatstore.ts","./src/features/snippets/types.ts","./src/features/snippets/api/snippets.api.ts","./src/features/snippets/components/categorychips.tsx","./src/features/snippets/components/editdialog.tsx","./src/features/snippets/components/previewpane.tsx","./src/features/snippets/components/snippetrow.tsx","./src/features/snippets/core/ranking.ts","./src/features/snippets/core/search.ts","./src/features/snippets/hooks/usesnippets.ts","./src/features/snippets/pages/snippetpalettepage.tsx","./src/lib/api/client.ts","./src/lib/api/errors.ts","./src/lib/auth/msal.ts","./src/lib/auth/tokenprovider.ts","./src/lib/bridge/bridgenavigate.ts","./src/lib/bridge/snippetbridge.ts","./src/lib/bridge/transport.ts","./src/lib/bridge/webviewbridge.ts","./src/lib/hooks/usedebounce.ts","./src/lib/query/queryclient.ts","./src/lib/streaming/stoppednotice.tsx","./src/lib/streaming/streamingtext.tsx","./src/lib/streaming/abort.ts","./src/lib/streaming/index.ts","./src/lib/streaming/sse.ts","./src/lib/streaming/streamllm.ts","./src/lib/streaming/usesmoothedtext.ts","./src/lib/streaming/usestreamsession.ts","./src/lib/utils/cn.ts","./src/lib/utils/randomid.ts","./src/lib/utils/relativetime.ts","./src/lib/utils/sleep.ts","./src/shared/components/centerspinner.tsx","./src/shared/components/desktopwindowframe.tsx","./src/shared/components/errorboundary.tsx","./src/shared/components/kbd.tsx","./src/shared/components/layout.tsx","./src/shared/components/logomark.tsx","./src/shared/components/paletteshell.tsx","./src/shared/components/pastetargetbadge.tsx","./src/shared/components/protectedroute.tsx","./src/shared/components/themetoggle.tsx","./src/shared/hooks/usepastetarget.ts","./src/shared/store/themestore.ts","./src/shared/theme/derive.ts","./src/shared/theme/palettes.ts","./src/shared/theme/usetheme.ts","./src/shared/ui/alert-dialog.tsx","./src/shared/ui/alert.tsx","./src/shared/ui/button.tsx","./src/shared/ui/dialog.tsx","./src/shared/ui/dropdown-menu.tsx","./src/shared/ui/input.tsx","./src/shared/ui/label.tsx","./src/shared/ui/separator.tsx","./src/shared/ui/skeleton.tsx","./src/shared/ui/textarea.tsx","./src/shared/ui/tooltip.tsx","./src/types/api.ts"],"version":"5.9.3"}
|
||||||
@@ -17,3 +17,4 @@
|
|||||||
| 14:58 | 팀원용 가이드 — ABAP_INDEXING 을 FabriX 로 돌리기. 직접 붙이면 401(헤더 3종)이라 CodeAssist 게이트웨이(/api/ito) 경유, LLM_MODEL=339, 확인 항목 3개 |
|
| 14:58 | 팀원용 가이드 — ABAP_INDEXING 을 FabriX 로 돌리기. 직접 붙이면 401(헤더 3종)이라 CodeAssist 게이트웨이(/api/ito) 경유, LLM_MODEL=339, 확인 항목 3개 |
|
||||||
| 15:21 | taste-skill 설치·적용 — 랜딩용이라 대부분 해당 없고 3개만: 삼성 팔레트 글씨 순검정→#111, radius 규칙(버튼 알약/카드·입력 10px), 버튼 눌림 피드백 |
|
| 15:21 | taste-skill 설치·적용 — 랜딩용이라 대부분 해당 없고 3개만: 삼성 팔레트 글씨 순검정→#111, radius 규칙(버튼 알약/카드·입력 10px), 버튼 눌림 피드백 |
|
||||||
| 15:28 | 새 대화 전송 시 세션 생성(OpenCode 왕복) 동안 헤로에 머물던 것 — 즉시 내 말풍선+'생각하는 중' 시머로 바꿔 넘어간 느낌, 실패하면 헤로 복귀+토스트(전엔 pending 안 풀려 영원히 '응답 중'). mutate 콜백 패턴으로 |
|
| 15:28 | 새 대화 전송 시 세션 생성(OpenCode 왕복) 동안 헤로에 머물던 것 — 즉시 내 말풍선+'생각하는 중' 시머로 바꿔 넘어간 느낌, 실패하면 헤로 복귀+토스트(전엔 pending 안 풀려 영원히 '응답 중'). mutate 콜백 패턴으로 |
|
||||||
|
| 15:34 | '생각하는 중' 자리가 답변(Message)과 다른 모양(테두리 카드, AI 배지 없음)이던 것 — ThinkingBubble 로 통일. 세 군데(새 대화·스트리밍 첫 토큰 전·재진입 대기) |
|
||||||
|
|||||||
Reference in New Issue
Block a user