""" 포기 화면 컴포넌트 검증 포기 시 전체 화면 전환 통합 버전: Backend API 대신 Gradio file URL 사용 """ import gradio as gr class GiveUpScreenComponent: """포기 화면 컴포넌트""" # HTML 템플릿 (data-* 속성으로 값 식별) HTML_TEMPLATE = """

😢 Game Over

-
Today Participants
-
Today Success Rate
-
Today Attempts
-
Total Participants
-
Total Success Rate
-
Total Attempts
-
""" @staticmethod def get_js_on_load(): """JS 코드 - game_state에서 데이터 가져오기 (통합 버전)""" return """ // MutationObserver로 giveup-screen이 visible 될 때 감지 const giveupScreen = document.getElementById('giveup-screen'); if (giveupScreen) { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'attributes' && mutation.attributeName === 'class') { // visible 상태 체크 (Gradio가 hidden 클래스 제거할 때) const isVisible = !giveupScreen.classList.contains('hidden') && giveupScreen.style.display !== 'none'; if (isVisible) { updateGiveupScreen(); } } }); }); observer.observe(giveupScreen, { attributes: true, attributeFilter: ['class', 'style'] }); } // API에서 실시간 stats 데이터 가져오기 (Gradio API 사용) async function updateGiveupScreen() { try { // Gradio API 호출 (event_id 받고 결과 가져오기) const callResponse = await fetch('/gradio_api/call/dashboard', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: [] }) }); const { event_id } = await callResponse.json(); // SSE로 결과 가져오기 const resultResponse = await fetch('/gradio_api/call/dashboard/' + event_id); const text = await resultResponse.text(); const lines = text.trim().split('\\n'); const dataLine = lines.find(l => l.startsWith('data:')); const stats = JSON.parse(dataLine.replace('data: ', ''))[0]; console.log('[GIVEUP SCREEN] Stats:', stats); // Answer Word 설정 const answerWordEl = document.getElementById('giveup-answer-word'); if (answerWordEl && stats.answer_word) { answerWordEl.textContent = stats.answer_word; } // Correct Audio 설정 (절대 경로) const correctAudio = document.getElementById('giveup-correct-audio'); if (correctAudio && stats.reference_audio_path) { const audioUrl = '/gradio_api/file=' + stats.reference_audio_path; correctAudio.src = audioUrl; correctAudio.load(); console.log('[GIVEUP SCREEN] Audio URL:', audioUrl); } // 통계 업데이트 const todayParticipants = document.getElementById('giveup-today-participants'); const todaySuccessRate = document.getElementById('giveup-today-success-rate'); const todayAttempts = document.getElementById('giveup-today-attempts'); const totalParticipants = document.getElementById('giveup-total-participants'); const totalSuccessRate = document.getElementById('giveup-total-success-rate'); const totalAttempts = document.getElementById('giveup-total-attempts'); if (todayParticipants) todayParticipants.textContent = stats.today_participants || 0; if (todaySuccessRate) todaySuccessRate.textContent = (stats.today_success_rate || 0) + '%'; if (todayAttempts) todayAttempts.textContent = stats.today_attempts || 0; if (totalParticipants) totalParticipants.textContent = stats.total_participants || 0; if (totalSuccessRate) totalSuccessRate.textContent = (stats.total_success_rate || 0) + '%'; if (totalAttempts) totalAttempts.textContent = stats.total_attempts || 0; } catch (e) { console.error('[GIVEUP SCREEN] Error:', e); } } // 초기 로드 시에도 한번 호출 (visible 상태면) if (giveupScreen && !giveupScreen.classList.contains('hidden')) { updateGiveupScreen(); } """ def __init__(self): self.giveup_screen = None self.giveup_content = None self.restart_btn = None def render(self, stats: dict = None): """ 포기 화면 UI 렌더링 Args: stats: 통계 데이터 딕셔너리 (초기값, JS가 실시간 업데이트) Returns: tuple: (giveup_screen, giveup_content, restart_btn) """ self.giveup_screen = gr.Column(visible=False, elem_id="giveup-screen") with self.giveup_screen: gr.Markdown("") # HTML + JS로 실시간 통계 업데이트 self.giveup_content = gr.HTML( value=self.HTML_TEMPLATE, js_on_load=self.get_js_on_load(), elem_id="giveup-content" ) self.restart_btn = gr.Button( "Restart", variant="primary", size="lg", elem_id="restart-btn" ) return self.giveup_screen, self.giveup_content, self.restart_btn def setup_events(self): """처음부터 다시 버튼 이벤트 바인딩 (로컬 스토리지 초기화 후 새로고침)""" self.restart_btn.click( fn=None, js="""() => { // 날짜가 유효하지 않을 때와 동일하게 로컬 스토리지 초기화 const today = new Date().toISOString().split('T')[0]; const newStorage = { date: today, failures: [], successes: [] }; // Gradio BrowserState 키로 저장 localStorage.setItem('audio_validation_history', JSON.stringify(newStorage)); localStorage.removeItem('game_state'); localStorage.removeItem('dashboard_stats'); // 페이지 새로고침 window.location.reload(); }""" )