carlosduplar
Restructure repo: src/, frontend/, modal/ dirs; update app_file paths; update .gitignore
3ab287a Download src/server_app.py from build-small-hackathon/patient-virtuel-dentiste: direct link, hf CLI and curl.
- Browser
- Download file 5.62 kB
-
https://huggingface.co/spaces/build-small-hackathon/patient-virtuel-dentiste/resolve/main/src/server_app.py
- Command line
-
hf download hf://spaces/build-small-hackathon/patient-virtuel-dentiste/src/server_app.py
-
curl -L -o server_app.py https://huggingface.co/spaces/build-small-hackathon/patient-virtuel-dentiste/resolve/main/src/server_app.py
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() | |
| 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"], | |
| } | |
| 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"], | |
| } | |
| def api_reset_session() -> dict: | |
| """Reset to a fresh session.""" | |
| state = reset_session() | |
| return {"state": state} | |
| 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") | |
| 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>""" | |
| 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() | |