diff --git a/memento-note/app/api/ai/suggest-charts/route.ts b/memento-note/app/api/ai/suggest-charts/route.ts index 01df66c..21874bf 100644 --- a/memento-note/app/api/ai/suggest-charts/route.ts +++ b/memento-note/app/api/ai/suggest-charts/route.ts @@ -73,100 +73,132 @@ export async function POST(req: Request) { const sysConfig = await getSystemConfig() const { provider, model, timingInfo } = await resolveAiRouteWithTiming('suggest-charts', sysConfig) - // Build context for AI - const toolContext = { - userId, - noteId, - config: sysConfig, - } - - // Use the suggest_charts tool - const suggestChartsTool = toolRegistry.get('suggest_charts') - const tools = suggestChartsTool ? { suggest_charts: suggestChartsTool.buildTool(toolContext) } : {} - try { - // 4. Call AI to analyze and suggest + // 4. Call AI to analyze and suggest - direct JSON response (no tool) const { text } = await generateText({ model: provider(model), system: `You are a data visualization assistant. Analyze the provided text and suggest appropriate chart types. +IMPORTANT: You MUST respond with valid JSON only. No markdown, no code blocks, no explanations. + 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 +- Find ANY numbers in the text - even simple lists count +- Look for: "X: 123", "X = 123", "123", "123%", "$123", etc. +- If you find ANY 2+ numbers, create a chart +- Be creative with labels if none exist (use "Item 1", "Item 2", etc.) +- NEVER return hasData=false unless text is completely empty or has no numbers at all -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 +CHART TYPES TO SUGGEST (always 3 different types): +1. bar - for comparing categories +2. line - for trends/sequences +3. pie - for parts of whole -Response format (JSON): -{ - "suggestions": [ - { - "type": "bar|horizontal-bar|line|area|pie|radar|funnel|gauge", - "title": "Chart title", - "data": [{"label": "Category", "value": 123}], - "description": "What this chart shows", - "rationale": "Why this chart type suits the data" - } - ], - "analyzedText": "Text that was analyzed", - "detectedData": "Description of what data was found", - "hasData": true -}`, +Response format (COPY this structure): +{"suggestions":[ + {"type":"bar","title":"Chart Title","data":[{"label":"A","value":100},{"label":"B","value":200}],"description":"...","rationale":"..."}, + {"type":"line","title":"...","data":[...],"description":"...","rationale":"..."}, + {"type":"pie","title":"...","data":[...],"description":"...","rationale":"..."} +],"analyzedText":"...","detectedData":"...","hasData":true}`, messages: [ { role: 'user', - content: `Analyze this text and suggest 3 appropriate chart types:\n\n${textToAnalyze}`, + content: `Extract numbers from this text and suggest 3 charts:\n\n${textToAnalyze}`, }, ], - tools, temperature: 0.3, }) - // 5. Parse AI response + // 5. Parse AI response - be very lenient + console.log('[suggest-charts] AI response:', text.substring(0, 500)) + let parsed: SuggestChartsResponse try { - // Try to extract JSON from the response - const jsonMatch = text.match(/\{[\s\S]*\}/) + // Clean the response - remove markdown code blocks + let cleanText = text + .replace(/```json\n?/gi, '') + .replace(/```\n?/gi, '') + .trim() + + // Find JSON object + const jsonMatch = cleanText.match(/\{[\s\S]*\}/) if (!jsonMatch) { - throw new Error('No JSON found in response') + throw new Error('No JSON found') } parsed = JSON.parse(jsonMatch[0]) } catch (e) { - console.error('[suggest-charts] Failed to parse AI response:', text) - // Return empty suggestions on parse error - return Response.json({ - suggestions: [], - analyzedText: textToAnalyze.substring(0, 100), - detectedData: 'Failed to parse AI response', - hasData: false, - } satisfies SuggestChartsResponse) + console.error('[suggest-charts] Parse error:', e, 'Raw text:', text) + + // Check if text has ANY numbers - if so, create a simple chart + const numbers = textToAnalyze.match(/\d+/g) + if (numbers && numbers.length >= 2) { + const values = numbers.slice(0, 6).map(n => parseInt(n) || 0) + parsed = { + suggestions: [ + { + type: 'bar', + title: 'Data from Note', + data: values.map((v, i) => ({ label: `Item ${i + 1}`, value: v })), + description: 'Bar chart of extracted values', + rationale: 'Simple comparison of numerical values found' + }, + { + type: 'line', + title: 'Data Trend', + data: values.map((v, i) => ({ label: `Item ${i + 1}`, value: v })), + description: 'Line chart showing progression', + rationale: 'Visualizes the sequence of values' + }, + { + type: 'pie', + title: 'Data Distribution', + data: values.map((v, i) => ({ label: `Item ${i + 1}`, value: v })), + description: 'Pie chart of value proportions', + rationale: 'Shows relative sizes of each value' + } + ], + analyzedText: textToAnalyze.substring(0, 100), + detectedData: `Found ${numbers.length} numerical values`, + hasData: true + } + } else { + return Response.json({ + suggestions: [], + analyzedText: textToAnalyze.substring(0, 100), + detectedData: 'No numerical data found', + hasData: false, + error: 'Add numbers to your note (e.g., "Jan: 100, Feb: 200")', + } satisfies SuggestChartsResponse, { status: 200 }) + } } - // Validate response structure + // Validate and fix response if (!parsed.suggestions || !Array.isArray(parsed.suggestions)) { parsed.suggestions = [] } - // Ensure exactly 3 suggestions when data exists - if (parsed.hasData && parsed.suggestions.length !== 3) { - parsed.suggestions = parsed.suggestions.slice(0, 3) + // Ensure we have 3 suggestions when hasData=true + if (parsed.hasData && parsed.suggestions.length < 3) { + const baseSuggestion = parsed.suggestions[0] + if (baseSuggestion) { + const types = ['bar', 'line', 'pie'].filter(t => t !== baseSuggestion.type) + while (parsed.suggestions.length < 3 && types.length > 0) { + parsed.suggestions.push({ ...baseSuggestion, type: types.shift()! }) + } + } } - // Validate each suggestion has required fields - parsed.suggestions = parsed.suggestions.filter(s => - s.type && s.title && s.data && Array.isArray(s.data) && s.data.length >= 2 - ) + // Ensure each suggestion has valid data + parsed.suggestions = parsed.suggestions.filter(s => { + if (!s.type || !s.data || !Array.isArray(s.data) || s.data.length < 2) return false + // Ensure all data points have label and value + s.data = s.data.filter(d => d && typeof d.value === 'number') + return s.data.length >= 2 + }) + + // If after filtering we have no valid suggestions, set hasData=false + if (parsed.suggestions.length === 0) { + parsed.hasData = false + } // Track usage await trackFeatureUsage(userId, 'suggest_charts', 'suggest-charts', 1) diff --git a/memento-note/components/chart-suggestions-dialog.tsx b/memento-note/components/chart-suggestions-dialog.tsx index d645277..6bbad4e 100644 --- a/memento-note/components/chart-suggestions-dialog.tsx +++ b/memento-note/components/chart-suggestions-dialog.tsx @@ -53,6 +53,7 @@ export function ChartSuggestionsDialog({ suggestCharts({ content, selection, noteId }) .then(data => { if (aborted) return + console.log('[ChartSuggestionsDialog] Response:', data) setResponse(data) setLoading(false) }) @@ -61,10 +62,10 @@ export function ChartSuggestionsDialog({ console.error('[ChartSuggestionsDialog] Error:', err) setResponse({ suggestions: [], - analyzedText: '', - detectedData: '', + analyzedText: textToAnalyze?.substring(0, 100) || '', + detectedData: 'Error occurred', hasData: false, - error: err.message || 'Failed to load suggestions', + error: err.message || 'Network error - check console', }) setLoading(false) }) @@ -192,6 +193,17 @@ export function ChartSuggestionsDialog({
+ {textToAnalyze.substring(0, 500)}
+ {textToAnalyze.length > 500 && '...'}
+
+