diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c3822f..04b3b97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,39 @@ # Changelog — FlowDeck -## v5.1.2 (2026-09-07) — Éditeur style Notion AI (hint, menu slash, AI inline) +## v5.1.3 (2026-09-07) — Corrections : AI inline (400), bloc vide, menu `/`, poignée, contour + +> Résout les régressions du composer AI et de l'éditeur Notion-style v5.1.2. + +### AI inline — erreur 400 DeepSeek (espace → AI) +- `fdAgent.generate` passait par l'engine à outils (`/conversations/{id}/run`) qui + envoie un schéma `tools`/`tool_choice` que DeepSeek rejette en **400 Bad Request**. +- Bascule de `generate` sur l'endpoint **`/api/agent/generate`** (sans outils, prompt + propre + contexte) : le composer AI, « Ask AI », la note de réunion et la synthèse + renvoient désormais un simple texte Markdown. Le bloc « 400 » disparaît. + +### Bloc vide (placeholder) +- Le hint « Press 'space' for AI or '/' for commands » était rendu trop discret + (`--text-dim`). Il passe à `--text-secondary` avec opacité, plus lisible. + (Le texte n'apparaît que sur un paragraphe réellement vide — comportement Notion.) + +### Slash menu — flèches ↑/↓ +- La sélection n'était **pas visible** au démarrage (rien de surligné) → les flèches + semblaient inactives. `_rs()` est désormais appelé à l'ouverture et après filtrage, + et l'item sélectionné reçoit un fond accentué + liseré gauche clair. Testé en + jsdom : ↓/↑ déplacent la sélection, Entrée applique l'élément. + +### Contour du bloc sélectionné +- Suppression des anneaux de focus (`outline`/`box-shadow`) sur tous les blocs + éditables (`[data-bid]`, `.block-content`, titre de page) quand sélectionné. + +### Poignée de déplacement (⠿) +- La poignée 6 points restait invisible (seule `.block-column-wrapper:hover` la + montrait). Ajout de `.block-wrapper:hover`/`:focus-within .block-handle { opacity:1 }` + pour les blocs normaux. + +### Tests / validation +- 271 tests pytest verts ; rendu édition 200 (hint/AIC/trigger présents) ; + route `/api/agent/generate` enregistrée ; JS validé (`node --check` + jsdom). > Adapte le visuel des blocs à l'expérience Notion AI : hint sur bloc vide, menu > `/` compact avec raccourcis + aperçu au survol, et composer AI inline diff --git a/VERSION b/VERSION index 1b47e8f..935851a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.1.2 \ No newline at end of file +5.1.3 \ No newline at end of file diff --git a/app/main.py b/app/main.py index 030b285..eb7bf9f 100644 --- a/app/main.py +++ b/app/main.py @@ -60,7 +60,7 @@ async def lifespan(_app: FastAPI): app = FastAPI( title="FlowDeck", - version="5.1.2", + version="5.1.3", docs_url="/docs" if settings.log_level == "DEBUG" else None, redoc_url=None, lifespan=lifespan, diff --git a/app/templates/_page_editor_scripts.html b/app/templates/_page_editor_scripts.html index 27819e9..65da18a 100644 --- a/app/templates/_page_editor_scripts.html +++ b/app/templates/_page_editor_scripts.html @@ -238,7 +238,7 @@ const r=el.getBoundingClientRect();SM._e.style.display='flex'; SM._e.style.top=Math.max(4,(r.bottom+4>window.innerHeight-360)?r.top-364:r.bottom+4)+'px'; SM._e.style.left=Math.min(r.left,window.innerWidth-340)+'px'; - setTimeout(()=>SM._in.focus(),10);SM._o=true;}, + SM._rs();setTimeout(()=>SM._in.focus(),10);SM._o=true;}, close(){if(SM._e)SM._e.style.display='none';SM._o=false;SM._in&&(SM._in.value='');if(SM._pv)SM._pv.style.display='none';}, flt(){if(!SM._li)return;SM._it=[];const q=(SM._in.value||'').toLowerCase();SM._q=q;let h=''; for(const g of CMDS){let items=g.items;if(q)items=items.filter(c=>c.name.toLowerCase().includes(q)||c.id.includes(q));if(!items.length)continue; @@ -248,7 +248,7 @@ ${c.icon} ${c.name} ${_SHORTCUT[c.id]||''}`;});} - SM._li.innerHTML=h;}, + SM._li.innerHTML=h;SM._rs();}, _kd(e){if(e.key==='ArrowDown'){e.preventDefault();SM._s=Math.min(SM._s+1,SM._it.length-1);SM._rs();} else if(e.key==='ArrowUp'){e.preventDefault();SM._s=Math.max(SM._s-1,0);SM._rs();} else if(e.key==='Enter'){e.preventDefault();const c=SM._it[SM._s];if(c)SM.sel(c.id);} diff --git a/app/templates/agent_panel.html b/app/templates/agent_panel.html index ce2628f..8e6619d 100644 --- a/app/templates/agent_panel.html +++ b/app/templates/agent_panel.html @@ -457,44 +457,24 @@ self.setComposerText(t); setTimeout(function(){ self.send(); }, 60); }; + // Headless text generation (no tools) via /agent/generate — the clean + // path used by "Ask AI" / "AI meeting note" / summarization. Sending a + // plain write request through the tool-enabled conversation engine sends + // a tools/tool_choice schema that some providers (e.g. DeepSeek) reject + // with a 400. This endpoint returns {ok, text, offline, model}. window.fdAgent.generate = function(message, context){ message = (message || '').trim(); if(!message) return Promise.resolve(''); - var payload = {message: message}; + var payload = {prompt: message}; if(context && context.trim()) payload.context = String(context).slice(0, 20000); - return fetch('/api/agent/conversations', { + return fetch('/api/agent/generate', { method:'POST', headers:{'Content-Type':'application/json'}, - body: JSON.stringify({title:'Agent'}) - }).then(function(r){ return r.json(); }).then(function(conv){ - var cid = conv && conv.id; - if(!cid) throw new Error('Impossible de créer une conversation agent'); - return fetch('/api/agent/conversations/' + cid + '/run', { - method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload) - }).then(function(resp){ - if(!resp.ok){ return resp.json().then(function(j){ throw new Error(j.detail || ('HTTP ' + resp.status)); }); } - var reader = resp.body.getReader(); - var dec = new TextDecoder(); - var buf = '', finalText = '', lastError = ''; - function pump(){ - return reader.read().then(function(res){ - if(res.done){ if(lastError) throw new Error(lastError); return finalText; } - buf += dec.decode(res.value, {stream:true}); - var parts = buf.split('\n\n'); - buf = parts.pop(); - for(var i=0;i