From c1a28bfdccacf724a6713d7c68381f54b6adb887 Mon Sep 17 00:00:00 2001 From: shinalok Date: Tue, 2 Jun 2026 14:28:21 +0900 Subject: [PATCH] UI/UX D1: examples, image accordion, layout improvements, table select, button size - app.py: add gr.Examples with 3 sample questions (D1-5) - app.py: wrap image input in gr.Accordion(open=False) (D1-6) - app.py: checkboxes left Column(scale=3), reset btn right Column(scale=1) (D1-7) - app.py: msg_box lines=2, send_btn .send-btn CSS min-height 80px (D1-8) - app.py: doc_table.select -> select_doc_row auto-fills delete_source (D1-9) - app.py: ingest_btn scale=0 min_width=200 to avoid full-width stretch (D1-10) - ROADMAP.md: mark all D1 items complete Co-Authored-By: Claude Sonnet 4.6 --- ROADMAP.md | 11 ++++++----- app.py | 43 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index a3d098f..9959159 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -331,11 +331,12 @@ youlbot-webui/ - [x] `결과` 박스 초기 `visible=False` — `ingest_files`에서 `gr.update(visible=True)` 반환 #### D1 -- [ ] 웰컴 메시지 또는 예시 질문 버블 추가 -- [ ] 이미지 첨부 영역 `gr.Accordion`으로 접이식 변경 -- [ ] 하단 컨트롤 `gr.Row` 균등 배치 -- [ ] 문서 관리 탭 — 테이블 행 클릭 → 삭제 경로 자동 채움 -- [ ] `문서 수집` 버튼 크기 적정화 +- [x] 웰컴 메시지 또는 예시 질문 버블 추가 — `gr.Examples` 3개 예시 질문 +- [x] 이미지 첨부 영역 `gr.Accordion`으로 접이식 변경 — `open=False` 기본 접힘 +- [x] 하단 컨트롤 `gr.Row` 균등 배치 — 체크박스 좌측 `Column(scale=3)`, 초기화 버튼 우측 `Column(scale=1)` +- [x] 입력창 `lines=2` + 전송 버튼 높이 CSS 맞춤 — `.send-btn { min-height: 80px }` +- [x] 문서 관리 탭 — 테이블 행 클릭 → 삭제 경로 자동 채움 — `doc_table.select` + `select_doc_row` +- [x] `문서 수집` 버튼 크기 적정화 — `scale=0, min_width=200` #### D2 - [ ] 헤더 아이콘/로고 추가 diff --git a/app.py b/app.py index 0f4da1b..acc96ed 100644 --- a/app.py +++ b/app.py @@ -189,6 +189,16 @@ async def list_docs(): return [[f"오류: {e}", ""]] +def select_doc_row(evt: gr.SelectData, doc_data): + row = evt.index[0] + try: + if hasattr(doc_data, "iloc"): + return str(doc_data.iloc[row, 1]) + return str(doc_data[row][1]) + except Exception: + return gr.update() + + async def delete_doc(source): if not source.strip(): return "삭제할 파일 경로를 입력하세요.", await list_docs() @@ -264,6 +274,7 @@ def _sources_html(sources: list) -> str: _CUSTOM_CSS = """ footer { display: none !important; } +.send-btn { min-height: 80px !important; align-self: stretch !important; } """ with gr.Blocks(title="율봇", css=_CUSTOM_CSS) as demo: @@ -288,19 +299,28 @@ with gr.Blocks(title="율봇", css=_CUSTOM_CSS) as demo: msg_box = gr.Textbox( placeholder="질문을 입력하세요... (Enter로 전송)", show_label=False, + lines=2, scale=5, autofocus=True, ) - send_btn = gr.Button("전송", variant="primary", scale=1) - with gr.Row(): + send_btn = gr.Button("전송", variant="primary", scale=1, elem_classes=["send-btn"]) + gr.Examples( + examples=[ + ["육아휴직 급여 신청 방법을 알려주세요"], + ["어린이집 입소 대기 기간은 얼마나 걸리나요?"], + ["아이 의료비 세금 공제는 어떻게 하나요?"], + ], + inputs=[msg_box], + label="💡 예시 질문", + ) + + with gr.Accordion("📷 이미지 첨부 (선택)", open=False): image_input = gr.Image( type="filepath", - label="📷 이미지 첨부 (선택)", + show_label=False, sources=["upload", "clipboard"], height=160, - scale=1, ) - gr.HTML("
", visible=False) # spacer with gr.Accordion("🎤 음성으로 질문하기", open=False): with gr.Row(): @@ -313,9 +333,12 @@ with gr.Blocks(title="율봇", css=_CUSTOM_CSS) as demo: transcribe_btn = gr.Button("음성 → 텍스트 변환", scale=1) with gr.Row(): - show_thinking = gr.Checkbox(label="사고 과정 표시", value=True) - use_tts = gr.Checkbox(label="음성으로 답변 읽기 (TTS)", value=False) - reset_btn = gr.Button("대화 초기화", size="sm") + with gr.Column(scale=3): + with gr.Row(): + show_thinking = gr.Checkbox(label="사고 과정 표시", value=True) + use_tts = gr.Checkbox(label="음성으로 답변 읽기 (TTS)", value=False) + with gr.Column(scale=1, min_width=120): + reset_btn = gr.Button("대화 초기화", size="sm") tts_output = gr.Audio(label="음성 답변", autoplay=True, visible=False) use_tts.change(lambda v: gr.Audio(visible=v), inputs=[use_tts], outputs=[tts_output]) @@ -350,7 +373,8 @@ with gr.Blocks(title="율봇", css=_CUSTOM_CSS) as demo: file_count="multiple", label="파일 선택", ) - ingest_btn = gr.Button("문서 수집", variant="primary") + with gr.Row(): + ingest_btn = gr.Button("문서 수집", variant="primary", scale=0, min_width=200) ingest_status = gr.Textbox(label="결과", interactive=False, visible=False) ingest_btn.click(ingest_files, inputs=[file_input], outputs=[ingest_status]) @@ -374,6 +398,7 @@ with gr.Blocks(title="율봇", css=_CUSTOM_CSS) as demo: refresh_btn.click(list_docs, outputs=[doc_table]) delete_btn.click(delete_doc, inputs=[delete_source], outputs=[delete_status, doc_table]) + doc_table.select(select_doc_row, inputs=[doc_table], outputs=[delete_source]) demo.load(list_docs, outputs=[doc_table])