Spaces:
Running
Running
Commit ·
55dc8b5
1
Parent(s): 1ba52f6
Make diagnostics backend-aware (Gemini/Gmail)
Browse filesThe /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 |
-
"
|
| 618 |
-
"
|
| 619 |
-
"daemon":
|
| 620 |
-
"wifi":
|
| 621 |
-
"audio":
|
| 622 |
"config": {
|
| 623 |
-
"
|
| 624 |
-
"
|
| 625 |
-
"
|
|
|
|
|
|
|
| 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 |
-
|
| 1937 |
-
|
| 1938 |
-
|
| 1939 |
-
|
| 1940 |
-
|
| 1941 |
-
|
| 1942 |
-
|
| 1943 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 1960 |
-
!!cfg.
|
| 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('');
|