From c5f0348387daa92758b3013fbeb4449989356f62 Mon Sep 17 00:00:00 2001 From: adibarra <93070681+adibarra@users.noreply.github.com> Date: Mon, 30 Mar 2026 21:04:36 -0500 Subject: [PATCH 01/14] feat: add AI-powered chart generation tab (#485) --- .../app/src/app/(dashboard)/ai-chart/page.tsx | 10 + .../components/ai-chart/AiChartDisplay.tsx | 213 ++++++++++++++++++ .../src/components/ai-chart/AiChartResult.tsx | 174 ++++++++++++++ .../components/ai-chart/example-prompts.ts | 8 + .../components/ai-chart/prompt-templates.ts | 74 ++++++ packages/app/src/components/ai-chart/types.ts | 23 ++ packages/app/src/components/tab-nav.tsx | 1 + packages/app/src/hooks/api/use-ai-chart.ts | 207 +++++++++++++++++ packages/app/src/lib/ai-providers.ts | 141 ++++++++++++ packages/app/src/lib/tab-meta.ts | 6 + 10 files changed, 857 insertions(+) create mode 100644 packages/app/src/app/(dashboard)/ai-chart/page.tsx create mode 100644 packages/app/src/components/ai-chart/AiChartDisplay.tsx create mode 100644 packages/app/src/components/ai-chart/AiChartResult.tsx create mode 100644 packages/app/src/components/ai-chart/example-prompts.ts create mode 100644 packages/app/src/components/ai-chart/prompt-templates.ts create mode 100644 packages/app/src/components/ai-chart/types.ts create mode 100644 packages/app/src/hooks/api/use-ai-chart.ts create mode 100644 packages/app/src/lib/ai-providers.ts diff --git a/packages/app/src/app/(dashboard)/ai-chart/page.tsx b/packages/app/src/app/(dashboard)/ai-chart/page.tsx new file mode 100644 index 0000000..b299e8a --- /dev/null +++ b/packages/app/src/app/(dashboard)/ai-chart/page.tsx @@ -0,0 +1,10 @@ +import type { Metadata } from 'next'; + +import AiChartDisplay from '@/components/ai-chart/AiChartDisplay'; +import { tabMetadata } from '@/lib/tab-meta'; + +export const metadata: Metadata = tabMetadata('ai-chart'); + +export default function AiChartPage() { + return ; +} diff --git a/packages/app/src/components/ai-chart/AiChartDisplay.tsx b/packages/app/src/components/ai-chart/AiChartDisplay.tsx new file mode 100644 index 0000000..ff86d71 --- /dev/null +++ b/packages/app/src/components/ai-chart/AiChartDisplay.tsx @@ -0,0 +1,213 @@ +'use client'; + +import { useCallback, useEffect, useState } from 'react'; +import { AlertCircle, Eye, EyeOff, Sparkles } from 'lucide-react'; + +import { track } from '@/lib/analytics'; +import { PROVIDER_OPTIONS, getProviderLabel } from '@/lib/ai-providers'; +import { useAiChart } from '@/hooks/api/use-ai-chart'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Textarea } from '@/components/ui/textarea'; + +import type { AiProvider } from './types'; +import { EXAMPLE_PROMPTS } from './example-prompts'; +import AiChartResult from './AiChartResult'; + +const STORAGE_PREFIX = 'inferencex-ai-key-'; + +function getStoredKey(provider: AiProvider): string { + if (typeof window === 'undefined') return ''; + return sessionStorage.getItem(`${STORAGE_PREFIX}${provider}`) ?? ''; +} + +function storeKey(provider: AiProvider, key: string) { + if (typeof window === 'undefined') return; + if (key) { + sessionStorage.setItem(`${STORAGE_PREFIX}${provider}`, key); + } else { + sessionStorage.removeItem(`${STORAGE_PREFIX}${provider}`); + } +} + +export default function AiChartDisplay() { + const [provider, setProvider] = useState('openai'); + const [apiKey, setApiKey] = useState(''); + const [prompt, setPrompt] = useState(''); + const [showKey, setShowKey] = useState(false); + const { result, isLoading, error, generate, reset } = useAiChart(); + + // Load stored key on provider change + useEffect(() => { + setApiKey(getStoredKey(provider)); + }, [provider]); + + const handleProviderChange = useCallback((value: string) => { + const newProvider = value as AiProvider; + setProvider(newProvider); + track('ai_chart_provider_changed', { provider: newProvider }); + }, []); + + const handleSubmit = useCallback(() => { + if (!apiKey.trim() || !prompt.trim()) return; + storeKey(provider, apiKey); + track('ai_chart_prompt_submitted', { provider, prompt_length: prompt.length }); + generate(prompt, provider, apiKey); + }, [apiKey, prompt, provider, generate]); + + const handleExampleClick = useCallback((example: string, index: number) => { + setPrompt(example); + track('ai_chart_example_clicked', { example_index: index }); + }, []); + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + handleSubmit(); + } + }, + [handleSubmit], + ); + + return ( +
+ {/* Provider & API Key */} + + + + + AI Chart Generation + + + Describe the chart you want in natural language. Your API key is stored in your browser + and only used by your selected provider. We never see it. + + + +
+
+ +
+
+ setApiKey(e.target.value)} + /> + +
+
+ +
+