carlosduplar commited on
Commit
3ab287a
·
1 Parent(s): a31d332

Restructure repo: src/, frontend/, modal/ dirs; update app_file paths; update .gitignore

Browse files
.gitignore CHANGED
@@ -1,15 +1,33 @@
 
 
 
 
 
 
 
1
  node_modules/
2
- build/
 
3
  dist/
 
4
  coverage/
5
- .DS_Store
6
- *.log
7
  .env*
8
  !.env.example
9
- __pycache__/
10
- *.pyc
11
- .gradio/
12
- test-qwen/
 
 
 
 
 
 
 
 
 
 
13
  legacy/
14
  experimental/
15
- *.swp
 
1
+ # Python
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ .gradio/
6
+
7
+ # Node
8
  node_modules/
9
+
10
+ # Build artifacts
11
  dist/
12
+ build/
13
  coverage/
14
+
15
+ # Environment
16
  .env*
17
  !.env.example
18
+
19
+ # OS
20
+ .DS_Store
21
+ *.swp
22
+
23
+ # Logs
24
+ *.log
25
+
26
+ # Data / temp
27
+ trace.jsonl
28
+ metadata.json
29
+ *.mp4
30
+
31
+ # Legacy / experiments
32
  legacy/
33
  experimental/
 
README.md CHANGED
@@ -5,12 +5,13 @@ colorFrom: red
5
  colorTo: gray
6
  sdk: gradio
7
  sdk_version: 6.16.0
8
- app_file: server_app.py
9
  pinned: false
10
  license: apache-2.0
11
  tags:
12
  - hackathon
13
  - build-small-hackathon
 
14
  - voice-ai
15
  - french-learning
16
  - dental
@@ -18,6 +19,8 @@ tags:
18
  - modal
19
  - llama-cpp
20
  - gemma
 
 
21
  ---
22
 
23
  # Patient Virtuel · Français en médecine dentaire
@@ -37,18 +40,24 @@ See [`space_README.md`](space_README.md) for the full description.
37
  📖 [Field Notes](FIELD_NOTES.md) — architecture decisions, lessons learned, and the story behind the app.
