7f. Actions UI (HUD)

En termes Open WebUI, une Action est un bouton interactif injecté dans l'interface de chat, déclenché explicitement par l'utilisateur. ECHO déploie cinq actions couvrant la maintenance cognitive, l'archivage visuel, l'export documentaire et l'édition de code souverain. Elles sont définies dans le répertoire 13-owui-actions/ du dépôt.

💡 Actions vs Outils — Distinction fondamentale

Les Outils (12-owui-tools/) sont déclenchés par le modèle (Tool Call) au cours de la génération d'une réponse. Les Actions (13-owui-actions/) sont déclenchées par l'utilisateur via un bouton visible sous chaque message. Elles exécutent du code Python côté serveur (dans le conteneur echo-open-webui) sans interagir avec le flux de génération Gemini.

Mécanique interne — __event_call__ et le Pattern Promise

Les Actions ECHO n'utilisent pas de HTMLResponse (iframe). Elles injectent du JavaScript directement dans le DOM d'Open WebUI via le mécanisme __event_call__({ "type": "execute", "data": { "code": js } }). Ce JS s'exécute dans le contexte de la page principale, avec un accès complet au DOM et aux APIs du navigateur.

La communication bidirectionnelle Python ↔ JS repose sur le Pattern Promise : le code JS retourné à __event_call__ peut être une Promise dont la valeur résolue est renvoyée comme résultat de l'appel Python. Cela permet une boucle de dialogue asynchrone :

# Côté Python (action)
result = await __event_call__({
    "type": "execute",
    "data": { "code": "return new Promise(r => window.myResolve = r);" }
})
# result contient la valeur passée à window.myResolve(...) depuis le JS

# Côté JS (injecté dans le DOM)
document.getElementById('mon-btn').onclick = () => window.myResolve({ action: 'ok' });

Ce pattern est utilisé pour les HUD interactifs (confirmation, sélection, choix) et pour la boucle de streaming frame-par-frame du Cockpit de Replay Web.

Priorité d'affichage et ordre des boutons

Chaque action déclare un entier priority dans sa classe Valves. Open WebUI trie les boutons d'action par valeur croissante : la priorité 1 s'affiche en premier à gauche, la priorité la plus haute à droite.

# Fichier Titre OWUI Priorité Rôle résumé
1 reset_auth_action.py Réinitialiser Authentification Gemini /!\ 1 Invalide les tokens OAuth2 / clés PKCE
2 purge_memory_action.py Purge Mémoire Long Terme /!\ 2 Suppression granulaire de la collection Qdrant utilisateur
3 web_navigation_replay_action.py Revue Navigation Web 3 Cockpit de rejeu des sessions de navigation Playwright
4 export_pdf_action.py Export PDF 4 Capture conversation → PDF (texte + Rich Embeds)
5 subagent_monitor_action.py Sub-Agent Monitor 4 HUD de visualisation arborescente des threads cognitifs en temps réel
6 echo_codex_action.py ECHO Codex 5 HUD Monaco éditeur de code avec Git intégré et assistance AI

Action 1 — Réinitialiser Authentification Gemini (reset_auth_action.py)

Invalide l'ensemble des tokens OAuth2 et clés API enregistrés dans identity.db pour l'utilisateur courant. Au prochain message, le Cortex (pipe_engine.py) détecte l'absence de token valide et déclenche automatiquement un nouveau flux d'authentification PKCE via echo_auth.py.

Cette action est utile dans les cas suivants :

⚠️ Effet immédiat

L'invalidation est immédiate et ne nécessite pas de redémarrage du conteneur. L'utilisateur sera invité à se ré-authentifier dès l'envoi du prochain message. Aucune donnée de mémoire ou de conversation n'est supprimée.

Action 2 — Purge Mémoire Long Terme (purge_memory_action.py, v3.2)

Supprime des souvenirs ciblés depuis la collection echo_memory dans Qdrant, filtrés obligatoirement par user_id (clause must). L'action concerne exclusivement la mémoire long terme (organique) ; la mémoire de travail de la session en cours n'est pas affectée. L'utilisateur est guidé via un flux de dialogue en 5 étapes :

  1. Analyse multidimensionnelle : récupération et agrégation de tous les tags de l'utilisateur avec métadonnées (memory_importance, fréquence d'apparition). Tri hiérarchique : importance croissante → fréquence décroissante → ordre alphabétique. Affichage dans un HUD déroulant (div scrollable, max-height: 200px) avec indicateurs colorés par niveau d'importance (🟢 1, 🔵 2, 🟡 3, 🟠 4, 🔴 5).
  2. Sélection des catégories : l'utilisateur saisit les numéros des tags à purger, avec support des plages (ex : 1-4, 7, 10-12). Si le champ est laissé vide, toutes les catégories sont sélectionnées et une confirmation supplémentaire (events.confirm) est requise avant de continuer. OWUI retournant False (booléen) pour un input vide confirmé, le code utilise isinstance(selection_raw, str) pour distinguer les types.
  3. Choix du périmètre : un encart explicatif (voix ECHO, 1re personne) précise que seule la mémoire long terme est concernée. Deux options : « Uniquement ce que j'ai retenu de cette discussion » (filtre chat_id) ou « TOUT ce que je sais sur ces sujets, toutes discussions confondues » (global).
  4. Dry Run : prévisualisation des slugs des souvenirs ciblés, triés par ordre alphabétique et affichés dans un conteneur scrollable (max-height: 150px). Tous les slugs sont listés (pas de limite à 10).
  5. Suppression : appel POST /collections/echo_memory/points/delete sur l'API Qdrant interne (http://echo-qdrant:6333), avec le filtre must incluant systématiquement user_id + tags (et optionnellement chat_id).

