Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb4c42b0b5 | ||
|
|
73b3fbce6e | ||
|
|
c7da7002ca |
@@ -1,5 +1,52 @@
|
|||||||
# Changelog - FlowDeck
|
# 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
|
||||||
|
panneau Spark (demande de Bruno) : ouvre la console web du
|
||||||
|
service d'agents (https://agent.dracodev.net/) dans un nouvel
|
||||||
|
onglet, sur la discussion actuellement affichée (`?disc=<id>`,
|
||||||
|
URL construite au clic) et avec l'app d'origine
|
||||||
|
(`?app=flowdeck`, déduite du sous-domaine). Panneau recopié
|
||||||
|
octet-identique dans les 4 apps (TikTak 0.59.1, Flowdeck
|
||||||
|
7.80.1, Kanbante 0.28.1, ObsiGate 2.76.1).
|
||||||
|
|
||||||
## v7.80.0 (2026-10-22) — Émission d'événements vers le journal du service d'agents (Lot 2)
|
## v7.80.0 (2026-10-22) — Émission d'événements vers le journal du service d'agents (Lot 2)
|
||||||
|
|
||||||
- **Nouveau module `app/services/agent_evenements.py`** : Flowdeck
|
- **Nouveau module `app/services/agent_evenements.py`** : Flowdeck
|
||||||
|
|||||||
+25
-1
@@ -190,9 +190,33 @@ async def lifespan(_app: FastAPI):
|
|||||||
pass
|
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(
|
app = FastAPI(
|
||||||
title="FlowDeck",
|
title="FlowDeck",
|
||||||
version="7.74.3",
|
version=_lire_version(),
|
||||||
docs_url="/docs",
|
docs_url="/docs",
|
||||||
redoc_url="/redoc",
|
redoc_url="/redoc",
|
||||||
lifespan=lifespan,
|
lifespan=lifespan,
|
||||||
|
|||||||
+138
-1
@@ -18,6 +18,16 @@
|
|||||||
* Panneau v2 (Lot 1) : rendu Markdown, largeur réglable à la souris
|
* Panneau v2 (Lot 1) : rendu Markdown, largeur réglable à la souris
|
||||||
* (persistée), thème clair/sombre aligné sur l'app hôte
|
* (persistée), thème clair/sombre aligné sur l'app hôte
|
||||||
* (html[data-theme], repli prefers-color-scheme, suivi en direct).
|
* (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 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
|
* États honnêtes : service injoignable / session non reconnue / LLM non
|
||||||
* configuré / exécuteur non configuré sont affichés tels quels. */
|
* configuré / exécuteur non configuré sont affichés tels quels. */
|
||||||
(function () {
|
(function () {
|
||||||
@@ -44,6 +54,124 @@
|
|||||||
catch (e) { return null; }
|
catch (e) { return null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── 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 urlConsole() {
|
||||||
|
var params = [];
|
||||||
|
var id = convId || convStockee();
|
||||||
|
if (id) params.push("disc=" + encodeURIComponent(id));
|
||||||
|
var app = appHote();
|
||||||
|
if (app) params.push("app=" + encodeURIComponent(app));
|
||||||
|
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 ─────────────────────────────── */
|
/* ── Thème : suivre l'app hôte ─────────────────────────────── */
|
||||||
function themeHote() {
|
function themeHote() {
|
||||||
var t = document.documentElement.getAttribute("data-theme");
|
var t = document.documentElement.getAttribute("data-theme");
|
||||||
@@ -57,6 +185,7 @@
|
|||||||
}
|
}
|
||||||
function appliquerTheme() {
|
function appliquerTheme() {
|
||||||
if (root) root.setAttribute("data-draco-theme", themeHote());
|
if (root) root.setAttribute("data-draco-theme", themeHote());
|
||||||
|
if (surEl) surEl.setAttribute("data-draco-theme", themeHote());
|
||||||
}
|
}
|
||||||
function surveillerTheme() {
|
function surveillerTheme() {
|
||||||
try {
|
try {
|
||||||
@@ -122,6 +251,7 @@
|
|||||||
' <span class="d-titre">Discussion principale</span>' +
|
' <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="liste">☰ Discussions</button>' +
|
||||||
' <button type="button" class="d-btn" data-disc="nouvelle">+ Nouvelle</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) en pleine page par-dessus l\'application, sur cette discussion">Console ⤢</button>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<div class="draco-discussions" hidden></div>' +
|
'<div class="draco-discussions" hidden></div>' +
|
||||||
'<div class="draco-panneau-outils">' +
|
'<div class="draco-panneau-outils">' +
|
||||||
@@ -145,6 +275,7 @@
|
|||||||
root.querySelector(".fermer").addEventListener("click", fermer);
|
root.querySelector(".fermer").addEventListener("click", fermer);
|
||||||
root.querySelector('[data-disc="liste"]').addEventListener("click", basculerTiroir);
|
root.querySelector('[data-disc="liste"]').addEventListener("click", basculerTiroir);
|
||||||
root.querySelector('[data-disc="nouvelle"]').addEventListener("click", nouvelleDiscussion);
|
root.querySelector('[data-disc="nouvelle"]').addEventListener("click", nouvelleDiscussion);
|
||||||
|
root.querySelector('[data-disc="console"]').addEventListener("click", ouvrirConsole);
|
||||||
root.querySelector('[data-outil="aujourdhui"]').addEventListener("click", chargerAujourdhui);
|
root.querySelector('[data-outil="aujourdhui"]').addEventListener("click", chargerAujourdhui);
|
||||||
root.querySelector('[data-outil="approbations"]').addEventListener("click", chargerApprobations);
|
root.querySelector('[data-outil="approbations"]').addEventListener("click", chargerApprobations);
|
||||||
root.querySelector('[data-outil="missions"]').addEventListener("click", ouvrirMissions);
|
root.querySelector('[data-outil="missions"]').addEventListener("click", ouvrirMissions);
|
||||||
@@ -155,7 +286,13 @@
|
|||||||
appliquerLargeur();
|
appliquerLargeur();
|
||||||
window.addEventListener("resize", appliquerLargeur);
|
window.addEventListener("resize", appliquerLargeur);
|
||||||
document.addEventListener("keydown", function (ev) {
|
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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user