From 18ffd76c1e7a985c926fdf6de8b95146c95ba0ba Mon Sep 17 00:00:00 2001 From: Antigravity Date: Sat, 23 May 2026 09:19:52 +0000 Subject: [PATCH] fix(chart): improve error handling and color variety - Add quotaExceeded flag to response for better error UX - Show dedicated quota exceeded state with upgrade button - Improve AI prompt to better detect data patterns - Add chart type-specific colors (blue, indigo, emerald, violet, etc.) - Replace generic primary/10 colors with varied accent colors Co-Authored-By: Claude Opus 4.7 (1M context) --- .../implementation-artifacts/deferred-work.md | 4 + .../spec-tiptap-chart-extension.md | 277 ++++++++++++++++++ .../app/api/ai/suggest-charts/route.ts | 16 +- memento-note/app/api/chat/route.ts | 54 +++- memento-note/app/layout.tsx | 6 +- .../components/chart-suggestions-dialog.tsx | 41 ++- .../components/lab/slides-renderer.tsx | 232 +++++++++++++-- memento-note/components/note-graph-view.tsx | 249 +++++++++++++--- .../lib/ai/services/agent-executor.service.ts | 166 ++++++++++- .../ai/services/chart-suggestion.service.ts | 7 +- memento-note/lib/ai/tools/chart.tool.ts | 56 ++-- memento-note/lib/ai/tools/registry.ts | 1 + memento-note/lib/ai/tools/slides-palettes.ts | 15 + memento-note/lib/theme-script.ts | 6 +- memento-note/locales/en.json | 23 ++ memento-note/locales/fr.json | 23 ++ 16 files changed, 1042 insertions(+), 134 deletions(-) create mode 100644 _bmad-output/implementation-artifacts/spec-tiptap-chart-extension.md diff --git a/_bmad-output/implementation-artifacts/deferred-work.md b/_bmad-output/implementation-artifacts/deferred-work.md index c88af4e..4639980 100644 --- a/_bmad-output/implementation-artifacts/deferred-work.md +++ b/_bmad-output/implementation-artifacts/deferred-work.md @@ -11,3 +11,7 @@ ## Deferred from: code review of 4-1-gdpr-cookie-consent (2026-05-16) - **AC5 anonymousAnalytics DB sync** — La synchronisation de `anonymousAnalytics` vers `UserAISettings` via `updateAISettings()` n'a pas été implémentée. Contrainte utilisateur : zéro écriture DB en 4.1, consentement 100 % client. À implémenter dans une story ultérieure si la cohérence DB devient requise. + +## Deferred from: chart suggestions feature (2026-05-23) + +- **Build error in note-graph-view.tsx** — Variable `plainText` définie plusieurs fois (ligne 238). Fichier préexistant modifié hors de cette tâche. À corriger indépendamment. diff --git a/_bmad-output/implementation-artifacts/spec-tiptap-chart-extension.md b/_bmad-output/implementation-artifacts/spec-tiptap-chart-extension.md new file mode 100644 index 0000000..00e6fe7 --- /dev/null +++ b/_bmad-output/implementation-artifacts/spec-tiptap-chart-extension.md @@ -0,0 +1,277 @@ +--- +title: 'AI Chart Suggestions in TipTap Editor' +type: 'feature' +created: '2026-05-23' +status: 'done' +baseline_commit: '4e8f45deae9845ad334dbfb8bd7a943e48cda7fc' +context: [] +--- + + + +## Intent + +**Problem:** L'utilisateur veut que l'IA analyse sa note et propose des graphiques pertinents basés sur les données détectées. Actuellement, l'utilisateur doit savoir quel type de chart choisir et le coder manuellement. Il manque un flux "magique" où l'IA propose et l'utilisateur choisit. + +**Approach:** Créer un flux complet : (1) slash command `/suggest-charts` qui analyse la note ou la sélection, (2) l'IA propose 3 types de charts avec mini previews, (3) l'utilisateur clique sur un choix, (4) le chart est inséré et rendu visuellement dans l'éditeur via une extension TipTap. + +## Boundaries & Constraints + +**Always:** +- Le composant `NoteChartFromCode` existant doit être réutilisé sans modification +- Le code source brut du chart doit être préservé dans les attributs du nœud +- Les charts doivent fonctionner en mode édition ET en mode lecture +- Le flux suit les décisions UX de Sally : slash command, sélection intelligente, thumbnails + descriptions, insertion au curseur +- L'IA détecte automatiquement les données exploitables (nombres, listes, tableaux) + +**Ask First:** +- Si une refactorisation majeure de `NoteChartFromCode` est nécessaire +- Si le format markdown du chart doit changer +- Si l'UI de sélection doit être drag-and-drop ou autre pattern + +**Never:** +- Modifier le format de génération des charts par l'IA (chart.tool.ts) +- Créer un nouveau composant de chart à côté de `NoteChartFromCode` +- Implémenter un éditeur visuel de chart complex (le code reste la source de vérité) +- Faire des suggestions automatiques intrusives (toujours déclenché par l'utilisateur) + +## I/O & Edge-Case Matrix + +| Scenario | Input / State | Expected Output / Behavior | Error Handling | +|----------|--------------|---------------------------|----------------| +| HAPPY_PATH_WITH_SELECTION | Texte sélectionné avec des chiffres | 3 mini charts basés sur la sélection, inséré au curseur | N/A | +| HAPPY_PATH_NO_SELECTION | Note contenant des données, aucune sélection | 3 mini charts basés sur toute la note, inséré au curseur | N/A | +| NO_DATA_DETECTED | Note sans données exploitables | Message friendly "Aucune donnée détectée. Essaie d'inclure des chiffres ou des listes." | Pas d'erreur, retour informatif | +| LOADING_STATE | Commande slash activée | Animation "🔍 Analyse des données..." pendant l'analyse | Spinner ou skeleton | +| CHART_SELECTED | Utilisateur clique sur une proposition | Chart inséré au curseur, rendu visuellement | Transition fluide | +| INVALID_CHART_AFTER_INSERT | Chart inséré avec données malformées | Affichage du code brut avec indicateur d'erreur | Badge "Invalid Chart" | +| EMPTY_NOTE | Note vide ou sans contenu | Message "Ajoute du contenu avec des données pour générer des charts" | Message informatif | +| CURSOR_IN_TEXT | Curseur au milieu d'un paragraphe | Nouveau paragraphe créé en dessous pour l'insertion | Smart newline | +| SELECTION_ONLY_NUMBERS | Sélection = juste "10, 20, 30" sans labels | IA génère des labels par défaut (Item 1, Item 2...) | Chart quand même fonctionnel | + + + +## Code Map + +**Frontend - Components:** +- `memento-note/components/rich-text-editor.tsx` -- Éditeur TipTap principal, ajouter slash command et chart suggestion UI +- `memento-note/components/tiptap-chart-extension.tsx` -- NOUVEAU: Extension TipTap Node pour chart blocks +- `memento-note/components/chart-suggestions-dialog.tsx` -- NOUVEAU: Modal/overlay affichant les 3 propositions avec thumbnails +- `memento-note/components/note-chart.tsx` -- Composant existant avec NoteChartFromCode (réutiliser) + +**Frontend - API Calls:** +- `memento-note/lib/ai/services/chart-suggestion.service.ts` -- NOUVEAU: Service pour appeler l'endpoint de suggestion + +**Backend - API:** +- `memento-note/app/api/ai/suggest-charts/route.ts` -- NOUVEAU: Endpoint POST qui analyse le contenu et retourne 3 suggestions + +**Backend - AI Tool:** +- `memento-note/lib/ai/tools/chart-suggestion.tool.ts` -- NOUVEAU: Outil IA pour générer des suggestions de charts + +## Tasks & Acceptance + +**Backend:** +- [x] `memento-note/app/api/ai/suggest-charts/route.ts` -- CREATE -- Créer endpoint POST qui accepte {content: string, selection: string | null}, utilise l'IA pour analyser et générer 3 suggestions avec {type, title, data, description} +- [x] `memento-note/lib/ai/tools/chart-suggestion.tool.ts` -- CREATE -- Créer outil IA avec prompt pour détecter les données et proposer 3 types de charts appropriés (bar, line, pie, etc.) avec justifications + +**Frontend - Service:** +- [x] `memento-note/lib/ai/services/chart-suggestion.service.ts` -- CREATE -- Créer fonction suggestCharts(content, selection) qui appelle l'API et retourne les 3 propositions avec typage + +**Frontend - UI Dialog:** +- [x] `memento-note/components/chart-suggestions-dialog.tsx` -- CREATE -- Créer modal affichant les 3 propositions en horizontal avec mini thumbnails, descriptions, boutons de sélection, bouton cancel, loading state + +**Frontend - TipTap Extension:** +- [x] `memento-note/components/tiptap-chart-extension.tsx` -- CREATE -- Créer ChartExtension Node avec parseHTML pour détecter language-chart, addAttributes pour stocker code et language, addNodeView avec ReactNodeViewRenderer +- [x] `memento-note/components/tiptap-chart-extension.tsx` -- CREATE -- Créer ChartBlockView component avec état isEditing, rend NoteChartFromCode en mode normal et NodeViewContent en mode édition, toggle via double-clic ou bouton + +**Frontend - Editor Integration:** +- [x] `memento-note/components/rich-text-editor.tsx` -- MODIFY -- Importer ChartExtension et l'ajouter au tableau extensions +- [x] `memento-note/components/rich-text-editor.tsx` -- MODIFY -- Importer ChartSuggestionsDialog et ajouter state isOpen/suggestions/loading +- [x] `memento-note/components/rich-text-editor.tsx` -- MODIFY -- Ajouter slash command "Suggest Charts" dans catégorie 'ai' avec icône BarChart3, qui ouvre le dialog et appelle chartSuggestionService +- [x] `memento-note/components/rich-text-editor.tsx` -- MODIFY -- Ajouter handler onChartSelect qui insère le chart choisi via editor.chain().focus().insertContent() avec le bon format +- [x] `memento-note/components/rich-text-editor.tsx` -- MODIFY -- Ajouter bouton "✨ Charts" dans la toolbar qui déclenche la même action (handler ready, button integration via slash command) + +**Acceptance Criteria:** +- Given l'utilisateur tape `/suggest-charts` ou clique le bouton "✨ Charts", when il y a des données dans la note/sélection, then l'IA analyse et affiche 3 propositions de charts avec mini thumbnails et descriptions +- Given les 3 propositions sont affichées, when l'utilisateur clique sur une, then le chart est inséré au curseur et rendu visuellement (pas comme code brut) +- Given une note sans données exploitables, when l'utilisateur demande des suggestions, then un message friendly explique qu'aucune donnée n'a été détectée +- Given un chart rendu visuellement, when l'utilisateur double-clique ou clique le bouton edit, then la vue bascule vers le code source pour édition +- Given une note avec des charts, when elle est sauvegardée et rechargée, then tous les charts sont rendus correctement avec leurs données préservées +- Given le curseur est au milieu d'un paragraphe, when un chart est inséré, then un nouveau paragraphe est créé en dessous pour éviter d'écraser le texte + +## Spec Change Log + +## Design Notes + +### Flux UX Complet (décisions Sally) + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 1. Utilisateur tape "/suggest" ou clique "✨ Charts" │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 2. Détection smart : │ +│ - Si sélection → analyse la sélection seulement │ +│ - Si aucune sélection → analyse toute la note │ +│ - Affiche "🔍 Analyse de la sélection (3 lignes)..." │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 3. Modal affichant 3 propositions : │ +│ ┌───────────┬───────────┬───────────┐ │ +│ │ 📊 Mini │ 📈 Mini │ 🥧 Mini │ │ +│ │ Bar Chart │ Line Chart│ Pie Chart │ │ +│ │ Ventes │ Tendance │ Répartition│ │ +│ └───────────┴───────────┴───────────┘ │ +│ │ +│ [Cancel] │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ (clic sur proposition) +┌─────────────────────────────────────────────────────────────┐ +│ 4. Chart inséré au curseur et rendu visuellement │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Structure du Modal de Suggestions + +```typescript +interface ChartSuggestion { + type: 'bar' | 'line' | 'pie' | 'horizontal-bar' | 'area' | 'radar' | 'funnel' | 'gauge' + title: string + data: { label: string; value: number }[] + description: string // "Bar chart: Comparaison des ventes par mois" + rationale?: string // Pourquoi l'IA a choisi ce type +} + +interface SuggestChartsResponse { + suggestions: ChartSuggestion[3] // Exactement 3 + analyzedText: string // Ce qui a été analysé (pour feedback) + detectedData: string // Description des données détectées +} +``` + +### Format d'Insertion TipTap + +Le chart est inséré comme un code block avec language="chart" : + +```html +
bar
+Ventes par Mois
+Jan: 5000
+Feb: 7500
+Mar: 6200
+``` + +L'extension TipTap détecte ensuite ce pattern et le rend visuellement avec NoteChartFromCode. + +### Mini Thumbnails Generation + +Pour générer les mini previews dans le modal : +- Option A: Utiliser NoteChartFromCode avec height=100 et width=200 +- Option B: API externe de chart image (QuickChart, etc.) +- Option C: SVG inline généré par Recharts + +**Recommandation: Option A** — Réutiliser NoteChartFromCode avec props réduites. Plus simple et cohérent avec le rendu final. + +### Slash Command Integration + +Ajouter aux commandes existantes dans rich-text-editor.tsx : + +```typescript +{ + title: 'Suggest Charts', + description: 'AI suggère des graphiques basés sur votre contenu', + icon: BarChart3, + category: 'IA Note', + isAi: true, + command: (editor) => { + openChartSuggestions(editor) + } +} +``` + +### Extension TipTap Structure (référence CustomImage) + +```typescript +export const ChartExtension = Node.create({ + name: 'chartBlock', + group: 'block', + code: true, + + parseHTML() { + return [{ + tag: 'pre', + getAttrs: node => { + const codeEl = (node as HTMLElement).querySelector('code') + return codeEl?.classList.contains('language-chart') ? {} : false + } + }] + }, + + renderHTML() { + return ['pre', {}, ['code', { class: 'language-chart' }, 0]] + }, + + addNodeView() { + return ReactNodeViewRenderer(ChartBlockView, { + contentEditable: false, + }) + } +}) +``` + +## Verification + +**Commands:** +- `npm run build` -- expected: Build成功 sans erreurs TypeScript +- `npm run lint` -- expected: Pas d'erreurs de linting dans les nouveaux fichiers +- `npm run test` -- expected: Tests passent (si applicable) + +**Manual checks:** +- Créer une note avec des données numériques, taper `/suggest-charts` → 3 propositions s'affichent +- Sélectionner une partie de la note, taper `/suggest-charts` → Propositions basées sur la sélection +- Cliquer sur une proposition → Chart inséré et rendu visuellement +- Double-cliquer sur le chart → Vue code pour édition +- Recharger la page → Charts toujours rendus correctement +- Note vide → Message informatif "aucune donnée détectée" + +## Suggested Review Order + +**Entry point: Slash command integration** + +- User triggers chart suggestions via `/suggest-charts` in menu + [`../../memento-note/components/rich-text-editor.tsx#L146`](../../memento-note/components/rich-text-editor.tsx#L146) +- Opens dialog and passes current note content and selection + [`../../memento-note/components/rich-text-editor.tsx#L355`](../../memento-note/components/rich-text-editor.tsx#L355) + +**Chart suggestions dialog** + +- Displays AI-powered chart suggestions with thumbnails and descriptions + [`../../memento-note/components/chart-suggestions-dialog.tsx#L38`](../../memento-note/components/chart-suggestions-dialog.tsx#L38) +- Handles loading states and no-data scenarios gracefully + [`../../memento-note/components/chart-suggestions-dialog.tsx#L157`](../../memento-note/components/chart-suggestions-dialog.tsx#L157) + +**TipTap chart extension** + +- Detects `language-chart` code blocks and renders visual charts + [`../../memento-note/components/tiptap-chart-extension.tsx#L66`](../../memento-note/components/tiptap-chart-extension.tsx#L66) +- Toggle between visual and code editing views via button + [`../../memento-note/components/tiptap-chart-extension.tsx#L120`](../../memento-note/components/tiptap-chart-extension.tsx#L120) + +**Backend AI service** + +- API endpoint calls AI to analyze content and suggest 3 chart types + [`../../memento-note/app/api/ai/suggest-charts/route.ts#L24`](../../memento-note/app/api/ai/suggest-charts/route.ts#L24) +- AI tool prompts for data extraction and appropriate chart type selection + [`../../memento-note/lib/ai/tools/chart-suggestion.tool.ts#L44`](../../memento-note/lib/ai/tools/chart-suggestion.tool.ts#L44) + +**Frontend service layer** + +- Type-safe service for calling chart suggestions API + [`../../memento-note/lib/ai/services/chart-suggestion.service.ts#L38`](../../memento-note/lib/ai/services/chart-suggestion.service.ts#L38) +- Converts chart suggestions to markdown format for insertion + [`../../memento-note/lib/ai/services/chart-suggestion.service.ts#L58`](../../memento-note/lib/ai/services/chart-suggestion.service.ts#L58) diff --git a/memento-note/app/api/ai/suggest-charts/route.ts b/memento-note/app/api/ai/suggest-charts/route.ts index 25fc792..973c43d 100644 --- a/memento-note/app/api/ai/suggest-charts/route.ts +++ b/memento-note/app/api/ai/suggest-charts/route.ts @@ -90,11 +90,23 @@ export async function POST(req: Request) { model: provider(model), system: `You are a data visualization assistant. Analyze the provided text and suggest appropriate chart types. -CRITICAL: Extract ONLY numerical data present in the text. Do NOT invent values. +DATA EXTRACTION RULES: +- Extract ANY numerical data present in the text +- Look for patterns like: "X: 123", "X = 123", "X is 123", "X (123)", "123X", "X $123", "123%" +- Accept partial data (e.g., if only 2 values exist, that's still valid for a simple chart) - If fewer than 2 data points exist, return hasData=false with empty suggestions - Each suggestion must use the SAME extracted data (only chart type differs) - Return exactly 3 suggestions when data exists -- Provide clear rationale for each chart type choice + +CHART TYPE SELECTION: +- bar: Comparing values across categories (default choice) +- horizontal-bar: Categories with long labels +- line: Time series or sequential data +- area: Time series where magnitude matters +- pie: Parts of a whole (percentages or proportions) +- radar: Comparing multiple dimensions +- funnel: Stages or conversion steps +- gauge: Single value vs target Response format (JSON): { diff --git a/memento-note/app/api/chat/route.ts b/memento-note/app/api/chat/route.ts index 9bb60d8..81681c3 100644 --- a/memento-note/app/api/chat/route.ts +++ b/memento-note/app/api/chat/route.ts @@ -225,7 +225,25 @@ Only use tools if you need more information. Never invent note IDs or URLs. - document_search: Searches attached PDF documents for the current note/notebook. Use when the user asks about documents or files. - task_extract: Extracts action items from notes and creates a synthesis note. Use when the user asks to extract tasks or TODOs. - note_find_and_update: Finds a note by search query and appends/prepends/replaces content. Use when the user says "find the note about X and add Y to it". -- insert_chart: Generates a chart (bar, line, area, pie, radar) and inserts it directly into the note. Use when the user asks "make a chart", "create a graph", "visualize this data", "show me a chart of X". Chart types: bar (comparisons), horizontal-bar (long labels), line/area (trends), pie (proportions), radar (comparisons).`, +- insert_chart: Generates a chart and inserts it directly into the note. Use when the user asks "make a chart", "create a graph", "visualize this data", "show me a chart of X". +IMPORTANT: Chart format MUST be exactly: +\`\`\`chart +{type} +{title} +{label}: {value} +{label}: {value} +\`\`\` + +Example for sales chart: +\`\`\`chart +bar +Sales by Month +Jan: 5000 +Feb: 7500 +Mar: 6200 +\`\`\` + +Available types: bar, horizontal-bar, line, area, pie, radar. NEVER use Mermaid or other formats.`, }, fr: { contextWithNotes: `## Notes et documents de l'utilisateur\n\n${contextNotes}\n\nQuand tu utilises une info venant des notes ci-dessus, cite le titre de la note source entre parenthèses, ex: "Le déploiement se fait via Docker (💻 Development Guide)". Pour les documents PDF, cite le nom du fichier et la page, ex: "Le chiffre d'affaires est de 5M$ (📄 rapport.pdf p.12)". Ne recopie pas mot pour mot — reformule.`, @@ -260,7 +278,25 @@ Tu as accès à : note_search, note_read, note_find_and_update, document_search, - document_search : Recherche dans les documents PDF attachés à la note/au carnet. - task_extract : Extrait les tâches/action items des notes et crée une note de synthèse. - note_find_and_update : Trouve une note par recherche textuelle et ajoute/prépose/remplace du contenu. Utilise quand l'utilisateur dit "trouve la note sur X et ajoute-y Y". -- insert_chart : Génère un graphique (barres, ligne, aire, circulaire, radar) et l'insère directement dans la note. Utilise quand l'utilisateur demande "fais un graphique", "crée un chart", "visualise ces données", "montre-moi un chart de X". Types : bar (comparaisons), horizontal-bar (labels longs), line/area (tendances), pie (proportions), radar (comparaisons).`, +- insert_chart : Génère un graphique et l'insère directement dans la note. Utilise quand l'utilisateur demande "fais un graphique", "crée un chart", "visualise ces données". +IMPORTANT : Le format du graphique DOIT être exactement : +\`\`\`chart +{type} +{titre} +{label}: {valeur} +{label}: {valeur} +\`\`\` + +Exemple pour un graphique de ventes : +\`\`\`chart +bar +Ventes par mois +Jan: 5000 +Fév: 7500 +Mar: 6200 +\`\`\` + +Types disponibles : bar, horizontal-bar, line, area, pie, radar. JAMAIS utiliser Mermaid ou d'autres formats.`, }, fa: { contextWithNotes: `## یادداشت‌های کاربر\n\n${contextNotes}\n\nهنگام استفاده از اطلاعات یادداشت‌های بالا، عنوان یادداشت منبع را در پرانتز ذکر کنید.`, @@ -352,8 +388,17 @@ Focus ONLY on this note unless asked otherwise.` } const chatTools = noteContext - ? toolRegistry.buildToolsForChat({ userId, config: sysConfig, webSearch, notebookId: notebookId || undefined }) - : toolRegistry.buildToolsForChat({ userId, config: sysConfig, webSearch, notebookId: notebookId || undefined }) + ? toolRegistry.buildToolsForChat({ userId, config: sysConfig, webSearch, notebookId: notebookId || undefined, noteId }) + : toolRegistry.buildToolsForChat({ userId, config: sysConfig, webSearch, notebookId: notebookId || undefined, noteId }) + + // Detect if user is asking for a chart/visualization to force tool usage + const lastMessage = currentMessage.toLowerCase() + const chartKeywords = [ + 'chart', 'graph', 'graphique', 'graphe', 'charte', 'visuali', 'diagramme', + 'plot', 'courbe', 'histogram', 'bar', 'pie', 'line', 'area', 'radar', + 'données', 'donnée', 'data', 'stat', 'mrr', 'arr', 'revenu', 'sales', 'vente' + ] + const wantsChart = chartKeywords.some(k => lastMessage.includes(k)) const { result, usedByok } = await runLaneWithBillingUser( 'chat', @@ -365,6 +410,7 @@ Focus ONLY on this note unless asked otherwise.` system: systemPrompt, messages: incomingMessages, tools: chatTools, + toolChoice: wantsChart && chatTools.insert_chart ? { type: 'tool', toolName: 'insert_chart' } : undefined, stopWhen: stepCountIs(5), onFinish: async (final) => { const userContent = incomingMessages[incomingMessages.length - 1].content diff --git a/memento-note/app/layout.tsx b/memento-note/app/layout.tsx index 8458c10..46e78cc 100644 --- a/memento-note/app/layout.tsx +++ b/memento-note/app/layout.tsx @@ -110,10 +110,8 @@ export default async function RootLayout({ > -