- presets Discord / Telegram : colonnes kind + auth (bearer/bot/none) sur
agent_connectors (migration 34) ; Discord envoie le jeton préfixé dans
l'en-tête d'autorisation, Telegram le place dans l'URL via le substitut
{secret} remplacé à l'appel — jamais stocké en clair
- Teams : scope ChannelMessage.Read.All ajouté aux Graph scopes M365
- app/services/mcp_client.py : handshake initialize → notifications/initialized
→ tools/list → tools/call (JSON-RPC 2.0, réponse JSON ou premier data: d'un
text/event-stream), garde SSRF, outils cachés en base (tools_json) —
le bouton « Tester » rejoue le handshake
- ToolRegistry._all() merge le cache MCP à chaque run : outils exposés au LLM
sous mcp_<serveur>_<outil> avec leur inputSchema, dispatch tools/call ;
serveur désactivé → outil absent du schéma, échec réseau → ToolResult(error)
- menu + : champ Type dans le formulaire connecteur (preset URL + auth),
badge « · N outil(s) » sur la fiche d'un serveur MCP
- tests : tests/test_v757_mcp_discord.py (12 tests, 0 appel réseau réel) ;
suite complète 1331 verts, ruff 0, eslint 0
- livraison : VERSION + app/main = 7.57.0, OpenAPI 523 chemins, CHANGELOG,
ROADMAP phase 7 cochée, avenant phase 7 dans docs/V74_Agent_Plus_Menu.md
536 lines
41 KiB
HTML
536 lines
41 KiB
HTML
{# ════════════ 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:var(--fd-agent-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}
|
||
|
||
/* Resize handle (left edge) */
|
||
.fd-ap-resize{position:absolute;top:0;bottom:0;left:0;width:6px;cursor:col-resize;z-index:5}
|
||
.fd-ap-resize::before{content:"";position:absolute;top:0;bottom:0;left:2px;width:2px;background:transparent;border-radius:2px;transition:background .12s ease}
|
||
.fd-ap-resize:hover::before,body.fd-ap-resizing .fd-ap-resize::before{background:var(--accent,#2383E2)}
|
||
body.fd-ap-resizing{cursor:col-resize!important;user-select:none}
|
||
body.fd-ap-resizing *{cursor:col-resize!important}
|
||
|
||
/* 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}
|
||
|
||
/* Menu + (sections) — réutilise le skin du menu de mentions */
|
||
.fd-ap-plus-menu{max-height:min(340px,60vh)}
|
||
.fd-ap-plus-menu .fd-mention-item.disabled{opacity:.45;cursor:default;background:none}
|
||
.fd-ap-plus-menu .fd-mention-item.disabled:hover{background:none}
|
||
.fd-plus-head{display:flex;align-items:center;gap:8px;padding:7px 11px 6px;border-bottom:1px solid var(--border,rgba(255,255,255,.08));margin-bottom:3px}
|
||
.fd-plus-back{background:none;border:1px solid var(--border,rgba(255,255,255,.14));color:var(--text,#ddd);border-radius:7px;width:26px;height:22px;cursor:pointer;line-height:1;flex-shrink:0}
|
||
.fd-plus-back:hover{background:var(--bg-hover,#2b2b2b)}
|
||
.fd-plus-title{font-size:11px;font-weight:600;color:var(--text-secondary,#ddd);text-transform:uppercase;letter-spacing:.4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||
.fd-plus-filter{padding:7px 11px 2px}
|
||
.fd-plus-filter input{width:100%;background:var(--bg-tertiary,#232323);border:1px solid var(--border,rgba(255,255,255,.14));border-radius:8px;color:var(--text,#fff);font-size:12.5px;padding:6px 8px;outline:none;box-sizing:border-box}
|
||
.fd-plus-form{padding:4px 11px 12px}
|
||
.fd-plus-form label{display:block;font-size:10.5px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-dim,#999);margin:9px 0 3px}
|
||
.fd-plus-form input,.fd-plus-form textarea{width:100%;background:var(--bg-tertiary,#232323);border:1px solid var(--border,rgba(255,255,255,.14));border-radius:8px;color:var(--text,#fff);font-size:12.5px;padding:7px 8px;outline:none;font-family:inherit;box-sizing:border-box}
|
||
.fd-plus-form textarea{min-height:110px;resize:vertical;line-height:1.5}
|
||
.fd-plus-file{display:none}
|
||
.fd-plus-chev{margin-left:auto;color:var(--text-dim,#999);opacity:.75;font-size:15px;flex-shrink:0}
|
||
.fd-plus-crumb{display:flex;gap:5px;flex-wrap:wrap;padding:7px 11px 3px;font-size:11px;border-bottom:1px solid var(--border,rgba(255,255,255,.08));margin-bottom:3px}
|
||
.fd-plus-crumb-it{color:var(--accent,#2383E2);cursor:pointer;white-space:nowrap}
|
||
.fd-plus-crumb-it:hover{text-decoration:underline}
|
||
|
||
/* 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,var(--bg-tertiary,#1f1f1f));border:1px solid var(--border,rgba(255,255,255,.12));border-radius:8px;padding:12px;overflow-x:auto;margin:8px 0;color:var(--text,#ececec)}
|
||
.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-resize" x-ref="resizeHandle" role="separator" aria-orientation="vertical" aria-label="Redimensionner le panneau" tabindex="0" title="Glisser pour redimensionner — double-clic pour réinitialiser"></div>
|
||
<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 & 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 class="fd-ap-mention-menu fd-ap-plus-menu" :class="{show: plusOpen}" x-ref="plusMenu">
|
||
<div class="fd-plus-head" x-show="plusSection!=='root'" x-cloak>
|
||
<button class="fd-plus-back" type="button" @click="plusBack()" title="Retour" aria-label="Retour">←</button>
|
||
<span class="fd-plus-title" x-text="plusTitle"></span>
|
||
</div>
|
||
<div class="fd-plus-filter" x-show="plusSection==='skills-gallery'" x-cloak>
|
||
<input type="search" placeholder="Filtrer les compétences…" aria-label="Filtrer les compétences" x-model="galleryFilter">
|
||
</div>
|
||
<div class="fd-plus-crumb" x-show="plusSection==='browse'">
|
||
<span class="fd-plus-crumb-it" @click="plusGoto(-1)">Racine</span>
|
||
<template x-for="(c, ci) in plusCrumb" :key="c.id">
|
||
<span class="fd-plus-crumb-it" @click="plusGoto(ci)" x-text="'› ' + c.name"></span>
|
||
</template>
|
||
</div>
|
||
<div class="fd-mention-load" x-show="plusSection==='browse' && plusLoading" x-cloak>Chargement…</div>
|
||
<div class="fd-mention-empty" x-show="plusSection==='browse' && !plusLoading && !plusItems.length" x-cloak>Dossier vide</div>
|
||
<template x-for="(it, i) in plusItems" :key="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: i===plusFocus, disabled: !!it.disabled}"
|
||
@click="plusAction(it)" @mouseenter="it.disabled ? null : plusFocus = 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>
|
||
<span class="fd-plus-chev" x-show="it.chev || it._folder">›</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<div class="fd-plus-form" x-show="plusSection==='skills-edit'" x-cloak>
|
||
<label for="fd-plus-skill-name">Nom</label>
|
||
<input id="fd-plus-skill-name" type="text" x-model="skillForm.name" placeholder="ex. synthese-reunion" @keydown.enter.prevent="saveSkill()">
|
||
<label for="fd-plus-skill-desc">Description</label>
|
||
<input id="fd-plus-skill-desc" type="text" x-model="skillForm.description" placeholder="Une phrase">
|
||
<label for="fd-plus-skill-prompt">Prompt (consigne appliquée à la demande)</label>
|
||
<textarea id="fd-plus-skill-prompt" x-model="skillForm.prompt_template" placeholder="Fais…"></textarea>
|
||
<div class="fd-proposal-bar">
|
||
<button class="fd-proposal-btn primary" type="button" @click="saveSkill()">✓ Enregistrer</button>
|
||
<button class="fd-proposal-btn" type="button" x-show="skillForm.id" @click="deleteSkill()" x-cloak>✕ Supprimer</button>
|
||
<button class="fd-proposal-btn" type="button" x-show="skillForm.id" @click="exportSkill()" x-cloak>⤓ Export</button>
|
||
<button class="fd-proposal-btn" type="button" @click="plusBack()">← Retour</button>
|
||
</div>
|
||
</div>
|
||
<div class="fd-plus-form" x-show="plusSection==='connector-new'" x-cloak>
|
||
<label for="fd-plus-cn-kind">Type</label>
|
||
<select id="fd-plus-cn-kind" x-model="connectorForm.kind" @change="connectorPreset()">
|
||
<option value="custom">Connecteur personnalisé (HTTP + clé)</option>
|
||
<option value="discord">Discord (bot)</option>
|
||
<option value="telegram">Telegram (bot)</option>
|
||
<option value="mcp">Serveur MCP (streamable HTTP)</option>
|
||
</select>
|
||
<label for="fd-plus-cn-name">Nom</label>
|
||
<input id="fd-plus-cn-name" type="text" x-model="connectorForm.name" placeholder="ex. Base de connaissances" @keydown.enter.prevent="connectorCreate()">
|
||
<label for="fd-plus-cn-url" x-text="connectorForm.hint || 'URL publique (hôtes privés refusés)'"></label>
|
||
<input id="fd-plus-cn-url" type="text" x-model="connectorForm.url" placeholder="https://api.exemple.com">
|
||
<label for="fd-plus-cn-secret">Clé / jeton — chiffrée, jamais renvoyée</label>
|
||
<input id="fd-plus-cn-secret" type="password" x-model="connectorForm.secret" placeholder="sk-…" autocomplete="off">
|
||
<div class="fd-proposal-bar">
|
||
<button class="fd-proposal-btn primary" type="button" @click="connectorCreate()">✓ Ajouter</button>
|
||
<button class="fd-proposal-btn" type="button" @click="plusBack()">← Retour</button>
|
||
</div>
|
||
</div>
|
||
<input type="file" accept="application/json,.json" x-ref="skillImport" class="fd-plus-file" aria-label="Importer un skill JSON" @change="importSkillFile($event)">
|
||
</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 au contexte : fichiers, répertoires, compétences…" aria-label="Menu d'ajout au contexte" aria-haspopup="menu" :aria-expanded="plusOpen" @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>
|