diff --git a/ROADMAP.md b/ROADMAP.md index d8d05ab..a3d098f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -278,6 +278,79 @@ youlbot-webui/ --- +## UI/UX 디자인 개선 + +> 실제 UI 스크린샷 분석(2026-06-02) 기반 — 우선순위 순 정렬 + +### D0 — 즉시 수정 (노출 결함) + +| # | 위치 | 문제 | 개선 방안 | +|---|------|------|-----------| +| 1 | 대화 탭 · 입력창 | `"Textbox"` 레이블이 입력창 위에 그대로 노출 | `gr.Textbox(label="", show_label=False, ...)` 또는 `label=None` | +| 2 | 대화 탭 · 음성 영역 | `"마이크를 찾을 수 ..."` 오류 메시지가 항상 노출 | 마이크 미사용 시 해당 텍스트 숨김 처리, 또는 `visible=False` 기본값 | +| 3 | 전체 탭 · 푸터 | `"Gradio로 제작됨 🎉"` Gradio 브랜딩 노출 | `css="footer { display: none; }"` 추가 또는 `gr.Blocks(show_footer=False)` | +| 4 | 문서 등록 탭 · 결과 박스 | 업로드 전에도 빈 `결과` 박스가 항상 노출 | 초기 `visible=False`, 수집 완료 시에만 `visible=True` 반환 | + +### D1 — 1주일 내 (레이아웃 개선) + +| # | 위치 | 문제 | 개선 방안 | +|---|------|------|-----------| +| 5 | 대화 탭 · 채팅 영역 | 초기 화면이 빈 흰 공간으로 시작 — 어색한 첫 인상 | 웰컴 메시지 또는 예시 질문 버블(`gr.Examples`) 추가 | +| 6 | 대화 탭 · 이미지 첨부 | 이미지 업로드 영역이 항상 전체 크기로 펼쳐짐 | `gr.Accordion("이미지 첨부 (선택)", open=False)` 로 접이식 변경 | +| 7 | 대화 탭 · 하단 컨트롤 | `사고 과정 표시`, `TTS`, `대화 초기화` 배치가 불규칙 | `gr.Row`로 균등 3분할, `대화 초기화`는 오른쪽 정렬 | +| 8 | 대화 탭 · 입력 영역 | 텍스트 입력창과 `전송` 버튼이 시각적으로 분리됨 | 입력창 높이를 `lines=2`로 통일, 버튼 높이 CSS로 맞춤 | +| 9 | 문서 관리 탭 · 삭제 UX | 경로를 테이블에서 복사해 입력 필드에 붙여넣기 해야 함 | 테이블 행 클릭 → 입력 필드 자동 채움 (`select` 이벤트 활용) | +| 10 | 문서 등록 탭 · 버튼 | `문서 수집` 버튼이 전체 너비를 차지해 무게감 과도 | `scale=0` 또는 `min_width=200` 으로 적정 크기 조절 | + +### D2 — 2주일 내 (시각 품질) + +| # | 항목 | 설명 | +|---|------|------| +| 11 | 헤더 브랜딩 | 텍스트 전용 헤더 → 아이콘/로고 이미지 + 서브타이틀 레이아웃으로 개선 | +| 12 | 커스텀 테마 | Gradio 기본 보라색 → `gr.themes.Soft()` 또는 커스텀 `primary_hue` 색상 지정 | +| 13 | 사용자 선택 위치 | `사용자` 드롭다운이 채팅 위에 있어 흐름 방해 → 헤더 우측 또는 사이드바로 이동 | +| 14 | 채팅 버블 스타일 | Gradio 기본 스타일 → CSS로 사용자/봇 버블 배경색·radius 차별화 | +| 15 | 응답 로딩 표시 | 스트리밍 중 시각적 피드백 없음 → 입력 비활성화 + 스피너 CSS 추가 | +| 16 | 반응형 레이아웃 | 좁은 뷰포트에서 요소 겹침 → `gr.Column`/`gr.Row` 비율 재조정 | + +### D3 — 선택 사항 (장기) + +| # | 항목 | 설명 | +|---|------|------| +| 17 | 다크 모드 | `gr.themes.Base()` + CSS 변수로 다크/라이트 토글 지원 | +| 18 | 채팅 내보내기 | 대화 내용을 `.txt`/`.md`로 다운로드하는 버튼 추가 | +| 19 | 접근성 | `aria-label` 속성 추가, 키보드 포커스 표시 개선 | +| 20 | 온보딩 투어 | 첫 방문 사용자 대상 단계별 기능 안내 (JS 오버레이) | + +### D 체크리스트 + +#### D0 +- [x] `gr.Textbox(show_label=False)` — `"Textbox"` 레이블 제거 +- [x] 음성 오류 메시지 기본 숨김 처리 — `gr.Accordion("🎤 음성으로 질문하기", open=False)` 로 기본 접힘 +- [x] Gradio 푸터 CSS 숨김 — `footer { display: none !important; }` 추가 +- [x] `결과` 박스 초기 `visible=False` — `ingest_files`에서 `gr.update(visible=True)` 반환 + +#### D1 +- [ ] 웰컴 메시지 또는 예시 질문 버블 추가 +- [ ] 이미지 첨부 영역 `gr.Accordion`으로 접이식 변경 +- [ ] 하단 컨트롤 `gr.Row` 균등 배치 +- [ ] 문서 관리 탭 — 테이블 행 클릭 → 삭제 경로 자동 채움 +- [ ] `문서 수집` 버튼 크기 적정화 + +#### D2 +- [ ] 헤더 아이콘/로고 추가 +- [ ] `gr.themes.Soft()` 또는 커스텀 테마 적용 +- [ ] 사용자 드롭다운 위치 이동 +- [ ] 채팅 버블 커스텀 CSS +- [ ] 스트리밍 로딩 표시 + +#### D3 +- [ ] 다크 모드 토글 +- [ ] 채팅 내보내기 버튼 +- [ ] 접근성 개선 + +--- + ## 진행 체크리스트 ### P0 diff --git a/app.py b/app.py index c8015b9..0f4da1b 100644 --- a/app.py +++ b/app.py @@ -168,7 +168,7 @@ async def reset_chat(user_id): async def ingest_files(files): if not files: - return "파일을 선택해주세요." + return gr.update(value="파일을 선택해주세요.", visible=True) paths = [f if isinstance(f, str) else f.name for f in files] results = [] for path in paths: @@ -178,7 +178,7 @@ async def ingest_files(files): results.append(f"{name} → {result.get('chunks', '?')}개 청크") except Exception as e: results.append(f"{os.path.basename(path)} 오류: {e}") - return "\n".join(results) + return gr.update(value="\n".join(results), visible=True) async def list_docs(): @@ -262,7 +262,11 @@ def _sources_html(sources: list) -> str: ) -with gr.Blocks(title="율봇") as demo: +_CUSTOM_CSS = """ +footer { display: none !important; } +""" + +with gr.Blocks(title="율봇", css=_CUSTOM_CSS) as demo: gr.Markdown("# 율봇\n육아·금융 전문 AI 상담 도우미") user_state = gr.State(DEFAULT_USER) @@ -283,7 +287,7 @@ with gr.Blocks(title="율봇") as demo: with gr.Row(): msg_box = gr.Textbox( placeholder="질문을 입력하세요... (Enter로 전송)", - label="", + show_label=False, scale=5, autofocus=True, ) @@ -298,14 +302,15 @@ with gr.Blocks(title="율봇") as demo: ) gr.HTML("
", visible=False) # spacer - with gr.Row(): - audio_input = gr.Audio( - sources=["microphone"], - type="filepath", - label="음성으로 질문하기", - scale=4, - ) - transcribe_btn = gr.Button("음성 → 텍스트 변환", scale=1) + with gr.Accordion("🎤 음성으로 질문하기", open=False): + with gr.Row(): + audio_input = gr.Audio( + sources=["microphone"], + type="filepath", + show_label=False, + scale=4, + ) + transcribe_btn = gr.Button("음성 → 텍스트 변환", scale=1) with gr.Row(): show_thinking = gr.Checkbox(label="사고 과정 표시", value=True) @@ -346,7 +351,7 @@ with gr.Blocks(title="율봇") as demo: label="파일 선택", ) ingest_btn = gr.Button("문서 수집", variant="primary") - ingest_status = gr.Textbox(label="결과", interactive=False) + ingest_status = gr.Textbox(label="결과", interactive=False, visible=False) ingest_btn.click(ingest_files, inputs=[file_input], outputs=[ingest_status]) with gr.Tab("문서 관리"):