File size: 8,517 Bytes
3a9eacd 9f30ef0 3a9eacd 0082fdf 3a9eacd 9f30ef0 3a9eacd 9f30ef0 3a9eacd 9f30ef0 3a9eacd 9f30ef0 3a9eacd 9f30ef0 3a9eacd 9f30ef0 3a9eacd 9f30ef0 3a9eacd 9f30ef0 3a9eacd 9f30ef0 3a9eacd 9f30ef0 3a9eacd 9f30ef0 3a9eacd 9f30ef0 0082fdf 9f30ef0 0082fdf 9f30ef0 0082fdf dca3f18 0082fdf 6d34043 dca3f18 6d34043 0082fdf 9f30ef0 0082fdf 9f30ef0 0082fdf 9f30ef0 0082fdf 9f30ef0 3a9eacd 9f30ef0 3a9eacd 9f30ef0 3a9eacd 9f30ef0 3a9eacd 9f30ef0 3a9eacd 9f30ef0 3a9eacd 9f30ef0 3a9eacd 9f30ef0 3a9eacd 9f30ef0 3a9eacd 0082fdf 3a9eacd |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
"""
ํฌ๊ธฐ ํ๋ฉด ์ปดํฌ๋ํธ
๊ฒ์ฆ ํฌ๊ธฐ ์ ์ ์ฒด ํ๋ฉด ์ ํ
ํตํฉ ๋ฒ์ : Backend API ๋์ Gradio file URL ์ฌ์ฉ
"""
import gradio as gr
class GiveUpScreenComponent:
"""ํฌ๊ธฐ ํ๋ฉด ์ปดํฌ๋ํธ"""
# HTML ํ
ํ๋ฆฟ (data-* ์์ฑ์ผ๋ก ๊ฐ ์๋ณ)
HTML_TEMPLATE = """
<div class="giveup-content-wrapper" id="giveup-content-inner">
<h1 class="giveup-title">๐ข Game Over</h1>
<!-- ์ ๋ต ์ธ์
-->
<div class="audio-compare-section">
<div class="answer-word" id="giveup-answer-word">-</div>
<!-- ์ ๋ต ์์ฑ -->
<div class="audio-card">
<audio controls preload="auto" id="giveup-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="giveup-today-participants">-</div>
</div>
<div class="stat-card stat-green">
<div class="stat-label">Today Success Rate</div>
<div class="stat-value" id="giveup-today-success-rate">-</div>
</div>
<div class="stat-card stat-yellow">
<div class="stat-label">Today Attempts</div>
<div class="stat-value" id="giveup-today-attempts">-</div>
</div>
<!-- Row 2: ์ ์ฒด ํต๊ณ -->
<div class="stat-card stat-indigo">
<div class="stat-label">Total Participants</div>
<div class="stat-value" id="giveup-total-participants">-</div>
</div>
<div class="stat-card stat-emerald">
<div class="stat-label">Total Success Rate</div>
<div class="stat-value" id="giveup-total-success-rate">-</div>
</div>
<div class="stat-card stat-orange">
<div class="stat-label">Total Attempts</div>
<div class="stat-value" id="giveup-total-attempts">-</div>
</div>
</div>
</div>
"""
@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();
}"""
)
|