""" 메인 UI 조립 파일 모든 프론트엔드 컴포넌트를 조합하여 완전한 UI 구성 이 파일은 각 컴포넌트를 import하고 연결하는 역할만 수행 """ import os import gradio as gr from frontend.components.header import HeaderComponent # docs 디렉토리 절대 경로 # app_ui.py: /home/.../gradio/client/frontend/app_ui.py # docs: /home/.../gradio/docs _CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) # client/frontend/ _CLIENT_DIR = os.path.dirname(_CURRENT_DIR) # client/ _PROJECT_ROOT = os.path.dirname(_CLIENT_DIR) # gradio/ (project root) DOCS_DIR = os.path.join(_PROJECT_ROOT, "docs") from frontend.components.audio_input import AudioInputComponent from frontend.components.history_display import HistoryDisplayComponent from frontend.components.failure_modal import FailureModalComponent from frontend.components.success_screen import SuccessScreenComponent from frontend.components.giveup_screen import GiveUpScreenComponent from frontend.components.floating_chatbot import FloatingChatbotComponent from frontend.components.welcome_modal import WelcomeModal from frontend.styles.custom_css import get_all_css # Gradio 6: launch()에서 사용 # 게임 상태 관리 (UUID, 세션 등) from utils.game_state import GameStateManager, create_default_game_state class AppUI: """애플리케이션 UI 빌더""" def __init__(self, validator): """ Args: validator: AudioValidator 인스턴스 """ self.validator = validator # 컴포넌트 초기화 self.header = HeaderComponent(validator) self.audio_input = AudioInputComponent(validator) self.history_display = HistoryDisplayComponent() self.failure_modal = FailureModalComponent() self.success_screen = SuccessScreenComponent() self.giveup_screen = GiveUpScreenComponent() self.floating_chatbot = FloatingChatbotComponent() self.welcome_modal = WelcomeModal() # 상태 변수 self.failure_history = None self.browser_history = None self.game_state = None # UUID, 세션 정보 등 def build(self, handlers, stats: dict = None): """ 전체 UI 빌드 (이벤트 바인딩 포함) Args: handlers (dict): 이벤트 핸들러 함수 딕셔너리 - on_load: 페이지 로드 핸들러 - validate_audio: 검증 핸들러 stats (dict): 통계 데이터 (success/giveup 화면 초기값) Returns: tuple: 모든 UI 컴포넌트들 """ if stats is None: stats = {} # stats를 JSON으로 변환 import json stats_json = json.dumps(stats) with gr.Blocks(title="VOICE SEMENTLE") as demo: # ============== Dashboard Stats (JS에서 접근 가능) ============== gr.HTML( f'
' ) # ============== 상태 변수 ============== self.failure_history = gr.State([]) # localStorage 기반 영구 저장소 (날짜별 기록 관리) self.browser_history = gr.BrowserState( default_value={"date": "", "failures": [], "successes": []}, storage_key="audio_validation_history" ) # 게임 상태 (UUID, 세션, 통계 등) self.game_state = gr.BrowserState( default_value=create_default_game_state(), storage_key="game_state" ) # ============== 헤더 ============== self.header.render() # ============== 메인 화면 ============== main_screen = gr.Column(visible=True, elem_id="main-screen") with main_screen: # 오디오 입력 영역 audio_input, submit_btn = self.audio_input.render() # 실패 기록 영역 history_html, give_up_trigger_btn = self.history_display.render() # docs 링크 (절대 경로 사용) gr.HTML( f""" """ ) # ============== 실패 모달 (custom_modal 사용) ============== failure_modal = self.failure_modal.render() # ============== 성공 화면 ============== success_screen, success_content, restart_btn = self.success_screen.render(stats=stats) # ============== 포기 화면 ============== giveup_screen, giveup_content, giveup_restart_btn = self.giveup_screen.render(stats=stats) # ============== 플로팅 AI 챗봇 (UUID 공유) ============== self.floating_chatbot.render(game_state=self.game_state) # ============== 웰컴 모달 (첫 방문 시 YouTube 튜토리얼) ============== self.welcome_modal.render() # 반환할 컴포넌트들 components = { 'demo': demo, 'failure_history': self.failure_history, 'browser_history': self.browser_history, 'game_state': self.game_state, # UUID, 세션 정보 'main_screen': main_screen, 'audio_input': audio_input, 'submit_btn': submit_btn, 'history_html': history_html, 'failure_modal': failure_modal, 'success_screen': success_screen, 'success_content': success_content, 'restart_btn': restart_btn, 'giveup_screen': giveup_screen, 'giveup_content': giveup_content, 'giveup_restart_btn': giveup_restart_btn, 'give_up_trigger_btn': give_up_trigger_btn } # ============== 이벤트 바인딩 (gr.Blocks 컨텍스트 내부) ============== # 2. 페이지 로드 (UUID 체크/생성 포함) demo.load( fn=handlers['on_load'], inputs=[self.browser_history, self.game_state], outputs=[ history_html, self.browser_history, self.failure_history, self.game_state ] ) # 3. 오디오 입력 - 검증 버튼 (UUID 포함) self.audio_input.setup_events( validate_handler=handlers['validate_audio'], inputs=[ audio_input, self.failure_history, self.browser_history, self.game_state # UUID 전송용 ], outputs=[ history_html, self.failure_history, self.browser_history, self.game_state, # UUID 업데이트 failure_modal, main_screen, success_screen, success_content ] ) # 4. 실패 모달 - 닫기 버튼 self.failure_modal.setup_events() # 5. 성공 화면 - 처음부터 다시 버튼 self.success_screen.setup_events() # 6. 포기 화면 - 처음부터 다시 버튼 self.giveup_screen.setup_events() # 7. 포기하기 버튼 이벤트 if 'give_up' in handlers: give_up_trigger_btn.click( fn=handlers['give_up'], inputs=[self.game_state], outputs=[ main_screen, giveup_screen, giveup_content, self.game_state ] ) return components