lumicollect / ui /layout.py
nemabruh404's picture
Create layout.py
c5dcb55 verified
Raw
History Blame Contribute Delete
4.94 kB
import gradio as gr
from pathlib import Path
from ui.handlers import handle_verify, handle_upload, handle_reset, handle_audio_change
# Đọc CSS và JS từ file thật
# Bỏ @import trong CSS vì Gradio không cho phép
_CSS = (Path(__file__).parent / "styles.css").read_text()
_JS = (Path(__file__).parent / "recorder.js").read_text()
_RECORDER_HTML = """
<div id="lumi-recorder-ui">
<button id="lumi-mic-btn" title="Nhấn để ghi âm">🎙️</button>
<div id="lumi-rec-label">Nhấn để bắt đầu ghi âm</div>
<canvas id="lumi-canvas" width="520" height="72"></canvas>
<div id="lumi-proc-bar">
<div class="plabel"><span class="pdot">●</span>&nbsp;Đang xử lý âm thanh...</div>
<div class="ptrack"><div class="pfill"></div></div>
</div>
<div id="lumi-audio-playback">
<div class="play-label">🎧 Nghe lại bản ghi:</div>
<audio id="lumi-audio-el" controls></audio>
</div>
</div>
"""
def build_ui() -> gr.Blocks:
with gr.Blocks(theme=gr.themes.Base(), css=_CSS, title="Lumi Voice Collector") as demo:
# --- Font + permissions (thay thế @import trong CSS) ---
gr.HTML("""
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<meta http-equiv="Permissions-Policy" content="microphone=*">
""")
# --- Header ---
gr.HTML("""
<div class="lumi-header">
<div class="lumi-logo-ring">🎙️</div>
<h1>Thu thập giọng nói "Lumi ơi"</h1>
<p>Giúp huấn luyện wake word cho trợ lý Lumi</p>
</div>
""")
# --- Steps ---
gr.HTML("""
<div class="lumi-steps">
<div class="lumi-step">
<div class="lumi-step-num">1</div>
<div class="lumi-step-lbl">Ghi âm</div>
</div>
<div class="lumi-step-line"></div>
<div class="lumi-step">
<div class="lumi-step-num">2</div>
<div class="lumi-step-lbl">Kiểm tra</div>
</div>
<div class="lumi-step-line"></div>
<div class="lumi-step">
<div class="lumi-step-num">3</div>
<div class="lumi-step-lbl">Gửi lên</div>
</div>
</div>
""")
# --- Instruction ---
gr.HTML("""
<div class="lumi-instruction">
<div class="ititle">📋 Hướng dẫn</div>
<p>
<span class="hi">1.</span> Nhấn nút mic để bắt đầu ghi âm<br>
<span class="hi">2.</span> Nói rõ <span class="hw">"Lumi ơi"</span> vào micro<br>
<span class="hi">3.</span> Nhấn lại để dừng — đợi thanh xử lý biến mất<br>
<span class="hi">4.</span> Nhấn <b>Kiểm tra</b> → đạt ≥ 0.8 → nhấn <b>Gửi</b>
</p>
</div>
""")
# --- Recorder card ---
with gr.Group(elem_classes="lumi-card"):
gr.HTML(_RECORDER_HTML)
gr.HTML(f"<script>{_JS}</script>")
# --- Hidden Gradio audio ---
with gr.Group(elem_classes="hidden-audio-wrap"):
audio_inst = gr.Audio(
sources=["upload"],
type="filepath",
label="hidden",
visible=False
)
# --- Status ---
status_txt = gr.Label(
value='🎙️ Sẵn sàng — Nhấn mic và đọc "Lumi ơi"',
elem_classes="lumi-status",
show_label=False
)
# --- Buttons ---
with gr.Row(elem_classes="lumi-btn-row"):
btn_check = gr.Button("🔍 Kiểm tra mẫu", variant="secondary", elem_classes="lb-check")
btn_reset = gr.Button("🗑 Ghi lại", variant="stop", elem_classes="lb-reset")
btn_send = gr.Button(
"🚀 Gửi lên hệ thống",
variant="primary",
interactive=False,
elem_classes="lb-send"
)
# --- State ---
score_state = gr.State(0.0)
# --- Event bindings ---
btn_check.click(
fn=handle_verify,
inputs=audio_inst,
outputs=[status_txt, btn_send, score_state]
)
btn_send.click(
fn=handle_upload,
inputs=[audio_inst, score_state],
outputs=[status_txt, btn_send, audio_inst]
)
btn_reset.click(
fn=handle_reset,
outputs=[audio_inst, status_txt, btn_send, score_state]
)
audio_inst.change(
fn=handle_audio_change,
outputs=btn_send
)
return demo