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

12 KiB
Raw Blame History

Changelog — s2s-client (Azurian Vocero)

Todos los cambios relevantes del proyecto están documentados en este archivo. Formato basado en Keep a Changelog.


[2026-09-07]

Añadido — Soporte episode_id (inject de episodio podcast)

Nuevo parámetro query string ?episode_id= junto a ?phoneNumber= que habilita la carga y la inyección automática de un episodio podcast como contexto PRIMARIO en el prompt LLM.

URL de ejemplo:

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

Archivos modificados:

  • server.py (server.py):

    1. Añadida la constante PODCAST_EPISODE_HINT (L426-L429) para el hint semántico en el prompt.
    2. Añadida la función _resolve_episode_context(raw_value) (L475-L504): normaliza la respuesta API (dict/list/str, posible wrapper {"result": …}), la serializa en JSON (tope 24.000 chars) y la envuelve con los marcadores <<<PODCAST_EPISODE_BEGIN/END>>>.
    3. Añadida la función asíncrona _fetch_podcast_episode(episode_id) (L1010-L1078): GET directo a
      https://aquachile.azurianlab.com/api/v1/podcast/episodes/{episode_id}
      (timeout 15s, follow redirects, manejo de errores tolerante — nunca rompe el flujo, devuelve None en caso de fallo).
    4. Extendida _augment_instructions_with_profile(...) (L1137-L1181) con el nuevo parámetro episode_context_text; el orden de inject ahora es (más específico → más genérico): episode → profile → reports → summary.
    5. Extendida _patch_client_session_update_with_profile(...) (L1184-L1306) con soporte a episode_context_text y logging dedicado [INJECT-EPISODE] / [DEBUG-EPISODE].
    6. WebSocket connect handler (L1791-L1959):
      • lee episode_id desde query params junto a phoneNumber;
      • ejecuta fetch episodio + resolve ANTES de profile/reports/summary;
      • guarda injected_episode_context e inject_episode_id en el session_ctx compartido.
    7. _client_to_upstream (L2649-L2661): pasa ctx_episode_context al patch de session.update.
    8. Endpoint POST /api/reasoning:
      • extendido el modelo ReasoningRequest con el campo episode_id: str | None = None (L1309-L1323);
      • en el handler (L1411-L1577) fetch episodio independiente de phone;
      • inject_episode_text se hace prepend sobre user_context_parts (primero de todos);
      • reenvío a NODE_RED / OPENAI con logging extendido (episodeIdLen).
  • main.js (main.js):

    1. Lectura desde query string: const voceroEpisodeId = pageParams.get("episode_id") (L676-L680).
    2. fetchOpenAiReasoningAnswer (L1337-L1364): campo episode_id añadido al body POST /api/reasoning, logKey y console.info extendidos.
    3. Construcción URL WebSocket (L2323-L2342): episode_id añadido a query params tanto con la API URL.searchParams como en el fallback string (concat con &).

Añadido — S2S_START_SESSION_MODE: 3 modos de inicio de sesión

Variable de entorno leída por el servidor y propagada al cliente vía GET /api/config.startSessionMode. Objetivo: controlar si y cómo se enciende automáticamente el micrófono y si el modelo saluda primero (flujo similar al wake-word). Valores soportados:

Valor Descripción
(vacío) / default Inicio manual: solo tap sobre el Orb / botón Start
TALK Después de 2s overlay a pantalla completa "Toca en cualquier lugar para empezar" (policy safe para Safari/iOS getUserMedia). Click/touch/tecla dentro de la user gesture → doStart(). El cliente WS inyecta bootstrap "hola vocero" + requestResponse().
TALKFIRST Después de 2s intenta doStart() DIRECTAMENTE SIN click, precargando pendingWakeWordConfirmationText = "hola vocero" (misma variable de la wake-word real). Si los permisos de mic ya están "allow always" funciona sin click. Si falla (policy Safari/iOS) → fallback sobre el mismo unlocker de TALK con mensaje "permiso micrófono".

