- 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) <noreply@anthropic.com>
278 lines
15 KiB
Markdown
278 lines
15 KiB
Markdown
---
|
|
title: 'AI Chart Suggestions in TipTap Editor'
|
|
type: 'feature'
|
|
created: '2026-05-23'
|
|
status: 'done'
|
|
baseline_commit: '4e8f45deae9845ad334dbfb8bd7a943e48cda7fc'
|
|
context: []
|
|
---
|
|
|
|
<frozen-after-approval reason="human-owned intent — do not modify unless human renegotiates">
|
|
|
|
## 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 |
|
|
|
|
</frozen-after-approval>
|
|
|
|
## 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
|
|
<pre><code class="language-chart">bar
|
|
Ventes par Mois
|
|
Jan: 5000
|
|
Feb: 7500
|
|
Mar: 6200</code></pre>
|
|
```
|
|
|
|
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)
|