From f946b3155a6c5c821a001b5516c6b06e6eefb79c Mon Sep 17 00:00:00 2001 From: jmj Date: Thu, 4 Jun 2026 14:16:35 +0900 Subject: [PATCH] =?UTF-8?q?feat(interview):=20=EB=9D=BC=EC=9D=B4=EB=B8=8C?= =?UTF-8?q?=20=EC=8A=A4=ED=85=8C=EC=9D=B4=EC=A7=80=EC=97=90=20=EB=A9=B4?= =?UTF-8?q?=EC=A0=91=EA=B4=80=20=EC=95=84=EB=B0=94=ED=83=80=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 면접 창이 텍스트만 노출돼 면접관 부재감이 있어, 스테이지 상단에 면접관 아바타를 상시 노출. /interviewer.png 사용, 없으면 사람 아이콘 폴백. thinking 상태는 펄스 링, asking 상태는 primary 링으로 표현. Co-Authored-By: Claude Opus 4.8 --- .../interview/ui/live/InterviewStage.tsx | 4 +- .../ui/live/InterviewerAvatar.test.tsx | 17 +++++++ .../interview/ui/live/InterviewerAvatar.tsx | 51 +++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 frontend/src/features/interview/ui/live/InterviewerAvatar.test.tsx create mode 100644 frontend/src/features/interview/ui/live/InterviewerAvatar.tsx diff --git a/frontend/src/features/interview/ui/live/InterviewStage.tsx b/frontend/src/features/interview/ui/live/InterviewStage.tsx index 317a1cdd..d34f7cac 100644 --- a/frontend/src/features/interview/ui/live/InterviewStage.tsx +++ b/frontend/src/features/interview/ui/live/InterviewStage.tsx @@ -7,6 +7,7 @@ import type { ConnectionStatus, ThreadItem } from '../../model/useLiveInterview' import { ConnectionBanner } from './ConnectionBanner' import { AnswerComposer } from './AnswerComposer' import { StageQuestion } from './StageQuestion' +import { InterviewerAvatar } from './InterviewerAvatar' import { TranscriptDrawer } from './TranscriptDrawer' const BG = '/interview-session-background.png' @@ -108,7 +109,8 @@ export function InterviewStage({ -
+
+ {awaitingQuestion || !currentQuestion ? ( ) : ( diff --git a/frontend/src/features/interview/ui/live/InterviewerAvatar.test.tsx b/frontend/src/features/interview/ui/live/InterviewerAvatar.test.tsx new file mode 100644 index 00000000..a3aba09a --- /dev/null +++ b/frontend/src/features/interview/ui/live/InterviewerAvatar.test.tsx @@ -0,0 +1,17 @@ +import { describe, it, expect } from 'vitest' +import { render, screen, fireEvent } from '@testing-library/react' +import { InterviewerAvatar } from './InterviewerAvatar' + +describe('InterviewerAvatar', () => { + it('기본적으로 면접관 이미지를 렌더한다', () => { + render() + expect(screen.getByAltText('면접관')).toBeInTheDocument() + }) + + it('이미지 로드 실패 시 사람 아이콘 폴백을 보여준다', () => { + render() + fireEvent.error(screen.getByAltText('면접관')) + expect(screen.queryByAltText('면접관')).not.toBeInTheDocument() + expect(screen.getByRole('img', { name: '면접관' })).toBeInTheDocument() + }) +}) diff --git a/frontend/src/features/interview/ui/live/InterviewerAvatar.tsx b/frontend/src/features/interview/ui/live/InterviewerAvatar.tsx new file mode 100644 index 00000000..df716b59 --- /dev/null +++ b/frontend/src/features/interview/ui/live/InterviewerAvatar.tsx @@ -0,0 +1,51 @@ +import { useState } from 'react' + +export type InterviewerState = 'idle' | 'thinking' | 'asking' + +// public/interviewer.png 가 있으면 사용, 없으면 onError 로 사람 아이콘 폴백. +const SRC = '/interviewer.png' + +function FallbackFace() { + return ( + + + + + + ) +} + +// 라이브 면접 스테이지 상단에서 "사람이 묻는" 존재감을 주는 면접관 아바타. +// 캡션(면접관/카테고리)은 아래 질문 카드가 이미 보여주므로 여기서는 시각 요소만 담당한다. +export function InterviewerAvatar({ state }: { state: InterviewerState }) { + const [imgFailed, setImgFailed] = useState(false) + + return ( +
+ {state === 'thinking' && ( + + )} +
+ {imgFailed ? ( + + ) : ( + 면접관 setImgFailed(true)} + /> + )} +
+
+ ) +}