Archivos modificados:

  • server.py (server.py):

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

    • Añadido nuevo overlay #talk-unlocker a pantalla completa (z-index 9999), hidden por defecto, con tarjeta "Preparando Vocero… / Toca en cualquier lugar para empezar a hablar" + spinner degradado.
  • style.css (style.css):

    • Clases .talk-unlocker, .talk-unlocker-hint, .talk-unlocker-spinner, .talk-unlocker-title, .talk-unlocker-sub + animaciones fade-in y pop-up de tarjeta.
  • main.js (main.js):

    1. Variable global startSessionMode = "" + comentario (L628-L632).
    2. Parse dentro de fetchConfig() → uppercase, log en reportDebug("B",…) (L2309-L2320).
    3. Paso a cliente WS → startWithResponse: startSessionMode === "TALK" (L2683-L2692).
    4. Bloque scheduling auto-start (L2976-L3083):
      • bootstrapWithAutoStart(): await fetchConfig() → maybeScheduleAutoStart();
      • setTimeout(2000 ms) warmup para finalizar fetch de episode/profile/reports;
      • showTalkUnlocker(fallbackHint): gestiona labels personalizadas en caso de fallback TALKFIRST;
      • tryTalkFirstDirect() (TALKFIRST): set pendingWakeWordConfirmationText="hola vocero" + doStart() + try/catch con teardown y fallback unlocker;
      • listener unlocker click/touchstart/keydown → startTalkFromGesture() con createResumedAudioContext() dentro de la gesture (para iOS silent-free).
    5. onClientStatus() (L2878-L2932) añadido hideTalkUnlocker() en connecting/queued/your-turn/connected.
  • ws/s2s-ws-client.js (s2s-ws-client.js):

    1. Constructor: nuevas propiedades _startWithResponse, _startWithResponseText (default "hola vocero"), _initialResponseSent guardia one-shot (L163-L178).
    2. Handler session.updated (L688-L713): Solo para TALK (no TALKFIRST, que en cambio usa pendingWakeWordConfirmationText): this.sendUserText("hola vocero") + this.requestResponse() — mismo code path identico de la wake-word.
    3. requestResponse / _createResponseNow restaurados limpios (solo payload opcional image; ningún shaped response.create).

Modificado — #reports-sidebar-toggle visible solo con ?debug=true

El botón "reports" en la topbar (toggle del panel lateral reports) ahora es data-debug-only="true" — por lo tanto solo se muestra si la URL contiene ?debug=true, de manera análoga al cluster .topbar-right.

Archivos modificados:

  • index.html → #reports-sidebar-toggle (index.html): añadido data-debug-only="true".
  • main.js → applyDebugOnlyVisibilityGate() (L48-L109): extendido para iterar todos los elementos [data-debug-only="true"] (no solo .topbar-right); línea 1227: reportsSidebarToggle.hidden = false condicionado a qs.get("debug") === "true" (L1227-L1246) + log rojo 🚫 FORZADO HIDDEN cuando falta debug=true.
  • style.css → regla de seguridad ya presente: [data-debug-only]:not([data-debug-enabled]) { display:none !important; visibility:hidden; opacity:0 } (L532-L537).

[2026-09-04]

Modificado — Chat siempre visible + integración seamless

Reorganizado el layout para mostrar el chat textual de forma permanente e integrado visualmente con el Orb en la parte superior del stage.

  • Eliminado botón toggle chat en la topbar (#chat-btn) y relativo overlay.
  • Stage con el Orb en la parte superior; nuevo #chat-panel a continuación (historial
    • form input fijo sobre el footer sticky).
  • Actualizada lógica en main.js y ui/chat.js para el envío de texto mediante client.sendUserText y auto-scroll sin controles de visibilidad del panel.
  • Versión banner deploy: 2026-09-03 14:40.

Resuelto — Estabilidad integración chat mobile

  • Error Missing element: #chat-btn resuelto con selectores non-strict (document.querySelector) para elementos opcionales eliminados.
  • Refactorización estética "seamless": chat sin marcos, sombras ni fondos (background:transparent, box-shadow:none).
  • Layout mobile:
    • padding incrementado en #app (56px) y .chat-history (96px) para evitar superposiciones con el footer sticky;
    • márgenes fijos en las burbujas: margin-right:22px (Assistant), margin-left:6px (User).
  • Auto-scroll streaming: implementado _scrollToBottom con cadena de retry múltiples (requestAnimationFrame + setTimeout) para compensar el line-wrap asíncrono durante la llegada de los tokens; overflow-anchor:none + scroll-behavior:auto !important.

Añadido — Avatar usuario con iniciales + modal de perfil

  • Sustituido el texto del nombre de usuario en el header por avatar circular 40×40px (gradient de marca) conteniendo las iniciales calculadas (ej. "AV").
  • Nueva <dialog id="user-profile-dialog"> en index.html que se abre al click sobre el avatar: muestra email, teléfono, rol, empresa, con soporte a shapes anidadas del payload de perfil.
  • Cierre del modal: tecla ESC, click fuera de la tarjeta, botón × dedicado.

[Notas de formato]

  • Tope campo prompt episodio: 24.000 caracteres (transcript largos).
  • Tope profile / summary: 1.600 caracteres cada uno.
  • Tope reports list: 12.000 caracteres.
  • Orden inject prompt LLM (WS session.update + reasoning):
    1. Episodio podcast actual (PRIMARIO)
    2. Perfil llamador (identidad)
    3. Lista reports / scripts podcast programados
    4. Summary última conversación
  • Todos los bloques están marcados con <<<NOMBRE_BLOQUE_BEGIN/END>>> para inspección de logs.