Files
vocero-client/s2s-client/CHANGELOG.md
T
2026-09-10 15:57:48 -04:00

11 KiB
Raw Blame History

Changelog — s2s-client (Azurian Vocero)

Tutte le modifiche rilevanti al progetto sono documentate in questo file. Formato basato su Keep a Changelog.


[2026-09-07]

Aggiunto — Supporto episode_id (podcast episode inject)

Nuovo parametro query string ?episode_id= affiancato a ?phoneNumber= che abilita il caricamento e l'iniezione automatica di un episodio podcast come contesto PRIMARIO nel prompt LLM.

URL di esempio:

/?phoneNumber=%2B447432477433&episode_id=episode-19569f77-a06e-5ce5-a52f-6299dbd67d03

File modificati:

  • server.py (server.py):

    1. Aggiunta costante PODCAST_EPISODE_HINT (L426-L429) per l'hint semantico nel prompt.
    2. Aggiunta funzione _resolve_episode_context(raw_value) (L475-L504): normalizza la risposta API (dict/list/str, eventuale wrapper {"result": …}), la serializza in JSON (cap 24.000 chars) e la avvolge con i marker <<<PODCAST_EPISODE_BEGIN/END>>>.
    3. Aggiunta funzione asincrona _fetch_podcast_episode(episode_id) (L1010-L1078): GET plain verso
      https://aquachile.azurianlab.com/api/v1/podcast/episodes/{episode_id}
      (timeout 15s, follow redirects, error handling soft — non rompe mai il flusso, restituisce None su fail).
    4. Estesa _augment_instructions_with_profile(...) (L1137-L1181) con nuovo parametro episode_context_text; l'ordine di inject ora è (più specifico → più generico): episode → profile → reports → summary.
    5. Estesa _patch_client_session_update_with_profile(...) (L1184-L1306) con supporto a episode_context_text e logging dedicato [INJECT-EPISODE] / [DEBUG-EPISODE].
    6. WebSocket connect handler (L1791-L1959):
      • legge episode_id da query params assieme a phoneNumber;
      • esegue fetch episodio + resolve PRIMA di profile/reports/summary;
      • salva injected_episode_context e inject_episode_id nel session_ctx condiviso.
    7. _client_to_upstream (L2649-L2661): passa ctx_episode_context al patch di session.update.
    8. Endpoint POST /api/reasoning:
      • esteso modello ReasoningRequest con campo episode_id: str | None = None (L1309-L1323);
      • nel handler (L1411-L1577) fetch episodio indipendente da phone;
      • inject_episode_text viene prepend a user_context_parts (prima di tutti);
      • inoltro a NODE_RED / OPENAI con logging esteso (episodeIdLen).
  • main.js (main.js):

    1. Lettura da query string: const voceroEpisodeId = pageParams.get("episode_id") (L676-L680).
    2. fetchOpenAiReasoningAnswer (L1337-L1364): campo episode_id aggiunto al body POST /api/reasoning, logKey e console.info estesi.
    3. Costruzione URL WebSocket (L2323-L2342): episode_id appeso a query params sia con API URL.searchParams sia nel fallback stringa (concat con &).

Aggiunto — S2S_START_SESSION_MODE: 3 modalità di start sessione

Env var letta dal server e propagata al client via GET /api/config.startSessionMode. Scopo: governare se e come il microfono si auto-accende e se il modello saluta per primo (flow simile a wake-word). Valori supportati:

Valore Descrizione
(vuoto) / default Start manuale: solo tap sull'Orb / pulsante Start
TALK Dopo 2s overlay fullscreen "Tocca ovunque per iniziare" (policy safe per Safari/iOS getUserMedia). Click/touch/tasto dentro la user gesture → doStart(). WS client inietta bootstrap "hola vocero" + requestResponse().
TALKFIRST Dopo 2s tenta doStart() DIRETTAMENTE SENZA click, pre-caricando pendingWakeWordConfirmationText = "hola vocero" (stessa variabile della wake-word vera). Se i permessi mic sono già "allow always" funziona senza click. Se fallisce (policy Safari/iOS) → fallback sullo stesso unlocker di TALK con messaggio "permesso microfono".

