mukul-chauhan-methdai Claude Opus 4.8 commited on
Commit
55dc8b5
·
1 Parent(s): 1ba52f6

Make diagnostics backend-aware (Gemini/Gmail)

Browse files

The /api/diagnostics/health endpoint hardcoded OpenAI + Resend checks regardless of the active backends, so the cards showed red 'OpenAI / key NOT set' tiles for services not in use. Now it resolves the live VOICE_BACKEND (Gemini vs OpenAI) and email backend (Gmail vs Resend vs outbox-only), pings the correct host, reports the right config flags, and runs checks concurrently (~2s vs ~8s). Frontend tiles updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

src/reachy_mini_receptionist/main.py CHANGED
@@ -613,16 +613,74 @@ def _mount_dashboard_api(
613
  @app.get("/api/diagnostics/health")
614
  def _api_diagnostics_health():
615
  from reachy_mini_receptionist.config import config as _cfg
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
616
  results = {
617
- "openai_realtime": _check_tcp("api.openai.com", 443),
618
- "resend": _check_tcp("api.resend.com", 443),
619
- "daemon": _check_daemon(),
620
- "wifi": _check_wifi(),
621
- "audio": _check_audio(),
622
  "config": {
623
- "openai_key_set": bool(_cfg.OPENAI_API_KEY),
624
- "resend_key_set": bool(os.getenv("RESEND_API_KEY")),
625
- "resend_from": os.getenv("RESEND_FROM", "onboarding@resend.dev"),
 
 
626
  "ical_url_set": bool(os.getenv("RECEPTION_ICS_URL")),
627
  "model": _cfg.MODEL_NAME,
628
  },
 
613
  @app.get("/api/diagnostics/health")
614
  def _api_diagnostics_health():
615
  from reachy_mini_receptionist.config import config as _cfg
616
+ import concurrent.futures
617
+
618
+ # Resolve the ACTIVE backends so the checks reflect reality instead of
619
+ # always pinging OpenAI + Resend. Mirrors the selection logic in
620
+ # main.run() (voice) and tools/send_email.py (email).
621
+ voice_backend = (os.getenv("VOICE_BACKEND") or "gemini").strip().lower()
622
+ if voice_backend == "openai":
623
+ voice_host = "api.openai.com"
624
+ voice_key_set = bool(_cfg.OPENAI_API_KEY)
625
+ else:
626
+ voice_backend = "gemini"
627
+ voice_host = "generativelanguage.googleapis.com"
628
+ voice_key_set = bool(os.getenv("GEMINI_API_KEY"))
629
+
630
+ eb = (os.getenv("EMAIL_BACKEND") or "").strip().lower()
631
+ gmail_ready = bool(
632
+ (os.getenv("GMAIL_ADDRESS") or "").strip()
633
+ and (os.getenv("GMAIL_APP_PASSWORD") or "").strip()
634
+ )
635
+ resend_ready = bool((os.getenv("RESEND_API_KEY") or "").strip())
636
+ if eb not in ("gmail", "resend"):
637
+ eb = "gmail" if gmail_ready else ("resend" if resend_ready else "outbox")
638
+ if eb == "gmail":
639
+ email_ready = gmail_ready
640
+ email_from = (os.getenv("GMAIL_ADDRESS") or "").strip()
641
+ elif eb == "resend":
642
+ email_ready = resend_ready
643
+ email_from = os.getenv("RESEND_FROM", "onboarding@resend.dev")
644
+ else:
645
+ email_ready = False
646
+ email_from = ""
647
+
648
+ def _email_check() -> dict:
649
+ if eb == "gmail":
650
+ return _check_tcp("smtp.gmail.com", 465)
651
+ if eb == "resend":
652
+ return _check_tcp("api.resend.com", 443)
653
+ return {"ok": None, "note": "outbox only — no email backend configured"}
654
+
655
+ # Run the (slow, blocking) checks concurrently so the panel fills in
656
+ # ~2s instead of ~8s sequentially.
657
+ with concurrent.futures.ThreadPoolExecutor(max_workers=5) as ex:
658
+ f_voice = ex.submit(_check_tcp, voice_host, 443)
659
+ f_email = ex.submit(_email_check)
660
+ f_daemon = ex.submit(_check_daemon)
661
+ f_wifi = ex.submit(_check_wifi)
662
+ f_audio = ex.submit(_check_audio)
663
+ voice_check = f_voice.result()
664
+ email_check = f_email.result()
665
+ daemon_check = f_daemon.result()
666
+ wifi_check = f_wifi.result()
667
+ audio_check = f_audio.result()
668
+
669
+ voice_check["backend"] = voice_backend
670
+ email_check["backend"] = eb
671
+
672
  results = {
673
+ "voice": voice_check,
674
+ "email": email_check,
675
+ "daemon": daemon_check,
676
+ "wifi": wifi_check,
677
+ "audio": audio_check,
678
  "config": {
679
+ "voice_backend": voice_backend,
680
+ "voice_key_set": voice_key_set,
681
+ "email_backend": eb,
682
+ "email_ready": email_ready,
683
+ "email_from": email_from,
684
  "ical_url_set": bool(os.getenv("RECEPTION_ICS_URL")),
685
  "model": _cfg.MODEL_NAME,
686
  },
src/reachy_mini_receptionist/static/dashboard.html CHANGED
@@ -1933,14 +1933,28 @@
1933
  const c = d.checks || {};
1934
  const tiles = [];
1935
  const cfg = c.config || {};
1936
- const openai = c.openai_realtime || {};
1937
- tiles.push(diagTile('OpenAI Realtime',
1938
- openai.ok === undefined ? null : openai.ok,
1939
- openai.latency_ms != null ? `Latency: ${openai.latency_ms} ms${cfg.openai_key_set ? '' : ' · key NOT set'}` : (openai.error || '')));
1940
- const resend = c.resend || {};
1941
- tiles.push(diagTile('Resend',
1942
- resend.ok === undefined ? null : resend.ok,
1943
- resend.latency_ms != null ? `Latency: ${resend.latency_ms} ms${cfg.resend_key_set ? '' : ' · key NOT set'}` : (resend.error || '')));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1944
  const daemon = c.daemon || {};
1945
  tiles.push(diagTile('Reachy Daemon',
1946
  daemon.ok === undefined ? null : daemon.ok,
@@ -1956,8 +1970,8 @@
1956
  audio.sinks ? `${audio.sinks.length} sink(s)` : (audio.error || '—')));
1957
  tiles.push(diagTile('Calendar (iCal)',
1958
  cfg.ical_url_set, cfg.ical_url_set ? 'URL set' : 'No URL — walk-in flow only (lookup_employee)'));
1959
- tiles.push(diagTile('Sender (Resend From)',
1960
- !!cfg.resend_from, cfg.resend_from || ''));
1961
  tiles.push(diagTile('Model',
1962
  !!cfg.model, cfg.model || ''));
1963
  grid.innerHTML = tiles.join('');
 
1933
  const c = d.checks || {};
1934
  const tiles = [];
1935
  const cfg = c.config || {};
1936
+
1937
+ // Voice — backend-aware (Gemini by default, OpenAI if switched)
1938
+ const voice = c.voice || {};
1939
+ const voiceLabel = cfg.voice_backend === 'openai' ? 'Voice · OpenAI' : 'Voice · Gemini';
1940
+ tiles.push(diagTile(voiceLabel,
1941
+ voice.ok === undefined ? null : voice.ok,
1942
+ voice.latency_ms != null
1943
+ ? `Latency: ${voice.latency_ms} ms${cfg.voice_key_set ? '' : ' · key NOT set'}`
1944
+ : (voice.error || '')));
1945
+
1946
+ // Email — backend-aware (Gmail / Resend / outbox-only)
1947
+ const email = c.email || {};
1948
+ const emailLabel = cfg.email_backend === 'gmail' ? 'Email · Gmail'
1949
+ : cfg.email_backend === 'resend' ? 'Email · Resend'
1950
+ : 'Email · outbox only';
1951
+ const emailMeta = email.note
1952
+ ? email.note
1953
+ : (email.latency_ms != null
1954
+ ? `Latency: ${email.latency_ms} ms${cfg.email_ready ? '' : ' · not configured'}`
1955
+ : (email.error || ''));
1956
+ tiles.push(diagTile(emailLabel, email.ok === undefined ? null : email.ok, emailMeta));
1957
+
1958
  const daemon = c.daemon || {};
1959
  tiles.push(diagTile('Reachy Daemon',
1960
  daemon.ok === undefined ? null : daemon.ok,
 
1970
  audio.sinks ? `${audio.sinks.length} sink(s)` : (audio.error || '—')));
1971
  tiles.push(diagTile('Calendar (iCal)',
1972
  cfg.ical_url_set, cfg.ical_url_set ? 'URL set' : 'No URL — walk-in flow only (lookup_employee)'));
1973
+ tiles.push(diagTile('Sender',
1974
+ !!cfg.email_from, cfg.email_from || 'Not set'));
1975
  tiles.push(diagTile('Model',
1976
  !!cfg.model, cfg.model || ''));
1977
  grid.innerHTML = tiles.join('');