VoiceSementle / gradio_ui.py
Sungjoon Lee
[DOCS] λ¬Έμ„œ μˆ˜μ • 및 디버그 μ½”λ“œ 제거
48b92eb
raw
history blame
8.58 kB
"""
Gradio UI for Chloe's Voice Komentle Game
Connects to FastAPI backend for voice analysis
"""
import os
# Set Gradio temp directory BEFORE importing gradio
_upload_dir = os.path.join(os.path.dirname(__file__), "gradio_uploads")
os.makedirs(_upload_dir, exist_ok=True)
os.environ["GRADIO_TEMP_DIR"] = _upload_dir
import gradio as gr
from datetime import datetime
import uuid
import asyncio
from sqlalchemy import create_engine, text
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Import backend functions
from backend import (
analyze_voice_logic,
get_puzzle_by_date,
lifespan,
app as backend_app,
)
# Database connection
DATABASE_URL = os.getenv("DATABASE_URL")
engine = create_engine(
DATABASE_URL,
pool_size=10, # κΈ°λ³Έ μ—°κ²° ν’€ 크기
max_overflow=20, # μ΅œλŒ€ μΆ”κ°€ μ—°κ²° 수
pool_pre_ping=True, # μ—°κ²° μ‚¬μš© μ „ μœ νš¨μ„± 검사
pool_recycle=3600, # 1μ‹œκ°„λ§ˆλ‹€ μ—°κ²° μž¬μƒμ„±
connect_args={
"connect_timeout": 10, # μ—°κ²° νƒ€μž„μ•„μ›ƒ 10초
"options": "-c statement_timeout=30000" # 쿼리 νƒ€μž„μ•„μ›ƒ 30초
}
)
# Session ID (persistent across attempts)
session_id = str(uuid.uuid4())
# Backend initialization flag
backend_initialized = False
async def analyze_voice_async(audio_file, date_str):
"""
Analyze voice using backend logic directly
Args:
audio_file: Path to recorded audio file
date_str: Date string for puzzle lookup
Returns:
tuple: (result_text, scores_text, hint_text, image_path)
"""
if audio_file is None:
return "❌ μ˜€λ””μ˜€λ₯Ό λ¨Όμ € λ…ΉμŒν•΄μ£Όμ„Έμš”!", "", "", None
try:
# Read audio file
with open(audio_file, "rb") as f:
audio_bytes = f.read()
# Call backend logic directly
result = await analyze_voice_logic(audio_bytes, date_str, session_id)
# Handle errors
if result.get("status") == "error":
return f"❌ {result.get('message', 'Unknown error')}", "", "", None
# Parse response (already in 0-100 range from backend)
category = result.get("category", "unknown")
pitch = result.get("pitch", 0.0)
rhythm = result.get("rhythm", 0.0)
energy = result.get("energy", 0.0)
pronunciation = result.get("pronunciation", 0.0)
transcript = result.get("transcript", 0.0)
overall = result.get("overall", 0.0)
advice = result.get("advice", "")
is_correct = result.get("is_correct", False)
hints = {} # hints are embedded in advice now
# Format result message
if is_correct:
result_msg = f"πŸŽ‰ μ •λ‹΅μž…λ‹ˆλ‹€! 전체 점수: {overall:.1f}/100"
else:
result_msg = f"πŸ“Š 전체 점수: {overall:.1f}/100 - λ‹€μ‹œ μ‹œλ„ν•΄λ³΄μ„Έμš”!"
# Format scores
scores_text = f"""
### πŸ“Š 점수 상세
**μΉ΄ν…Œκ³ λ¦¬:** {category.upper()}
- **발음 (Pronunciation):** {pronunciation:.1f}/100
- **μŒλ†’μ΄ (Pitch):** {pitch:.1f}/100
- **리듬 (Rhythm):** {rhythm:.1f}/100
- **μ—λ„ˆμ§€ (Energy):** {energy:.1f}/100
- **전사 (Transcript):** {transcript:.1f}/100
- **전체 (Overall):** {overall:.1f}/100
"""
# Format hints
hint_text = ""
hint_image = None
if hints and "answer" in hints:
hint_type = hints.get("type", "hint")
hint_items = hints.get("answer", [])
if hint_type == "hint":
hint_text = "πŸ’‘ **힌트:**\n\n"
else:
hint_text = "🎯 **발음 μ‘°μ–Έ:**\n\n"
for item in hint_items:
hint_text += f"{item.get('text', '')}\n\n"
# Get image path if exists
img_path = item.get("path", "")
if img_path and os.path.exists(img_path):
hint_image = img_path
# Add advice if no hints
if not hint_text and advice:
hint_text = f"πŸ’¬ **μ‘°μ–Έ:**\n\n{advice}"
return result_msg, scores_text, hint_text, hint_image
except Exception as e:
return f"❌ 였λ₯˜ λ°œμƒ: {str(e)}", "", "", None
def analyze_voice(audio_file, date_str):
"""Synchronous wrapper for async analyze_voice_async"""
return asyncio.run(analyze_voice_async(audio_file, date_str))
def get_today_puzzle():
"""Get today's puzzle information from database"""
try:
today = datetime.now().strftime("%Y-%m-%d")
# Use backend function to get puzzle
puzzle = get_puzzle_by_date(today)
# print(puzzle)
if puzzle:
return f"""
### πŸ“… 였늘의 퍼즐
**λ‚ μ§œ:** {puzzle.get('puzzle_date', 'N/A')}
**퍼즐 번호:** #{puzzle.get('puzzle_number', 'N/A')}
**μΉ΄ν…Œκ³ λ¦¬:** {puzzle.get('category', 'N/A').upper()}
**λ‚œμ΄λ„:** {puzzle.get('difficulty', 'N/A')}
μ •λ‹΅ 단어λ₯Ό λ°œμŒν•΄λ³΄μ„Έμš”! (μ΅œλŒ€ 6회 μ‹œλ„)
"""
else:
return "❌ 였늘의 퍼즐을 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."
except Exception as e:
return f"❌ 퍼즐 정보λ₯Ό κ°€μ Έμ˜¬ 수 μ—†μŠ΅λ‹ˆλ‹€: {str(e)}"
def reset_session():
"""Reset session for new game"""
global session_id
session_id = str(uuid.uuid4())
return "βœ… μƒˆ κ²Œμž„ μ‹œμž‘! μ˜€λ””μ˜€λ₯Ό λ…ΉμŒν•΄μ£Όμ„Έμš”.", "", "", None
# Create Gradio Interface
with gr.Blocks(title="Chloe's Voice Komentle") as demo:
gr.Markdown("# 🎀 Chloe's Voice Komentle")
# Puzzle info section
with gr.Row():
puzzle_info = gr.Markdown(value=get_today_puzzle())
refresh_btn = gr.Button("πŸ”„ 퍼즐 정보 μƒˆλ‘œκ³ μΉ¨", size="sm")
with gr.Row():
with gr.Column(scale=1):
# Audio recording
gr.Markdown("### πŸŽ™οΈ μŒμ„± λ…ΉμŒ")
audio_input = gr.Audio(
sources=["microphone"],
type="filepath",
label="마이크둜 λ…ΉμŒ",
format="wav",
)
# Date input (auto-filled with today)
date_input = gr.Textbox(
label="λ‚ μ§œ (YYYY-MM-DD)",
value=datetime.now().strftime("%Y-%m-%d"),
interactive=True,
)
# Submit button
submit_btn = gr.Button("🎯 λΆ„μ„ν•˜κΈ°", variant="primary", size="lg")
reset_btn = gr.Button("πŸ”„ μƒˆ κ²Œμž„ μ‹œμž‘", variant="secondary")
with gr.Column(scale=1):
# Results
gr.Markdown("### πŸ“Š κ²°κ³Ό")
result_output = gr.Markdown(label="κ²°κ³Ό")
scores_output = gr.Markdown(label="점수 상세")
# Hints section
with gr.Row():
with gr.Column():
hint_output = gr.Markdown(label="힌트 및 μ‘°μ–Έ")
with gr.Column():
hint_image = gr.Image(label="힌트 이미지", show_label=True)
# Event handlers
submit_btn.click(
fn=analyze_voice,
inputs=[audio_input, date_input],
outputs=[result_output, scores_output, hint_output, hint_image],
)
reset_btn.click(
fn=reset_session,
inputs=[],
outputs=[result_output, scores_output, hint_output, hint_image],
)
refresh_btn.click(fn=get_today_puzzle, inputs=[], outputs=[puzzle_info])
# Footer
gr.Markdown("---\n**Powered by:** VoiceKit MCP + Gemini AI")
# Launch configuration
if __name__ == "__main__":
# Initialize backend (VoiceKit MCP session)
print("⏳ Initializing VoiceKit MCP...")
async def init_backend():
"""Initialize backend resources"""
async with lifespan(backend_app):
print("βœ“ VoiceKit MCP initialized")
# Keep the lifespan context active
await asyncio.Event().wait() # Wait forever
# Run backend initialization in background
import threading
def run_backend_init():
asyncio.run(init_backend())
backend_thread = threading.Thread(target=run_backend_init, daemon=True)
backend_thread.start()
# Wait a bit for initialization
import time
time.sleep(5)
print("βœ“ Backend initialized")
# Launch Gradio
server_host = os.getenv("SERVER_HOST")
frontend_port = int(os.getenv("FRONTEND_PORT"))
demo.launch(
server_name=server_host, # Listen on all interfaces
server_port=frontend_port, # Default Gradio port
share=False, # Set to True for public link
show_error=True,
allowed_paths=[os.path.join(os.path.dirname(__file__), "hints", "audio")], # Allow serving TTS audio hints
)