File modificati:

  • server.py (server.py):

    1. Lettura env: S2S_START_SESSION_MODE = os.environ.get(...), .strip().upper() (L150).
    2. Banner [STARTUP] esteso con campo startSessionMode= (L196-L212).
    3. Endpoint GET /api/config → nuovo campo startSessionMode + log [INTERNAL] /api/config (L1383-L1400).
  • index.html (index.html):

    • Aggiunto nuovo #talk-unlocker overlay fullscreen (z-index 9999), hidden di default, con card "Preparando Vocero… / Toca en cualquier lugar para empezar a hablar" + spinner gradient.
  • style.css (style.css):

    • Classi .talk-unlocker, .talk-unlocker-hint, .talk-unlocker-spinner, .talk-unlocker-title, .talk-unlocker-sub + animazioni fade-in e pop-up card.
  • main.js (main.js):

    1. Variabile globale startSessionMode = "" + commento (L628-L632).
    2. Parse dentro fetchConfig() → uppercase, log in reportDebug("B",…) (L2309-L2320).
    3. Passaggio a WS client → startWithResponse: startSessionMode === "TALK" (L2683-L2692).
    4. Blocco scheduling auto-start (L2976-L3083):
      • bootstrapWithAutoStart(): await fetchConfig() → maybeScheduleAutoStart();
      • setTimeout(2000 ms) warmup per finire fetch di episode/profile/reports;
      • showTalkUnlocker(fallbackHint): gestisce label custom nel caso fallback TALKFIRST;
      • tryTalkFirstDirect() (TALKFIRST): set pendingWakeWordConfirmationText="hola vocero" + doStart() + try/catch con teardown e fallback unlocker;
      • listener unlocker click/touchstart/keydown → startTalkFromGesture() con createResumedAudioContext() dentro la gesture (per iOS silent-free).
    5. onClientStatus() (L2878-L2932) aggiunto hideTalkUnlocker() in connecting/queued/your-turn/connected.
  • ws/s2s-ws-client.js (s2s-ws-client.js):

    1. Costruttore: nuove proprietà _startWithResponse, _startWithResponseText (default "hola vocero"), _initialResponseSent guardia one-shot (L163-L178).
    2. Handler session.updated (L688-L713): Solo per TALK (non TALKFIRST, che usa invece pendingWakeWordConfirmationText): this.sendUserText("hola vocero") + this.requestResponse() — stesso identico code path della wake-word.
    3. requestResponse / _createResponseNow ripristinati puliti (solo payload opzionale image; nessun shaped response.create).

Modificato — #reports-sidebar-toggle visibile solo con ?debug=true

Il bottone "reports" nella topbar (toggle del pannello laterale reports) è ora data-debug-only="true" — viene quindi mostrato solo se l'URL contiene ?debug=true, analogamente al cluster .topbar-right.

File modificati:

  • index.html → #reports-sidebar-toggle (index.html): aggiunto data-debug-only="true".
  • main.js → applyDebugOnlyVisibilityGate() (L48-L109): esteso per iterare tutti gli elementi [data-debug-only="true"] (non solo .topbar-right); riga 1227: reportsSidebarToggle.hidden = false gated a qs.get("debug") === "true" (L1227-L1246) + log rosso 🚫 FORZATO HIDDEN quando manca debug=true.
  • style.css → regola di sicurezza già presente: [data-debug-only]:not([data-debug-enabled]) { display:none !important; visibility:hidden; opacity:0 } (L532-L537).

[2026-09-04]

Modificato — Chat sempre visibile + integrazione seamless

Riorganizzato il layout per mostrare la chat testuale in modo permanente e integrato visivamente con l'Orb nella parte superiore dello stage.

  • Rimosso pulsante toggle chat nella topbar (#chat-btn) e relativo overlay.
  • Stage con l'Orb nella parte superiore; nuovo #chat-panel a seguire (cronologia
    • form input fisso sopra il footer sticky).
  • Aggiornata logica in main.js e ui/chat.js per l'invio testo tramite client.sendUserText e auto-scroll senza controlli di visibilità pannello.
  • Versione banner deploy: 2026-09-03 14:40.

Risolto — Stabilità integrazione chat mobile

  • Errore Missing element: #chat-btn risolto con selettori non-strict (document.querySelector) per elementi opzionali rimossi.
  • Refactoring estetico "seamless": chat senza cornici, ombre o sfondi (background:transparent, box-shadow:none).
  • Layout mobile:
    • padding incrementato in #app (56px) e .chat-history (96px) per evitare sovrapposizioni col footer sticky;
    • margini fissi bolle: margin-right:22px (Assistant), margin-left:6px (User).
  • Auto-scroll streaming: implementato _scrollToBottom con catena di retry multipli (requestAnimationFrame + setTimeout) per compensare il line-wrap asincrono durante l'arrivo dei token; overflow-anchor:none + scroll-behavior:auto !important.

Aggiunto — Avatar utente iniziali + modale profilo

  • Sostituito testo nome utente nell'header con avatar circolare 40×40px (gradient brand) contenente le iniziali calcolate (es. "AV").
  • Nuova <dialog id="user-profile-dialog"> in index.html che si apre al click sull'avatar: mostra email, telefono, ruolo, azienda, con supporto a shape annidate del payload profilo.
  • Chiusura modale: tasto ESC, click fuori card, pulsante × dedicato.

[Note di formato]

  • Cap campo prompt episodio: 24.000 caratteri (transcript lunghi).
  • Cap profilo / summary: 1.600 caratteri ciascuno.
  • Cap reports list: 12.000 caratteri.
  • Ordine inject prompt LLM (WS session.update + reasoning):
    1. Episodio podcast corrente (PRIMARIO)
    2. Profilo chiamante (identity)
    3. Lista reports / script podcast schedulati
    4. Summary ultima conversazione
  • Tutti i blocchi sono marcati con <<<NOME_BLOCCO_BEGIN/END>>> per ispezione log.