Files
flowdeck/app/templates/agent_panel.html
T
bruno 6d7af3fb64
FlowDeck CI / docker (push) Successful in 1m51s
FlowDeck CI / lint (push) Successful in 1m56s
FlowDeck CI / test (push) Successful in 15m9s
feat: éditeur visuel d'automations (pipeline steps) + fix CSP multi-instructions (v7.45.0)
Added — Settings → Automations, pipeline visuel (API steps v7.0) :
- Cartes ordinees (trigger/condition/delay/action) : resume + edition
  TYPÉE par kind/type (datalist evenements, 7 ops, 8 types d'action avec
  leurs champs reels), ajout/edition/suppression/haut-bas via
  POST/PUT/DELETE /workspace/automations[/steps]/...
- ✨ Convertir le JSON en pipeline (legacy → steps ordonnes) ; textareas
  JSON masques des qu'un step existe.
- Gate E2E « editeur visuel de steps » : creation → edition → ajout →
  carte « Action · webhook » sous CSP reel.

Fixed (trouve par le gate) — 51 expressions Alpine MULTI-INSTRUCTIONS
(`a=1; b()`) = interdites par le parseur CSP (une seule expression par
directive ; ';' = token inattendu) — INVISIBLE pour le scan par tokens :
- Conversion en methodes dans 11 fichiers : nav settings x8 (navTo),
  menu section base x7 (closeAndSetCount/Move/...), parts+editeur x13
  (setSharePerm, more*, markAndSave...), breadcrumb x5 (hover*/goClose),
  library/local x6 (menus popup), board x3 (pickStatus/...), ctx-menu x2
  (addTagAndClear), agent/card/gitea/workspaces x6.
- Scanner dedie scan_semi (inventaire ';' hors chaines) ajoute au lot.

Verifs : 39 templates Jinja parse OK · scan expressions = 0 incompatible
(4 faux positifs en chaines) · **E2E 8/8** · suite **1094/1094** ·
ruff OK · docs a jour
2026-10-02 16:56:29 -04:00

