diff --git a/CHANGELOG.md b/CHANGELOG.md index 142142a..286dbe3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ 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 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 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 : diff --git a/app/services/llm_client.py b/app/services/llm_client.py index 3a125ab..a7c7472 100644 --- a/app/services/llm_client.py +++ b/app/services/llm_client.py @@ -247,6 +247,10 @@ class LLMClient: ) # Plain conversational objective → final answer (no tool). + draft = self._draft_reply(user_content) + if draft: + return LLMResponse(text=draft, model=model) + return LLMResponse( text=self._summarize(user_content), model=model, @@ -288,4 +292,80 @@ class LLMClient: def _summarize(content: str) -> str: """Produce a terse final summary from a conversational objective.""" content = content.split("\n# Contexte")[0] - return (content[:600] + "…" if len(content) > 600 else content) \ No newline at end of file + 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