Spaces:
Sleeping
Sleeping
| """ | |
| Verifies the app ACTUALLY LAUNCHES under the pinned gradio, and that every | |
| event handler is wired with a matching signature. | |
| This is the check that was missing before: the previous app.py used | |
| `stream_every`, which does not exist in gradio 4.44 β it would have crashed on | |
| Space startup no matter how correct the rest of the code was. | |
| Models are stubbed, so this tests the app wiring, not the ML. | |
| """ | |
| import os | |
| import sys | |
| import types | |
| import numpy as np | |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |
| # ββ Stub the pipeline so no 3.5GB download happens βββββββββββββββββββββββββββ | |
| import pipeline as _pl | |
| def _fake_transcribe(self, audio, sr=16000): | |
| return "duba asusuna sannan ka aika 35000 zuwa Amina" | |
| def _fake_translate(self, text, src, tgt, **kw): | |
| table = { | |
| "duba asusuna sannan ka aika 35000 zuwa Amina": | |
| "check my balance and also send 35000 to amina", | |
| "2234567890": "2234567890", | |
| "eh": "yes", | |
| } | |
| return table.get(text, text) | |
| def _fake_tts(self, text): | |
| return 16000, (np.sin(np.arange(8000) / 20) * 8000).astype(np.int16) | |
| _pl.HausaVoiceAIPipeline.transcribe = _fake_transcribe | |
| _pl.HausaVoiceAIPipeline.transcribe_partial = _fake_transcribe | |
| _pl.HausaVoiceAIPipeline.translate = _fake_translate | |
| _pl.HausaVoiceAIPipeline.synthesize = _fake_tts | |
| _pl.HausaVoiceAIPipeline.hausa_text_to_audio = _fake_tts | |
| _pl.HausaVoiceAIPipeline._load_asr = lambda self: None | |
| _pl.HausaVoiceAIPipeline._load_asr_fast = lambda self: None | |
| _pl.HausaVoiceAIPipeline._load_nllb = lambda self: None | |
| _pl.HausaVoiceAIPipeline._load_tts = lambda self: None | |
| os.environ.setdefault("SHOW_PARTIALS", "0") | |
| PASS, FAIL = "\033[92mPASS\033[0m", "\033[91mFAIL\033[0m" | |
| results = [] | |
| def check(name, cond, detail=""): | |
| results.append((name, cond)) | |
| print(f" [{PASS if cond else FAIL}] {name}" + (f" β {detail}" if detail else "")) | |
| print("\nββ App imports and builds under the pinned gradio ββ") | |
| import gradio as gr | |
| print(f" gradio {gr.__version__}") | |
| import app as A | |
| check("app module imported", True) | |
| check("Blocks object built", isinstance(A.demo, gr.Blocks)) | |
| print("\nββ Streaming API exists in this gradio version ββ") | |
| import inspect | |
| p = inspect.signature(gr.Audio(streaming=True).stream).parameters | |
| check("Audio.stream accepts stream_every", "stream_every" in p) | |
| check("gr.skip available", hasattr(gr, "skip")) | |
| print("\nββ Event handlers are wired ββ") | |
| fns = A.demo.fns | |
| check("events registered", len(fns) > 0, f"{len(fns)} handlers") | |
| print("\nββ Text handler: full compound-request conversation ββ") | |
| st = None | |
| audio, convo, status, st = A.on_text( | |
| "duba asusuna sannan ka aika 35000 zuwa Amina", st) | |
| check("turn 1 returns audio", audio is not None and audio is not gr.skip()) | |
| check("turn 1 acknowledges BOTH tasks", | |
| "one at a time" in convo.lower() or "balance" in convo.lower(), | |
| status[:70]) | |
| audio, convo, status, st = A.on_text("2234567890", st) | |
| check("turn 2 delivers balance + continues transfer", | |
| "balance is" in convo.lower() and "amina" in convo.lower()) | |
| audio, convo, status, st = A.on_text("eh", st) | |
| check("turn 3 executes the transfer", "sent to amina" in convo.lower()) | |
| check("latency breakdown in status", "total" in status, status[-52:]) | |
| print("\nββ Empty input does not crash ββ") | |
| a, c, s, st2 = A.on_text("", None) | |
| check("empty text handled", a == gr.skip() or a is None) | |
| a, c, s, st2 = A.on_record(None, None) | |
| check("no recording handled", a == gr.skip() or a is None) | |
| print("\nββ Streaming handler with synthetic audio ββ") | |
| SR = 16000 | |
| def sil(ms, n=0.0005): | |
| k = int(SR * ms / 1000) | |
| return (np.random.randn(k) * n).astype(np.float32) | |
| def sp(ms, amp=0.25): | |
| k = int(SR * ms / 1000) | |
| t = np.arange(k) / SR | |
| s = (np.sin(2*np.pi*120*t) + 0.5*np.sin(2*np.pi*700*t) | |
| + 0.3*np.sin(2*np.pi*1220*t)) | |
| s = s * (0.6 + 0.4*np.sin(2*np.pi*4*t)) + np.random.randn(k)*0.01 | |
| return (s / np.abs(s).max() * amp).astype(np.float32) | |
| st = A.new_state() | |
| stream = np.concatenate([sil(400), sp(1600), sil(1200)]) | |
| step = int(SR * 0.5) | |
| got_reply = False | |
| n_skips = 0 | |
| for i in range(0, len(stream), step): | |
| out = A.on_stream((SR, stream[i:i+step]), st) | |
| check_len = len(out) == 5 | |
| audio_o, convo_o, status_o, partial_o, st = out | |
| if audio_o == gr.skip(): | |
| n_skips += 1 | |
| elif audio_o is not None: | |
| got_reply = True | |
| check("stream handler returns 5 values", check_len) | |
| check("VAD endpointed and produced a reply", got_reply) | |
| check("idle ticks return gr.skip (no player restart)", n_skips > 0, | |
| f"{n_skips} skipped ticks") | |
| print("\nββ Reset ββ") | |
| r = A.reset(st) | |
| check("reset returns 5 values", len(r) == 5) | |
| check("history cleared", "Press" in r[1] or "empty" in r[1]) | |
| print("\nββ Launch smoke test (real server, then shut down) ββ") | |
| try: | |
| A.demo.queue(max_size=4) | |
| _, url, _ = A.demo.launch(prevent_thread_lock=True, quiet=True, | |
| server_port=7899, show_api=False) | |
| check("Gradio server started", True, url or "local") | |
| import urllib.request | |
| code = urllib.request.urlopen("http://127.0.0.1:7899/", timeout=15).getcode() | |
| check("HTTP 200 from the app root", code == 200, f"status {code}") | |
| A.demo.close() | |
| except Exception as e: | |
| check("Gradio server started", False, str(e)[:110]) | |
| passed = sum(1 for _, ok in results if ok) | |
| print(f"\n{'='*62}\n {passed}/{len(results)} checks passed\n{'='*62}") | |
| sys.exit(0 if passed == len(results) else 1) | |