fix(chat): '생각하는 중'과 '작성 중'이 둘 다 보이던 것 — 상태줄 하나로 합치고 첫 글자 전/후로 라벨만 바뀜. 0.1.3

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
lee-hyeon-cheol
2026-09-22 10:47:25 +09:00
co-authored by Claude Fable 5.1
parent 69d4dd340f
commit 596c7e52a8
8 changed files with 19 additions and 21 deletions
@@ -1,7 +1,7 @@
import type { ReactNode } from "react" import type { ReactNode } from "react"
/** 답변 자리(첫 토큰 전·재진입 대기·세션 생성 중). Message 의 assistant 모양과 똑같이 — /** 답변 자리(첫 토큰 전·재진입 대기·세션 생성 중). Message 의 assistant 모양과 똑같이 —
* AI 배지 + CODEASSIST 라벨, 본문은 상자 없이. 안에 시머 텍스트나 스트리밍 본문을 넣는다. */ * AI 배지 + CODEASSIST 라벨, 본문은 상자 없이. "생각하는 중" 표시는 아래 WorkingRow 가 맡음. */
export function ThinkingBubble({ children }: { children?: ReactNode }) { export function ThinkingBubble({ children }: { children?: ReactNode }) {
return ( return (
<div className="flex flex-col items-start gap-1"> <div className="flex flex-col items-start gap-1">
@@ -13,9 +13,7 @@ export function ThinkingBubble({ children }: { children?: ReactNode }) {
CODEASSIST CODEASSIST
</span> </span>
</div> </div>
<div className="text-foreground w-full text-sm leading-[1.9]"> <div className="text-foreground w-full text-sm leading-[1.9]">{children}</div>
{children ?? <span className="shimmer-text text-sm"> </span>}
</div>
</div> </div>
) )
} }
@@ -66,8 +66,8 @@ export function ThinkingSteps({
) )
} }
/** 하단 "작성 중… · N초" — 스피너 + 경과 초. busy 가 true 인 동안만 그림. */ /** 하단 상태줄 — 스피너 + 경과 초. 답변 글자 오기 전엔 "생각하는 중", 오기 시작하면 "작성 중". */
export function WorkingRow({ busy }: { busy: boolean }) { export function WorkingRow({ busy, label = "작성 중…" }: { busy: boolean; label?: string }) {
const [elapsed, setElapsed] = useState(0) const [elapsed, setElapsed] = useState(0)
useEffect(() => { useEffect(() => {
if (!busy) return if (!busy) return
@@ -80,7 +80,7 @@ export function WorkingRow({ busy }: { busy: boolean }) {
return ( return (
<div className="text-muted-foreground flex items-center gap-2 text-[12.5px]"> <div className="text-muted-foreground flex items-center gap-2 text-[12.5px]">
<Loader2 size={14} className="animate-spin" /> <Loader2 size={14} className="animate-spin" />
· {elapsed} {label} · {elapsed}
</div> </div>
) )
} }
@@ -30,7 +30,7 @@ describe("NewChatPage 전송", () => {
mount() mount()
fireEvent.click(screen.getByText("MARA 에서 자재번호로 자재유형(MTART) 읽는 SELECT SINGLE")) fireEvent.click(screen.getByText("MARA 에서 자재번호로 자재유형(MTART) 읽는 SELECT SINGLE"))
expect(mutate).toHaveBeenCalledOnce() expect(mutate).toHaveBeenCalledOnce()
expect(await screen.findByText("생각하는 중…")).toBeTruthy() expect(await screen.findByText(/생각하는 중…/)).toBeTruthy()
expect(screen.getByText("MARA 에서 자재번호로 자재유형(MTART) 읽는 SELECT SINGLE")).toBeTruthy() expect(screen.getByText("MARA 에서 자재번호로 자재유형(MTART) 읽는 SELECT SINGLE")).toBeTruthy()
expect(screen.queryByText("무엇을 도와드릴까요?")).toBeNull() expect(screen.queryByText("무엇을 도와드릴까요?")).toBeNull()
act(() => lastCallbacks().onSuccess({ id: "ses_new" })) act(() => lastCallbacks().onSuccess({ id: "ses_new" }))
@@ -40,10 +40,10 @@ describe("NewChatPage 전송", () => {
it("세션 생성 실패면 헤로로 돌아온다", async () => { it("세션 생성 실패면 헤로로 돌아온다", async () => {
mount() mount()
fireEvent.click(screen.getByText("내부 테이블 LOOP 에서 그룹 소계 구하는 관용구")) fireEvent.click(screen.getByText("내부 테이블 LOOP 에서 그룹 소계 구하는 관용구"))
expect(await screen.findByText("생각하는 중…")).toBeTruthy() expect(await screen.findByText(/생각하는 중…/)).toBeTruthy()
act(() => lastCallbacks().onError()) act(() => lastCallbacks().onError())
expect(await screen.findByText("무엇을 도와드릴까요?")).toBeTruthy() expect(await screen.findByText("무엇을 도와드릴까요?")).toBeTruthy()
expect(screen.queryByText("생각하는 중…")).toBeNull() expect(screen.queryByText(/생각하는 중…/)).toBeNull()
}) })
it("생성 중엔 두 번째 전송을 무시한다", () => { it("생성 중엔 두 번째 전송을 무시한다", () => {
@@ -9,6 +9,7 @@ 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 { ThinkingBubble } from "../components/ThinkingBubble"
import { WorkingRow } from "../components/ThinkingSteps"
import { useEscapeKey } from "../hooks/useEscapeKey" import { useEscapeKey } from "../hooks/useEscapeKey"
import type { SnapImageInput } from "../contract/types" import type { SnapImageInput } from "../contract/types"
@@ -61,6 +62,7 @@ export default function NewChatPage() {
{/* eslint-disable-next-line jsx-a11y/aria-role -- Message 의 role 은 ARIA 아니고 화자 */} {/* eslint-disable-next-line jsx-a11y/aria-role -- Message 의 role 은 ARIA 아니고 화자 */}
<Message role="user" content={pending} /> <Message role="user" content={pending} />
<ThinkingBubble /> <ThinkingBubble />
<WorkingRow busy label="생각하는 중…" />
</div> </div>
)} )}
<Composer <Composer
@@ -174,12 +174,7 @@ export default function SessionChatPage() {
isDone={!isStreaming} isDone={!isStreaming}
answerStarted={m.content !== ""} answerStarted={m.content !== ""}
/> />
{m.content === "" ? ( {m.content !== "" && (
// 첫 토큰 오기 전 빈 시간 메움 — 과정도 아직 없으면 시머로 "생각 중" 신호
steps.length === 0 && (
<span className="shimmer-text text-sm"> </span>
)
) : (
<StreamingText <StreamingText
text={m.content} text={m.content}
isStreaming={isStreaming} isStreaming={isStreaming}
@@ -188,7 +183,10 @@ export default function SessionChatPage() {
/> />
)} )}
</ThinkingBubble> </ThinkingBubble>
<WorkingRow busy={isStreaming} /> <WorkingRow
busy={isStreaming}
label={m.content === "" ? "생각하는 중…" : "작성 중…"}
/>
</div> </div>
) )
} }
@@ -222,7 +220,7 @@ export default function SessionChatPage() {
{generatingRemotely && ( {generatingRemotely && (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<ThinkingBubble /> <ThinkingBubble />
<WorkingRow busy /> <WorkingRow busy label="생각하는 중…" />
</div> </div>
)} )}
</div> </div>
+1 -1
View File
@@ -485,7 +485,7 @@ dependencies = [
[[package]] [[package]]
name = "codeassist-tauri" name = "codeassist-tauri"
version = "0.1.1" version = "0.1.2"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"png 0.17.16", "png 0.17.16",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "codeassist-tauri" name = "codeassist-tauri"
version = "0.1.2" version = "0.1.3"
description = "CodeAssist 표준 Rust/Tauri 데스크톱 앱" description = "CodeAssist 표준 Rust/Tauri 데스크톱 앱"
authors = ["justdodev"] authors = ["justdodev"]
edition = "2021" edition = "2021"
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "CodeAssist", "productName": "CodeAssist",
"version": "0.1.2", "version": "0.1.3",
"identifier": "com.codeassist.app", "identifier": "com.codeassist.app",
"build": { "build": {
"beforeDevCommand": { "cwd": "../../2_frontend", "script": "npm run dev" }, "beforeDevCommand": { "cwd": "../../2_frontend", "script": "npm run dev" },