feat(agent): mock hors-ligne genere du contenu utile (Ask AI page + AI meeting note)
This commit is contained in:
@@ -11,6 +11,10 @@
|
|||||||
n'existait (`send()` retournait silencieusement sans `currentConv`). Ajout d'une création de
|
n'existait (`send()` retournait silencieusement sans `currentConv`). Ajout d'une création de
|
||||||
conversation **lazy** (`ensureConversation()`) effectuée avant chaque envoi — la 1ère saisie crée
|
conversation **lazy** (`ensureConversation()`) effectuée avant chaque envoi — la 1ère saisie crée
|
||||||
la conversation puis l'exécute (mode offline ou LLM réel).
|
la conversation puis l'exécute (mode offline ou LLM réel).
|
||||||
|
- **Mode hors-ligne plus utile** : le mock LLM (`_draft_reply`) génère désormais un **vrai contenu
|
||||||
|
structuré** pour les demandes de rédaction de page (« Ask AI », titre détecté via « intitulée »)
|
||||||
|
et les **AI meeting notes** (squelette Résumé / Décisions / Action items), au lieu de se contenter
|
||||||
|
de renvoyer le prompt tel quel — les boutons produisent donc du contenu même sans clé API.
|
||||||
- **« Ask AI » & « AI meeting note »** : ne faisaient qu'insérer du texte placeholder
|
- **« Ask AI » & « AI meeting note »** : ne faisaient qu'insérer du texte placeholder
|
||||||
(« Ask AI to write... » / « AI Meeting Note ») sans aucun appel IA. Ils déclenchent désormais une
|
(« Ask AI to write... » / « AI Meeting Note ») sans aucun appel IA. Ils déclenchent désormais une
|
||||||
**génération inline réelle** via le FlowDeck Agent :
|
**génération inline réelle** via le FlowDeck Agent :
|
||||||
|
|||||||
@@ -247,6 +247,10 @@ class LLMClient:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Plain conversational objective → final answer (no tool).
|
# Plain conversational objective → final answer (no tool).
|
||||||
|
draft = self._draft_reply(user_content)
|
||||||
|
if draft:
|
||||||
|
return LLMResponse(text=draft, model=model)
|
||||||
|
|
||||||
return LLMResponse(
|
return LLMResponse(
|
||||||
text=self._summarize(user_content),
|
text=self._summarize(user_content),
|
||||||
model=model,
|
model=model,
|
||||||
@@ -288,4 +292,80 @@ class LLMClient:
|
|||||||
def _summarize(content: str) -> str:
|
def _summarize(content: str) -> str:
|
||||||
"""Produce a terse final summary from a conversational objective."""
|
"""Produce a terse final summary from a conversational objective."""
|
||||||
content = content.split("\n# Contexte")[0]
|
content = content.split("\n# Contexte")[0]
|
||||||
return (content[:600] + "…" if len(content) > 600 else content)
|
return (content[:600] + "…" if len(content) > 600 else content)
|
||||||
|
|
||||||
|
def _draft_reply(self, content: str) -> str | None:
|
||||||
|
"""Generate usable structured copy for content/meeting requests when no
|
||||||
|
real LLM is configured (offline mock). Returns None when the message is
|
||||||
|
not a clear content-generation intent so other paths keep their behavior.
|
||||||
|
"""
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
low = content.lower()
|
||||||
|
title = None
|
||||||
|
m = re.search(r"intitul[ée]e\s*[«\"']([^»\"']+)[»\"']", content)
|
||||||
|
if m:
|
||||||
|
title = m.group(1).strip()
|
||||||
|
today = date.today().isoformat()
|
||||||
|
|
||||||
|
# ── AI meeting note template ──
|
||||||
|
if any(k in low for k in ("ai meeting note", "meeting note",
|
||||||
|
"compte-rendu", "compte rendu",
|
||||||
|
"notes de réunion", "réunion")):
|
||||||
|
return (
|
||||||
|
"📅 AI Meeting Note\n"
|
||||||
|
f"Date : {today} · Participants : (à renseigner)\n"
|
||||||
|
"\n"
|
||||||
|
"## Résumé\n"
|
||||||
|
"Point central de la discussion et contexte (à compléter).\n"
|
||||||
|
"\n"
|
||||||
|
"## Décisions\n"
|
||||||
|
"• Décision 1 — valider le périmètre et les responsables.\n"
|
||||||
|
"• Décision 2 — définir la prochaine échéance.\n"
|
||||||
|
"\n"
|
||||||
|
"## Action items\n"
|
||||||
|
"☐ Action 1 — responsable : …, échéance : …\n"
|
||||||
|
"☐ Action 2 — responsable : …, échéance : …\n"
|
||||||
|
"\n"
|
||||||
|
"## Prochaines étapes\n"
|
||||||
|
"• Planifier le suivi et archiver ce compte-rendu.\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── Page / document draft (contextual "Ask AI") ──
|
||||||
|
if title:
|
||||||
|
t = title[:80]
|
||||||
|
return (
|
||||||
|
f"{t}\n"
|
||||||
|
"\n"
|
||||||
|
f"Présentation générale du sujet « {t} » : objectif, contexte et "
|
||||||
|
"public visé en quelques phrases. (Document généré hors-ligne — "
|
||||||
|
"connectez une clé API pour une rédaction complète.)\n"
|
||||||
|
"\n"
|
||||||
|
"## Objectif\n"
|
||||||
|
"• Clarifier le besoin couvert par ce document.\n"
|
||||||
|
"• Lister les livrables attendus.\n"
|
||||||
|
"\n"
|
||||||
|
"## Points clés\n"
|
||||||
|
"• Idée principale 1 avec les arguments associés.\n"
|
||||||
|
"• Idée principale 2 et les exemples concrets.\n"
|
||||||
|
"\n"
|
||||||
|
"## Prochaines étapes\n"
|
||||||
|
"• Relire, compléter et mettre en forme ce contenu.\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── Generic drafting verb, no title (typing directly in the chat) ──
|
||||||
|
if re.search(r"^(r[ée]dige|[ée]cris|g[ée]n[èe]re|produis|d[ée]veloppe|"
|
||||||
|
r"[ée]cris\s+un|g[ée]n[èe]re\s+un|r[ée]dige\s+un)\b", low):
|
||||||
|
return (
|
||||||
|
"## Introduction\n"
|
||||||
|
"Contexte et objectif de ce texte, en une à deux phrases.\n"
|
||||||
|
"\n"
|
||||||
|
"## Développement\n"
|
||||||
|
"• Premier argument structuré avec un exemple.\n"
|
||||||
|
"• Deuxième argument appuyé par une donnée ou un fait.\n"
|
||||||
|
"\n"
|
||||||
|
"## Conclusion\n"
|
||||||
|
"Synthèse et prochaine étape recommandée.\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|||||||
Reference in New Issue
Block a user