feat(agent): v4.15.0 - panneau Agent style Notion AI (mentions @ inline + skills / + actions sous chaque réponse)
- Composeur contenteditable : mentions inline (pastilles icône+nom), barre de contexte groupée documents/skills, sélecteur @ (icône+titre+chemin), skills intégrés + agent_skills + commandes admin via '/' - Actions sous chaque réponse agent : copier, insérer dans la page, feedback 👍/👎 - Backend : table agent_feedback, GET /api/agent/mentions, POST /api/agent/feedback, contexte document:<id>/page:<id>/collection:<id> résolu en contenu réel - Inclut le travail v4.14.0 (documents/espace de travail + outils + auto-titre de conversation)
This commit is contained in:
@@ -442,6 +442,98 @@ async def apply_skill(request: Request, skill_id: int):
|
||||
return {"conversation_id": cur.lastrowid, "skill": skill["name"], "status": "ready"}
|
||||
|
||||
|
||||
# ── Mentions (commande @ / +) & feedback (boutons 👍 / 👎) ──
|
||||
|
||||
|
||||
@router.get("/mentions")
|
||||
async def list_mentions(request: Request, q: str = ""):
|
||||
"""Éléments mentionnables dans le panneau agent (commande « @ » / bouton « + »).
|
||||
|
||||
Retourne un flux unique d'objets FlowDeck (documents éditeur, collections,
|
||||
pages de collection) que l'utilisateur peut épingler au contexte du chat.
|
||||
Chaque entrée porte un jeton ``token`` (``document:<id>``, ``collection:<id>``,
|
||||
``page:<id>``) que ContextBuilder résout en contenu réel pour le LLM.
|
||||
"""
|
||||
query = (q or "").strip()[:60]
|
||||
like = f"%{query}%"
|
||||
limit = 8
|
||||
items: list[dict] = []
|
||||
|
||||
with get_conn() as conn:
|
||||
# 1) Documents (pages éditeur) — token document:<id>
|
||||
sql_docs = (
|
||||
"SELECT p.id, p.title, p.workspace_id, w.name AS ws_name "
|
||||
"FROM pages p LEFT JOIN workspaces w ON w.id=p.workspace_id "
|
||||
"WHERE p.deleted_at IS NULL AND (? = '' OR p.title LIKE ?) "
|
||||
"ORDER BY p.updated_at DESC LIMIT ?"
|
||||
)
|
||||
for r in conn.execute(sql_docs, (query, like, limit)).fetchall():
|
||||
items.append({
|
||||
"type": "document", "icon": "📄",
|
||||
"id": r["id"], "label": r["title"] or "Sans titre",
|
||||
"sub": r["ws_name"] or "", "token": f"document:{r['id']}",
|
||||
})
|
||||
|
||||
# 2) Collections (bases de données) — token collection:<id>
|
||||
sql_colls = (
|
||||
"SELECT id, name, icon FROM collections "
|
||||
"WHERE (? = '' OR name LIKE ?) ORDER BY updated_at DESC LIMIT ?"
|
||||
)
|
||||
for r in conn.execute(sql_colls, (query, like, limit)).fetchall():
|
||||
items.append({
|
||||
"type": "collection", "icon": r["icon"] or "🗄️",
|
||||
"id": r["id"], "label": r["name"],
|
||||
"sub": "Base de données", "token": f"collection:{r['id']}",
|
||||
})
|
||||
|
||||
# 3) Pages de collection — token page:<id>
|
||||
sql_pages = (
|
||||
"SELECT cp.id, cp.title, cp.icon, c.name AS coll_name "
|
||||
"FROM collection_pages cp LEFT JOIN collections c ON c.id=cp.collection_id "
|
||||
"WHERE (? = '' OR cp.title LIKE ?) ORDER BY cp.updated_at DESC LIMIT ?"
|
||||
)
|
||||
for r in conn.execute(sql_pages, (query, like, limit)).fetchall():
|
||||
items.append({
|
||||
"type": "page", "icon": r["icon"] if str(r["icon"]).startswith(("📄", "📑", "🗂️", "📁")) else "📑",
|
||||
"id": r["id"], "label": r["title"] or "Sans titre",
|
||||
"sub": f"Page · {r['coll_name']}" if r["coll_name"] else "Page",
|
||||
"token": f"page:{r['id']}",
|
||||
})
|
||||
|
||||
return {"mentions": items}
|
||||
|
||||
|
||||
@router.post("/feedback")
|
||||
async def add_feedback(request: Request):
|
||||
"""Enregistre le retour (👍 / 👎) porté sur une réponse de l'agent."""
|
||||
user_id = await _current_user_id(request)
|
||||
body = await request.json() if request.headers.get("content-type") else {}
|
||||
rating = (body.get("rating") or "").strip().lower()
|
||||
if rating not in ("up", "down"):
|
||||
raise HTTPException(status_code=400, detail="rating doit être 'up' ou 'down'")
|
||||
conversation_id = body.get("conversation_id")
|
||||
message_id = body.get("message_id")
|
||||
snippet = (body.get("content") or "")[:1000].strip()
|
||||
comment = (body.get("comment") or "").strip()[:2000]
|
||||
try:
|
||||
conversation_id = int(conversation_id) if conversation_id not in (None, "") else None
|
||||
except (TypeError, ValueError):
|
||||
conversation_id = None
|
||||
try:
|
||||
message_id = int(message_id) if message_id not in (None, "") else None
|
||||
except (TypeError, ValueError):
|
||||
message_id = None
|
||||
with get_conn() as conn:
|
||||
cur = conn.execute(
|
||||
"""INSERT INTO agent_feedback
|
||||
(conversation_id, message_id, user_id, rating, snippet, comment)
|
||||
VALUES (?,?,?,?,?,?)""",
|
||||
(conversation_id, message_id, user_id, rating, snippet, comment),
|
||||
)
|
||||
conn.commit()
|
||||
return {"id": cur.lastrowid, "rating": rating, "status": "recorded"}
|
||||
|
||||
|
||||
# ── Triggers & tools ──
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user