fix(agent): v4.13.1 - actions contenu document (resume/traduire/ameliorer/meeting) sans outils, apercu + appliquer/rejeter, mock ignore contexte
FlowDeck CI / test (push) Failing after 14s
FlowDeck CI / docker (push) Skipped

This commit is contained in:
2026-09-06 01:14:52 -04:00
parent 0b63ed17bc
commit 75b7f29826
8 changed files with 291 additions and 18 deletions
+16 -1
View File
@@ -629,8 +629,23 @@
// (Notion-style inline generation). The reply is streamed in the agent
// panel and inserted into this page via window.__fdInsertAI.
askAI(){
const self=this;
const title=(this.pageTitle||'Sans titre').trim();
this.aiRun('Rédige le contenu de la page FlowDeck intitulée « '+title+' ». Fournis un texte structuré et directement utilisable : paragraphes et, si utile, des listes à puces. Sépare chaque paragraphe par une ligne vide. Ne précède pas le texte de commentaires.');
if(!(window.fdAgent && window.fdAgent.generate)){ self.showToast('Agent IA indisponible — configurez-le dans Paramètres → Agent & IA'); return; }
if(self.aiBusy) return;
self.aiBusy=true;
self.showToast('FlowDeck Agent : génération en cours…');
let ctx='';
try{ if(window.fdGetPageContext) ctx=String(window.fdGetPageContext()||''); }catch(e){}
const prompt='Rédige le contenu de la page FlowDeck intitulée « '+title+' ». Fournis un texte structuré et directement utilisable : paragraphes et, si utile, des listes à puces. Sépare chaque paragraphe par une ligne vide. Ne précède pas le texte de commentaires.';
window.fdAgent.generate(prompt, ctx).then(function(text){
self.aiBusy=false;
if(text && text.trim()){ self.applyAIBlocks(text); }
else { self.showToast('Aucun retour généré par l\u2019agent'); }
}).catch(function(err){
self.aiBusy=false;
self.showToast((err && err.message) ? err.message : 'Erreur de génération');
});
},
meetingNote(){
// v4.12.0: crée une vraie « boîte de réunion » (bloc 'meeting') au lieu
+110 -6
View File
@@ -55,6 +55,13 @@
.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}
@@ -175,11 +182,20 @@
</div>
<template x-for="m in messages" :key="m.id">
<div class="fd-agent-msg" :class="'fd-agent-msg-'+(m.role||'user')">
<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>
<div class="fd-agent-msg-content" x-text="m.content"></div>
<div class="fd-agent-msg-content" x-text="m.generating ? 'Génération…' : 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>
</div>
</div>
</template>
@@ -376,10 +392,10 @@
var c = this.activeContext || {};
var out = [];
if(c.kind === 'page'){
out.push({icon:'📄', label:'Résumer ce document', prompt:'Résume ce document de façon structurée et concise.'});
out.push({icon:'🌐', label:'Traduire cette page', prompt:'Traduis le contenu de ce document en anglais.', new:false});
out.push({icon:'✍️', label:'Proposer des améliorations', prompt:'Analyse ce document et propose des améliorations concrètes de structure et de contenu.'});
out.push({icon:'📝', label:'Créer une note de réunion', prompt:'Crée un squelette d\u2019AI meeting note structuré à partir de ce document.'});
out.push({icon:'📄', label:'Résumer ce document', prompt:'Résume ce document de façon structurée et concise.', docRun:true, mode:'insert'});
out.push({icon:'🌐', label:'Traduire cette page', prompt:'Traduis le contenu de ce document en anglais, en conservant sa structure (titres, listes, paragraphes).', docRun:true, mode:'replace'});
out.push({icon:'✍️', label:'Proposer des améliorations', prompt:'Analyse ce document et propose une version améliorée (structure, clarté, complétude) sans en perdre le sens.', docRun:true, mode:'insert'});
out.push({icon:'📝', label:'Créer une note de réunion', prompt:'Rédige une « AI Meeting Note » structurée à partir de ce document : en-tête avec date, sections Résumé, Décisions, Action items et Prochaines étapes.', docRun:true, mode:'insert'});
} else if(c.kind === 'db'){
out.push({icon:'🗄️', label:'Analyser cette base', prompt:'Analyse cette base de données et résume sa structure, ses propriétés et ses données.'});
out.push({icon:'📊', label:'Créer une vue', prompt:'Propose une vue adaptée (board, tableau, calendrier…) pour cette base et explique pourquoi.'});
@@ -399,6 +415,7 @@
},
runQuick(q){
if(!q || !q.prompt) return;
if(q.docRun){ this.runDocAction(q); return; }
var self = this;
this.ensureConversation().then(function(){
self.input = q.prompt;
@@ -406,6 +423,63 @@
}).catch(function(){});
},
// ── Document actions (no tools) → proposition à approuver / rejeter ──
runDocAction(q){
var self = this;
this.ensureConversation().then(function(){
self.errorMsg = '';
self.messages.push({id:'udoc-'+Date.now(), role:'user', content: q.label});
self.refreshContext();
var ctxText = (self.activeContext && self.activeContext.kind === 'page' && self.activeContext.text) ? self.activeContext.text : '';
var thinkId = 'g-'+Date.now()+Math.random();
self.messages.push({id: thinkId, role:'assistant', generating: true});
self.scrollBottom();
var payload = {prompt: q.prompt};
if(ctxText) payload.context = ctxText;
if(self.llmProvider) payload.provider = self.llmProvider;
if(self.llmModel) payload.model = self.llmModel;
fetch('/api/agent/generate', {
method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload)
}).then(function(r){ return r.json(); }).then(function(d){
self.messages = self.messages.filter(function(m){ return m.id !== thinkId; });
if(d && d.ok && d.text){
self.messages.push({
id:'prop-'+Date.now()+Math.random(), role:'assistant',
content: d.text, proposal: true, mode: q.mode || 'insert',
offline: !!d.offline, model: d.model || '', applied: false, rejected: false
});
} else {
self.messages.push({id:'err-'+Date.now()+Math.random(), role:'assistant',
content: '⚠️ ' + ((d && d.error) || 'Échec de la génération'), model:''});
}
self.scrollBottom();
}).catch(function(err){
self.messages = self.messages.filter(function(m){ return m.id !== thinkId; });
self.messages.push({id:'err-'+Date.now()+Math.random(), role:'assistant',
content: '⚠️ ' + ((err && err.message) || 'Erreur réseau'), model:''});
self.scrollBottom();
});
}).catch(function(){});
},
approveProposal(m){
if(!m || m.applied || m.offline) return;
var ok = window.fdApplyDocument ? window.fdApplyDocument(m.content, m.mode) : false;
m.applied = true;
m.rejected = false;
if(!ok){
m.content = '⚠️ Aucun document éditable n\u2019est ouvert pour appliquer cette proposition.';
}
this.$nextTick(function(){ this.scrollBottom(); });
},
rejectProposal(m){
if(!m || m.applied) return;
m.applied = true;
m.rejected = true;
this.$nextTick(function(){ this.scrollBottom(); });
},
// ── Providers / models ──
fetchProviders(){
var self = this;
@@ -832,6 +906,36 @@
})();
window.FlowDeckCtx = FlowDeckCtx;
// Apply an AI-produced document change to the open page editor.
// mode 'replace' → remplace tout le contenu de la page ;
// mode 'insert' → insère la proposition en fin de page (comme Ask AI).
window.fdApplyDocument = function(text, mode){
var E = window.E;
if(!E || !E.blocks || typeof E.mkB !== 'function') return false;
text = (text || '').trim();
if(!text) return false;
try{
E.sync();
if(mode === 'replace'){
var nb = (typeof E.md2b === 'function') ? E.md2b(text) : null;
if(!nb || !nb.length){ E.blocks = [E.mkB('paragraph', text)]; }
else { E.blocks = nb; }
} else {
if(typeof E.applyAIBlocks === 'function'){ E.applyAIBlocks(text); }
else {
var lines = text.split(/\r?\n/).map(function(s){ return s.trim(); }).filter(Boolean);
var startEmpty = E.blocks.length === 1 && E.blocks[0].type === 'paragraph' && !(E.blocks[0].content || '').trim();
var nb2 = lines.map(function(l){ return E.mkB('paragraph', l); });
E.blocks = startEmpty ? nb2 : E.blocks.concat(nb2);
}
}
E.dirty = true;
if(E.autoSave) E.autoSave();
if(E.render) E.render();
return true;
}catch(e){ return false; }
};
// Backward-compatible detection hooks (banner + Ask AI)
window.fdPageContextProvider = function(){
var c = FlowDeckCtx.current();