SJLee-0525
[TEST] test29
8374119
"""
์˜ค๋””์˜ค ์ž…๋ ฅ ์ปดํฌ๋„ŒํŠธ - Voice Semantle ์Šคํƒ€์ผ
์• ๋‹ˆ๋ฉ”์ด์…˜ํ’ ํ•˜๋Š˜์ƒ‰ ํ…Œ๋งˆ์˜ ์Œ์„ฑ ์ž…๋ ฅ ์ธํ„ฐํŽ˜์ด์Šค
์ปค์Šคํ…€ ๋ฒ„ํŠผ์œผ๋กœ Gradio Audio ์ปดํฌ๋„ŒํŠธ ์ œ์–ด
๐Ÿ‘จโ€๐Ÿ’ป ๋‹ด๋‹น: ๊ฐœ๋ฐœ์ž A
"""
import gradio as gr
class AudioInputComponent:
"""Voice Semantle ์Šคํƒ€์ผ ์˜ค๋””์˜ค ์ž…๋ ฅ ์ปดํฌ๋„ŒํŠธ"""
# ๋งˆ์ดํฌ ๋ฒ„ํŠผ HTML ํ…œํ”Œ๋ฆฟ
MIC_BUTTON_HTML_TEMPLATE = """
<div class="mic-section">
<div class="mic-status" id="mic-status">Click the play button to start game</div>
<button class="mic-btn" id="mic-btn" title="๋…น์Œ ์‹œ์ž‘">
<svg xmlns="http://www.w3.org/2000/svg" height="44" viewBox="0 0 64 64" width="44">
<path fill="#fff" d="M24 18 Q20 18 20 22 L20 42 Q20 46 24 46 L46 34 Q50 32 46 30 Z" stroke="#fff" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"/>
</svg>
</button>
</div>
"""
MIC_BUTTON_CSS_TEMPLATE = """
.mic-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
padding: 30px 0;
width: 100%;
}
.mic-status {
font-size: 13px;
color: #5a7a9a;
text-align: center;
min-height: 20px;
}
.mic-btn {
width: 80px;
height: 80px;
border-radius: 50%;
border: 3px solid #4db8ff;
background: linear-gradient(135deg, #4db8ff 0%, #5bc0eb 100%);
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
box-shadow: 0 4px 16px rgba(77, 184, 255, 0.4);
padding: 0 !important;
}
.mic-btn:hover {
transform: scale(1.08);
box-shadow: 0 6px 24px rgba(77, 184, 255, 0.5);
}
.mic-btn:active {
transform: scale(0.95);
}
.mic-btn.recording {
background: linear-gradient(135deg, #ff9f43 0%, #e08b2d 100%);
border-color: #ff9f43;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(255, 159, 67, 0.5); }
50% { box-shadow: 0 0 0 15px rgba(255, 159, 67, 0); }
}
/* ๋‹คํฌ๋ชจ๋“œ */
.dark .mic-status {
color: #818384;
}
"""
# JavaScript: ์ปค์Šคํ…€ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ Gradio Audio ์ปดํฌ๋„ŒํŠธ ์ง์ ‘ ์ œ์–ด
MIC_BUTTON_JS = """
const micBtn = element.querySelector('#mic-btn');
const uploadLink = element.querySelector('#upload-link');
const statusText = element.querySelector('#mic-status');
// ์˜ค๋””์˜ค wrapper ํ‘œ์‹œ ํ•จ์ˆ˜
function showAudioWrapper() {
const wrapper = document.querySelector('#audio-wrapper');
if (wrapper) {
wrapper.style.display = 'block';
}
}
// ๋งˆ์ดํฌ ๋ฒ„ํŠผ ํด๋ฆญ โ†’ ์˜ค๋””์˜ค ์ปดํฌ๋„ŒํŠธ ํ‘œ์‹œ + ๋…น์Œ ์‹œ์ž‘
micBtn.addEventListener('click', () => {
showAudioWrapper();
statusText.textContent = 'Ready to play';
});
"""
def __init__(self, validator):
"""
Args:
validator: AudioValidator ์ธ์Šคํ„ด์Šค
"""
self.validator = validator
self.audio_input = None
self.submit_btn = None
self.mic_button_html = None
self.audio_wrapper = None
def render(self):
"""
Wordle ์Šคํƒ€์ผ ์˜ค๋””์˜ค ์ž…๋ ฅ UI ๋ Œ๋”๋ง
Returns:
tuple: (audio_input, submit_btn)
"""
# ๋งˆ์ดํฌ ์„น์…˜ ์‹œ๊ฐ์  ์š”์†Œ (Gradio 6 html_template + js_on_load)
self.mic_button_html = gr.HTML(
value=self.MIC_BUTTON_HTML_TEMPLATE,
css_template=self.MIC_BUTTON_CSS_TEMPLATE,
js_on_load=self.MIC_BUTTON_JS,
elem_id="mic-section",
padding=False
)
# Gradio Audio ์ปดํฌ๋„ŒํŠธ (CSS๋กœ ์ฒ˜์Œ์—๋Š” ์ˆจ๊น€)
self.audio_wrapper = gr.Column(elem_id="audio-wrapper")
with self.audio_wrapper:
self.audio_input = gr.Audio(
label="Audio recording",
type="filepath",
sources=["microphone", "upload"],
elem_id="audio-input"
)
# ๊ฒ€์ฆ ๋ฒ„ํŠผ (Gradio Button์œผ๋กœ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ)
self.submit_btn = gr.Button(
"SUBMIT",
variant="primary",
size="lg",
elem_id="verify-btn"
)
return self.audio_input, self.submit_btn
def setup_events(self, validate_handler, inputs, outputs):
"""
์ด๋ฒคํŠธ ๋ฐ”์ธ๋”ฉ
Args:
validate_handler: ๊ฒ€์ฆ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜
inputs: ์ž…๋ ฅ ์ปดํฌ๋„ŒํŠธ ๋ฆฌ์ŠคํŠธ
outputs: ์ถœ๋ ฅ ์ปดํฌ๋„ŒํŠธ ๋ฆฌ์ŠคํŠธ
"""
# ๊ฒ€์ฆ ๋ฒ„ํŠผ ํด๋ฆญ
self.submit_btn.click(
fn=validate_handler,
inputs=inputs,
outputs=outputs
)