feat(v5.9.0): AI Writing Assist - slash /ai, autocompletion, AI properties
- Service app/services/ai_writing.py: 6 actions sans outils (write, summarize, translate, continue, autocomplete, properties) + replis deterministes offline - Endpoints POST /api/agent/writing et /api/agent/writing/properties - Editeur: groupe slash AI (Write/Summarize/Translate/Continue) via E.aiSlash - Autocompletion inline AIAC (suggestion ~900ms, Tab accepte, Escape rejette) - Database: bouton AI fill (suggestions de proprietes Status/Priority/Resume) - Tests tests/test_ai_writing.py (+29) ; version 5.9.0 (VERSION, main.py) - CHANGELOG + ROADMAP v5.9.0 completes
This commit is contained in:
@@ -84,7 +84,7 @@
|
||||
h += '</div><div class="db-col-resize-handle" data-prop-id="'+prop.id+'"></div></th>';
|
||||
}
|
||||
h += '<th class="db-th db-th-add-prop"><button class="db-add-prop-btn db-show-prop-modal-btn">+ Add property</button></th>';
|
||||
h += '<th class="db-th" style="width:40px;min-width:40px"></th>';
|
||||
h += '<th class="db-th" style="width:72px;min-width:72px"></th>';
|
||||
h += '</tr></thead>';
|
||||
|
||||
// Body
|
||||
@@ -104,7 +104,7 @@
|
||||
}
|
||||
h += '</div></td>';
|
||||
}
|
||||
h += '<td class="db-td db-td-open db-hover-only" style="visibility:hidden"><button class="db-open-btn db-open-page-btn" data-page-id="'+cp.id+'" title="Open">↗</button></td>';
|
||||
h += '<td class="db-td db-td-open db-hover-only" style="visibility:hidden;white-space:nowrap"><button class="db-ai-btn db-ai-fill-btn" data-page-id="'+cp.id+'" title="Remplir les propriétés avec l\'IA" style="background:none;border:none;cursor:pointer;font-size:14px;padding:2px 4px;color:var(--text-dim)">✨</button><button class="db-open-btn db-open-page-btn" data-page-id="'+cp.id+'" title="Open">↗</button></td>';
|
||||
h += '</tr>';
|
||||
}
|
||||
// Empty rows + New Page at bottom
|
||||
@@ -141,6 +141,13 @@
|
||||
openPageInSidePeek(parseInt(this.dataset.pageId));
|
||||
};
|
||||
});
|
||||
// AI fill properties
|
||||
containerEl.querySelectorAll('.db-ai-fill-btn').forEach(btn => {
|
||||
btn.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
aiFillRow(parseInt(this.dataset.pageId));
|
||||
};
|
||||
});
|
||||
// Hover
|
||||
containerEl.querySelectorAll('.db-row').forEach(row => {
|
||||
row.onmouseenter = function() { this.querySelectorAll('.db-hover-only').forEach(el => el.style.visibility = 'visible'); };
|
||||
@@ -249,6 +256,53 @@
|
||||
cellEl.onclick = function(e) { if(!state.editingCell && !e.target.closest('.db-open-btn')) startEditCell(pageId,propId,cellEl); };
|
||||
}
|
||||
|
||||
// ── v5.9.0: AI property suggestions ──
|
||||
async function aiFillRow(pageId) {
|
||||
const cp = state.pages.find(p => p.id === pageId);
|
||||
if (!cp) return;
|
||||
const props = state.properties.filter(p => p.prop_type !== 'title');
|
||||
if (!props.length) {
|
||||
if (typeof window.showToast === 'function') window.showToast('Aucune propriété à remplir');
|
||||
return;
|
||||
}
|
||||
const payload = {
|
||||
title: cp.title || '',
|
||||
content: cp.content || '',
|
||||
properties: props.map(p => ({ name: p.name, type: p.prop_type }))
|
||||
};
|
||||
if (typeof window.showToast === 'function') window.showToast('✨ FlowDeck AI : analyse en cours…');
|
||||
try {
|
||||
const r = await fetch('/api/agent/writing/properties', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCsrf() },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const d = await r.json();
|
||||
if (!d || !d.ok) {
|
||||
if (typeof window.showToast === 'function') window.showToast((d && d.error) || 'Échec de la génération');
|
||||
return;
|
||||
}
|
||||
let pv;
|
||||
try { pv = typeof cp.property_values_json === 'string' ? JSON.parse(cp.property_values_json) : (cp.property_values_json || {}); }
|
||||
catch (e) { pv = {}; }
|
||||
let filled = 0;
|
||||
for (const p of props) {
|
||||
const v = d.suggestions ? d.suggestions[p.name] : undefined;
|
||||
if (v !== undefined && v !== null && v !== '') { pv[String(p.id)] = v; filled++; }
|
||||
}
|
||||
cp.property_values_json = pv;
|
||||
await fetch('/db/pages/' + pageId + '/api', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCsrf() },
|
||||
body: JSON.stringify({ title: cp.title, properties: pv })
|
||||
});
|
||||
render();
|
||||
if (typeof window.showToast === 'function') window.showToast(filled ? ('✨ ' + filled + ' propriété(s) remplie(s)') : 'Aucune suggestion disponible');
|
||||
} catch (e) {
|
||||
if (typeof window.showToast === 'function') window.showToast('Erreur IA');
|
||||
}
|
||||
}
|
||||
|
||||
async function addNewPage() {
|
||||
try {
|
||||
const r = await fetch('/api/collections/'+collectionId+'/pages',{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':getCsrf()},body:JSON.stringify({title:''})});
|
||||
|
||||
Reference in New Issue
Block a user