# 🎨 ν”„λ‘ νŠΈμ—”λ“œ 파일 ꡬ쑰 λ¬Έμ„œ ## πŸ“ 디렉토리 ꡬ쑰 ``` audio_validation_app/ β”œβ”€β”€ app.py # κΈ°μ‘΄ 파일 (λ°±μ—…μš©) β”œβ”€β”€ app_new.py # μƒˆλ‘œμš΄ 메인 μ•± (μ»΄ν¬λ„ŒνŠΈ 뢄리 버전) β”‚ β”œβ”€β”€ frontend/ # πŸ†• ν”„λ‘ νŠΈμ—”λ“œ 디렉토리 β”‚ β”œβ”€β”€ __init__.py β”‚ β”‚ β”‚ β”œβ”€β”€ app_ui.py # UI 쑰립 메인 파일 β”‚ β”‚ β”‚ β”œβ”€β”€ components/ # UI μ»΄ν¬λ„ŒνŠΈ β”‚ β”‚ β”œβ”€β”€ __init__.py β”‚ β”‚ β”œβ”€β”€ header.py # πŸ‘¨β€πŸ’» 개발자 A β”‚ β”‚ β”œβ”€β”€ audio_input.py # πŸ‘¨β€πŸ’» 개발자 A β”‚ β”‚ β”œβ”€β”€ history_display.py # πŸ‘¨β€πŸ’» 개발자 B β”‚ β”‚ β”œβ”€β”€ failure_modal.py # πŸ‘¨β€πŸ’» 개발자 B β”‚ β”‚ └── success_screen.py # πŸ‘¨β€πŸ’» 개발자 A β”‚ β”‚ β”‚ └── styles/ # CSS μŠ€νƒ€μΌ β”‚ └── custom_css.py β”‚ β”œβ”€β”€ components/ # λ°±μ—”λ“œ 둜직 (κΈ°μ‘΄) β”‚ β”œβ”€β”€ audio_validator.py β”‚ └── ui_renderer.py β”‚ β”œβ”€β”€ utils/ # μœ ν‹Έλ¦¬ν‹° (κΈ°μ‘΄) β”‚ β”œβ”€β”€ stt_handler.py β”‚ └── state_manager.py β”‚ └── config/ # μ„€μ • (κΈ°μ‘΄) └── settings.py ``` --- ## πŸ‘₯ νŒ€ λΆ„λ‹΄ ### πŸ‘¨β€πŸ’» 개발자 A: μž…λ ₯ κ΄€λ ¨ μ»΄ν¬λ„ŒνŠΈ - `frontend/components/header.py` - 헀더 및 λ‚œμ΄λ„ μ„€μ • - `frontend/components/audio_input.py` - μ˜€λ””μ˜€ μž…λ ₯ 및 검증 λ²„νŠΌ - `frontend/components/success_screen.py` - 성곡 ν™”λ©΄ ### πŸ‘¨β€πŸ’» 개발자 B: ν”Όλ“œλ°± κ΄€λ ¨ μ»΄ν¬λ„ŒνŠΈ - `frontend/components/history_display.py` - μ‹€νŒ¨ 기둝 ν‘œμ‹œ - `frontend/components/failure_modal.py` - μ‹€νŒ¨ νŒμ—… λͺ¨λ‹¬ ### 🀝 곡톡 μž‘μ—… - `frontend/styles/custom_css.py` - CSS μŠ€νƒ€μΌ (ν•„μš” μ‹œ 각자 μΆ”κ°€) - `frontend/app_ui.py` - UI 쑰립 (μ΅œμ’… 톡합 μ‹œ μˆ˜μ •) --- ## πŸ“„ νŒŒμΌλ³„ 상세 μ„€λͺ… ### 1. `frontend/app_ui.py` (UI 쑰립 메인) **μ—­ν• **: λͺ¨λ“  μ»΄ν¬λ„ŒνŠΈλ₯Ό importν•˜κ³  μ—°κ²° **μ£Όμš” 클래슀**: `AppUI` ```python # μ‚¬μš© μ˜ˆμ‹œ ui_builder = AppUI(validator, ui_renderer) components = ui_builder.build() # UI λΉŒλ“œ ui_builder.setup_events(components, handlers) # 이벀트 μ—°κ²° ``` **μ£Όμš” λ©”μ„œλ“œ**: - `build()`: 전체 UI ꡬ성 - `setup_events()`: 이벀트 바인딩 --- ### 2. `frontend/components/header.py` (πŸ‘¨β€πŸ’» 개발자 A) **μ—­ν• **: μ•± 타이틀, λ‚œμ΄λ„ μŠ¬λΌμ΄λ”, λͺ©ν‘œ λ¬Έμž₯ ν‘œμ‹œ **μ£Όμš” 클래슀**: `HeaderComponent` **λ Œλ”λ§ μ»΄ν¬λ„ŒνŠΈ**: - 타이틀: "πŸŽ™οΈ μŒμ„± 검증 μ‹œμŠ€ν…œ" - λ‚œμ΄λ„ μŠ¬λΌμ΄λ” (1-5) - ν˜„μž¬ λͺ©ν‘œ λ¬Έμž₯ ν…μŠ€νŠΈλ°•μŠ€ **이벀트**: - λ‚œμ΄λ„ λ³€κ²½ μ‹œ β†’ λͺ©ν‘œ λ¬Έμž₯ μ—…λ°μ΄νŠΈ **μˆ˜μ • κ°€λŠ₯ μ˜μ—­**: - 타이틀 μŠ€νƒ€μΌ λ³€κ²½ - μŠ¬λΌμ΄λ” λ²”μœ„/λ ˆμ΄λΈ” μˆ˜μ • - μ„€μ • Accordion 내뢀에 μΆ”κ°€ μ˜΅μ…˜ μΆ”κ°€ --- ### 3. `frontend/components/audio_input.py` (πŸ‘¨β€πŸ’» 개발자 A) **μ—­ν• **: μŒμ„± λ…ΉμŒ/μ—…λ‘œλ“œ 및 검증 λ²„νŠΌ **μ£Όμš” 클래슀**: `AudioInputComponent` **λ Œλ”λ§ μ»΄ν¬λ„ŒνŠΈ**: - λͺ©ν‘œ λ¬Έμž₯ ν‘œμ‹œ (Markdown) - μ˜€λ””μ˜€ μž…λ ₯ μœ„μ ― (λ…ΉμŒ + μ—…λ‘œλ“œ) - 검증 λ²„νŠΌ **이벀트**: - 검증 λ²„νŠΌ 클릭 β†’ `validate_audio_handler` 호좜 **μˆ˜μ • κ°€λŠ₯ μ˜μ—­**: - λ²„νŠΌ ν…μŠ€νŠΈ/μŠ€νƒ€μΌ - μ˜€λ””μ˜€ μž…λ ₯ μ„€μ • (μƒ˜ν”Œλ ˆμ΄νŠΈ λ“±) - λͺ©ν‘œ λ¬Έμž₯ ν‘œμ‹œ μŠ€νƒ€μΌ --- ### 4. `frontend/components/history_display.py` (πŸ‘¨β€πŸ’» 개발자 B) **μ—­ν• **: μ‹€νŒ¨ 기둝을 HTML둜 ν‘œμ‹œ **μ£Όμš” 클래슀**: `HistoryDisplayComponent` **λ Œλ”λ§ μ»΄ν¬λ„ŒνŠΈ**: - μ‹€νŒ¨ 기둝 HTML μ˜μ—­ **데이터 흐름**: - `ui_renderer.render_failure_history()` β†’ HTML 생성 - 검증 μ‹€νŒ¨ μ‹œ μžλ™ μ—…λ°μ΄νŠΈ **μˆ˜μ • κ°€λŠ₯ μ˜μ—­**: - 기둝 ν‘œμ‹œ λ ˆμ΄μ•„μ›ƒ - μ„Ήμ…˜ 타이틀 λ³€κ²½ - μΆ”κ°€ 톡계 정보 ν‘œμ‹œ --- ### 5. `frontend/components/failure_modal.py` (πŸ‘¨β€πŸ’» 개발자 B) **μ—­ν• **: μ‹€νŒ¨ μ‹œ νŒμ—… λͺ¨λ‹¬λ‘œ κ²°κ³Ό ν‘œμ‹œ **μ£Όμš” 클래슀**: `FailureModalComponent` **λ Œλ”λ§ μ»΄ν¬λ„ŒνŠΈ**: - Modal μ»¨ν…Œμ΄λ„ˆ - HTML μ½˜ν…μΈ  μ˜μ—­ - λ‹«κΈ° λ²„νŠΌ **정적 λ©”μ„œλ“œ**: - `create_modal_content(recognized_text, score, hint)`: λͺ¨λ‹¬ HTML 생성 **이벀트**: - λ‹«κΈ° λ²„νŠΌ 클릭 β†’ λͺ¨λ‹¬ μˆ¨κΉ€ **μˆ˜μ • κ°€λŠ₯ μ˜μ—­**: - λͺ¨λ‹¬ HTML ν…œν”Œλ¦Ώ (색상, λ ˆμ΄μ•„μ›ƒ) - λ²„νŠΌ μŠ€νƒ€μΌ - μ• λ‹ˆλ©”μ΄μ…˜ 효과 μΆ”κ°€ **λͺ¨λ‹¬ HTML ꡬ쑰**: ```html

