fix(agent): boutons d'action a icones seules + contexte d'ouverture en puce de mention (@) au lieu du bandeau statique
This commit is contained in:
+5
-3
@@ -8,9 +8,9 @@
|
|||||||
> propose des **skills** de travail réutilisables en plus des commandes admin existantes.
|
> propose des **skills** de travail réutilisables en plus des commandes admin existantes.
|
||||||
|
|
||||||
### Sous chaque réponse de l'agent
|
### Sous chaque réponse de l'agent
|
||||||
- **⧉ copy response** : copie la réponse dans le presse-papiers.
|
- **⧉ / ➕ / 👍 / 👎** : boutons **à icônes seules** (infobulle au survol) — copier la réponse,
|
||||||
- **➕ insert into this page** : insère la réponse dans la page ouverte (désactivé sans page éditable).
|
insérer dans la page ouverte, feedback 👍/👎 enregistré en base.
|
||||||
- **👍 / 👎 Share feedback** : enregistre le retour en base (`agent_feedback`) avec confirmation.
|
- Le bandeau statique « Contexte : … » du haut du panneau est supprimé.
|
||||||
|
|
||||||
### Contexte épinglé — commande « @ » / bouton « + » (style Notion AI)
|
### Contexte épinglé — commande « @ » / bouton « + » (style Notion AI)
|
||||||
- Le composeur est maintenant un éditeur **contenteditable** : les références sélectionnées
|
- Le composeur est maintenant un éditeur **contenteditable** : les références sélectionnées
|
||||||
@@ -20,6 +20,8 @@
|
|||||||
- **@** ouvre le sélecteur (icône + titre + chemin) listant documents, pages et bases ; l'élément
|
- **@** ouvre le sélecteur (icône + titre + chemin) listant documents, pages et bases ; l'élément
|
||||||
choisi devient un jeton inline **et** une puce de contexte. **+** épingle un élément au contexte
|
choisi devient un jeton inline **et** une puce de contexte. **+** épingle un élément au contexte
|
||||||
sans l'insérer inline. Les puces persistent jusqu'au retrait (et sont vidées en nouveau chat).
|
sans l'insérer inline. Les puces persistent jusqu'au retrait (et sont vidées en nouveau chat).
|
||||||
|
- **Contexte d'ouverture** : ouvrir l'agent sur un document (`/pages/:id`) ou une base (`/db/:id`)
|
||||||
|
ajoute automatiquement ce contexte comme **mention** (puce épinglée) — plus de bandeau statique.
|
||||||
- À l'envoi, chaque référence est transmise comme **mention** ; le backend résout son **contenu réel**.
|
- À l'envoi, chaque référence est transmise comme **mention** ; le backend résout son **contenu réel**.
|
||||||
|
|
||||||
### Skills — commande « / »
|
### Skills — commande « / »
|
||||||
|
|||||||
@@ -105,7 +105,7 @@
|
|||||||
|
|
||||||
/* Action bar under each assistant post */
|
/* Action bar under each assistant post */
|
||||||
.fd-agent-msg-actions{display:flex;gap:5px;flex-wrap:wrap;margin-top:7px;align-items:center}
|
.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;gap:4px;background:transparent;border:1px solid var(--border,rgba(255,255,255,.09));color:var(--text-dim,#9aa);border-radius:7px;padding:3px 8px;font-size:11px;cursor:pointer;transition:background .12s,color .12s,border-color .12s;line-height:1.5}
|
.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: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.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}
|
.fd-act-btn:disabled,.fd-act-btn.disabled{opacity:.4;cursor:not-allowed}
|
||||||
@@ -179,11 +179,6 @@
|
|||||||
<button class="fd-ap-headbtn" title="Fermer (Ctrl+J)" @click="close()">✕</button>
|
<button class="fd-ap-headbtn" title="Fermer (Ctrl+J)" @click="close()">✕</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="fd-ap-ctx" x-show="activeContext" x-cloak>
|
|
||||||
<span class="fd-ctx-ico" x-text="activeContext ? activeContext.icon : ''"></span>
|
|
||||||
<span class="fd-ctx-lbl" x-text="activeContext ? ('Contexte : ' + activeContext.label) : ''"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="fd-ap-noconfig" x-show="!hasActiveProvider">
|
<div class="fd-ap-noconfig" x-show="!hasActiveProvider">
|
||||||
<span>⚠️ Aucun fournisseur activé et fonctionnel.</span>
|
<span>⚠️ Aucun fournisseur activé et fonctionnel.</span>
|
||||||
<a href="/settings" target="_blank" @click.stop>Configurer → Agent & IA</a>
|
<a href="/settings" target="_blank" @click.stop>Configurer → Agent & IA</a>
|
||||||
@@ -243,10 +238,10 @@
|
|||||||
</template>
|
</template>
|
||||||
<template x-if="m.role==='assistant' && !m.generating && m.content && !m.act">
|
<template x-if="m.role==='assistant' && !m.generating && m.content && !m.act">
|
||||||
<div class="fd-agent-msg-actions">
|
<div class="fd-agent-msg-actions">
|
||||||
<button class="fd-act-btn" type="button" @click="copyResponse(m)" title="Copier la réponse">⧉ copy response</button>
|
<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">➕ insert into this page</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'">👍 Share feedback</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'">👎 Share feedback</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -493,32 +488,43 @@
|
|||||||
},
|
},
|
||||||
toggle(){ this.open ? this.close() : this.openAgent(); },
|
toggle(){ this.open ? this.close() : this.openAgent(); },
|
||||||
|
|
||||||
// ── Context (pinned at open, refreshed when a new document is open) ──
|
// ── Context (reflected as an @-style mention chip in the composer) ──
|
||||||
refreshContext(force){
|
refreshContext(force){
|
||||||
var ctx = FlowDeckCtx.current();
|
var ctx = FlowDeckCtx.current();
|
||||||
if(!ctx){ this.activeContext = null; return; }
|
if(!ctx){ this.activeContext = null; return; }
|
||||||
if(!this.activeContext) { this.activeContext = ctx; return; }
|
if(!this.activeContext) { this.activeContext = ctx; this._syncContextChip(); return; }
|
||||||
// Always follow a currently-open document so follow-up messages use it.
|
// Always follow a currently-open document so follow-up messages use it.
|
||||||
if(ctx.kind === 'page' || force || ctx.key !== this.activeContext.key){
|
if(ctx.kind === 'page' || force || ctx.key !== this.activeContext.key){
|
||||||
this.activeContext = ctx;
|
this.activeContext = ctx;
|
||||||
|
this._syncContextChip();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
_syncContextChip(){
|
||||||
|
var c = this.activeContext;
|
||||||
|
if(!c || !c.mentionToken) return;
|
||||||
|
var exists = this.contextChips.some(function(ch){ return ch.token === c.mentionToken; });
|
||||||
|
if(exists) return;
|
||||||
|
this.contextChips.push({
|
||||||
|
key: c.mentionToken, kind: 'mention', type: c.refType || 'document',
|
||||||
|
icon: c.icon || '📎', label: c.title || c.label, token: c.mentionToken, refId: c.refId
|
||||||
|
});
|
||||||
|
},
|
||||||
get chatContextActive(){ return !!(this.activeContext && this.activeContext.kind === 'page'); },
|
get chatContextActive(){ return !!(this.activeContext && this.activeContext.kind === 'page'); },
|
||||||
contextPayload(){
|
contextPayload(){
|
||||||
this.refreshContext();
|
this.refreshContext();
|
||||||
var c = this.activeContext;
|
var c = this.activeContext;
|
||||||
if(!c) return '';
|
if(!c) return '';
|
||||||
|
// Si le contexte est déjà épinglé comme mention (@), le backend résout son
|
||||||
|
// contenu via context_builder — on évite de dupliquer le texte du document.
|
||||||
|
if(c.mentionToken && this.contextChips.some(function(ch){ return ch.token === c.mentionToken; })) return '';
|
||||||
if(c.text && c.text.trim()) return c.text.trim().slice(0, 9000);
|
if(c.text && c.text.trim()) return c.text.trim().slice(0, 9000);
|
||||||
return c.label ? ('Contexte : ' + c.label + '.').slice(0, 2000) : '';
|
return c.title ? ('Contexte : ' + c.title + '.').slice(0, 2000) : '';
|
||||||
},
|
},
|
||||||
get composerHint(){
|
get composerHint(){
|
||||||
var c = this.activeContext;
|
var chips = this.contextChips.length;
|
||||||
var ctx;
|
var base = 'Demandez, tapez / pour un skill, @ pour référencer un élément';
|
||||||
if(!c) ctx = 'Demandez, tapez / pour un skill, @ pour référencer un élément';
|
var extra = chips ? ' · ' + chips + ' réf. épinglée(s)' : '';
|
||||||
else if(c.kind === 'page') ctx = 'Contexte : le document ouvert est pris en compte par l\u2019agent.';
|
return base + extra + ' · Entrée ↵ = envoyer · Maj+Entrée = ligne';
|
||||||
else ctx = 'Contexte : ' + c.label;
|
|
||||||
var extra = this.contextChips.length ? ' · ' + this.contextChips.length + ' réf. épinglée(s)' : '';
|
|
||||||
return ctx + extra + ' · Entrée ↵ = envoyer · Maj+Entrée = ligne';
|
|
||||||
},
|
},
|
||||||
get canInsert(){ return !!(this.activeContext && this.activeContext.kind === 'page'); },
|
get canInsert(){ return !!(this.activeContext && this.activeContext.kind === 'page'); },
|
||||||
|
|
||||||
@@ -1515,6 +1521,12 @@
|
|||||||
// data-fd-context), (2) the currently open document (page editor),
|
// data-fd-context), (2) the currently open document (page editor),
|
||||||
// (3) the route the user is on. Every screen of the app yields a context.
|
// (3) the route the user is on. Every screen of the app yields a context.
|
||||||
var FlowDeckCtx = (function(){
|
var FlowDeckCtx = (function(){
|
||||||
|
function idFromPath(prefix){
|
||||||
|
try{
|
||||||
|
var m = (window.location.pathname || '').match(new RegExp('^\\/?' + prefix + '\\/(\\d+)'));
|
||||||
|
return m ? m[1] : null;
|
||||||
|
}catch(e){ return null; }
|
||||||
|
}
|
||||||
function labelFromTitle(){
|
function labelFromTitle(){
|
||||||
try{
|
try{
|
||||||
var t = document.title || '';
|
var t = document.title || '';
|
||||||
@@ -1531,7 +1543,14 @@
|
|||||||
if(!raw) return null;
|
if(!raw) return null;
|
||||||
var firstLine = raw.split('\n')[0] || 'Document';
|
var firstLine = raw.split('\n')[0] || 'Document';
|
||||||
var title = firstLine.replace(/^Titre du document\s*:\s*/i, '').trim() || 'Document en cours';
|
var title = firstLine.replace(/^Titre du document\s*:\s*/i, '').trim() || 'Document en cours';
|
||||||
return {kind:'page', icon:'📄', label:'Document « ' + title + ' »', text: raw, key:'page'};
|
var pid = idFromPath('pages');
|
||||||
|
return {
|
||||||
|
kind:'page', icon:'📄', label:'Document « ' + title + ' »', text: raw, key:'page',
|
||||||
|
title: title,
|
||||||
|
mentionToken: pid ? ('document:' + pid) : null,
|
||||||
|
refId: pid ? Number(pid) : null,
|
||||||
|
refType: pid ? 'document' : null
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}catch(e){}
|
}catch(e){}
|
||||||
return null;
|
return null;
|
||||||
@@ -1558,7 +1577,11 @@
|
|||||||
var kind = 'section';
|
var kind = 'section';
|
||||||
var t;
|
var t;
|
||||||
if(p === '/' || p === '/dashboard'){ icon='🏠'; label='Accueil'; kind='home'; }
|
if(p === '/' || p === '/dashboard'){ icon='🏠'; label='Accueil'; kind='home'; }
|
||||||
else if(/^\/pages\/\d+/.test(p)){ icon='📄'; label=labelFromTitle() || 'Page en cours'; kind='page'; }
|
else if(/^\/pages\/\d+/.test(p)){
|
||||||
|
icon='📄'; kind='page'; label=labelFromTitle() || 'Page en cours';
|
||||||
|
var pid = idFromPath('pages');
|
||||||
|
t = pid ? ('document:' + pid) : null;
|
||||||
|
}
|
||||||
else if(/^\/library/.test(p)){
|
else if(/^\/library/.test(p)){
|
||||||
icon='📚'; kind='library';
|
icon='📚'; kind='library';
|
||||||
var tab = (window.location.search.match(/tab=([^&]+)/)||[])[1] || '';
|
var tab = (window.location.search.match(/tab=([^&]+)/)||[])[1] || '';
|
||||||
@@ -1570,7 +1593,12 @@
|
|||||||
else if(/^\/local-workspace/.test(p)){ icon='🗂️'; label='Espace local'; kind='workspace'; }
|
else if(/^\/local-workspace/.test(p)){ icon='🗂️'; label='Espace local'; kind='workspace'; }
|
||||||
else if(/^\/workspace($|\?)/.test(p)){ icon='🗂️'; label='Espace de travail'; kind='workspace'; }
|
else if(/^\/workspace($|\?)/.test(p)){ icon='🗂️'; label='Espace de travail'; kind='workspace'; }
|
||||||
else if(/^\/gitea-workspace/.test(p)){ icon='🔗'; label='Workspace Gitea'; kind='workspace'; }
|
else if(/^\/gitea-workspace/.test(p)){ icon='🔗'; label='Workspace Gitea'; kind='workspace'; }
|
||||||
else if(/^\/db\//.test(p)){ icon='🗄️'; label='Base de données'; kind='db'; t = labelFromTitle(); if(t) label = 'Base — ' + t; }
|
else if(/^\/db\//.test(p)){
|
||||||
|
icon='🗄️'; kind='db';
|
||||||
|
label = labelFromTitle() || 'Base de données';
|
||||||
|
var cid = idFromPath('db');
|
||||||
|
t = cid ? ('collection:' + cid) : null;
|
||||||
|
}
|
||||||
else if(/^\/board\//.test(p)){
|
else if(/^\/board\//.test(p)){
|
||||||
icon='📋'; kind='board';
|
icon='📋'; kind='board';
|
||||||
if(/^\/board\/library/.test(p)){ icon='📚'; label='Bibliothèque'; kind='library'; }
|
if(/^\/board\/library/.test(p)){ icon='📚'; label='Bibliothèque'; kind='library'; }
|
||||||
@@ -1585,7 +1613,11 @@
|
|||||||
label = labelFromTitle() || 'FlowDeck';
|
label = labelFromTitle() || 'FlowDeck';
|
||||||
kind='section';
|
kind='section';
|
||||||
}
|
}
|
||||||
return {kind: kind, icon: icon, label: label, text: '', key: p};
|
var mentionToken = (kind === 'page' && pid) ? ('document:' + pid) : ((kind === 'db' && cid) ? ('collection:' + cid) : null);
|
||||||
|
return {kind: kind, icon: icon, label: label, text: '', key: p,
|
||||||
|
title: label, mentionToken: mentionToken,
|
||||||
|
refId: mentionToken ? Number(String(mentionToken).split(':')[1]) : null,
|
||||||
|
refType: mentionToken ? String(mentionToken).split(':')[0] : null};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
current: function(){
|
current: function(){
|
||||||
|
|||||||
Reference in New Issue
Block a user