feat(agent): v4.11.1 - branche entrées IA sur le FlowDeck Agent (Ask AI, AI meeting note, bouton flottant)
FlowDeck CI / test (push) Failing after 8s
FlowDeck CI / docker (push) Skipped

This commit is contained in:
2026-09-05 21:49:42 -04:00
parent 34368a4946
commit 9ba3480d4e
7 changed files with 475 additions and 7 deletions
+1 -1
View File
@@ -60,7 +60,7 @@ async def lifespan(_app: FastAPI):
app = FastAPI(
title="FlowDeck",
version="4.11.0",
version="4.11.1",
docs_url="/docs" if settings.log_level == "DEBUG" else None,
redoc_url=None,
lifespan=lifespan,
+2 -2
View File
@@ -509,13 +509,13 @@
<span class="gs-label">Get started with</span>
<button
class="gs-pill"
@click="replaceBlock(0, 'paragraph');blocks[0].content='Ask AI to write...';render()"
@click="askAI()"
>
<span class="gs-icon">{{ fd_icon("sparkles",14) }}</span> Ask AI
</button>
<button
class="gs-pill"
@click="replaceBlock(0, 'heading_1');blocks[0].content='AI Meeting Note';render()"
@click="meetingNote()"
>
<span class="gs-icon">{{ fd_icon('edit',14) }}</span> AI meeting note
</button>
+31
View File
@@ -289,6 +289,7 @@
updatedAt:'{{ page.get("updated_at", "") }}',
fmt:{open:false,top:0,left:0,idx:-1},
shareOpen:false,moreOpen:false,gsMoreOpen:false,accessMenuOpen:false,exportOpen:false,
aiBusy:false,
moveOpen:false,moveWorkspaces:[],moveLoading:false,
activityOpen:false,pageCreated:'{{ page.get("created_at", "") }}',
shareTab:'share',favorited:false,
@@ -391,6 +392,36 @@
addAfter(idx,type){return this.addAt(idx+1,type);},
removeBlock(idx){this.sync();if(this.blocks.length<=1){this.blocks[0]=this.mkB('paragraph','');}else{this.blocks.splice(idx,1);}const fi=Math.min(idx,this.blocks.length-1);this.render();this.dirty=true;this.autoSave();setTimeout(()=>{const el=this.getEl(this.blocks[fi]?.id);if(el){el.focus();ce(el);}},60);},
// ── v4.11.1: Inline AI — "Ask AI" & "AI meeting note" (Get started) ──
// Wire the contextual get-started actions into the FlowDeck Agent
// (Notion-style inline generation). The reply is streamed in the agent
// panel and inserted into this page via window.__fdInsertAI.
askAI(){
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.');
},
meetingNote(){
this.aiRun('Crée le squelette d\'une « AI meeting note » FlowDeck : une ligne d\'en-tête avec la date et les participants, puis les sections « Résumé », « Décisions » et « Action items » avec des exemples réalistes. Sépare chaque bloc/paragraphe par une ligne vide.');
},
aiRun(prompt){
const self=this;
if(self.aiBusy) return;
if(!(window.fdAgent && window.fdAgent.ask)){ self.showToast('Agent IA indisponible — configurez-le dans Paramètres → Agent & IA'); return; }
self.aiBusy=true;
window.__fdInsertAI = function(content){ window.__fdInsertAI=null; self.aiBusy=false; self.applyAIBlocks(content); };
self.showToast('FlowDeck Agent : génération en cours…');
window.fdAgent.ask(prompt);
},
applyAIBlocks(text){
const lines=(text||'').split(/\r?\n/).map(function(s){return s.trim();}).filter(Boolean);
if(!lines.length) return;
this.sync();
const startEmpty=this.blocks.length===1 && this.blocks[0].type==='paragraph' && !(this.blocks[0].content||'').trim();
const nb=lines.map(l=>this.mkB('paragraph',l));
this.blocks = startEmpty ? nb : this.blocks.concat(nb);
this.dirty=true; this.autoSave(); this.render();
},
toggleFavorite(){
const csrf=document.cookie.match(/csrf_token=([^;]+)/);
const m=this.favorited?'DELETE':'POST';
+42 -3
View File
@@ -135,6 +135,17 @@
});
this.fetchProviders();
this.fetchAgents();
// Programmatic entry point used by the page editor ("Ask AI",
// "AI meeting note") and any global UI: open the panel and run a prompt.
window.fdAgent = window.fdAgent || {};
window.fdAgent.ask = function(text){
var t = (text || '').trim();
if(!t) return;
self.openAgent();
self.input = t;
setTimeout(function(){ self.send(); }, 60);
};
},
fetchProviders(){
@@ -202,6 +213,30 @@
self.messages = []; self.errorMsg=''; self.tab='chat';
}).catch(function(){});
},
ensureConversation(){
// Make the chat usable immediately: reuse the open conversation or
// create one lazily so sending never silently no-ops (empty state).
var self = this;
if(self.currentConv && self.currentConv.id) return Promise.resolve(self.currentConv);
if(self._ensuring) return self._ensuring;
var body = {title:'New conversation'};
if(self.llmProvider) body.provider = self.llmProvider;
if(self.llmModel) body.model = self.llmModel;
self._ensuring = fetch('/api/agent/conversations', {
method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body)
}).then(function(r){return r.json()}).then(function(d){
var conv = {id:d.id, title:d.title, updated_at:new Date().toISOString()};
self.conversations.unshift(conv); self.currentConv = conv;
self.messages = []; self.errorMsg=''; self.tab='chat';
self._ensuring = null;
return conv;
}).catch(function(err){
self._ensuring = null;
self.errorMsg = (err && err.message) ? err.message : 'Impossible de créer la conversation';
throw err;
});
return self._ensuring;
},
openAgent(){ this.open=true; this.loadConversations(); },
close(){ this.open=false; },
get commandOpen(){ return /^\s*\//.test(this.input) && !this.streaming; },
@@ -292,12 +327,13 @@
}
return false;
},
send(){
async send(){
var self = this;
if(!self.currentConv || self.streaming) return;
var text = self.input.trim();
if(self.streaming) return;
var text = (self.input || '').trim();
if(!text) return;
if(self.commandOpen && self.handleCommand(text)){ self.input=''; return; }
try { await self.ensureConversation(); } catch(e){ return; }
self.input = '';
self.errorMsg = '';
self.streaming = true;
@@ -340,6 +376,9 @@
}
else if(ev.type==='final'){
self.messages.push({id:'f-'+Date.now(), role:'assistant', content: ev.content});
// "Ask AI" / "AI meeting note" (page editor) register this hook
// so the generated text is inserted directly into the page.
if(window.__fdInsertAI){ try{ var h=window.__fdInsertAI; window.__fdInsertAI=null; h(ev.content||''); }catch(e){} }
}
else if(ev.type==='error'){ self.errorMsg = ev.message; }
}catch(e){}