38
  📡 [Agent Trace](https://huggingface.co/datasets/build-small-hackathon/patient-virtuel-trace) — curated build log on HF Hub (Sharing is Caring badge).
39
 
 
 
40
  ## Architecture
41
 
42
  ```
43
- server_app.py → gr.Server backend (API endpoints)
44
- custom_index.html → Custom vanilla HTML/CSS/JS frontend
45
- core.py → Shared session logic
46
- llm_engine.py → Gemma 4 26B-A4B via Modal (llama.cpp runtime)
47
- stt_engine.py → faster-whisper via Modal
48
- tts_engine.py → piper-tts (local neural, fr_FR-siwis-medium)
49
- prompts.py → System prompts
50
- parse_feedback.py → Feedback parsing
51
- modal_app.py → Modal deployments (llama.cpp + CUDA)
 
 
 
 
52
  ```
53
 
54
  ## Running locally
 
5
  colorTo: gray
6
  sdk: gradio
7
  sdk_version: 6.16.0
8
+ app_file: src/server_app.py
9
  pinned: false
10
  license: apache-2.0
11
  tags:
12
  - hackathon
13
  - build-small-hackathon
14
+ - backyard-ai
15
  - voice-ai
16
  - french-learning
17
  - dental
 
19
  - modal
20
  - llama-cpp
21
  - gemma
22
+ - off-brand
23
+ - off-the-grid
24
  ---
25
 
26
  # Patient Virtuel · Français en médecine dentaire
 
40
  📖 [Field Notes](FIELD_NOTES.md) — architecture decisions, lessons learned, and the story behind the app.
41
  📡 [Agent Trace](https://huggingface.co/datasets/build-small-hackathon/patient-virtuel-trace) — curated build log on HF Hub (Sharing is Caring badge).
42
 
43
+ [Demo video](https://youtu.be/oP3KrlZFUPk) · [LinkedIn post](https://www.linkedin.com/posts/carlosduplar_my-wife-is-a-dentist-re-entering-the-workforce-activity-7471897489332289536-p5Ne)
44
+
45
  ## Architecture
46
 
47
  ```
48
+ src/
49
+ server_app.py → gr.Server backend (API endpoints)
50
+ core.py → Shared session logic
51
+ llm_engine.py → Gemma 4 26B-A4B via Modal (llama.cpp runtime)
52
+ stt_engine.py → faster-whisper via Modal
53
+ tts_engine.py → piper-tts (local neural, fr_FR-siwis-medium)
54
+ prompts.py → System prompts
55
+ parse_feedback.py → Feedback parsing
56
+ frontend/
57
+ custom_index.html → Custom vanilla HTML/CSS/JS frontend
58
+ style.css → Styles
59
+ modal/
60
+ modal_app.py → Modal deployments (llama.cpp + CUDA)
61
  ```
62
 
63
  ## Running locally
custom_index.html → frontend/custom_index.html RENAMED
File without changes
style.css → frontend/style.css RENAMED
File without changes
index.html DELETED
@@ -1,13 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>My Google AI Studio App</title>
7
- </head>
8
- <body>
9
- <div id="root"></div>
10
- <script type="module" src="/src/main.tsx"></script>
11
- </body>
12
- </html>
13
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
metadata.json DELETED
@@ -1,5 +0,0 @@
1
- {
2
- "name": "Patient Virtuel: Français en médecine dentaire",
3
- "description": "A real-time voice-only interactive app for dental hygienists to practice professional French in a Swiss clinic setting.",
4
- "requestFramePermissions": ["microphone"]
5
- }
 
 
 
 
 
 
modal_app.py → modal/modal_app.py RENAMED
File without changes
space_README.md CHANGED
@@ -6,9 +6,22 @@ colorTo: gray
6
  sdk: gradio
7
  sdk_version: 6.16.0
8
  python_version: "3.12"
9
- app_file: server_app.py
10
  pinned: false
11
  license: apache-2.0
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  ---
13
 
14
  # Patient Virtuel · Français en médecine dentaire
@@ -34,6 +47,14 @@ This app uses `gradio.Server` with a fully custom HTML/CSS/JS frontend instead o
34
  - **TTS**: [piper-tts](https://github.com/OHF-Voice/piper1-gpl) (local, `fr_FR-siwis-medium` voice)
35
  - **STT**: [faster-whisper-large-v3-turbo](https://github.com/SYSTRAN/faster-whisper) via Modal (T4 GPU) — MIT
36
 
 
 
 
 
 
 
 
 
37
  ## License
38
 
39
  All components are Apache 2.0 or MIT licensed.
 
6
  sdk: gradio
7
  sdk_version: 6.16.0
8
  python_version: "3.12"
9
+ app_file: src/server_app.py
10
  pinned: false
11
  license: apache-2.0
12
+ tags:
13
+ - hackathon
14
+ - build-small-hackathon
15
+ - backyard-ai
16
+ - voice-ai
17
+ - french-learning
18
+ - dental
19
+ - gradio
20
+ - modal
21
+ - llama-cpp
22
+ - gemma
23
+ - off-brand
24
+ - off-the-grid
25
  ---
26
 
27
  # Patient Virtuel · Français en médecine dentaire
 
47
  - **TTS**: [piper-tts](https://github.com/OHF-Voice/piper1-gpl) (local, `fr_FR-siwis-medium` voice)
48
  - **STT**: [faster-whisper-large-v3-turbo](https://github.com/SYSTRAN/faster-whisper) via Modal (T4 GPU) — MIT
49
 
50
+ ## Modal
51
+
52
+ Both GPU workloads (LLM + STT) run on Modal serverless containers. See `modal_app.py` for the full deployment config.
53
+
54
+ ## Social
55
+
56
+ [Demo video](https://youtu.be/oP3KrlZFUPk) · [LinkedIn post](https://www.linkedin.com/posts/carlosduplar_my-wife-is-a-dentist-re-entering-the-workforce-activity-7471897489332289536-p5Ne)
57
+
58
  ## License
59
 
60
  All components are Apache 2.0 or MIT licensed.
src/__init__.py ADDED
File without changes
core.py → src/core.py RENAMED
File without changes
llm_engine.py → src/llm_engine.py RENAMED
File without changes
parse_feedback.py → src/parse_feedback.py RENAMED
File without changes
prompts.py → src/prompts.py RENAMED
File without changes
server_app.py → src/server_app.py RENAMED
@@ -1,7 +1,10 @@
1
  import logging
2
  import os
 
3
  import tempfile
4
 
 
 
5
  from gradio import Server
6
  from gradio.data_classes import FileData
7
  from fastapi.responses import HTMLResponse, FileResponse
@@ -100,7 +103,7 @@ async def serve_audio(filename: str):
100
 
101
  @app.get("/", response_class=HTMLResponse)
102
  async def homepage():
103
- html_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "custom_index.html")
104
  with open(html_path, "r", encoding="utf-8") as f:
105
  return f.read()
106
 
@@ -149,7 +152,7 @@ _FIELD_NOTES_HTML_TEMPLATE = """\
149
 
150
  @app.get("/field-notes", response_class=HTMLResponse)
151
  async def field_notes():
152
- notes_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "FIELD_NOTES.md")
153
  try:
154
  with open(notes_path, "r", encoding="utf-8") as f:
155
  raw = f.read()
 
1
  import logging
2
  import os
3
+ import sys
4
  import tempfile
5
 
6
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
7
+
8
  from gradio import Server
9
  from gradio.data_classes import FileData
10
  from fastapi.responses import HTMLResponse, FileResponse
 
103
 
104
  @app.get("/", response_class=HTMLResponse)
105
  async def homepage():
106
+ html_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "frontend", "custom_index.html")
107
  with open(html_path, "r", encoding="utf-8") as f:
108
  return f.read()
109
 
 
152
 
153
  @app.get("/field-notes", response_class=HTMLResponse)
154
  async def field_notes():
155
+ notes_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "FIELD_NOTES.md")
156
  try:
157
  with open(notes_path, "r", encoding="utf-8") as f:
158
  raw = f.read()
stt_engine.py → src/stt_engine.py RENAMED
File without changes
tts_engine.py → src/tts_engine.py RENAMED
File without changes
vite.config.ts DELETED
@@ -1,24 +0,0 @@
1
- import tailwindcss from '@tailwindcss/vite';
2
- import react from '@vitejs/plugin-react';
3
- import path from 'path';
4
- import {defineConfig, loadEnv} from 'vite';
5
-
6
- export default defineConfig(({mode}) => {
7
- const env = loadEnv(mode, '.', '');
8
- return {
9
- plugins: [react(), tailwindcss()],
10
- define: {
11
- 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY),
12
- },
13
- resolve: {
14
- alias: {
15
- '@': path.resolve(__dirname, '.'),
16
- },
17
- },
18
- server: {
19
- // HMR is disabled in AI Studio via DISABLE_HMR env var.
20
- // Do not modify—file watching is disabled to prevent flickering during agent edits.
21
- hmr: process.env.DISABLE_HMR !== 'true',
22
- },
23
- };
24
- });