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 :
- Révocation de droits Google (compte suspendu, changement d'organisation)
- Changement de compte Google AI Studio
- Suspicion de compromission d'un refresh token
- Expiration anormale d'une session (erreur 401 persistante)
⚠️ 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 :
-
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). -
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 retournantFalse(booléen) pour un input vide confirmé, le code utiliseisinstance(selection_raw, str)pour distinguer les types. -
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). -
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). -
Suppression : appel
POST /collections/echo_memory/points/deletesur l'API Qdrant interne (http://echo-qdrant:6333), avec le filtremustincluant systématiquementuser_id+tags(et optionnellementchat_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 :
-
Injection du Shell : le cockpit HTML/JS est injecté dans le DOM
via
__event_call__({ type: "execute" }). -
Boucle
while True: Python encode la frame courante en base64 et appelleexecutepour mettre à jour l'image dans le cockpit (window.echoReplayUpdate(b64, ts, idx, total)). -
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). -
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
| Fonction | Description |
|---|---|
| Navigation | Première, Précédente, Suivante, Dernière frame |
| Lecture automatique | Play avant / arrière avec vitesse réglable (1–30s) |
| Zoom | Ctrl+Molette dans la viewport |
| Sélection Crop | Zone de recadrage redimensionnable par poignées |
| Export | Téléchargement PNG (plein ou recadré) / Copie presse-papier |
| Métadonnées | Horodatage 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èque | Version | Rôle | Source |
|---|---|---|---|
| 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
- Iframe Same-Origin Access — permet l'accès à
iframe.contentDocumentdepuis le JS de la page parente. - Iframe Allow Forms — requis pour certains éléments interactifs des Rich Embeds.
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)
-
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. -
Capture globale :
html2canvas(container)rasterise l'intégralité du conteneur de conversation (défilement complet,scrollY: 0avant capture, restauration après). - Restauration du DOM : les iframes originales sont réaffichées, les placeholders supprimés.
- 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.
-
Téléchargement :
pdf.save('ECHO_Export_AAAAMMJJ_HHMM.pdf')déclenche le téléchargement natif du navigateur.
Limites connues
| Limite | Cause | Impact |
|---|---|---|
| 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 Tree | Panneau 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 Monaco | Monaco 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 AI | Instruction 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/Export | Téléchargement depuis le PC vers le Codex (upload), téléchargement du fichier vers le PC (download natif navigateur). |
| Reset | Suppression 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 →.