⚠️ Opération irréversible

La suppression des vecteurs Qdrant est définitive. Les souvenirs purgés ne peuvent être restaurés qu'à partir d'une sauvegarde de volume Qdrant effectuée par l'Admin Manager (echo-backups). L'action présente systématiquement une confirmation finale avant toute suppression effective.

Action 3 — Revue Navigation Web (web_navigation_replay_action.py)

Ouvre un cockpit visuel plein écran permettant de consulter et de rejouer les captures d'écran des sessions de navigation web effectuées pendant la conversation courante par l'outil navigation_engine_tool.py.

Source de données — Le Vault utilisateur

Les captures sont stockées sous forme de fichiers PNG horodatés dans le Vault de l'utilisateur : /opt/ECHO/users/<user_id>/files/U_<uid>_C_<cid>_T_<ts>.png. L'action scanne ce répertoire, filtre par préfixe de conversation (chat_id) et trie chronologiquement.

Architecture de streaming frame-par-frame

L'action utilise le Pattern Promise en boucle bloquante pour un dialogue temps-réel entre Python et JS :

  1. Injection du Shell : le cockpit HTML/JS est injecté dans le DOM via __event_call__({ type: "execute" }).
  2. Boucle while True : Python encode la frame courante en base64 et appelle execute pour mettre à jour l'image dans le cockpit (window.echoReplayUpdate(b64, ts, idx, total)).
  3. Attente de commande : Python se bloque sur une Promise JS (window.echoReplayResolve) jusqu'à ce que l'utilisateur clique sur un bouton (Précédent, Suivant, Play, Fermer).
  4. Résolution : le bouton résout la Promise avec { action: "goto", index: N } ou { action: "close" }. Python met à jour l'index et reboucle, ou sort de la boucle.

Fonctionnalités du Cockpit

FonctionDescription
NavigationPremière, Précédente, Suivante, Dernière frame
Lecture automatiquePlay avant / arrière avec vitesse réglable (1–30s)
ZoomCtrl+Molette dans la viewport
Sélection CropZone de recadrage redimensionnable par poignées
ExportTéléchargement PNG (plein ou recadré) / Copie presse-papier
MétadonnéesHorodatage ISO de chaque capture, index / total

Action 4 — Export PDF (export_pdf_action.py)

Capture l'intégralité de la conversation courante et génère un fichier PDF téléchargeable directement dans le navigateur. L'action est entièrement côté client (aucun appel backend, aucun stockage serveur) et s'adapte automatiquement à la configuration de sandboxing des iframes.

Bibliothèques utilisées

BibliothèqueVersionRôleSource
html2canvas 1.4.1 Rasterisation du DOM en canvas PNG CDN Cloudflare (chargement dynamique)
jsPDF 2.5.1 Composition et export du fichier PDF CDN Cloudflare (chargement dynamique)

💡 Pourquoi les iframes disparaissent dans le PDF natif Open WebUI

Le pipeline « Download as PDF » d'Open WebUI utilise Playwright/Chromium en mode headless (page.pdf()). Les <iframe> sandboxées ne sont pas sérialisées dans ce snapshot — leur contenu reste vide. C'est une limite structurelle de Chromium : les iframes sont des documents isolés, non aplatis dans le flux d'impression du document parent.

L'action ECHO contourne ce problème en capturant le contenu de chaque iframe directement via iframe.contentDocument (accès possible uniquement si Iframe Same-Origin Access est activé), puis en substituant chaque iframe par un snapshot image avant de rasteriser le conteneur parent.

Prérequis — Paramètres Interface Open WebUI

⚠️ Configuration requise pour le Mode complet

Pour capturer le contenu des Rich Embeds (mindmaps, graphes D3, cartes, tableaux de bord), les deux paramètres suivants doivent être activés dans Open WebUI :

Settings → Interface

Ces paramètres sont stockés par utilisateur dans la table user de webui.db (clés JSON : ui.iframeSandboxAllowSameOrigin et ui.iframeSandboxAllowForms). Il n'existe pas de variable d'environnement Docker pour les forcer globalement.

Détection automatique et deux modes d'exécution

Au lancement, l'action tente d'accéder à iframe.contentDocument sur la première iframe trouvée dans la conversation. Ce test non-destructif détermine le mode d'exécution :

