patient-virtuel-dentiste / src /server_app.py
carlosduplar
Restructure repo: src/, frontend/, modal/ dirs; update app_file paths; update .gitignore
3ab287a
Raw History Blame Contribute Delete
5.62 kB
import logging
import os
import sys
import tempfile
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from gradio import Server
from gradio.data_classes import FileData
from fastapi.responses import HTMLResponse, FileResponse
from core import process_turn, end_session_click, reset_session, make_initial_state
from markdown import markdown
log = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO, force=True)
app = Server()
@app.api(name="process_turn")
def api_process_turn(audio: FileData, state: dict) -> dict:
"""Process one voice turn: STT → LLM → TTS. Returns chat, audio, state, feedback."""
try:
audio_path = audio.get("path") if isinstance(audio, dict) else audio.path if hasattr(audio, "path") else str(audio)
log.info("process_turn audio_path=%s state_turns=%s", audio_path, state.get("turn_count"))
result = process_turn(audio_path, state)
log.info("process_turn done status=%s", result.get("status"))
except Exception as e:
log.error("process_turn error: %s", e)
return {
"chat": state.get("messages", []),
"audio_url": None,
"state": state,
"feedback_intro": "",
"feedback_points_forts": [],
"feedback_table": [],
"feedback_vocabulaire": [],
"feedback_priorite": [],
"feedback_bilan": {},
"feedback_open": False,
"status": f"Erreur: {e}",
}
audio_url = None
if result["audio_file"]:
audio_url = f"/audio/{result['audio_file']}"
return {
"chat": result["chat"],
"audio_url": audio_url,
"state": result["state"],
"feedback_intro": result["feedback_intro"],
"feedback_points_forts": result["feedback_points_forts"],
"feedback_table": result["feedback_table"],
"feedback_vocabulaire": result["feedback_vocabulaire"],
"feedback_priorite": result["feedback_priorite"],
"feedback_bilan": result["feedback_bilan"],
"feedback_open": result["feedback_open"],
"status": result["status"],
}
@app.api(name="end_session")
def api_end_session(state: dict) -> dict:
"""End the session and generate a structured recap."""
result = end_session_click(state)
audio_url = None
if result["audio_file"]:
audio_url = f"/audio/{result['audio_file']}"
return {
"chat": result["chat"],
"audio_url": audio_url,
"state": result["state"],
"feedback_intro": result["feedback_intro"],
"feedback_points_forts": result["feedback_points_forts"],
"feedback_table": result["feedback_table"],
"feedback_vocabulaire": result["feedback_vocabulaire"],
"feedback_priorite": result["feedback_priorite"],
"feedback_bilan": result["feedback_bilan"],
"feedback_open": result["feedback_open"],
"status": result["status"],
}
@app.api(name="reset_session")
def api_reset_session() -> dict:
"""Reset to a fresh session."""
state = reset_session()
return {"state": state}
@app.get("/audio/{filename}")
async def serve_audio(filename: str):
"""Serve generated TTS audio files."""
path = os.path.join(tempfile.gettempdir(), filename)
if not os.path.exists(path):
return {"error": "Audio not found"}
return FileResponse(path, media_type="audio/wav")
@app.get("/", response_class=HTMLResponse)
async def homepage():
html_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "frontend", "custom_index.html")
with open(html_path, "r", encoding="utf-8") as f:
return f.read()
_FIELD_NOTES_HTML_TEMPLATE = """\
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Field Notes: Patient Virtuel</title>
<style>
*, *::before, *::after { margin: 0; box-sizing: border-box; }
body {
background: #19120c; color: #efe0d4;
font-family: "EB Garamond", Georgia, serif;
line-height: 1.7; padding: 2rem 1rem;
}
article {
max-width: 720px; margin: 0 auto;
}
h1 { font-size: 2rem; margin-bottom: 0.5rem; color: #ff5719; }
h2 { font-size: 1.4rem; margin: 2rem 0 0.5rem; color: #ff5719; }
h3 { font-size: 1.1rem; margin: 1.5rem 0 0.3rem; color: #e6beb2; }
p { margin: 0.6rem 0; }
pre { background: #221a13; padding: 1rem; border-radius: 8px;
overflow-x: auto; font-size: 0.85rem; margin: 1rem 0;
border: 1px solid rgba(255,200,150,0.1); }
code { background: #221a13; padding: 0.1em 0.3em; border-radius: 4px;
font-size: 0.9em; }
table { border-collapse: collapse; margin: 1rem 0; width: 100%; }
th, td { border: 1px solid #5c4037; padding: 0.4rem 0.6rem; text-align: left; }
th { background: #312821; color: #ff5719; }
a { color: #ff5719; }
hr { border: none; border-top: 1px solid #5c4037; margin: 2rem 0; }
ul, ol { margin: 0.5rem 0 0.5rem 1.5rem; }
</style>
</head>
<body>
<article>
%s
</article>
</body>
</html>"""
@app.get("/field-notes", response_class=HTMLResponse)
async def field_notes():
notes_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "FIELD_NOTES.md")
try:
with open(notes_path, "r", encoding="utf-8") as f:
raw = f.read()
except FileNotFoundError:
return "<h1>Field Notes not found</h1>"
body = markdown(raw, extensions=["fenced_code", "tables"])
return _FIELD_NOTES_HTML_TEMPLATE.replace("%s", body, 1)
if __name__ == "__main__":
app.launch()