/** * Chart Suggestion Service * Frontend service for calling the AI chart suggestions API * NOW WITH FAST PARSER - skips AI when possible (< 50ms vs 2 minutes) */ import { parseChartData, generateChartSuggestions } from '@/lib/chart/parser' export interface ChartSuggestion { type: 'bar' | 'horizontal-bar' | 'line' | 'area' | 'pie' | 'radar' | 'funnel' | 'gauge' title: string data: { label: string; value: number }[] description: string rationale?: string } export interface SuggestChartsResponse { suggestions: ChartSuggestion[] analyzedText: string detectedData: string hasData: boolean error?: string quotaExceeded?: boolean } export interface SuggestChartsRequest { content: string selection?: string | null noteId?: string } // Simple in-memory cache with 5-minute TTL const CACHE_TTL = 5 * 60 * 1000 // 5 minutes const cache = new Map() function getCacheKey(content: string, selection: string | null | undefined): string { // Use content hash + selection as cache key const textToHash = selection ? content + ':::' + selection : content // Simple hash function let hash = 0 for (let i = 0; i < textToHash.length; i++) { hash = ((hash << 5) - hash) + textToHash.charCodeAt(i) hash = hash & hash // Convert to 32bit integer } return String(hash) } function getCached(key: string): SuggestChartsResponse | null { const entry = cache.get(key) if (!entry) return null if (Date.now() - entry.timestamp > CACHE_TTL) { cache.delete(key) return null } console.log('[chart-suggestion] Cache hit for key:', key) return entry.data } function setCached(key: string, data: SuggestChartsResponse): void { cache.set(key, { data, timestamp: Date.now() }) // Limit cache size to 50 entries if (cache.size > 50) { const firstKey = cache.keys().next().value cache.delete(firstKey) } } /** * Call the AI chart suggestions API * FAST PATH: Try regex parser first (< 50ms), only call AI if no data found * @param request - The request parameters * @returns Promise with the chart suggestions */ export async function suggestCharts(request: SuggestChartsRequest): Promise { // Check cache first const cacheKey = getCacheKey(request.content || '', request.selection) console.log('[suggestCharts] Cache key:', cacheKey, 'contentLen:', request.content?.length, 'selectionLen:', request.selection?.length) const cached = getCached(cacheKey) if (cached) { console.log('[suggestCharts] CACHE HIT - returning cached data') return cached } console.log('[suggestCharts] CACHE MISS - trying fast parser first') // FAST PATH: Try regex parser first - NO AI call needed! const textToParse = request.selection || request.content || '' const parsed = parseChartData(textToParse) if (parsed.hasData && parsed.confidence > 0.3) { console.log('[suggestCharts] FAST PATH - regex parser found data, skipping AI!') const suggestions = generateChartSuggestions(parsed.data) const response: SuggestChartsResponse = { suggestions, analyzedText: textToParse.substring(0, 200), detectedData: `${parsed.data.length} data points found`, hasData: true, } // Cache the fast result setCached(cacheKey, response) return response } console.log('[suggestCharts] Parser found no good data, calling AI API (slow...)') try { const response = await fetch('/api/ai/suggest-charts', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(request), }) if (!response.ok) { const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) const isQuotaError = response.status === 402 || errorData.error === 'QUOTA_EXCEEDED' return { suggestions: [], analyzedText: '', detectedData: '', hasData: false, error: isQuotaError ? 'AI quota exceeded. Please upgrade your plan to continue using AI features.' : (errorData.error || `HTTP ${response.status}`), quotaExceeded: isQuotaError, } } const data = await response.json() as SuggestChartsResponse // Cache successful responses if (!data.error && !data.quotaExceeded) { setCached(cacheKey, data) console.log('[suggestCharts] Cached response for key:', cacheKey, 'cache size now:', cache.size) } else { console.log('[suggestCharts] NOT caching (has error or quota exceeded)') } return data } catch (error) { console.error('[suggestCharts] Network error:', error) return { suggestions: [], analyzedText: '', detectedData: '', hasData: false, error: error instanceof Error ? error.message : 'Network error', } } } /** * Convert a chart suggestion to raw chart format (no markdown ticks) * This is the content that goes inside * @param suggestion - The chart suggestion to convert * @returns Raw chart content string */ export function chartSuggestionToMarkdown(suggestion: ChartSuggestion): string { const lines = [ suggestion.type, suggestion.title, ...suggestion.data.map(d => `${d.label}: ${d.value}`), ] return lines.join('\n') }