Compare commits

..
2 Commits
Author SHA1 Message Date
Spark eb4c42b0b5 7.81.0 — Panneau Spark V3 : la console en surimpression pleine page (panneau octet-identique à TikTak 0.60.0)
FlowDeck CI / lint (push) Failing after 1m31s
FlowDeck CI / test (push) Failing after 32m14s
FlowDeck CI / docker (push) Skipped
2026-10-23 04:06:11 +00:00
Spark 73b3fbce6e fix(version): /api/health lit la version depuis le fichier VERSION — fin de la constante 7.74.3 périmée (7.80.2)
FlowDeck CI / test (push) Failing after 29m53s
FlowDeck CI / lint (push) Failing after 1m30s
FlowDeck CI / docker (push) Skipped
2026-10-22 17:41:24 -04:00
4 changed files with 186 additions and 12 deletions
+36
View File
@@ -1,5 +1,41 @@
# Changelog - FlowDeck
## v7.81.0 (2026-10-22) — Panneau Spark V3 : la console en surimpression pleine page
- **Fini le nouvel onglet** (demande de Bruno) : le bouton
« Console ↗ » du panneau Spark devient « Console ⤢ » et ouvre
la console du service d'agents **dans la même page**, en
surimpression qui naît à la géométrie du panneau (ancrée à
droite, sa largeur) puis **s'étend en ~300 ms jusqu'à recouvrir
toute la fenêtre**. L'URL profonde est inchangée
(`?disc=<discussion affichée>`, `?app=flowdeck`).
- **Barre d'outils de la surimpression** : « ◧ Mode sidebar »
(animation inverse, retour au panneau latéral resté ouvert
dessous) et « ✕ Fermer l'agent » (referme surimpression **et**
panneau). Échap hors iframe = « Mode sidebar ».
- **Iframe créé paresseusement et conservé** : chargé au premier
clic, puis masqué/réaffiché sans rechargement — l'état de la
console persiste entre ouvertures. `allow="microphone"` pour
la dictée encadrée (aucune Permissions-Policy de Flowdeck ne
bloque le micro, vérifié) ; côté service, la 0.20.0 autorise
l'encadrement par les seules origines des 4 applications
(CSP `frame-ancestors`).
- La surimpression vit entièrement dans le fichier panneau
(DOM + styles injectés, thème synchronisé) ; seul
`static/js/draco-panneau.js` change ici, **octet-identique**
au canonique TikTak 0.60.0 (md5 commun vérifié). Kanbante et
ObsiGate suivront en vague 2 du panneau V3.
## v7.80.2 (2026-10-22) — /api/health : version lue depuis le fichier VERSION
- La version de l'app FastAPI était une constante codée en dur
dans `app/main.py` (restée à **7.74.3**) : `/api/health`
affichait donc une version périmée alors que l'app déployée
était en 7.80.1 (signalé par Bruno). Elle est maintenant lue
depuis le fichier `VERSION` à la racine (source unique, même
repli `/app/VERSION` que le tableau de bord) — elle ne pourra
plus dériver.
## v7.80.1 (2026-10-22) — Panneau Spark : bouton « Console ↗ »
- **Bouton « Console ↗ »** dans la barre des discussions du
+1 -1
View File
@@ -1 +1 @@
7.80.1
7.81.0
+25 -1
View File
@@ -190,9 +190,33 @@ async def lifespan(_app: FastAPI):
pass
def _lire_version() -> str:
"""Version de l'app depuis le fichier VERSION (source unique).
Avant : une constante codée en dur dans FastAPI(...) — restée à
7.74.3 alors que l'app était en 7.80.1, donc /api/health mentait.
Même logique de repli que dashboard._common._get_app_version.
"""
import os
for chemin in (
os.path.join(os.path.dirname(__file__), "..", "VERSION"),
"/app/VERSION",
):
try:
if os.path.exists(chemin):
with open(chemin) as f:
v = f.read().strip()
if v:
return v
except Exception:
pass
return "0.0.0"
app = FastAPI(
title="FlowDeck",
version="7.74.3",
version=_lire_version(),
docs_url="/docs",
redoc_url="/redoc",
lifespan=lifespan,
+124 -10
View File
@@ -18,10 +18,16 @@
* Panneau v2 (Lot 1) : rendu Markdown, largeur réglable à la souris
* (persistée), thème clair/sombre aligné sur l'app hôte
* (html[data-theme], repli prefers-color-scheme, suivi en direct).
* Bouton « Console ↗ » (barre des discussions) : ouvre la console web
* du même service (agent.dracodev.net) dans un nouvel onglet, sur la
* discussion courante (?disc=) et avec l'app d'origine (?app=),
* déduite du sous-domaine de l'hôte.
* Bouton « Console » (barre des discussions) : ouvre la console web
* du même service (agent.dracodev.net) DANS la page, en surimpression
* pleine page (iframe) qui naît à la géométrie du panneau puis
* s'étend à toute la fenêtre ; l'URL profonde (?disc= discussion
* courante, ?app= app d'origine déduite du sous-domaine) est celle
* du premier chargement — l'iframe est créé paresseusement et
* conservé, la console garde son état entre ouvertures. La barre
* d'outils de la surimpression offre « Mode sidebar » (retour au
* panneau seul, animation inverse) et « Fermer l'agent » (referme
* surimpression et panneau).
* États honnêtes : service injoignable / session non reconnue / LLM non
* configuré / exécuteur non configuré sont affichés tels quels. */
(function () {
@@ -48,23 +54,124 @@
catch (e) { return null; }
}
/* ── Console web (même service, nouvel onglet) ─────────────── */
/* ── Console web (même service, surimpression pleine page) ─── */
function appHote() {
var h = (window.location.hostname || "").split(".")[0];
var correspondance = { tiktak: "tiktak", flowdeck: "flowdeck",
kanbante: "kanbante", og: "obsigate" };
return correspondance[h] || null;
}
function ouvrirConsole() {
function urlConsole() {
var params = [];
var id = convId || convStockee();
if (id) params.push("disc=" + encodeURIComponent(id));
var app = appHote();
if (app) params.push("app=" + encodeURIComponent(app));
window.open(API + "/" + (params.length ? "?" + params.join("&") : ""),
"_blank", "noopener");
return API + "/" + (params.length ? "?" + params.join("&") : "");
}
/* La surimpression vit entièrement dans ce fichier (DOM + styles
* injectés) : les apps hôtes n'ont que ce fichier à recopier. Son
* thème suit celui du panneau (data-draco-theme synchronisé par
* appliquerTheme) et sa palette reprend les mêmes valeurs que
* draco-panneau.css. L'iframe est créé au premier clic puis
* conservé : rouvrir la console ne le recharge pas. */
var surEl = null, surCadre = null, surOuverte = false;
var STYLES_CONSOLE =
".draco-console-sur{" +
"--p-fond:#fbfbfa;--p-surface:#ffffff;--p-bord:#e4e5e0;--p-texte:#242624;" +
"--p-chip:#f4f5f1;--p-chip-bord:#d6d8d1;--p-chip-survol:#e9ebe4;--p-accent:#7c5cd6;" +
"position:fixed;top:0;right:0;height:100dvh;width:388px;max-width:100vw;z-index:1000;" +
"background:var(--p-fond);color:var(--p-texte);" +
"box-shadow:-12px 0 36px rgb(0 0 0 / .22);" +
"display:flex;flex-direction:column;transition:width .3s ease;" +
"font:14px/1.5 system-ui,\"Segoe UI\",sans-serif}" +
".draco-console-sur[data-draco-theme=\"dark\"]{" +
"--p-fond:#17181a;--p-surface:#1f2023;--p-bord:#33353a;--p-texte:#eceeeb;" +
"--p-chip:#26272b;--p-chip-bord:#3a3c41;--p-chip-survol:#2e3036}" +
".draco-console-sur[hidden]{display:none}" +
".draco-console-barre{display:flex;align-items:center;gap:9px;flex:none;" +
"padding:9px 12px;border-bottom:1px solid var(--p-bord);background:var(--p-surface)}" +
".draco-console-barre .dc-titre{font-weight:700;font-size:14px;" +
"display:flex;align-items:center;gap:8px;white-space:nowrap}" +
".draco-console-barre .dc-etoile{color:var(--p-accent);font-size:17px}" +
".draco-console-barre .dc-actions{margin-left:auto;display:flex;gap:7px}" +
".draco-console-barre button{border:1px solid var(--p-chip-bord);" +
"background:var(--p-chip);color:var(--p-texte);border-radius:999px;" +
"padding:5px 12px;font:600 12.5px system-ui;cursor:pointer;white-space:nowrap}" +
".draco-console-barre button:hover{background:var(--p-chip-survol)}" +
".draco-console-cadre{flex:1;width:100%;border:0;display:block;background:var(--p-fond)}";
function assurerSurimpression() {
if (surEl) return;
if (!document.getElementById("draco-console-styles")) {
var st = document.createElement("style");
st.id = "draco-console-styles";
st.textContent = STYLES_CONSOLE;
document.head.appendChild(st);
}
surEl = document.createElement("div");
surEl.className = "draco-console-sur";
surEl.setAttribute("aria-label", "Console Spark");
surEl.hidden = true;
surEl.innerHTML =
'<div class="draco-console-barre">' +
' <span class="dc-titre"><span class="dc-etoile">✦</span> Console Spark</span>' +
' <span class="dc-actions">' +
' <button type="button" data-dc="sidebar" title="Réduire la console et revenir au panneau latéral">◧ Mode sidebar</button>' +
' <button type="button" data-dc="fermer" title="Fermer la console et le panneau Spark">✕ Fermer l\'agent</button>' +
' </span></div>' +
'<iframe class="draco-console-cadre" title="Console Spark" allow="microphone"></iframe>';
document.body.appendChild(surEl);
surCadre = surEl.querySelector("iframe");
surEl.querySelector('[data-dc="sidebar"]')
.addEventListener("click", modeSidebarConsole);
surEl.querySelector('[data-dc="fermer"]')
.addEventListener("click", fermerAgentConsole);
appliquerTheme();
}
function largeurPanneau() {
if (root) {
var w = root.getBoundingClientRect().width;
if (w > 0) return Math.round(w);
}
return 388;
}
function ouvrirConsole() {
assurerSurimpression();
if (!surCadre.getAttribute("src")) surCadre.src = urlConsole();
if (surOuverte) return;
surOuverte = true;
/* Naissance à la géométrie du panneau (ancrée à droite), puis
* extension à toute la page ; le double rAF laisse le temps de
* peindre la largeur de départ, sinon la transition ne joue pas. */
surEl.style.width = largeurPanneau() + "px";
surEl.hidden = false;
requestAnimationFrame(function () {
requestAnimationFrame(function () { surEl.style.width = "100%"; });
});
}
function retrecirConsole() {
if (!surEl || !surOuverte) return;
surOuverte = false;
var fini = false;
function terminer() {
if (fini) return;
fini = true;
surEl.removeEventListener("transitionend", surFin);
surEl.hidden = true; // l'iframe reste chargé, juste masqué
}
function surFin(ev) { if (ev.propertyName === "width") terminer(); }
surEl.addEventListener("transitionend", surFin);
setTimeout(terminer, 380); // filet si transitionend ne vient pas
surEl.style.width = largeurPanneau() + "px";
}
function modeSidebarConsole() { retrecirConsole(); }
function fermerAgentConsole() { fermer(); retrecirConsole(); }
/* ── Thème : suivre l'app hôte ─────────────────────────────── */
function themeHote() {
var t = document.documentElement.getAttribute("data-theme");
@@ -78,6 +185,7 @@
}
function appliquerTheme() {
if (root) root.setAttribute("data-draco-theme", themeHote());
if (surEl) surEl.setAttribute("data-draco-theme", themeHote());
}
function surveillerTheme() {
try {
@@ -143,7 +251,7 @@
' <span class="d-titre">Discussion principale</span>' +
' <button type="button" class="d-btn" data-disc="liste">☰ Discussions</button>' +
' <button type="button" class="d-btn" data-disc="nouvelle">+ Nouvelle</button>' +
' <button type="button" class="d-btn" data-disc="console" title="Ouvrir la console Spark (agent.dracodev.net) dans un nouvel onglet, sur cette discussion">Console ↗</button>' +
' <button type="button" class="d-btn" data-disc="console" title="Ouvrir la console Spark (agent.dracodev.net) en pleine page par-dessus l\'application, sur cette discussion">Console ⤢</button>' +
'</div>' +
'<div class="draco-discussions" hidden></div>' +
'<div class="draco-panneau-outils">' +
@@ -178,7 +286,13 @@
appliquerLargeur();
window.addEventListener("resize", appliquerLargeur);
document.addEventListener("keydown", function (ev) {
if (ev.key === "Escape" && root.classList.contains("open")) fermer();
if (ev.key !== "Escape") return;
/* Surimpression ouverte : Échap = retour au panneau seul
* (« Mode sidebar »). Quand le focus est dans l'iframe de la
* console (autre origine), l'événement ne remonte pas
* jusqu'ici : il ne peut donc pas fermer par surprise. */
if (surOuverte) { modeSidebarConsole(); return; }
if (root.classList.contains("open")) fermer();
});
}