❌ μ‹€νŒ¨!

μΈμ‹λœ ν…μŠ€νŠΈ: ...
점수: ...점
πŸ’‘ 힌트: ...
``` --- ### 6. `frontend/components/success_screen.py` (πŸ‘¨β€πŸ’» 개발자 B) **μ—­ν• **: 성곡 μ‹œ 전체 ν™”λ©΄ μ „ν™˜ **μ£Όμš” 클래슀**: `SuccessScreenComponent` **λ Œλ”λ§ μ»΄ν¬λ„ŒνŠΈ**: - 성곡 ν™”λ©΄ μ»¨ν…Œμ΄λ„ˆ (`elem_id="success-screen"`) - μΆ•ν•˜ λ©”μ‹œμ§€ (Markdown) - μ²˜μŒλΆ€ν„° λ‹€μ‹œ λ²„νŠΌ **이벀트**: - μ²˜μŒλΆ€ν„° λ‹€μ‹œ λ²„νŠΌ β†’ `window.location.reload()` (JavaScript) **μˆ˜μ • κ°€λŠ₯ μ˜μ—­**: - μΆ•ν•˜ λ©”μ‹œμ§€ ν…μŠ€νŠΈ/μŠ€νƒ€μΌ - λ²„νŠΌ λ””μžμΈ - μ• λ‹ˆλ©”μ΄μ…˜ 효과 μΆ”κ°€ **CSS μ—°κ²°**: `frontend/styles/custom_css.py`의 `SUCCESS_SCREEN_CSS` μ°Έμ‘° --- ### 7. `frontend/styles/custom_css.py` (곡톡) **μ—­ν• **: 전체 μ•±μ˜ CSS μŠ€νƒ€μΌ μ •μ˜ **ν˜„μž¬ CSS**: - `#success-screen`: 성곡 ν™”λ©΄ 전체 μ˜€λ²„λ ˆμ΄ μŠ€νƒ€μΌ - κ³ μ • μœ„μΉ˜ (position: fixed) - 보라색 κ·ΈλΌλ°μ΄μ…˜ λ°°κ²½ - 쀑앙 μ •λ ¬ **μˆ˜μ • κ°€λŠ₯**: - μƒˆλ‘œμš΄ CSS μΆ”κ°€ - 색상 ν…Œλ§ˆ λ³€κ²½ - λ°˜μ‘ν˜• λ””μžμΈ μΆ”κ°€ **μ‚¬μš© 방법**: ```python from frontend.styles.custom_css import get_all_css gr.Blocks(css=get_all_css()) ``` --- ## πŸ”„ 데이터 흐름 ### 검증 ν”„λ‘œμ„ΈμŠ€ 1. **μ‚¬μš©μž μ•‘μ…˜**: μ˜€λ””μ˜€ μ—…λ‘œλ“œ β†’ 검증 λ²„νŠΌ 클릭 2. **ν•Έλ“€λŸ¬ 호좜**: `validate_audio_handler(audio, difficulty, history, storage)` 3. **더미 둜직 μ‹€ν–‰**: - 5λ²ˆκΉŒμ§€ μ‹€νŒ¨ β†’ λͺ¨λ‹¬ ν‘œμ‹œ + 기둝 μΆ”κ°€ - 6번째 성곡 β†’ 성곡 ν™”λ©΄ μ „ν™˜ 4. **UI μ—…λ°μ΄νŠΈ**: - μ‹€νŒ¨: `failure_modal` ν‘œμ‹œ, `history_html` μ—…λ°μ΄νŠΈ - 성곡: `main_screen` μˆ¨κΉ€, `success_screen` ν‘œμ‹œ ### μƒνƒœ 관리 - `gr.State`: μ„Έμ…˜ μƒνƒœ (μƒˆλ‘œκ³ μΉ¨ μ‹œ μ΄ˆκΈ°ν™”) - `difficulty`: ν˜„μž¬ λ‚œμ΄λ„ - `failure_history`: μ‹€νŒ¨ 기둝 리슀트 - `gr.BrowserState`: localStorage 영ꡬ μ €μž₯ - `storage_key="audio_validation_history"` - ꡬ쑰: `{"date": "YYYY-MM-DD", "failures": [...], "successes": [...]}` - λ‚ μ§œ λ³€κ²½ μ‹œ μžλ™ μ΄ˆκΈ°ν™” --- ## πŸ› οΈ 개발 κ°€μ΄λ“œ ### μ»΄ν¬λ„ŒνŠΈ μˆ˜μ • μ‹œ 1. ν•΄λ‹Ή μ»΄ν¬λ„ŒνŠΈ 파일만 μˆ˜μ • 2. 클래슀의 `render()` λ©”μ„œλ“œμ—μ„œ UI ꡬ성 3. ν•„μš” μ‹œ `setup_events()` λ©”μ„œλ“œμ—μ„œ 이벀트 μ—°κ²° ### μƒˆ μ»΄ν¬λ„ŒνŠΈ μΆ”κ°€ μ‹œ 1. `frontend/components/` 에 μƒˆ 파일 생성 2. 클래슀 μž‘μ„± (`render()`, `setup_events()` λ©”μ„œλ“œ κ΅¬ν˜„) 3. `frontend/app_ui.py`μ—μ„œ import 및 μ—°κ²° ### CSS μŠ€νƒ€μΌ μΆ”κ°€ μ‹œ 1. `frontend/styles/custom_css.py`에 CSS λ¬Έμžμ—΄ μΆ”κ°€ 2. `get_all_css()` ν•¨μˆ˜μ—μ„œ λ°˜ν™˜κ°’μ— 포함 --- ## πŸ§ͺ ν…ŒμŠ€νŠΈ 방법 ### κ°œλ³„ μ»΄ν¬λ„ŒνŠΈ ν…ŒμŠ€νŠΈ 각 μ»΄ν¬λ„ŒνŠΈλŠ” λ…λ¦½μ μœΌλ‘œ ν…ŒμŠ€νŠΈ κ°€λŠ₯: ```python # header.py ν…ŒμŠ€νŠΈ from frontend.components.header import HeaderComponent from components.audio_validator import AudioValidator validator = AudioValidator(config) header = HeaderComponent(validator) # Gradio μ•±μ—μ„œ header.render() 호좜 ``` ### 톡합 ν…ŒμŠ€νŠΈ ```bash python app_new.py ``` --- ## πŸ“ 컀밋 μ»¨λ²€μ…˜ (ꢌμž₯) ``` [개발자A] header: λ‚œμ΄λ„ μŠ¬λΌμ΄λ” λ²”μœ„ μˆ˜μ • [개발자B] modal: μ‹€νŒ¨ λͺ¨λ‹¬ μŠ€νƒ€μΌ κ°œμ„  [곡톡] css: λ°˜μ‘ν˜• λ””μžμΈ μΆ”κ°€ ``` --- ## πŸ”§ λ°±μ—”λ“œ μ—°κ²° (μΆ”ν›„ μž‘μ—…) ν˜„μž¬ 더미 λ°μ΄ν„°λ‘œ λ™μž‘ν•˜λ©°, μ‹€μ œ λ°±μ—”λ“œ μ—°κ²° μ‹œ: 1. `app_new.py`의 `validate_audio_handler()` μˆ˜μ • 2. `components/audio_validator.py` 둜직 ν™œμ„±ν™” 3. `utils/stt_handler.py` μ‹€μ œ STT API μ—°κ²° **변경이 ν•„μš”ν•œ λΆ€λΆ„**: ```python # app_new.py:72-90 (더미 데이터 λΆ€λΆ„) # μ‹€μ œ 검증 둜직으둜 ꡐ체 result = self.validator.validate(audio_path, expected_text) recognized_text = result['text'] score = result['score'] ``` --- ## πŸ“š μ°Έκ³  λ¬Έμ„œ - **Gradio 곡식 λ¬Έμ„œ**: `docs/GRADIO_BASICS.md` - **Gradio Modal μ»΄ν¬λ„ŒνŠΈ**: https://huggingface.co/spaces/aliabid94/gradio_modal - **κΈ°μ‘΄ μ•± ꡬ쑰**: `app.py` (λ°±μ—…) --- ## πŸš€ μ‹œμž‘ν•˜κΈ° ### 1. ν™˜κ²½ μ„€μ • ```bash # κ°€μƒν™˜κ²½ ν™œμ„±ν™” source .venv/bin/activate # Linux/Mac .venv\Scripts\activate # Windows # μ˜μ‘΄μ„± μ„€μΉ˜ (gradio_modal μΆ”κ°€) pip install gradio_modal ``` ### 2. μ•± μ‹€ν–‰ ```bash # μƒˆλ‘œμš΄ μ»΄ν¬λ„ŒνŠΈ 뢄리 버전 python app_new.py # κΈ°μ‘΄ 버전 (λ°±μ—…) python app.py ``` ### 3. λΈŒλΌμš°μ € 접속 ``` http://localhost:7860 ``` --- ## ❓ FAQ ### Q1: 개발자 A와 Bκ°€ λ™μ‹œμ— μž‘μ—…ν•˜λ©΄ 좩돌이 λ‚˜μ§€ μ•Šλ‚˜μš”? **A**: 각자 λ‹€λ₯Έ νŒŒμΌμ„ μˆ˜μ •ν•˜λ―€λ‘œ Git 좩돌이 거의 μ—†μŠ΅λ‹ˆλ‹€. `app_ui.py`만 μ΅œμ’… 톡합 μ‹œ μ‘°μ‹¬νžˆ λ³‘ν•©ν•˜λ©΄ λ©λ‹ˆλ‹€. ### Q2: μ»΄ν¬λ„ŒνŠΈ κ°„ 톡신은 μ–΄λ–»κ²Œ ν•˜λ‚˜μš”? **A**: `app_ui.py`의 `setup_events()`μ—μ„œ 이벀트λ₯Ό μ—°κ²°ν•©λ‹ˆλ‹€. 각 μ»΄ν¬λ„ŒνŠΈλŠ” 독립적이며, μƒνƒœλŠ” `gr.State`λ₯Ό 톡해 κ³΅μœ λ©λ‹ˆλ‹€. ### Q3: CSSλ₯Ό μ»΄ν¬λ„ŒνŠΈλ³„λ‘œ 뢄리할 수 μžˆλ‚˜μš”? **A**: λ„€, `frontend/styles/` 에 μ—¬λŸ¬ νŒŒμΌμ„ λ§Œλ“€κ³  `get_all_css()`μ—μ„œ ν•©μΉ  수 μžˆμŠ΅λ‹ˆλ‹€. ### Q4: κΈ°μ‘΄ app.pyλŠ” μ‚­μ œν•΄μ•Ό ν•˜λ‚˜μš”? **A**: μ•„λ‹ˆμš”, λ°±μ—…μš©μœΌλ‘œ λ‚¨κ²¨λ‘μ„Έμš”. `app_new.py`κ°€ μ•ˆμ •ν™”λ˜λ©΄ κ΅μ²΄ν•˜λ©΄ λ©λ‹ˆλ‹€. ### Q5: "Cannot call change outside of a gradio.Blocks context" μ—λŸ¬κ°€ λ°œμƒν•©λ‹ˆλ‹€! **A**: Gradio 이벀트(`.click()`, `.change()` λ“±)λŠ” λ°˜λ“œμ‹œ `gr.Blocks()` μ»¨ν…μŠ€νŠΈ λ‚΄λΆ€μ—μ„œ ν˜ΈμΆœν•΄μ•Ό ν•©λ‹ˆλ‹€. **문제 상황**: ```python # ❌ 잘λͺ»λœ 방법 with gr.Blocks() as demo: components = build() # UI만 λΉŒλ“œ return components # gr.Blocks μ»¨ν…μŠ€νŠΈ λ°–μ—μ„œ 이벀트 바인딩 (μ—λŸ¬ λ°œμƒ!) setup_events(components) ``` **ν•΄κ²° 방법**: ```python # βœ… μ˜¬λ°”λ₯Έ 방법 with gr.Blocks() as demo: components = build() # UI λΉŒλ“œ setup_events(components) # 이벀트 바인딩도 μ—¬κΈ°μ„œ! return components ``` **ν˜„μž¬ κ΅¬μ‘°μ—μ„œμ˜ κ΅¬ν˜„**: - `frontend/app_ui.py`의 `build(handlers)` λ©”μ„œλ“œ μ•ˆμ—μ„œ UI λ Œλ”λ§κ³Ό 이벀트 바인딩을 λͺ¨λ‘ μˆ˜ν–‰ - 각 μ»΄ν¬λ„ŒνŠΈμ˜ `setup_events()` λ©”μ„œλ“œλŠ” `gr.Blocks()` μ»¨ν…μŠ€νŠΈ λ‚΄λΆ€μ—μ„œ 호좜됨 --- ## πŸ“ž μ—°λ½μ²˜ λ¬Έμ œκ°€ λ°œμƒν•˜λ©΄: 1. ν•΄λ‹Ή μ»΄ν¬λ„ŒνŠΈ 파일의 docstring 확인 2. `GRADIO_BASICS.md` μ°Έκ³  3. νŒ€ 채널에 질문 --- ## πŸ“ λ³€κ²½ 이λ ₯ ### v1.1 (2024-11-25) - FAQ Q5 μΆ”κ°€: Gradio 이벀트 바인딩 μ»¨ν…μŠ€νŠΈ 이슈 ν•΄κ²° 방법 - `app_ui.py` ꡬ쑰 κ°œμ„ : `build()` λ©”μ„œλ“œμ—μ„œ 이벀트 바인딩 톡합 ### v1.0 (2024-11-24) - 초기 λ¬Έμ„œ μž‘μ„± - ν”„λ‘ νŠΈμ—”λ“œ μ»΄ν¬λ„ŒνŠΈ 뢄리 ꡬ쑰 섀계 --- **μ΅œμ’… μˆ˜μ •μΌ**: 2024-11-25 **버전**: 1.1 **μž‘μ„±μž**: Claude Code Assistant