Spaces:
Sleeping
Sleeping
remove eval log from git - now stored in Supabase
Browse files- .gitignore +1 -1
- app/app.py +72 -2
- app/evaluator.py +7 -0
- app/results/eval_log.csv +0 -68
- app/storage.py +126 -0
- pyproject.toml +2 -1
- requirements.txt +0 -0
- uv.lock +0 -0
.gitignore
CHANGED
|
@@ -19,7 +19,7 @@ voices/**/*.onnx
|
|
| 19 |
voices/**/*.json
|
| 20 |
|
| 21 |
# operational logs — machine specific
|
| 22 |
-
|
| 23 |
app/session_export.csv
|
| 24 |
app/results_export.csv
|
| 25 |
app/parler_test.wav
|
|
|
|
| 19 |
voices/**/*.json
|
| 20 |
|
| 21 |
# operational logs — machine specific
|
| 22 |
+
app/results/eval_log.csv
|
| 23 |
app/session_export.csv
|
| 24 |
app/results_export.csv
|
| 25 |
app/parler_test.wav
|
app/app.py
CHANGED
|
@@ -16,6 +16,7 @@ import os
|
|
| 16 |
import tempfile
|
| 17 |
import pandas as pd
|
| 18 |
import gradio as gr
|
|
|
|
| 19 |
|
| 20 |
sys.path.insert(0, os.path.dirname(__file__))
|
| 21 |
|
|
@@ -26,7 +27,8 @@ load_dotenv(os.path.join(os.path.dirname(__file__), ".env"), override=False)
|
|
| 26 |
from engines import ENGINES, ENGINE_MAP
|
| 27 |
from engines.kokoro_engine import KOKORO_VOICES, KOKORO_DEFAULT_VOICE
|
| 28 |
from evaluator import evaluate
|
| 29 |
-
|
|
|
|
| 30 |
# ── constants ─────────────────────────────────────────────────────────────────
|
| 31 |
|
| 32 |
BANDS = ["K-2", "3-5", "6-8", "9-12"]
|
|
@@ -83,6 +85,7 @@ def build_comparison_table(results: list[dict]) -> pd.DataFrame:
|
|
| 83 |
"RTF ↓ (synth time / audio dur, <1.0 = fast)",
|
| 84 |
"Latency (s)",
|
| 85 |
"Cost",
|
|
|
|
| 86 |
]
|
| 87 |
if not results:
|
| 88 |
return pd.DataFrame(columns=columns)
|
|
@@ -99,6 +102,7 @@ def build_comparison_table(results: list[dict]) -> pd.DataFrame:
|
|
| 99 |
"RTF ↓ (synth time / audio dur, <1.0 = fast)": format_rtf(r["rtf"]),
|
| 100 |
"Latency (s)": r["latency_s"],
|
| 101 |
"Cost": format_cost(r["engine_cost_usd"], r["chirp_equiv_usd"], r["engine"]),
|
|
|
|
| 102 |
})
|
| 103 |
return pd.DataFrame(rows)
|
| 104 |
|
|
@@ -293,8 +297,31 @@ def build_business_chart(results: list[dict]):
|
|
| 293 |
|
| 294 |
return fig
|
| 295 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
# ── event handlers ────────────────────────────────────────────────────────────
|
| 297 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
def on_engine_change(engine_name: str):
|
| 299 |
"""Show voice dropdown only for Kokoro."""
|
| 300 |
is_kokoro = engine_name == "Kokoro (tuned)"
|
|
@@ -354,6 +381,19 @@ def run_synthesis(engine_name: str, band: str, text: str, voice: str):
|
|
| 354 |
yield audio_path, f"✗ Eval failed: {e}", build_comparison_table(_session_results), build_business_chart(_session_results)
|
| 355 |
return
|
| 356 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
_session_results.append(eval_result)
|
| 358 |
|
| 359 |
status = (
|
|
@@ -385,15 +425,29 @@ def export_all():
|
|
| 385 |
return gr.update(value=_EVAL_LOG_PATH, visible=True), "✓ Full history log ready to download."
|
| 386 |
|
| 387 |
def load_history():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
if not os.path.exists(_EVAL_LOG_PATH):
|
| 389 |
return build_comparison_table([]), build_business_chart([]), "⚠ No history found."
|
| 390 |
try:
|
| 391 |
df = pd.read_csv(_EVAL_LOG_PATH)
|
|
|
|
|
|
|
|
|
|
| 392 |
records = df.to_dict(orient="records")
|
| 393 |
return build_comparison_table(records), build_business_chart(records), f"✓ Loaded {len(records)} historical runs."
|
| 394 |
except Exception as e:
|
| 395 |
return build_comparison_table([]), build_business_chart([]), f"✗ Failed: {e}"
|
| 396 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 397 |
# ── UI ───────────────────────────────���────────────────────────────────────────
|
| 398 |
|
| 399 |
def build_ui():
|
|
@@ -444,10 +498,16 @@ def build_ui():
|
|
| 444 |
|
| 445 |
comparison_table = gr.Dataframe(
|
| 446 |
value=build_comparison_table([]),
|
| 447 |
-
label="Eval Results",
|
| 448 |
interactive=False,
|
| 449 |
)
|
| 450 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 451 |
business_chart = gr.Plot(
|
| 452 |
value=build_business_chart([]),
|
| 453 |
label="Business Decision Chart",
|
|
@@ -455,6 +515,7 @@ def build_ui():
|
|
| 455 |
|
| 456 |
with gr.Row():
|
| 457 |
clear_btn = gr.Button("🗑 Clear Session")
|
|
|
|
| 458 |
load_history_btn = gr.Button("📂 Load History")
|
| 459 |
export_session_btn = gr.Button("⬇ Export Session")
|
| 460 |
export_all_btn = gr.Button("⬇ Export Full History")
|
|
@@ -487,6 +548,15 @@ def build_ui():
|
|
| 487 |
fn=clear_results,
|
| 488 |
outputs=[comparison_table, business_chart, export_status],
|
| 489 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 490 |
|
| 491 |
load_history_btn.click(
|
| 492 |
fn=load_history,
|
|
|
|
| 16 |
import tempfile
|
| 17 |
import pandas as pd
|
| 18 |
import gradio as gr
|
| 19 |
+
from storage import upload_audio_background, download_csv
|
| 20 |
|
| 21 |
sys.path.insert(0, os.path.dirname(__file__))
|
| 22 |
|
|
|
|
| 27 |
from engines import ENGINES, ENGINE_MAP
|
| 28 |
from engines.kokoro_engine import KOKORO_VOICES, KOKORO_DEFAULT_VOICE
|
| 29 |
from evaluator import evaluate
|
| 30 |
+
from storage import upload_audio_background
|
| 31 |
+
from pathlib import Path
|
| 32 |
# ── constants ─────────────────────────────────────────────────────────────────
|
| 33 |
|
| 34 |
BANDS = ["K-2", "3-5", "6-8", "9-12"]
|
|
|
|
| 85 |
"RTF ↓ (synth time / audio dur, <1.0 = fast)",
|
| 86 |
"Latency (s)",
|
| 87 |
"Cost",
|
| 88 |
+
"Audio URL"
|
| 89 |
]
|
| 90 |
if not results:
|
| 91 |
return pd.DataFrame(columns=columns)
|
|
|
|
| 102 |
"RTF ↓ (synth time / audio dur, <1.0 = fast)": format_rtf(r["rtf"]),
|
| 103 |
"Latency (s)": r["latency_s"],
|
| 104 |
"Cost": format_cost(r["engine_cost_usd"], r["chirp_equiv_usd"], r["engine"]),
|
| 105 |
+
"Audio URL": r.get("audio_url") or ""
|
| 106 |
})
|
| 107 |
return pd.DataFrame(rows)
|
| 108 |
|
|
|
|
| 297 |
|
| 298 |
return fig
|
| 299 |
|
| 300 |
+
def _make_audio_filename(engine_name: str, band: str, ext: str) -> str:
|
| 301 |
+
"""Generate a unique bucket filename for an audio file."""
|
| 302 |
+
from datetime import datetime
|
| 303 |
+
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 304 |
+
safe_engine = engine_name.replace(" ", "_").replace("(", "").replace(")", "")
|
| 305 |
+
safe_band = band.replace("-", "")
|
| 306 |
+
return f"{ts}_{safe_engine}_{safe_band}{ext}"
|
| 307 |
+
|
| 308 |
# ── event handlers ────────────────────────────────────────────────────────────
|
| 309 |
|
| 310 |
+
def on_row_select(evt: gr.SelectData, results_df: pd.DataFrame) -> tuple:
|
| 311 |
+
"""
|
| 312 |
+
When a row is selected, pass the Supabase public URL directly
|
| 313 |
+
to gr.Audio value — Gradio fetches it internally.
|
| 314 |
+
"""
|
| 315 |
+
try:
|
| 316 |
+
row_idx = evt.index[0]
|
| 317 |
+
url = results_df.iloc[row_idx]["Audio URL"]
|
| 318 |
+
if not url or not str(url).startswith("http"):
|
| 319 |
+
return gr.update(visible=False)
|
| 320 |
+
return gr.update(value=url, visible=True)
|
| 321 |
+
except Exception as e:
|
| 322 |
+
print(f"[Playback] Failed to load audio: {e}")
|
| 323 |
+
return gr.update(visible=False)
|
| 324 |
+
|
| 325 |
def on_engine_change(engine_name: str):
|
| 326 |
"""Show voice dropdown only for Kokoro."""
|
| 327 |
is_kokoro = engine_name == "Kokoro (tuned)"
|
|
|
|
| 381 |
yield audio_path, f"✗ Eval failed: {e}", build_comparison_table(_session_results), build_business_chart(_session_results)
|
| 382 |
return
|
| 383 |
|
| 384 |
+
# upload audio to Supabase in background — non-blocking
|
| 385 |
+
audio_ext = Path(audio_path).suffix
|
| 386 |
+
bucket_filename = _make_audio_filename(engine_name, band, audio_ext)
|
| 387 |
+
|
| 388 |
+
def _on_upload(url):
|
| 389 |
+
if url:
|
| 390 |
+
eval_result["audio_url"] = url
|
| 391 |
+
print(f"[Storage] Uploaded: {url}")
|
| 392 |
+
else:
|
| 393 |
+
eval_result["audio_url"] = None
|
| 394 |
+
|
| 395 |
+
upload_audio_background(audio_path, bucket_filename, callback=_on_upload)
|
| 396 |
+
eval_result["audio_url"] = None # placeholder until upload completes
|
| 397 |
_session_results.append(eval_result)
|
| 398 |
|
| 399 |
status = (
|
|
|
|
| 425 |
return gr.update(value=_EVAL_LOG_PATH, visible=True), "✓ Full history log ready to download."
|
| 426 |
|
| 427 |
def load_history():
|
| 428 |
+
# try Supabase first, fall back to local CSV
|
| 429 |
+
try:
|
| 430 |
+
from storage import download_csv
|
| 431 |
+
download_csv(_EVAL_LOG_PATH)
|
| 432 |
+
except Exception as e:
|
| 433 |
+
print(f"[Storage] Supabase download skipped, using local: {e}")
|
| 434 |
+
|
| 435 |
if not os.path.exists(_EVAL_LOG_PATH):
|
| 436 |
return build_comparison_table([]), build_business_chart([]), "⚠ No history found."
|
| 437 |
try:
|
| 438 |
df = pd.read_csv(_EVAL_LOG_PATH)
|
| 439 |
+
# fill missing audio_url column for old rows that predate storage
|
| 440 |
+
if "audio_url" not in df.columns:
|
| 441 |
+
df["audio_url"] = ""
|
| 442 |
records = df.to_dict(orient="records")
|
| 443 |
return build_comparison_table(records), build_business_chart(records), f"✓ Loaded {len(records)} historical runs."
|
| 444 |
except Exception as e:
|
| 445 |
return build_comparison_table([]), build_business_chart([]), f"✗ Failed: {e}"
|
| 446 |
|
| 447 |
+
def refresh_table():
|
| 448 |
+
"""Rebuild comparison table from current session results — picks up audio URLs from completed uploads."""
|
| 449 |
+
return build_comparison_table(_session_results)
|
| 450 |
+
|
| 451 |
# ── UI ───────────────────────────────���────────────────────────────────────────
|
| 452 |
|
| 453 |
def build_ui():
|
|
|
|
| 498 |
|
| 499 |
comparison_table = gr.Dataframe(
|
| 500 |
value=build_comparison_table([]),
|
| 501 |
+
label="Eval Results — click a row to play audio",
|
| 502 |
interactive=False,
|
| 503 |
)
|
| 504 |
|
| 505 |
+
row_audio_player = gr.Audio(
|
| 506 |
+
label="Selected Row Audio",
|
| 507 |
+
visible=False,
|
| 508 |
+
type="filepath",
|
| 509 |
+
)
|
| 510 |
+
|
| 511 |
business_chart = gr.Plot(
|
| 512 |
value=build_business_chart([]),
|
| 513 |
label="Business Decision Chart",
|
|
|
|
| 515 |
|
| 516 |
with gr.Row():
|
| 517 |
clear_btn = gr.Button("🗑 Clear Session")
|
| 518 |
+
refresh_btn = gr.Button("🔄 Refresh Table")
|
| 519 |
load_history_btn = gr.Button("📂 Load History")
|
| 520 |
export_session_btn = gr.Button("⬇ Export Session")
|
| 521 |
export_all_btn = gr.Button("⬇ Export Full History")
|
|
|
|
| 548 |
fn=clear_results,
|
| 549 |
outputs=[comparison_table, business_chart, export_status],
|
| 550 |
)
|
| 551 |
+
refresh_btn.click(
|
| 552 |
+
fn=refresh_table,
|
| 553 |
+
outputs=[comparison_table],
|
| 554 |
+
)
|
| 555 |
+
comparison_table.select(
|
| 556 |
+
fn=on_row_select,
|
| 557 |
+
inputs=[comparison_table],
|
| 558 |
+
outputs=[row_audio_player],
|
| 559 |
+
)
|
| 560 |
|
| 561 |
load_history_btn.click(
|
| 562 |
fn=load_history,
|
app/evaluator.py
CHANGED
|
@@ -186,4 +186,11 @@ def evaluate(
|
|
| 186 |
df = pd.DataFrame([result])
|
| 187 |
df.to_csv(results_path, mode="a", header=not os.path.exists(results_path), index=False)
|
| 188 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
return result
|
|
|
|
| 186 |
df = pd.DataFrame([result])
|
| 187 |
df.to_csv(results_path, mode="a", header=not os.path.exists(results_path), index=False)
|
| 188 |
|
| 189 |
+
# upload updated CSV to Supabase in background
|
| 190 |
+
try:
|
| 191 |
+
from storage import upload_csv_background
|
| 192 |
+
upload_csv_background(results_path)
|
| 193 |
+
except Exception as e:
|
| 194 |
+
print(f"[Storage] CSV background upload skipped: {e}")
|
| 195 |
+
|
| 196 |
return result
|
app/results/eval_log.csv
DELETED
|
@@ -1,68 +0,0 @@
|
|
| 1 |
-
timestamp,engine,engine_type,production_ready,band,input_text,voice,wer,utmos,rtf,latency_s,engine_cost_usd,chirp_equiv_usd,chars
|
| 2 |
-
2026-04-10 14:00:29,Kokoro (tuned),neural-local,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,af_heart,0.0588,4.515,0.506,3.064,0.0,0.001328,83
|
| 3 |
-
2026-04-10 14:01:08,Parler-TTS (mini),neural-local,False,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,described:K-2,0.0588,3.967,2.402,13.021,0.0,0.001328,83
|
| 4 |
-
2026-04-10 14:02:33,Parler-TTS (mini),neural-local,False,9-12,"""While your articulation was precise, the tone felt a bit overly formal for this specific prompt. Try to strike a balance between professional authority and conversational engagement.""",described:9-12,0.0741,4.269,1.923,19.376,0.0,0.002944,184
|
| 5 |
-
2026-04-10 14:02:53,Kokoro (tuned),neural-local,True,9-12,"""While your articulation was precise, the tone felt a bit overly formal for this specific prompt. Try to strike a balance between professional authority and conversational engagement.""",am_echo,0.0741,4.434,0.073,0.746,0.0,0.002944,184
|
| 6 |
-
2026-04-10 14:03:22,edge-tts (Microsoft),neural-cloud-free,False,9-12,"""While your articulation was precise, the tone felt a bit overly formal for this specific prompt. Try to strike a balance between professional authority and conversational engagement.""",en-US-JennyNeural,0.0741,,0.12,1.253,0.0,0.002944,184
|
| 7 |
-
2026-04-10 14:06:25,Parler-TTS (mini),neural-local,False,3-5,"""I am so impressed by your vocabulary today. Using words like 'energetic' and 'observant' really makes your speech stand out!""",described:3-5,0.3,4.355,1.94,13.742,0.0,0.002016,126
|
| 8 |
-
2026-04-10 14:06:53,Parler-TTS (mini),neural-local,False,9-12,"""I am so impressed by your vocabulary today. Using words like 'energetic' and 'observant' really makes your speech stand out!""",described:9-12,0.2,4.298,1.923,13.953,0.0,0.002016,126
|
| 9 |
-
2026-04-10 14:07:19,Parler-TTS (mini),neural-local,False,6-8,"""I am so impressed by your vocabulary today. Using words like 'energetic' and 'observant' really makes your speech stand out!""",described:6-8,0.25,4.421,1.849,14.361,0.0,0.002016,126
|
| 10 |
-
2026-04-10 14:07:56,Kokoro (tuned),neural-local,True,6-8,"""I am so impressed by your vocabulary today. Using words like 'energetic' and 'observant' really makes your speech stand out!""",af_heart,0.25,4.535,0.059,0.484,0.0,0.002016,126
|
| 11 |
-
2026-04-10 14:08:35,Kokoro (tuned),neural-local,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,af_heart,0.0588,4.515,0.053,0.319,0.0,0.001328,83
|
| 12 |
-
2026-04-10 14:21:03,Piper (ONNX),neural-local,False,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,en_US-amy-medium,0.0588,4.511,0.052,0.297,0.0,0.001328,83
|
| 13 |
-
2026-04-10 14:21:26,Piper (ONNX),neural-local,False,9-12,Excellent work on your enunciation. Be mindful of your filler words—like 'um' and 'like'—as eliminating those will significantly increase the impact of your speech.,en_US-lessac-medium,0.25,4.256,0.037,0.332,0.0,0.002624,164
|
| 14 |
-
2026-04-10 14:31:59,Kokoro (tuned),neural-local,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,af_heart,0.0588,4.515,0.217,1.312,0.0,0.001328,83
|
| 15 |
-
2026-04-10 14:46:40,Chatterbox Turbo (RunPod),neural-cloud-paid,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,lucy,0.0588,4.243,0.627,3.185,0.0034,0.001328,83
|
| 16 |
-
2026-04-10 14:46:54,Chatterbox Turbo (RunPod),neural-cloud-paid,True,6-8,You did such a great job speaking today! I loved how loud and clear your voice was.,laura,0.0588,4.003,0.539,2.438,0.0034,0.001328,83
|
| 17 |
-
2026-04-10 14:47:15,Chatterbox Turbo (RunPod),neural-cloud-paid,True,3-5,"""Let's try that one more time. Focus on making your voice go up and down so you don't sound like a robot. You've got this!""",chloe,0.08,4.11,0.431,3.279,0.005,0.001968,123
|
| 18 |
-
2026-04-10 14:47:40,Chatterbox Turbo (RunPod),neural-cloud-paid,True,9-12,"While your articulation was precise, the tone felt a bit overly formal for this specific prompt. Try to strike a balance between professional authority and conversational engagement",ethan,0.037,4.378,0.415,4.311,0.0054,0.002896,181
|
| 19 |
-
2026-04-10 14:48:28,Chatterbox Turbo (RunPod),neural-cloud-paid,True,K-2,That was a fantastic effort! I noticed how you paused at the periods to take a breath. It made your reading sound very natural and easy to follow.,lucy,0.1429,4.397,0.517,3.807,0.0056,0.002336,146
|
| 20 |
-
2026-04-10 14:53:17,Kokoro (tuned),neural-local,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,af_heart,0.0588,4.517,0.188,1.137,0.0,0.001328,83
|
| 21 |
-
2026-04-10 14:54:04,Kokoro (tuned),neural-local,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,af_heart,0.0588,4.518,0.067,0.403,0.0,0.001328,83
|
| 22 |
-
2026-04-10 14:54:28,Chatterbox Turbo (RunPod),neural-cloud-paid,True,6-8,"You are doing a great job. Next time, try to look at the ending of the words like 'ed' and 'ing' to make sure you’re saying the whole word clearly",madison,0.2333,4.314,0.53,4.093,0.00772,0.002336,146
|
| 23 |
-
2026-04-10 14:55:35,Parler-TTS (mini),neural-local,False,3-5,"""I am so impressed by your vocabulary today. Using words like 'energetic' and 'observant' really makes your speech stand out!",described:3-5,0.25,4.273,2.307,17.411,0.0,0.002,125
|
| 24 |
-
2026-04-10 14:56:02,Piper (ONNX),neural-local,False,9-12,"While your articulation was precise, the tone felt a bit overly formal for this specific prompt. Try to strike a balance between professional authority and conversational engagement.",en_US-lessac-medium,0.0741,3.901,0.04,0.377,0.0,0.002912,182
|
| 25 |
-
2026-04-10 14:56:23,Chatterbox Turbo (RunPod),neural-cloud-paid,True,9-12,"While your articulation was precise, the tone felt a bit overly formal for this specific prompt. Try to strike a balance between professional authority and conversational engagement.",ethan,0.0,4.403,0.403,3.962,0.00982,0.002912,182
|
| 26 |
-
2026-04-10 15:09:25,Chatterbox Turbo (RunPod),neural-cloud-paid,True,6-8,"Your ideas are very clear, but some of your words are blending together. Focus on 'crisp' consonants at the ends of your words—especially those 't' and 'd' sounds—to make sure everyone understands your points.",evelyn,0.2059,4.474,0.961,13.418,0.01396,0.003344,209
|
| 27 |
-
2026-04-10 15:56:11,Kokoro (tuned),neural-local,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,af_heart,0.0588,4.515,0.247,1.493,0.0,0.001328,83
|
| 28 |
-
2026-04-10 15:58:27,Kokoro (tuned),neural-local,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,af_heart,0.0588,4.515,0.153,0.925,0.0,0.001328,83
|
| 29 |
-
2026-04-10 16:07:16,Kokoro (tuned),neural-local,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,af_heart,0.0588,4.515,0.199,1.202,0.0,0.001328,83
|
| 30 |
-
2026-04-10 16:14:10,Chatterbox Turbo (RunPod),neural-cloud-paid,True,9-12,"Your introduction really grabbed my attention. To make your middle section just as strong, try to use transitional words like 'furthermore' or 'on the other hand' to help your ideas flow together.",ethan,0.125,4.453,0.408,4.315,0.01058,0.003136,196
|
| 31 |
-
2026-04-10 16:16:20,pyttsx3 (baseline),rule-based-local,False,9-12,"Your introduction really grabbed my attention. To make your middle section just as strong, try to use transitional words like 'furthermore' or 'on the other hand' to help your ideas flow together.",SAPI5-default,0.125,3.843,0.018,0.22,0.0,0.003136,196
|
| 32 |
-
2026-04-10 16:17:06,Kokoro (tuned),neural-local,True,9-12,"Your introduction really grabbed my attention. To make your middle section just as strong, try to use transitional words like 'furthermore' or 'on the other hand' to help your ideas flow together.",af_heart,0.125,4.545,0.085,0.987,0.0,0.003136,196
|
| 33 |
-
2026-04-10 16:17:19,edge-tts (Microsoft),neural-cloud-free,False,9-12,"Your introduction really grabbed my attention. To make your middle section just as strong, try to use transitional words like 'furthermore' or 'on the other hand' to help your ideas flow together.",en-US-JennyNeural,0.0938,,0.076,0.803,0.0,0.003136,196
|
| 34 |
-
2026-04-10 16:42:29,edge-tts (Microsoft),neural-cloud-free,False,6-8,You did such a great job speaking today! I loved how loud and clear your voice was.,en-US-JennyNeural,0.0588,4.515,0.129,0.798,0.0,0.001328,83
|
| 35 |
-
2026-04-10 16:43:04,edge-tts (Microsoft),neural-cloud-free,False,6-8,Excellent work on your enunciation. Be mindful of your filler words—like 'um' and 'like'—as eliminating those will significantly increase the impact of your speech,en-US-JennyNeural,0.25,4.306,0.078,0.823,0.0,0.002608,163
|
| 36 |
-
2026-04-10 16:43:23,Kokoro (tuned),neural-local,True,6-8,Excellent work on your enunciation. Be mindful of your filler words—like 'um' and 'like'—as eliminating those will significantly increase the impact of your speech,af_heart,0.25,,0.122,1.302,0.0,0.002608,163
|
| 37 |
-
2026-04-10 16:43:54,Kokoro (tuned),neural-local,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,af_heart,0.0588,,0.055,0.331,0.0,0.001328,83
|
| 38 |
-
2026-04-10 16:48:16,Chatterbox Turbo (RunPod),neural-cloud-paid,True,9-12,Excellent work on your enunciation. Be mindful of your filler words—like 'um' and 'like'—as eliminating those will significantly increase the impact of your speech,ethan,0.25,,2.544,24.774,0.00974,0.002608,163
|
| 39 |
-
2026-04-10 16:48:35,pyttsx3 (baseline),rule-based-local,False,9-12,Excellent work on your enunciation. Be mindful of your filler words—like 'um' and 'like'—as eliminating those will significantly increase the impact of your speech,SAPI5-default,0.25,,0.023,0.227,0.0,0.002608,163
|
| 40 |
-
2026-04-10 16:48:53,Piper (ONNX),neural-local,False,9-12,Excellent work on your enunciation. Be mindful of your filler words—like 'um' and 'like'—as eliminating those will significantly increase the impact of your speech,en_US-lessac-medium,0.2917,,0.042,0.376,0.0,0.002608,163
|
| 41 |
-
2026-04-10 16:49:48,Kokoro (tuned),neural-local,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,af_heart,0.0588,,0.108,0.651,0.0,0.001328,83
|
| 42 |
-
2026-04-10 16:51:32,Kokoro (tuned),neural-local,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,af_heart,0.0588,4.52,0.501,3.033,0.0,0.001328,83
|
| 43 |
-
2026-04-10 16:51:51,edge-tts (Microsoft),neural-cloud-free,False,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,en-US-JennyNeural,0.0588,4.515,0.108,0.665,0.0,0.001328,83
|
| 44 |
-
2026-04-10 16:52:35,edge-tts (Microsoft),neural-cloud-free,False,6-8,"""I noticed how you adjusted your pace during the presentation. That really helped the audience follow your main points. Great adjustment!""",en-US-JennyNeural,0.0952,4.504,0.151,1.398,0.0,0.002208,138
|
| 45 |
-
2026-04-10 16:53:37,Parler-TTS (mini),neural-local,False,9-12,"Your analysis of the character's motivation was quite sophisticated. To take this to the next level, consider how the historical context influenced those choices",described:9-12,0.0417,4.183,2.464,21.515,0.0,0.002576,161
|
| 46 |
-
2026-04-10 16:54:03,Piper (ONNX),neural-local,False,9-12,"Your analysis of the character's motivation was quite sophisticated. To take this to the next level, consider how the historical context influenced those choices",en_US-lessac-medium,0.0833,4.451,0.226,2.007,0.0,0.002576,161
|
| 47 |
-
2026-04-10 16:54:25,Chatterbox Turbo (RunPod),neural-cloud-paid,True,9-12,"Your analysis of the character's motivation was quite sophisticated. To take this to the next level, consider how the historical context influenced those choices",ethan,0.0417,4.429,0.414,3.601,0.0087,0.002576,161
|
| 48 |
-
2026-04-13 12:57:52,edge-tts (Microsoft),neural-cloud-free,False,6-8,You did such a great job speaking today! I loved how loud and clear your voice was.,en-US-JennyNeural,0.0588,4.515,0.247,1.526,0.0,0.001328,83
|
| 49 |
-
2026-04-13 12:58:46,Kokoro (tuned),neural-local,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,af_heart,0.0588,4.515,0.492,2.978,0.0,0.001328,83
|
| 50 |
-
2026-04-13 15:55:10,Kokoro (tuned),neural-local,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,af_heart,0.0588,4.516,0.509,3.08,0.0,0.001328,83
|
| 51 |
-
2026-04-13 15:55:17,edge-tts (Microsoft),neural-cloud-free,False,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,en-US-JennyNeural,0.0588,4.515,0.155,0.954,0.0,0.001328,83
|
| 52 |
-
2026-04-13 15:55:31,pyttsx3 (baseline),rule-based-local,False,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,SAPI5-default,0.0588,3.554,0.025,0.166,0.0,0.001328,83
|
| 53 |
-
2026-04-13 15:56:29,Parler-TTS (mini),neural-local,False,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,described:K-2,0.0588,4.209,4.379,23.997,0.0,0.001328,83
|
| 54 |
-
2026-04-13 15:56:50,Piper (ONNX),neural-local,False,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,en_US-amy-medium,0.0588,4.504,0.408,2.317,0.0,0.001328,83
|
| 55 |
-
2026-04-13 16:14:09,Kokoro (tuned),neural-local,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,af_heart,0.0588,4.513,0.2,1.208,0.0,0.001328,83
|
| 56 |
-
2026-04-13 16:18:11,Kokoro (tuned),neural-local,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,af_heart,0.0588,4.518,0.136,0.824,0.0,0.001328,83
|
| 57 |
-
2026-04-13 16:35:07,Parler-TTS (mini),neural-local,False,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,described:K-2,0.0588,4.301,44.085,237.486,0.0,0.001328,83
|
| 58 |
-
2026-04-13 18:21:33,ElevenLabs (Turbo),neural-cloud-paid,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,Jessica,0.0588,,,0.351,0.01245,0.001328,83
|
| 59 |
-
2026-04-13 18:22:04,Kokoro (tuned),neural-local,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,af_heart,0.0588,,0.44,2.66,0.0,0.001328,83
|
| 60 |
-
2026-04-13 18:23:11,Piper (ONNX),neural-local,False,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,en_US-amy-medium,0.0588,,0.05,0.276,0.0,0.001328,83
|
| 61 |
-
2026-04-13 18:25:45,Kokoro (tuned),neural-local,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,af_heart,0.0588,4.517,0.328,1.986,0.0,0.001328,83
|
| 62 |
-
2026-04-13 18:25:58,ElevenLabs (Turbo),neural-cloud-paid,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,Jessica,0.0588,4.154,0.08,0.441,0.01245,0.001328,83
|
| 63 |
-
2026-04-13 18:26:18,ElevenLabs (Turbo),neural-cloud-paid,True,6-8,You did such a great job speaking today! I loved how loud and clear your voice was.,Sarah,0.0588,4.453,0.071,0.354,0.01245,0.001328,83
|
| 64 |
-
2026-04-13 18:26:29,ElevenLabs (Turbo),neural-cloud-paid,True,9-12,You did such a great job speaking today! I loved how loud and clear your voice was.,Brian,0.0588,3.848,0.075,0.351,0.01245,0.001328,83
|
| 65 |
-
2026-04-13 18:27:02,ElevenLabs (Turbo),neural-cloud-paid,True,9-12,"""Your analysis of the primary source was incredibly insightful today. You managed to connect the historical context to modern-day implications with real clarity. Next time, try to slow down during your transitions to let those big ideas really sink in for the audience.""",Brian,0.0465,4.151,0.052,0.746,0.0405,0.00432,270
|
| 66 |
-
2026-04-13 18:43:42,ElevenLabs (Turbo),neural-cloud-paid,True,K-2,You did such a great job speaking today! I loved how loud and clear your voice was.,Jessica,0.0588,4.197,0.084,0.461,0.01245,0.001328,83
|
| 67 |
-
2026-04-13 18:43:53,ElevenLabs (Turbo),neural-cloud-paid,True,9-12,"""You've made significant progress on your vocal projection. Your voice was steady and easy to follow throughout the entire presentation. To take this to the next level, experiment with varying your pitch to emphasize your most critical data points.""",Brian,0.0513,4.019,0.045,0.602,0.03735,0.003984,249
|
| 68 |
-
2026-04-13 18:45:27,ElevenLabs (Turbo),neural-cloud-paid,True,6-8,"""You've made significant progress on your vocal projection. Your voice was steady and easy to follow throughout the entire presentation. To take this to the next level, experiment with varying your pitch to emphasize your most critical data points.""",Sarah,0.0513,4.255,0.043,0.579,0.03735,0.003984,249
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/storage.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app/storage.py
|
| 2 |
+
# Supabase Storage integration for persisting generated audio files.
|
| 3 |
+
# Uploads audio to the tts-audio public bucket and returns a public URL.
|
| 4 |
+
# Called as a background thread from run_synthesis — non-blocking.
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import threading
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
from supabase import create_client, Client
|
| 10 |
+
|
| 11 |
+
# --- client setup ---
|
| 12 |
+
_client: Client | None = None
|
| 13 |
+
|
| 14 |
+
def _get_client() -> Client:
|
| 15 |
+
global _client
|
| 16 |
+
if _client is None:
|
| 17 |
+
url = os.getenv("SUPABASE_URL")
|
| 18 |
+
key = os.getenv("SUPABASE_ANON_KEY")
|
| 19 |
+
if not url or not key:
|
| 20 |
+
raise ValueError(
|
| 21 |
+
"SUPABASE_URL and SUPABASE_ANON_KEY must be set in .env"
|
| 22 |
+
)
|
| 23 |
+
_client = create_client(url, key)
|
| 24 |
+
return _client
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def upload_audio(local_path: str, filename: str) -> str | None:
|
| 28 |
+
"""
|
| 29 |
+
Upload an audio file to Supabase tts-audio bucket.
|
| 30 |
+
Returns the public URL on success, None on failure.
|
| 31 |
+
|
| 32 |
+
Args:
|
| 33 |
+
local_path: full path to local audio file
|
| 34 |
+
filename: destination filename in bucket (e.g. '2026-04-14_kokoro_K-2.wav')
|
| 35 |
+
"""
|
| 36 |
+
try:
|
| 37 |
+
client = _get_client()
|
| 38 |
+
with open(local_path, "rb") as f:
|
| 39 |
+
data = f.read()
|
| 40 |
+
|
| 41 |
+
# detect content type
|
| 42 |
+
ext = Path(local_path).suffix.lower()
|
| 43 |
+
content_type = "audio/mpeg" if ext == ".mp3" else "audio/wav"
|
| 44 |
+
|
| 45 |
+
client.storage.from_("tts-audio").upload(
|
| 46 |
+
path=filename,
|
| 47 |
+
file=data,
|
| 48 |
+
file_options={"content-type": content_type, "upsert": "true"},
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
# build public URL
|
| 52 |
+
result = client.storage.from_("tts-audio").get_public_url(filename)
|
| 53 |
+
return result
|
| 54 |
+
|
| 55 |
+
except Exception as e:
|
| 56 |
+
print(f"[Storage] Upload failed for {filename}: {e}")
|
| 57 |
+
return None
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def upload_audio_background(local_path: str, filename: str, callback=None) -> None:
|
| 61 |
+
"""
|
| 62 |
+
Upload audio in a background thread — non-blocking.
|
| 63 |
+
Optionally calls callback(url) when done, where url is None on failure.
|
| 64 |
+
|
| 65 |
+
Args:
|
| 66 |
+
local_path: full path to local audio file
|
| 67 |
+
filename: destination filename in bucket
|
| 68 |
+
callback: optional function(url: str | None) called after upload
|
| 69 |
+
"""
|
| 70 |
+
def _run():
|
| 71 |
+
url = upload_audio(local_path, filename)
|
| 72 |
+
if callback:
|
| 73 |
+
callback(url)
|
| 74 |
+
|
| 75 |
+
thread = threading.Thread(target=_run, daemon=True)
|
| 76 |
+
thread.start()
|
| 77 |
+
|
| 78 |
+
def upload_csv(local_path: str) -> bool:
|
| 79 |
+
"""
|
| 80 |
+
Upload eval_log.csv to Supabase tts-audio bucket.
|
| 81 |
+
Uses upsert so it overwrites the existing file.
|
| 82 |
+
Returns True on success, False on failure.
|
| 83 |
+
"""
|
| 84 |
+
try:
|
| 85 |
+
client = _get_client()
|
| 86 |
+
with open(local_path, "rb") as f:
|
| 87 |
+
data = f.read()
|
| 88 |
+
|
| 89 |
+
client.storage.from_("tts-audio").upload(
|
| 90 |
+
path="eval_log.csv",
|
| 91 |
+
file=data,
|
| 92 |
+
file_options={"content-type": "text/csv", "upsert": "true"},
|
| 93 |
+
)
|
| 94 |
+
print("[Storage] eval_log.csv uploaded to Supabase")
|
| 95 |
+
return True
|
| 96 |
+
|
| 97 |
+
except Exception as e:
|
| 98 |
+
print(f"[Storage] CSV upload failed: {e}")
|
| 99 |
+
return False
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def download_csv(local_path: str) -> bool:
|
| 103 |
+
"""
|
| 104 |
+
Download eval_log.csv from Supabase tts-audio bucket to local path.
|
| 105 |
+
Returns True on success, False on failure.
|
| 106 |
+
"""
|
| 107 |
+
try:
|
| 108 |
+
client = _get_client()
|
| 109 |
+
response = client.storage.from_("tts-audio").download("eval_log.csv")
|
| 110 |
+
|
| 111 |
+
os.makedirs(os.path.dirname(local_path), exist_ok=True)
|
| 112 |
+
with open(local_path, "wb") as f:
|
| 113 |
+
f.write(response)
|
| 114 |
+
|
| 115 |
+
print("[Storage] eval_log.csv downloaded from Supabase")
|
| 116 |
+
return True
|
| 117 |
+
|
| 118 |
+
except Exception as e:
|
| 119 |
+
print(f"[Storage] CSV download failed (will use local fallback): {e}")
|
| 120 |
+
return False
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def upload_csv_background(local_path: str) -> None:
|
| 124 |
+
"""Upload CSV in background thread — non-blocking."""
|
| 125 |
+
thread = threading.Thread(target=upload_csv, args=(local_path,), daemon=True)
|
| 126 |
+
thread.start()
|
pyproject.toml
CHANGED
|
@@ -16,7 +16,7 @@ dependencies = [
|
|
| 16 |
"librosa",
|
| 17 |
"ipywidgets",
|
| 18 |
"nest-asyncio",
|
| 19 |
-
"gradio",
|
| 20 |
"jiwer",
|
| 21 |
"faster-whisper",
|
| 22 |
"torch>=2.6.0",
|
|
@@ -27,6 +27,7 @@ dependencies = [
|
|
| 27 |
"python-dotenv>=1.2.2",
|
| 28 |
"plotly>=6.7.0",
|
| 29 |
"elevenlabs>=2.43.0",
|
|
|
|
| 30 |
]
|
| 31 |
|
| 32 |
[tool.uv.sources]
|
|
|
|
| 16 |
"librosa",
|
| 17 |
"ipywidgets",
|
| 18 |
"nest-asyncio",
|
| 19 |
+
"gradio>=6.12.0",
|
| 20 |
"jiwer",
|
| 21 |
"faster-whisper",
|
| 22 |
"torch>=2.6.0",
|
|
|
|
| 27 |
"python-dotenv>=1.2.2",
|
| 28 |
"plotly>=6.7.0",
|
| 29 |
"elevenlabs>=2.43.0",
|
| 30 |
+
"supabase>=2.28.3",
|
| 31 |
]
|
| 32 |
|
| 33 |
[tool.uv.sources]
|
requirements.txt
CHANGED
|
Binary files a/requirements.txt and b/requirements.txt differ
|
|
|
uv.lock
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|