|
|
""" |
|
|
์ฑ๊ณต ํ๋ฉด ์ปดํฌ๋ํธ |
|
|
๊ฒ์ฆ ์ฑ๊ณต ์ ์ ์ฒด ํ๋ฉด ์ ํ |
|
|
|
|
|
ํตํฉ ๋ฒ์ : Backend API ๋์ Gradio file URL ์ฌ์ฉ |
|
|
""" |
|
|
|
|
|
import gradio as gr |
|
|
|
|
|
|
|
|
class SuccessScreenComponent: |
|
|
"""์ฑ๊ณต ํ๋ฉด ์ปดํฌ๋ํธ""" |
|
|
|
|
|
|
|
|
HTML_TEMPLATE = """ |
|
|
<div class="success-content-wrapper" id="success-content-inner"> |
|
|
<h1 class="success-title">๐ Congratulations!</h1> |
|
|
|
|
|
<!-- ์ ๋ต ์ธ์
--> |
|
|
<div class="audio-compare-section"> |
|
|
<div class="answer-word" id="success-answer-word">-</div> |
|
|
|
|
|
<!-- ์ ๋ต ์์ฑ --> |
|
|
<div class="audio-card"> |
|
|
<audio controls preload="auto" id="success-correct-audio" src=""></audio> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<!-- ํต๊ณ ๊ทธ๋ฆฌ๋ (3x2) --> |
|
|
<div class="stats-grid"> |
|
|
<!-- Row 1: ์ค๋ ํต๊ณ --> |
|
|
<div class="stat-card stat-blue"> |
|
|
<div class="stat-label">Today Participants</div> |
|
|
<div class="stat-value" id="success-today-participants">-</div> |
|
|
</div> |
|
|
<div class="stat-card stat-green"> |
|
|
<div class="stat-label">Today Success Rate</div> |
|
|
<div class="stat-value" id="success-today-success-rate">-</div> |
|
|
</div> |
|
|
<div class="stat-card stat-yellow"> |
|
|
<div class="stat-label">Today Attempts</div> |
|
|
<div class="stat-value" id="success-today-attempts">-</div> |
|
|
</div> |
|
|
|
|
|
<!-- Row 2: ์ ์ฒด ํต๊ณ --> |
|
|
<div class="stat-card stat-indigo"> |
|
|
<div class="stat-label">Total Participants</div> |
|
|
<div class="stat-value" id="success-total-participants">-</div> |
|
|
</div> |
|
|
<div class="stat-card stat-emerald"> |
|
|
<div class="stat-label">Total Success Rate</div> |
|
|
<div class="stat-value" id="success-total-success-rate">-</div> |
|
|
</div> |
|
|
<div class="stat-card stat-orange"> |
|
|
<div class="stat-label">Total Attempts</div> |
|
|
<div class="stat-value" id="success-total-attempts">-</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
@staticmethod |
|
|
def get_js_on_load(): |
|
|
"""JS ์ฝ๋ - game_state์์ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ (ํตํฉ ๋ฒ์ )""" |
|
|
return """ |
|
|
// MutationObserver๋ก success-screen์ด visible ๋ ๋ ๊ฐ์ง |
|
|
const successScreen = document.getElementById('success-screen'); |
|
|
if (successScreen) { |
|
|
const observer = new MutationObserver((mutations) => { |
|
|
mutations.forEach((mutation) => { |
|
|
if (mutation.type === 'attributes' && mutation.attributeName === 'class') { |
|
|
// visible ์ํ ์ฒดํฌ (Gradio๊ฐ hidden ํด๋์ค ์ ๊ฑฐํ ๋) |
|
|
const isVisible = !successScreen.classList.contains('hidden') && |
|
|
successScreen.style.display !== 'none'; |
|
|
if (isVisible) { |
|
|
updateSuccessScreen(); |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
observer.observe(successScreen, { attributes: true, attributeFilter: ['class', 'style'] }); |
|
|
} |
|
|
|
|
|
// API์์ ์ค์๊ฐ stats ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ (Gradio API ์ฌ์ฉ) |
|
|
async function updateSuccessScreen() { |
|
|
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('[SUCCESS SCREEN] Stats:', stats); |
|
|
|
|
|
// Answer Word ์ค์ |
|
|
const answerWordEl = document.getElementById('success-answer-word'); |
|
|
if (answerWordEl && stats.answer_word) { |
|
|
answerWordEl.textContent = stats.answer_word; |
|
|
} |
|
|
|
|
|
// Correct Audio ์ค์ (์ ๋ ๊ฒฝ๋ก) |
|
|
const correctAudio = document.getElementById('success-correct-audio'); |
|
|
if (correctAudio && stats.reference_audio_path) { |
|
|
const audioUrl = '/gradio_api/file=' + stats.reference_audio_path; |
|
|
correctAudio.src = audioUrl; |
|
|
correctAudio.load(); |
|
|
console.log('[SUCCESS SCREEN] Audio URL:', audioUrl); |
|
|
} |
|
|
|
|
|
// ํต๊ณ ์
๋ฐ์ดํธ |
|
|
const todayParticipants = document.getElementById('success-today-participants'); |
|
|
const todaySuccessRate = document.getElementById('success-today-success-rate'); |
|
|
const todayAttempts = document.getElementById('success-today-attempts'); |
|
|
const totalParticipants = document.getElementById('success-total-participants'); |
|
|
const totalSuccessRate = document.getElementById('success-total-success-rate'); |
|
|
const totalAttempts = document.getElementById('success-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; |
|
|
|
|
|
// User Audio ์ค์ (game_state์์) |
|
|
const gameStateStr = localStorage.getItem('game_state'); |
|
|
if (gameStateStr) { |
|
|
const gameState = JSON.parse(gameStateStr); |
|
|
const guesses = gameState.guesses || []; |
|
|
const lastGuess = guesses.length > 0 ? guesses[guesses.length - 1] : null; |
|
|
|
|
|
const userAudio = document.getElementById('success-user-audio'); |
|
|
if (userAudio && lastGuess && lastGuess.audioFile) { |
|
|
userAudio.src = '/gradio_api/file=' + lastGuess.audioFile; |
|
|
userAudio.load(); |
|
|
} |
|
|
} |
|
|
|
|
|
} catch (e) { |
|
|
console.error('[SUCCESS SCREEN] Error:', e); |
|
|
} |
|
|
} |
|
|
|
|
|
// ์ด๊ธฐ ๋ก๋ ์์๋ ํ๋ฒ ํธ์ถ (visible ์ํ๋ฉด) |
|
|
if (successScreen && !successScreen.classList.contains('hidden')) { |
|
|
updateSuccessScreen(); |
|
|
} |
|
|
""" |
|
|
|
|
|
def __init__(self): |
|
|
self.success_screen = None |
|
|
self.success_content = None |
|
|
self.restart_btn = None |
|
|
|
|
|
def render(self, stats: dict = None): |
|
|
""" |
|
|
์ฑ๊ณต ํ๋ฉด UI ๋ ๋๋ง |
|
|
|
|
|
Args: |
|
|
stats: ํต๊ณ ๋ฐ์ดํฐ ๋์
๋๋ฆฌ (์ด๊ธฐ๊ฐ, JS๊ฐ ์ค์๊ฐ ์
๋ฐ์ดํธ) |
|
|
|
|
|
Returns: |
|
|
tuple: (success_screen, success_content, restart_btn) |
|
|
""" |
|
|
self.success_screen = gr.Column(visible=False, elem_id="success-screen") |
|
|
with self.success_screen: |
|
|
gr.Markdown("") |
|
|
|
|
|
|
|
|
self.success_content = gr.HTML( |
|
|
value=self.HTML_TEMPLATE, |
|
|
js_on_load=self.get_js_on_load(), |
|
|
elem_id="success-content" |
|
|
) |
|
|
|
|
|
self.restart_btn = gr.Button( |
|
|
"Restart", |
|
|
variant="primary", |
|
|
size="lg", |
|
|
elem_id="restart-btn" |
|
|
) |
|
|
|
|
|
return self.success_screen, self.success_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(); |
|
|
}""" |
|
|
) |
|
|
|