Condition détectée Mode Comportement
Same-Origin activé ou aucune iframe dans la conversation Mode A — Complet Capture texte + iframes Rich Embeds → PDF haute résolution
Same-Origin absent et iframes présentes Mode B — HUD de choix Affichage d'un overlay expliquant le prérequis.
Continuer sans schémas → Mode B1 : PDF texte uniquement, iframes remplacées par un placeholder textuel.
Annuler → Fermeture sans génération.

Pipeline de capture (Mode A)

  1. Capture des iframes : pour chaque <iframe> Rich Embed, html2canvas(iframe.contentDocument.body) produit un canvas. L'iframe est temporairement remplacée dans le DOM par un élément image (<div> contenant un <img>) pour que la capture parent la voit comme un contenu plat.
  2. Capture globale : html2canvas(container) rasterise l'intégralité du conteneur de conversation (défilement complet, scrollY: 0 avant capture, restauration après).
  3. Restauration du DOM : les iframes originales sont réaffichées, les placeholders supprimés.
  4. Composition PDF : jsPDF découpe le canvas en tranches A4, ajoute un en-tête (titre + date) et un pied de page numéroté sur chaque page.
  5. Téléchargement : pdf.save('ECHO_Export_AAAAMMJJ_HHMM.pdf') déclenche le téléchargement natif du navigateur.

Limites connues

LimiteCauseImpact
Images externes dans les messages Politique CORS des serveurs tiers Images remplacées par un rectangle vide (useCORS: true atténue)
Webfonts dans les iframes html2canvas ne charge pas les fonts cross-origin Fallback system-font dans les Rich Embeds capturés
Très longue conversation Canvas géant en mémoire Latence de 2–5s sur conversations de +50 messages ; risque OOM navigateur
Rich Embeds A-Frame / 3D WebGL non supporté par html2canvas Capture noire ou vide pour les scènes 3D

✅ Paramètres configurables (Valves)

image_scale (défaut : 2.0) contrôle la résolution de rasterisation. Une valeur de 1.0 réduit la taille du fichier PDF au détriment de la netteté. pdf_orientation accepte portrait (défaut, A4 210×297mm) ou landscape.

Action 5 — Sub-Agent Monitor (subagent_monitor_action.py, v1.1)

Ouvre un HUD interactif (div draggable et redimensionnable) injecté dans le DOM permettant de visualiser l'arborescence des threads des sub-agents cognitifs en temps réel.

Basé sur une boucle d'événements bidirectionnelle avec le frontend (Pattern Promise), ce cockpit interroge le statut des agents pour afficher l'état courant de l'escalade cognitive. L'interface présente sous forme d'arbre hiérarchique les délégations en cours, les modèles sollicités et l'état de la réflexion sans saturer l'interface principale de chat.

Action 6 — ECHO Codex (echo_codex_action.py, v1.8)

Ouvre un éditeur de code Monaco complet dans un HUD draggable et redimensionnable, directement intégré dans le DOM d'Open WebUI. Chaque conversation dispose de son propre dépôt Git isolé (via dulwich, Python pur sans binaire git) dans le Vault utilisateur. La position et la taille du HUD sont persistantes via localStorage (clé echo_codex_{chat_id}).

Fonctionnalités principales

FonctionnalitéDescription
File TreePanneau gauche : liste des fichiers du Codex (tri dynamique par mtime). Clic → chargement dans l'éditeur. Action contextuelle → Renommage intégré (OS + Git). Bouton × → suppression avec commit. Bouton + → création fichier.
Éditeur MonacoMonaco Editor 0.52.2 via CDN jsDelivr. Coloration syntaxique 30+ langages. Intègre un panneau de prévisualisation WYSIWYG. Bouton de sauvegarde explicite et Ctrl+S → sauvegarde + commit immédiat.
Édition AIInstruction libre ou action rapide prédéfinie (shorter, comment, refactor, fix, tests…). Sélecteur LITE/FLASH/PRO. Affiche un diff côte-à-côte Accept/Rejeter.
Navigation Git ◀ ▶Navigation commit par commit pour un fichier. Mode read-only visuel. Bouton Restaurer → nouveau commit de restauration.
Import/ExportTéléchargement depuis le PC vers le Codex (upload), téléchargement du fichier vers le PC (download natif navigateur).
ResetSuppression intégrale du dépôt (confirmation JS + purge SQLite). Irréversible.

💡 Complémentarité HUD (Action) vs. Outil LLM (Tool Call)

L'Action ECHO Codex (ce HUD) est complémentaire de l'outil LLM echo_codex_tool.py : le HUD est piloté par l'utilisateur (bouton sous le message), l'outil LLM est piloté par Gemini (Tool Call au cours d'une génération). Les deux accèdent au même dépôt Git et au même registre SQLite codex_docs.

Pour la documentation technique complète (architecture, API JS, protocole Python↔JS), voir ECHO Codex →.

← Exécution & Pilotage    Visual Intelligence →