443 lines
33 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{# ════════════ FlowDeck Agent panel v4.13.0 — Notion-AI-style sidebar ════════════ #}
<style>
#fd-agent-fab{position:fixed;right:22px;bottom:22px;width:54px;height:54px;border-radius:50%;background:var(--text-primary,#37352F);color:var(--bg-primary,#fff);border:none;cursor:pointer;box-shadow:0 8px 26px rgba(0,0,0,.35);display:flex;align-items:center;justify-content:center;z-index:1700;transition:transform .15s,box-shadow .15s}
#fd-agent-fab:hover{transform:scale(1.06);box-shadow:0 12px 34px rgba(0,0,0,.45)}
#fd-agent-panel{position:fixed;top:0;right:0;bottom:0;width:470px;max-width:100vw;background:var(--bg-primary,#191919);border-left:1px solid var(--border,rgba(255,255,255,.1));z-index:1850;display:flex;flex-direction:column;box-shadow:-16px 0 48px rgba(0,0,0,.55);transform:translateX(102%);transition:transform .24s cubic-bezier(.16,1,.3,1)}
#fd-agent-panel.open{transform:translateX(0)}
#fd-agent-panel ::-webkit-scrollbar{width:8px;height:8px}
#fd-agent-panel ::-webkit-scrollbar-thumb{background:rgba(128,128,128,.35);border-radius:4px}
#fd-agent-panel ::-webkit-scrollbar-track{background:transparent}
/* Header */
.fd-ap-head{display:flex;align-items:center;gap:12px;padding:16px 18px 12px}
.fd-ap-logo{width:36px;height:36px;border-radius:50%;background:var(--text-primary,#37352F);color:var(--bg-primary,#fff);display:flex;align-items:center;justify-content:center;flex-shrink:0;box-shadow:0 2px 8px rgba(0,0,0,.25)}
.fd-ap-head-main{flex:1;min-width:0}
.fd-ap-title{font-size:15px;font-weight:600;color:var(--text,#f0f0f0);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.fd-ap-sub{font-size:11.5px;color:var(--text-dim,#9aa);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.fd-ap-headbtn{background:none;border:none;color:var(--text-dim,#999);width:30px;height:30px;border-radius:8px;cursor:pointer;font-size:18px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.fd-ap-headbtn:hover{background:var(--bg-hover,#2b2b2b);color:var(--text,#fff)}
/* Context bar */
.fd-ap-ctx{display:flex;align-items:center;gap:8px;margin:0 16px 8px;padding:7px 11px;border-radius:9px;background:rgba(35,131,226,.09);border:1px solid rgba(35,131,226,.16);font-size:12px;color:var(--text-secondary,#ddd);min-height:18px}
.fd-ap-ctx .fd-ctx-ico{font-size:13px}
.fd-ap-ctx .fd-ctx-lbl{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* No-config banner */
.fd-ap-noconfig{padding:8px 14px;font-size:12px;color:#e6b4b4;background:rgba(227,98,98,.1);border:1px solid rgba(227,98,98,.18);margin:0 16px 10px;border-radius:9px;display:flex;gap:6px;flex-wrap:wrap;align-items:center}
.fd-ap-noconfig a{color:#ff9d9d;font-weight:600;text-decoration:none}
.fd-ap-noconfig .fd-k-sub{width:100%;color:var(--text-dim,#aaa)}
/* Tabs */
.fd-ap-tabs{display:flex;gap:2px;padding:0 16px 8px;border-bottom:1px solid var(--border,rgba(255,255,255,.08))}
.fd-ap-tab{flex:1;text-align:center;padding:7px;background:none;border:none;border-radius:8px;color:var(--text-dim,#999);font-size:12.5px;cursor:pointer}
.fd-ap-tab.active{background:var(--bg-tertiary,#242424);color:var(--text,#fff);font-weight:600}
/* Body */
.fd-agent-body{flex:1;overflow-y:auto;padding:12px 16px}
.fd-ap-hist-item{padding:8px 11px;border-radius:9px;cursor:pointer;color:var(--text,#e0e0e0);font-size:13px;border:1px solid transparent;margin-bottom:2px}
.fd-ap-hist-item:hover,.fd-ap-hist-item.active{background:var(--bg-hover,#262626);border-color:var(--border,rgba(255,255,255,.08))}
/* History hover / long-press preview */
.fd-ap-preview{position:fixed;width:290px;max-width:calc(100vw - 24px);background:var(--bg-modal,#1c1c1c);border:1px solid var(--border,rgba(255,255,255,.16));border-radius:12px;box-shadow:0 16px 44px rgba(0,0,0,.6);z-index:1990;padding:11px 13px;font-size:12px;color:var(--text,#e6e6e6);display:none;pointer-events:auto}
.fd-ap-preview.show{display:block}
.fd-ap-preview-title{font-weight:600;font-size:12.5px;margin-bottom:2px;color:var(--text,#fff);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fd-ap-preview-meta{font-size:10px;color:var(--text-dim,#999);margin-bottom:8px}
.fd-ap-preview-empty{font-size:11px;color:var(--text-dim,#aaa);padding:4px 0}
.fd-ap-preview-label{font-size:9.5px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-dim,#999);margin:7px 0 3px}
.fd-ap-preview-txt{white-space:pre-wrap;word-break:break-word;line-height:1.55;max-height:72px;overflow:hidden;color:var(--text-secondary,#d0d0d0)}
/* Empty / welcome state */
.fd-ap-empty{padding:6px 2px 4px}
.fd-ap-empty-title{font-size:14px;font-weight:600;color:var(--text,#eee);margin-bottom:2px}
.fd-ap-empty-desc{font-size:12px;color:var(--text-dim,#999);margin-bottom:12px}
.fd-ap-qa{display:flex;flex-direction:column;gap:6px}
.fd-ap-qa-item{display:flex;align-items:center;gap:10px;text-align:left;background:var(--bg-tertiary,#202020);border:1px solid var(--border,rgba(255,255,255,.07));border-radius:10px;padding:9px 12px;color:var(--text,#e6e6e6);font-size:13px;cursor:pointer;transition:background .12s,border-color .12s}
.fd-ap-qa-item:hover{background:var(--bg-hover,#2a2a2a);border-color:rgba(35,131,226,.4)}
.fd-ap-qa-item .fd-qa-ico{font-size:16px;flex-shrink:0}
.fd-ap-qa-item .fd-qa-new{margin-left:auto;font-size:9px;font-weight:700;color:#fff;background:var(--accent);border-radius:99px;padding:2px 8px;letter-spacing:.3px}
/* Messages */
.fd-agent-msg{display:flex;gap:9px;margin-bottom:14px}
.fd-agent-msg-avatar{width:28px;height:28px;border-radius:50%;background:var(--bg-tertiary,#2b2b2b);display:flex;align-items:center;justify-content:center;font-size:15px;flex-shrink:0}
.fd-agent-msg-assistant .fd-agent-msg-avatar{background:rgba(35,131,226,.18)}
.fd-agent-msg-body{flex:1;min-width:0}
.fd-agent-msg-meta{font-size:10px;color:var(--text-dim,#888);margin-bottom:2px;text-transform:uppercase;letter-spacing:.5px}
.fd-agent-msg-content{font-size:13.5px;color:var(--text,#ececec);white-space:pre-wrap;word-break:break-word;line-height:1.55}
.fd-agent-msg-content a{color:var(--accent)}
.fd-agent-msg-proposal .fd-agent-msg-content{background:rgba(35,131,226,.07);border:1px solid rgba(35,131,226,.22);border-radius:10px;padding:10px 12px}
.fd-proposal-bar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:8px}
.fd-proposal-btn{background:var(--bg-tertiary,#242424);border:1px solid var(--border,rgba(255,255,255,.12));color:var(--text,#e6e6e6);border-radius:8px;padding:6px 12px;font-size:12.5px;cursor:pointer}
.fd-proposal-btn:hover{background:var(--bg-hover,#2e2e2e)}
.fd-proposal-btn.primary{background:var(--accent,#2383E2);border-color:transparent;color:#fff;font-weight:600}
.fd-proposal-btn:disabled{opacity:.5;cursor:not-allowed}
.fd-proposal-note{font-size:11px;color:var(--text-dim,#999)}
.fd-agent-thinking{font-size:12.5px;color:var(--text-dim,#999);padding:10px 12px;border-radius:9px;background:rgba(255,255,255,.04);margin:4px 0 8px;font-style:italic}
.fd-ap-err{color:#ef8a8a;font-size:12px;padding:4px 2px}
.fd-ap-notice{color:var(--text-secondary,#ccc);font-size:12px;padding:7px 10px;margin:2px 0 8px;border-radius:9px;background:rgba(128,128,128,.12);border:1px solid var(--border,rgba(255,255,255,.12))}
/* Steps accordion (Notion-style reasoning + actions audit trail) */
.fd-steps{margin:0 0 8px;border:1px solid var(--border,rgba(255,255,255,.11));border-radius:10px;background:rgba(255,255,255,.03);overflow:hidden}
.fd-steps-head{display:flex;align-items:center;gap:7px;width:100%;padding:7px 11px;background:transparent;border:none;color:var(--text-secondary,#ccc);font-size:12px;cursor:pointer;text-align:left}
.fd-steps-head:hover{background:rgba(255,255,255,.05)}
.fd-steps-chev{display:inline-block;font-size:9px;color:var(--text-dim,#999);transition:transform .15s;width:12px;flex-shrink:0;text-align:center}
.fd-steps-chev.down{transform:rotate(90deg)}
.fd-steps-count{font-weight:600;color:var(--text,#eee)}
.fd-steps-elem{font-weight:400;color:var(--text-dim,#999);margin-left:6px}
.fd-steps-body{border-top:1px solid var(--border,rgba(255,255,255,.08));padding:6px 8px 8px;max-height:340px;overflow-y:auto}
.fd-step-thought{margin:3px 0}
.fd-step-thought-head{display:flex;align-items:center;gap:6px;width:100%;border:none;background:transparent;color:var(--text-dim,#b9b9b9);font-size:12px;text-align:left;cursor:pointer;padding:4px 5px;border-radius:7px}
.fd-step-thought-head:hover{background:rgba(128,128,128,.1);color:var(--text,#fff)}
.fd-step-thought-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fd-step-thought-title .fd-t-ico{opacity:.85}
.fd-step-thought-body{margin:2px 0 4px 27px;padding:7px 10px;font-size:12px;color:var(--text-secondary,#cfcfcf);background:rgba(128,128,128,.1);border-left:2px solid rgba(35,131,226,.45);border-radius:0 8px 8px 0;white-space:pre-wrap;word-break:break-word;line-height:1.55}
.fd-step-action{display:flex;align-items:flex-start;gap:7px;padding:4px 7px;font-size:12px;color:var(--text-secondary,#c9c9c9)}
.fd-step-action .fd-step-ico{flex-shrink:0;width:14px;text-align:center;font-size:11px;margin-top:1px}
.fd-step-action.success{color:var(--text,#d9d9d9)}
.fd-step-action.success .fd-step-ico{color:#58c47a}
.fd-step-action.error{color:#e78c8c}
.fd-step-action.error .fd-step-ico{color:#ef6a6a}
.fd-step-action-detail{font-size:11px;color:#e0a3a3;display:block;margin-top:1px}
/* Composer */
.fd-ap-composer{padding:10px 14px 14px;border-top:1px solid var(--border,rgba(255,255,255,.09));position:relative}
.fd-ap-box{background:var(--bg-tertiary,#1f1f1f);border:1px solid var(--border,rgba(255,255,255,.13));border-radius:16px;padding:10px 12px 8px;transition:border-color .12s,box-shadow .12s}
.fd-ap-box:focus-within{border-color:rgba(35,131,226,.65);box-shadow:0 0 0 3px rgba(35,131,226,.12)}
.fd-ap-input{width:100%;background:transparent;border:none;outline:none;color:var(--text,#fff);font-size:14px;resize:none;font-family:inherit;line-height:1.6;max-height:220px;box-sizing:border-box;overflow-y:auto;white-space:pre-wrap;word-break:break-word;min-height:26px;position:relative}
.fd-ap-input.empty::before{content:attr(data-placeholder);color:var(--text-dim,#8a8a8a);position:absolute;left:0;top:0;pointer-events:none;white-space:pre-wrap;max-width:100%}
.ment-token{display:inline-flex;align-items:center;gap:4px;vertical-align:middle;background:rgba(35,131,226,.14);border:1px solid rgba(35,131,226,.35);color:var(--text,#eaf2ff);border-radius:6px;padding:0 3px 0 4px;font-size:13px;line-height:1.5;margin:0 1px;max-width:200px;overflow:hidden;white-space:nowrap}
.ment-token .tk-lbl{overflow:hidden;text-overflow:ellipsis}
.ment-token.skill{background:rgba(168,85,247,.16);border-color:rgba(168,85,247,.5);color:#f3e8ff}
.ment-token .tk-ico{flex-shrink:0;opacity:.9}
.fd-ap-toolbar{display:flex;align-items:center;gap:8px;margin-top:6px;flex-wrap:wrap}
.fd-ap-tip{font-size:11px;color:var(--text-dim,#8f8f8f);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fd-ap-tools{display:flex;align-items:center;gap:6px;margin-left:auto}
.fd-ap-model{display:flex;align-items:center;gap:6px;background:rgba(255,255,255,.05);border:1px solid var(--border);border-radius:99px;padding:4px 10px;font-size:11.5px;color:var(--text-secondary,#ddd);cursor:pointer;white-space:nowrap;max-width:220px;overflow:hidden;text-overflow:ellipsis}
.fd-ap-model:hover{background:var(--bg-hover,#2c2c2c)}
.fd-ap-model.off{opacity:.75}
.fd-ap-send{width:34px;height:34px;border-radius:50%;border:none;background:var(--accent,#2383E2);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:15px;transition:background .12s,transform .12s;flex-shrink:0}
.fd-ap-send:hover{background:#2c8ceb}
.fd-ap-send:disabled{opacity:.4;cursor:not-allowed;transform:none}
.fd-ap-send:not(:disabled):active{transform:scale(.94)}
/* Model picker popover */
.fd-ap-pop{position:absolute;right:14px;bottom:calc(100% + 6px);width:270px;background:var(--bg-modal,#1c1c1c);border:1px solid var(--border,rgba(255,255,255,.12));border-radius:12px;box-shadow:0 12px 40px rgba(0,0,0,.5);padding:12px;z-index:60}
.fd-ap-pop label{display:block;font-size:10.5px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-dim,#999);margin:8px 0 4px}
.fd-ap-pop select{width:100%;background:var(--bg-tertiary,#232323);border:1px solid var(--border);border-radius:8px;color:var(--text,#fff);font-size:12.5px;padding:7px 8px;outline:none}
.fd-ap-pop a{display:inline-block;margin-top:10px;font-size:12px;color:var(--accent);text-decoration:none}
/* Slash command menu */
.fd-agent-cmd-menu{position:absolute;bottom:calc(100% + 4px);left:0;right:0;background:var(--bg-modal,#1e1e1e);border:1px solid var(--border,rgba(255,255,255,.12));border-radius:11px;max-height:190px;overflow-y:auto;box-shadow:0 -8px 26px rgba(0,0,0,.4);z-index:70;display:none}
.fd-agent-cmd-menu.show{display:block}
.fd-agent-cmd-item{padding:8px 12px;font-size:12px;color:var(--text,#eee);cursor:pointer;border-radius:7px;margin:3px;display:flex;gap:8px;align-items:baseline}
.fd-agent-cmd-item:hover{background:var(--bg-hover,#2b2b2b)}
.fd-agent-cmd-item.focus{background:var(--bg-hover,#2b2b2b);box-shadow:inset 2px 0 0 var(--accent,#2383E2);color:var(--text,#fff)}
.fd-agent-cmd-item b{color:var(--accent,#2383E2);font-family:monospace;white-space:nowrap}
.fd-agent-cmd-item b.k-cmd{color:var(--accent,#2383E2)}
.fd-agent-cmd-item b.k-skill{color:#c084fc}
.fd-agent-cmd-item span{color:var(--text-dim,#999);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.fd-agent-cmd-sep{font-size:10px;text-transform:uppercase;letter-spacing:.6px;color:var(--text-dim,#888);padding:8px 14px 2px}
/* Action bar under each assistant post */
.fd-agent-msg-actions{display:flex;gap:5px;flex-wrap:wrap;margin-top:7px;align-items:center}
.fd-act-btn{display:inline-flex;align-items:center;justify-content:center;gap:0;background:transparent;border:1px solid var(--border,rgba(255,255,255,.09));color:var(--text-dim,#9aa);border-radius:7px;width:30px;height:26px;font-size:13px;cursor:pointer;transition:background .12s,color .12s,border-color .12s;line-height:1}
.fd-act-btn:hover{background:var(--bg-hover,#262626);color:var(--text,#fff);border-color:rgba(255,255,255,.25)}
.fd-act-btn.on{background:rgba(35,131,226,.18);border-color:rgba(35,131,226,.55);color:#fff}
.fd-act-btn:disabled,.fd-act-btn.disabled{opacity:.4;cursor:not-allowed}
/* Chips (contexte épinglé @ / + / skills) */
.fd-ap-chips{display:flex;flex-direction:column;gap:5px;padding:8px 4px 4px;max-height:92px;overflow-y:auto}
.fd-chip-row{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.fd-chip{display:inline-flex;align-items:center;gap:6px;background:rgba(35,131,226,.10);border:1px solid rgba(35,131,226,.30);color:var(--text,#e8e8e8);border-radius:99px;padding:3px 5px 3px 9px;font-size:11.5px;max-width:220px}
.fd-chip.skill{background:rgba(168,85,247,.12);border-color:rgba(168,85,247,.38)}
.fd-chip.chip-open{cursor:pointer}
.fd-chip.chip-open:hover{background:rgba(35,131,226,.20);border-color:rgba(35,131,226,.55)}
.fd-chip-ico{flex-shrink:0;font-size:12px}
.fd-chip-lbl{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fd-chip-spacer{flex-basis:100%;height:0}
.fd-chip-x{background:none;border:none;color:var(--text-dim,#aaa);cursor:pointer;font-size:13px;line-height:1;padding:0 4px;border-radius:50%;flex-shrink:0}
.fd-chip-x:hover{color:#fff;background:rgba(255,255,255,.14)}
/* Add button (+ contexte) */
.fd-ap-add{width:30px;height:30px;border-radius:50%;background:var(--bg-tertiary,#262626);border:1px solid var(--border,rgba(255,255,255,.14));color:var(--text,#ddd);cursor:pointer;font-size:17px;display:flex;align-items:center;justify-content:center;flex-shrink:0;line-height:1}
.fd-ap-add:hover{background:var(--bg-hover,#303030);color:#fff}
/* Mention menu (@ / +) */
.fd-ap-mention-menu{position:absolute;bottom:calc(100% + 4px);left:0;right:0;background:var(--bg-modal,#1e1e1e);border:1px solid var(--border,rgba(255,255,255,.12));border-radius:11px;max-height:220px;overflow-y:auto;box-shadow:0 -8px 26px rgba(0,0,0,.4);z-index:70;display:none}
.fd-ap-mention-menu.show{display:block}
.fd-mention-item{display:flex;align-items:center;gap:9px;padding:7px 12px;font-size:12.5px;color:var(--text,#eee);cursor:pointer;border-radius:7px;margin:2px 4px}
.fd-mention-item:hover,.fd-mention-item.focus{background:var(--bg-hover,#2b2b2b)}
.fd-mention-ico{width:24px;text-align:center;flex-shrink:0;font-size:14px}
.fd-mention-main{flex:1;min-width:0;display:flex;flex-direction:column}
.fd-mention-lbl{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fd-mention-sub{font-size:10.5px;color:var(--text-dim,#999);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fd-mention-sep{font-size:10px;text-transform:uppercase;letter-spacing:.6px;color:var(--text-dim,#888);padding:8px 14px 2px}
.fd-mention-empty,.fd-mention-load{padding:11px 12px;font-size:12px;color:var(--text-dim,#aaa);text-align:center}
/* Toast */
.fd-ap-toast{position:absolute;left:14px;right:14px;bottom:calc(100% + 10px);background:var(--bg-modal,#1c1c1c);border:1px solid rgba(35,131,226,.5);color:var(--text,#fff);font-size:12px;border-radius:9px;padding:8px 12px;box-shadow:0 10px 30px rgba(0,0,0,.45);z-index:90;display:flex;gap:8px;align-items:center;justify-content:space-between}
.fd-ap-toast.err{border-color:rgba(227,98,98,.55)}
.fd-ap-toast button{background:none;border:none;color:var(--text-dim);cursor:pointer;font-size:13px;padding:0}
[x-cloak]{display:none!important}
.fd-agent-msg-markdown{line-height:1.6}
.fd-agent-msg-markdown p{margin:0 0 8px}
.fd-agent-msg-markdown h1,.fd-agent-msg-markdown h2,.fd-agent-msg-markdown h3,.fd-agent-msg-markdown h4{margin:12px 0 6px;font-weight:700;line-height:1.25}
.fd-agent-msg-markdown h1{font-size:1.4em}
.fd-agent-msg-markdown h2{font-size:1.25em}
.fd-agent-msg-markdown h3{font-size:1.1em}
.fd-agent-msg-markdown h4{font-size:1em}
.fd-agent-msg-markdown ul,.fd-agent-msg-markdown ol{margin:0 0 8px;padding-left:20px}
.fd-agent-msg-markdown li{margin:2px 0}
.fd-agent-msg-markdown code{background:rgba(127,127,127,.18);border-radius:4px;padding:1px 5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.9em}
.fd-agent-msg-markdown pre{background:var(--bg-code,#111);border:1px solid var(--border,#333);border-radius:8px;padding:12px;overflow-x:auto;margin:8px 0}
.fd-agent-msg-markdown pre code{background:none;padding:0;font-size:12px;line-height:1.5}
.fd-md-table-wrap{overflow-x:auto;margin:8px 0}
.fd-md-table{border-collapse:collapse;width:100%;font-size:13px}
.fd-md-table th,.fd-md-table td{border:1px solid var(--border,#333);padding:6px 10px;text-align:left}
.fd-md-table th{background:rgba(127,127,127,.14);font-weight:600}
.fd-md-table tr:nth-child(even) td{background:rgba(127,127,127,.06)}
.fd-md-quote{border-left:3px solid var(--accent,#2383E2);margin:8px 0;padding:2px 12px;color:var(--text-dim)}
.fd-md-quote p{margin:4px 0}
.fd-md-hr{border:none;border-top:1px solid var(--border,#333);margin:12px 0}
.fd-agent-msg-markdown a{color:var(--accent,#2383E2);text-decoration:none}
.fd-agent-msg-markdown a:hover{text-decoration:underline}
</style>
{# ── Global Agent API (available before Alpine for the FAB / shortcut) ── #}
<script data-cfasync="false" nonce="{{ csp_nonce() }}" src="/static/js/agent_panel_1.js?v={{ asset_version }}"></script>
{# ── Floating button ── #}
<button id="fd-agent-fab" title="FlowDeck Agent (Ctrl+J)" onclick="window.fdAgent&&window.fdAgent.toggle()"><svg viewBox="0 0 24 24" width="26" height="26" fill="currentColor" aria-hidden="true"><path d="M12 3l1.9 5.8a2 2 0 0 0 1.3 1.3L21 12l-5.8 1.9a2 2 0 0 0-1.3 1.3L12 21l-1.9-5.8a2 2 0 0 0-1.3-1.3L3 12l5.8-1.9a2 2 0 0 0 1.3-1.3L12 3Z"/></svg></button>
{# ── Panel ── #}
<div id="fd-agent-panel" class="fd-ap" :class="{open: open}" x-data="agentPanel()" @keydown.escape.window="close()" role="dialog" aria-label="Agent FlowDeck">
<div class="fd-ap-head">
<span class="fd-ap-logo"><svg viewBox="0 0 24 24" width="19" height="19" fill="currentColor" aria-hidden="true"><path d="M12 3l1.9 5.8a2 2 0 0 0 1.3 1.3L21 12l-5.8 1.9a2 2 0 0 0-1.3 1.3L12 21l-1.9-5.8a2 2 0 0 0-1.3-1.3L3 12l5.8-1.9a2 2 0 0 0 1.3-1.3L12 3Z"/></svg></span>
<div class="fd-ap-head-main">
<div class="fd-ap-title" x-text="currentConv && currentConv.title ? currentConv.title : 'How can I help you today?'"></div>
<div class="fd-ap-sub" x-text="providerModelLabel"></div>
</div>
<button class="fd-ap-headbtn" title="Nouvelle conversation" @click="newConversation()">+</button>
<button class="fd-ap-headbtn" title="Fermer (Ctrl+J)" @click="close()">✕</button>
</div>
<div class="fd-ap-noconfig" x-show="!hasActiveProvider">
<span>⚠️ Aucun fournisseur activé et fonctionnel.</span>
<a href="/settings" target="_blank" @click.stop>Configurer → Agent &amp; IA</a>
<template x-if="unverifiedCount">
<span class="fd-k-sub">🔑 <span x-text="unverifiedCount"></span> clé(s) à tester : Paramètres puis « Tester ».</span>
</template>
</div>
<div class="fd-ap-tabs">
<button class="fd-ap-tab" :class="{active: tab==='chat'}" @click="tab='chat'">Conversation</button>
<button class="fd-ap-tab" :class="{active: tab==='history'}" @click="showHistory()">Historique</button>
</div>
<div class="fd-agent-body">
<template x-if="tab==='history'">
<div>
<template x-for="c in conversations" :key="c.id">
<div class="fd-ap-hist-item" :class="{active: currentConv && currentConv.id===c.id}"
@click="historyClick(c.id)"
@mouseenter="previewConversation(c, $event)" @mouseleave="schedulePreviewHide()"
@touchstart="startLongPress(c, $event)" @touchend="onTouchEnd()"
@touchmove="onTouchCancel()" @touchcancel="onTouchCancel()">
<div x-text="c.title || 'New conversation'"></div>
<div style="font-size:11px;color:var(--text-dim)" x-text="c.updated_at"></div>
</div>
</template>
<div x-show="!conversations.length" style="font-size:13px;color:var(--text-dim);padding:10px">Aucune conversation. Démarrez-en une !</div>
</div>
</template>
<template x-if="tab==='chat'">
<div>
<!-- Welcome / quick actions -->
<div class="fd-ap-empty" x-show="!messages.length && !streaming" x-cloak>
<div class="fd-ap-empty-desc" x-text="quickIntro"></div>
<div class="fd-ap-qa">
<template x-for="q in quickActions" :key="q.label">
<button class="fd-ap-qa-item" type="button" @click="runQuick(q)">
<span class="fd-qa-ico" x-text="q.icon"></span>
<span x-text="q.label"></span>
<span class="fd-qa-new" x-show="q.new" x-cloak>New</span>
</button>
</template>
</div>
</div>
<template x-for="m in messages" :key="m.id">
<div class="fd-agent-msg" :class="'fd-agent-msg-'+(m.role||'user') + (m.proposal ? ' fd-agent-msg-proposal' : '')">
<div class="fd-agent-msg-avatar" x-text="m.role==='assistant'?'🤖':'👤'"></div>
<div class="fd-agent-msg-body">
<div class="fd-agent-msg-meta" x-text="(m.role==='assistant'?'Agent':'Vous')+(m.model?' · '+m.model:'')"></div>
<!-- Steps accordion (reasoning thoughts + tool actions) -->
<div class="fd-steps" x-show="m.steps && m.steps.length" x-cloak>
<button class="fd-steps-head" type="button" @click="toggleSteps(m)">
<span class="fd-steps-chev" :class="{down: m.stepsOpen}">▶</span>
<span class="fd-steps-count" x-text="!m.steps ? '' : (m.steps.length === 1 ? '1 étape' : m.steps.length + ' étapes')"></span>
<span class="fd-steps-elem" x-text="m.generating ? 'en cours…' : (m.stepsOpen ? 'réduire' : 'voir le déroulé')"></span>
</button>
<div class="fd-steps-body" x-show="m.stepsOpen" x-cloak>
<template x-for="(s, si) in m.steps" :key="si">
<div>
<template x-if="s.type==='thought'">
<div class="fd-step-thought">
<button class="fd-step-thought-head" type="button" @click="toggleStep(s)">
<span class="fd-steps-chev" :class="{down: s.open}">▶</span>
<span class="fd-step-thought-title"><span class="fd-t-ico">💭</span> <span x-text="s.title || 'Réflexion'"></span></span>
</button>
<div class="fd-step-thought-body" x-show="s.open" x-text="s.content"></div>
</div>
</template>
<template x-if="s.type==='action'">
<div class="fd-step-action" :class="s.status || 'success'" :title="(s.detail && s.status==='error') ? s.detail : s.tool || ''">
<span class="fd-step-ico" x-text="(s.status==='success') ? '✓' : '✗'"></span>
<div>
<div x-text="s.label"></div>
<div class="fd-step-action-detail" x-show="s.status==='error' && s.detail" x-text="s.detail" x-cloak></div>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
<div class="fd-agent-msg-content" x-show="m.generating" x-cloak>Génération…</div>
<div class="fd-agent-msg-content fd-agent-msg-markdown" x-show="!m.generating && m.role==='assistant' && m.content" x-init="bindMarkdown($el, m)"></div>
<div class="fd-agent-msg-content" x-show="!m.generating && m.role!=='assistant' && m.content" x-text="m.content"></div>
<template x-if="m.proposal">
<div class="fd-proposal-bar">
<button class="fd-proposal-btn primary" type="button" @click="approveProposal(m)" :disabled="m.applied || m.offline">
<span x-text="m.applied ? (m.rejected ? 'Rejetée' : '✓ Appliquée') : '✓ Appliquer au document'"></span>
</button>
<button class="fd-proposal-btn" type="button" @click="rejectProposal(m)" :disabled="m.applied">✕ Rejeter</button>
<span class="fd-proposal-note" x-show="m.offline" x-cloak>Hors-ligne — configurez une clé API pour appliquer la modification.</span>
</div>
</template>
<template x-if="m.role==='assistant' && !m.generating && m.content && !m.act">
<div class="fd-agent-msg-actions">
<button class="fd-act-btn" type="button" @click="copyResponse(m)" title="Copier la réponse">⧉</button>
<button class="fd-act-btn" type="button" @click="insertIntoPage(m)" :disabled="!canInsert" title="Insérer dans la page ouverte">➕</button>
<button class="fd-act-btn" :class="{on: m.fb==='up'}" type="button" @click="feedback(m,'up')" :disabled="!!m.fb && m.fb!=='up'" title="Feedback positif">👍</button>
<button class="fd-act-btn" :class="{on: m.fb==='down'}" type="button" @click="feedback(m,'down')" :disabled="!!m.fb && m.fb!=='down'" title="Feedback négatif">👎</button>
</div>
</template>
</div>
</div>
</template>
<div class="fd-agent-thinking" x-show="streaming && !(_livePh && _livePh.steps && _livePh.steps.length)">L'agent réfléchit et agit… <span x-text="liveReasoning"></span></div>
<div class="fd-ap-notice" x-show="notice" x-cloak x-text="notice"></div>
<div class="fd-ap-err" x-show="errorMsg" x-text="errorMsg"></div>
</div>
</template>
</div>
<div class="fd-ap-composer">
<div style="position:relative;">
<div class="fd-ap-toast" x-show="toastMsg" x-cloak :class="{err: toastErr}">
<span x-text="toastMsg"></span>
<button type="button" @click="toastMsg=''">✕</button>
</div>
<div class="fd-agent-cmd-menu" x-ref="cmdMenu" :class="{show: commandOpen && commandSuggestions.length && !slashDismiss}">
<template x-for="c in commandSuggestions" :key="c.key">
<div>
<div class="fd-agent-cmd-sep" x-show="c._sep" x-text="c._sep"></div>
<div class="fd-agent-cmd-item" x-show="!c._sep" :class="{focus: c._i===cmdFocus}" @click="useSuggestion(c)" @mouseenter="cmdFocus=c._i">
<b :class="c._admin ? 'k-cmd' : 'k-skill'" x-text="(c._admin ? c.cmd : c.cmd)"></b>
<span x-text="c.desc"></span>
</div>
</div>
</template>
</div>
<div class="fd-ap-mention-menu" :class="{show: mentionOpen}" x-ref="mentionMenu">
<div class="fd-mention-load" x-show="mentionLoad" x-cloak>Recherche…</div>
<template x-if="!mentionLoad && !hasMentionItems">
<div class="fd-mention-empty">Aucun élément. Tapez un nom de document, page ou base.</div>
</template>
<template x-if="!mentionLoad">
<template x-for="it in mentionItems" :key="it.token || it.key">
<div>
<div class="fd-mention-sep" x-show="it._sep" x-text="it._sep"></div>
<div class="fd-mention-item" x-show="!it._sep" :class="{focus: it._i===mentionFocus}"
@click="pickMention(it)" @mouseenter="setMentionFocus(it._i)">
<span class="fd-mention-ico" x-text="it.icon"></span>
<div class="fd-mention-main">
<div class="fd-mention-lbl" x-text="it.label"></div>
<div class="fd-mention-sub" x-text="it.sub"></div>
</div>
</div>
</div>
</template>
</template>
</div>
</div>
<div class="fd-ap-chips" x-show="contextChips.length" x-cloak>
<div class="fd-chip-row" x-show="mentionChips.length">
<template x-for="chip in mentionChips" :key="chip.key">
<span class="fd-chip chip-open" :title="chipAction(chip)" @click="openChip(chip)">
<span class="fd-chip-ico" x-text="chip.icon"></span>
<span class="fd-chip-lbl" x-text="chip.label"></span>
<button class="fd-chip-x" type="button" @click.stop="removeChip(chip.token)" title="Retirer du contexte">✕</button>
</span>
</template>
</div>
<div class="fd-chip-row" x-show="skillChips.length">
<template x-for="chip in skillChips" :key="chip.key">
<span class="fd-chip skill">
<span class="fd-chip-ico" x-text="chip.icon"></span>
<span class="fd-chip-lbl" x-text="chip.label"></span>
<button class="fd-chip-x" type="button" @click="removeChip(chip.token)" title="Retirer le skill">✕</button>
</span>
</template>
</div>
</div>
<div class="fd-ap-box">
<div class="fd-ap-input empty" x-ref="cmdInput" contenteditable="true" spellcheck="false" role="textbox" aria-multiline="true"
data-placeholder="Demandez à l'agent… Tapez @ pour référencer une page/base, / pour un skill. Entrée = envoyer, Maj+Entrée = ligne"
@input="onInput()" @keydown="onComposerKeydown($event)" @paste="onPaste($event)"></div>
<div class="fd-ap-toolbar">
<button class="fd-ap-add" type="button" title="Ajouter un élément au contexte (@ / +)" @click="toggleAddPicker()">+</button>
<span class="fd-ap-tip" x-text="composerHint"></span>
<div class="fd-ap-tools">
<button class="fd-ap-model" :class="{off: !hasActiveProvider}" type="button" @click="modelOpen=!modelOpen" title="Choisir fournisseur et modèle">
<span x-text="autoLabel"></span> <span style="opacity:.6">▾</span>
</button>
<button class="fd-ap-send" @click="send()" :disabled="streaming || !canSend" title="Envoyer">▲</button>
</div>
</div>
</div>
<div class="fd-ap-pop" x-show="modelOpen" @click.outside="modelOpen=false" x-cloak>
<label>Fournisseur</label>
<select x-model="llmProvider" @change="onProviderChange()">
<template x-for="p in providers" :key="p.id">
<option :value="p.id" x-text="p.id==='offline' ? 'Hors-ligne (mock)' : p.name"></option>
</template>
</select>
<label>Modèle</label>
<select x-model="llmModel" @change="patchConversation()" x-show="llmProvider!=='offline'">
<template x-for="m in providerModels" :key="m">
<option :value="m" x-text="m"></option>
</template>
</select>
<a href="/settings" target="_blank" @click="modelOpen=false">⚙️ Configurer (clés API) →</a>
</div>
</div>
{# Historique : aperçu (hover / long-press) #}
<div class="fd-ap-preview" x-ref="convPreview" :class="{show: previewVisible}"
@mouseenter="cancelPreviewHide()" @mouseleave="schedulePreviewHide()" x-cloak>
<div class="fd-ap-preview-title" x-text="previewTitle"></div>
<div class="fd-ap-preview-meta" x-text="previewMeta"></div>
<div class="fd-ap-preview-empty" x-show="!previewStart && !previewEnd" x-text="previewEmptyHint"></div>
<div class="fd-ap-preview-label" x-show="previewStart" x-cloak>Début</div>
<div class="fd-ap-preview-txt" x-show="previewStart" x-text="previewStart"></div>
<div class="fd-ap-preview-label" x-show="previewEnd" x-cloak>Réponse</div>
<div class="fd-ap-preview-txt" x-show="previewEnd" x-text="previewEnd"></div>
</div>
</div>
<script data-cfasync="false" nonce="{{ csp_nonce() }}" src="/static/js/agent_panel_2.js?v={{ asset_version }}"></script>