11 KiB
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):- Aggiunta costante
PODCAST_EPISODE_HINT(L426-L429) per l'hint semantico nel prompt. - 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>>>. - 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, restituisceNonesu fail). - Estesa
_augment_instructions_with_profile(...)(L1137-L1181) con nuovo parametroepisode_context_text; l'ordine di inject ora è (più specifico → più generico): episode → profile → reports → summary. - Estesa
_patch_client_session_update_with_profile(...)(L1184-L1306) con supporto aepisode_context_texte logging dedicato[INJECT-EPISODE]/[DEBUG-EPISODE]. - WebSocket connect handler (L1791-L1959):
- legge
episode_idda query params assieme aphoneNumber; - esegue fetch episodio + resolve PRIMA di profile/reports/summary;
- salva
injected_episode_contexteinject_episode_idnelsession_ctxcondiviso.
- legge
_client_to_upstream(L2649-L2661): passactx_episode_contextal patch disession.update.- Endpoint
POST /api/reasoning:- esteso modello
ReasoningRequestcon campoepisode_id: str | None = None(L1309-L1323); - nel handler (L1411-L1577) fetch episodio indipendente da phone;
inject_episode_textviene prepend auser_context_parts(prima di tutti);- inoltro a NODE_RED / OPENAI con logging esteso (
episodeIdLen).
- esteso modello
- Aggiunta costante
-
main.js(main.js):- Lettura da query string:
const voceroEpisodeId = pageParams.get("episode_id")(L676-L680). fetchOpenAiReasoningAnswer(L1337-L1364): campoepisode_idaggiunto al body POST/api/reasoning, logKey econsole.infoestesi.- Costruzione URL WebSocket (L2323-L2342):
episode_idappeso a query params sia con APIURL.searchParamssia nel fallback stringa (concat con&).
- Lettura da query string:
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):- Lettura env:
S2S_START_SESSION_MODE = os.environ.get(...),.strip().upper()(L150). - Banner
[STARTUP]esteso con campostartSessionMode=(L196-L212). - Endpoint
GET /api/config→ nuovo campostartSessionMode+ log[INTERNAL] /api/config(L1383-L1400).
- Lettura env:
-
index.html(index.html):- Aggiunto nuovo
#talk-unlockeroverlay fullscreen (z-index 9999),hiddendi default, con card "Preparando Vocero… / Toca en cualquier lugar para empezar a hablar" + spinner gradient.
- Aggiunto nuovo
-
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.
- Classi
-
main.js(main.js):- Variabile globale
startSessionMode = ""+ commento (L628-L632). - Parse dentro
fetchConfig()→ uppercase, log inreportDebug("B",…)(L2309-L2320). - Passaggio a WS client →
startWithResponse: startSessionMode === "TALK"(L2683-L2692). - 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): setpendingWakeWordConfirmationText="hola vocero"+doStart()+ try/catch con teardown e fallback unlocker;- listener unlocker click/touchstart/keydown →
startTalkFromGesture()concreateResumedAudioContext()dentro la gesture (per iOS silent-free).
onClientStatus()(L2878-L2932) aggiuntohideTalkUnlocker()inconnecting/queued/your-turn/connected.
- Variabile globale
-
ws/s2s-ws-client.js(s2s-ws-client.js):- Costruttore: nuove proprietà
_startWithResponse,_startWithResponseText(default"hola vocero"),_initialResponseSentguardia one-shot (L163-L178). - Handler
session.updated(L688-L713): Solo perTALK(non TALKFIRST, che usa invecependingWakeWordConfirmationText):this.sendUserText("hola vocero") + this.requestResponse()— stesso identico code path della wake-word. requestResponse/_createResponseNowripristinati puliti (solo payload opzionaleimage; nessun shapedresponse.create).
- Costruttore: nuove proprietà
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): aggiuntodata-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 = falsegated aqs.get("debug") === "true"(L1227-L1246) + log rosso🚫 FORZATO HIDDENquando 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-panela seguire (cronologia- form input fisso sopra il footer sticky).
- Aggiornata logica in
main.jseui/chat.jsper l'invio testo tramiteclient.sendUserTexte auto-scroll senza controlli di visibilità pannello. - Versione banner deploy: 2026-09-03 14:40.
Risolto — Stabilità integrazione chat mobile
- Errore
Missing element: #chat-btnrisolto 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).
- padding incrementato in
- Auto-scroll streaming: implementato
_scrollToBottomcon 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">inindex.htmlche 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):
- Episodio podcast corrente (PRIMARIO)
- Profilo chiamante (identity)
- Lista reports / script podcast schedulati
- Summary ultima conversazione
- Tutti i blocchi sono marcati con
<<<NOME_BLOCCO_BEGIN/END>>>per ispezione log.