VoiceSementle / client /FRONTEND_STRUCTURE.md
SJLee-0525
[CHORE] test3
ebcf639

A newer version of the Gradio SDK is available: 6.1.0

Upgrade

🎨 ν”„λ‘ νŠΈμ—”λ“œ 파일 ꡬ쑰 λ¬Έμ„œ

πŸ“ 디렉토리 ꡬ쑰

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

# μ‚¬μš© μ˜ˆμ‹œ
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 ꡬ쑰:

<div style="padding: 20px; text-align: center;">
  <h2>❌ μ‹€νŒ¨!</h2>
  <div><strong>μΈμ‹λœ ν…μŠ€νŠΈ:</strong> ...</div>
  <div><strong>점수:</strong> ...점</div>
  <div><strong>πŸ’‘ 힌트:</strong> ...</div>
</div>

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 μΆ”κ°€
  • 색상 ν…Œλ§ˆ λ³€κ²½
  • λ°˜μ‘ν˜• λ””μžμΈ μΆ”κ°€

μ‚¬μš© 방법:

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() ν•¨μˆ˜μ—μ„œ λ°˜ν™˜κ°’μ— 포함

πŸ§ͺ ν…ŒμŠ€νŠΈ 방법

κ°œλ³„ μ»΄ν¬λ„ŒνŠΈ ν…ŒμŠ€νŠΈ

각 μ»΄ν¬λ„ŒνŠΈλŠ” λ…λ¦½μ μœΌλ‘œ ν…ŒμŠ€νŠΈ κ°€λŠ₯:

# header.py ν…ŒμŠ€νŠΈ
from frontend.components.header import HeaderComponent
from components.audio_validator import AudioValidator

validator = AudioValidator(config)
header = HeaderComponent(validator)
# Gradio μ•±μ—μ„œ header.render() 호좜

톡합 ν…ŒμŠ€νŠΈ

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 μ—°κ²°

변경이 ν•„μš”ν•œ λΆ€λΆ„:

# app_new.py:72-90 (더미 데이터 λΆ€λΆ„)
# μ‹€μ œ 검증 둜직으둜 ꡐ체
result = self.validator.validate(audio_path, expected_text)
recognized_text = result['text']
score = result['score']

πŸ“š μ°Έκ³  λ¬Έμ„œ


πŸš€ μ‹œμž‘ν•˜κΈ°

1. ν™˜κ²½ μ„€μ •

# κ°€μƒν™˜κ²½ ν™œμ„±ν™”
source .venv/bin/activate  # Linux/Mac
.venv\Scripts\activate     # Windows

# μ˜μ‘΄μ„± μ„€μΉ˜ (gradio_modal μΆ”κ°€)
pip install gradio_modal

2. μ•± μ‹€ν–‰

# μƒˆλ‘œμš΄ μ»΄ν¬λ„ŒνŠΈ 뢄리 버전
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() μ»¨ν…μŠ€νŠΈ λ‚΄λΆ€μ—μ„œ ν˜ΈμΆœν•΄μ•Ό ν•©λ‹ˆλ‹€.

문제 상황:

# ❌ 잘λͺ»λœ 방법
with gr.Blocks() as demo:
    components = build()  # UI만 λΉŒλ“œ
    return components

# gr.Blocks μ»¨ν…μŠ€νŠΈ λ°–μ—μ„œ 이벀트 바인딩 (μ—λŸ¬ λ°œμƒ!)
setup_events(components)

ν•΄κ²° 방법:

# βœ… μ˜¬λ°”λ₯Έ 방법
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