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

157 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Changelog — s2s-client (Azurian Vocero)
Tutte le modifiche rilevanti al progetto sono documentate in questo file.
Formato basato su [Keep a Changelog](https://keepachangelog.com/it/1.1.0/).
---
## [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](file:///home/duxiter/s2s/s2s-client/server.py)):
1. Aggiunta costante `PODCAST_EPISODE_HINT` ([L426-L429](file:///home/duxiter/s2s/s2s-client/server.py#L426-L429)) per l'hint semantico nel prompt.
2. Aggiunta funzione `_resolve_episode_context(raw_value)` ([L475-L504](file:///home/duxiter/s2s/s2s-client/server.py#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](file:///home/duxiter/s2s/s2s-client/server.py#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](file:///home/duxiter/s2s/s2s-client/server.py#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](file:///home/duxiter/s2s/s2s-client/server.py#L1184-L1306)) con supporto a `episode_context_text` e logging dedicato `[INJECT-EPISODE]` / `[DEBUG-EPISODE]`.
6. WebSocket connect handler ([L1791-L1959](file:///home/duxiter/s2s/s2s-client/server.py#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](file:///home/duxiter/s2s/s2s-client/server.py#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](file:///home/duxiter/s2s/s2s-client/server.py#L1309-L1323));
- nel handler ([L1411-L1577](file:///home/duxiter/s2s/s2s-client/server.py#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](file:///home/duxiter/s2s/s2s-client/main.js)):
1. Lettura da query string: `const voceroEpisodeId = pageParams.get("episode_id")` ([L676-L680](file:///home/duxiter/s2s/s2s-client/main.js#L676-L680)).
2. `fetchOpenAiReasoningAnswer` ([L1337-L1364](file:///home/duxiter/s2s/s2s-client/main.js#L1337-L1364)): campo `episode_id` aggiunto al body POST `/api/reasoning`, logKey e `console.info` estesi.
3. Costruzione URL WebSocket ([L2323-L2342](file:///home/duxiter/s2s/s2s-client/main.js#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](file:///home/duxiter/s2s/s2s-client/server.py)):
1. Lettura env: `S2S_START_SESSION_MODE = os.environ.get(...)`, `.strip().upper()` ([L150](file:///home/duxiter/s2s/s2s-client/server.py#L150)).
2. Banner `[STARTUP]` esteso con campo `startSessionMode=` ([L196-L212](file:///home/duxiter/s2s/s2s-client/server.py#L196-L212)).
3. Endpoint `GET /api/config` → nuovo campo `startSessionMode` + log `[INTERNAL] /api/config` ([L1383-L1400](file:///home/duxiter/s2s/s2s-client/server.py#L1383-L1400)).
- **`index.html`** ([index.html](file:///home/duxiter/s2s/s2s-client/index.html#L32-L39)):
- 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](file:///home/duxiter/s2s/s2s-client/style.css#L145-L215)):
- 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](file:///home/duxiter/s2s/s2s-client/main.js)):
1. Variabile globale `startSessionMode = ""` + commento ([L628-L632](file:///home/duxiter/s2s/s2s-client/main.js#L628-L632)).
2. Parse dentro `fetchConfig()` → uppercase, log in `reportDebug("B",…)` ([L2309-L2320](file:///home/duxiter/s2s/s2s-client/main.js#L2309-L2320)).
3. Passaggio a WS client → `startWithResponse: startSessionMode === "TALK"` ([L2683-L2692](file:///home/duxiter/s2s/s2s-client/main.js#L2683-L2692)).
4. Blocco scheduling auto-start ([L2976-L3083](file:///home/duxiter/s2s/s2s-client/main.js#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](file:///home/duxiter/s2s/s2s-client/main.js#L2878-L2932)) aggiunto `hideTalkUnlocker()` in `connecting/queued/your-turn/connected`.
- **`ws/s2s-ws-client.js`** ([s2s-ws-client.js](file:///home/duxiter/s2s/s2s-client/ws/s2s-ws-client.js)):
1. Costruttore: nuove proprietà `_startWithResponse`, `_startWithResponseText` (default `"hola vocero"`), `_initialResponseSent` guardia one-shot ([L163-L178](file:///home/duxiter/s2s/s2s-client/ws/s2s-ws-client.js#L163-L178)).
2. Handler `session.updated` ([L688-L713](file:///home/duxiter/s2s/s2s-client/ws/s2s-ws-client.js#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](file:///home/duxiter/s2s/s2s-client/index.html#L47-L50)): aggiunto `data-debug-only="true"`.
- **`main.js`** → `applyDebugOnlyVisibilityGate()` ([L48-L109](file:///home/duxiter/s2s/s2s-client/main.js#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](file:///home/duxiter/s2s/s2s-client/main.js#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](file:///home/duxiter/s2s/s2s-client/style.css#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.