diff --git a/src/app/api/analyze/route.ts b/src/app/api/analyze/route.ts index b0d944f..8327884 100644 --- a/src/app/api/analyze/route.ts +++ b/src/app/api/analyze/route.ts @@ -1,5 +1,6 @@ import { NextRequest } from 'next/server'; import { streamAnalysis } from '@/lib/vertex'; +import '@/lib/env-validation'; import type { DataProfile } from '@/types'; export const runtime = 'nodejs'; @@ -23,43 +24,38 @@ export async function POST(request: NextRequest) { } let resultText = ''; - for await (const chunk of streamAnalysis( - question, - profile, - dataSample, - history - )) { - resultText += chunk; - } + try { + for await (const chunk of streamAnalysis( + question, + profile, + dataSample, + history + )) { + resultText += chunk; + } - resultText = resultText.trim(); + resultText = resultText.trim(); - // Strip markdown code fences if AI added them - if (resultText.startsWith('```')) { - resultText = resultText - .replace(/^```(?:json)?\s*/i, '') - .replace(/```\s*$/, '') - .trim(); - } + // Strip markdown code fences if AI added them + if (resultText.startsWith('```')) { + resultText = resultText + .replace(/^```(?:json)?\s*/i, '') + .replace(/```\s*$/, '') + .trim(); + } - if (!resultText) { - return new Response(JSON.stringify({ error: 'Empty response from AI' }), { - status: 500, - }); - } + if (!resultText) { + throw new Error('Empty response from AI'); + } - // Validate JSON before sending - try { + // Validate JSON before sending JSON.parse(resultText); - } catch { + } catch (e) { + const errorMsg = e instanceof Error ? e.message : String(e); + console.error('[AI Data Lens] Pipeline error:', errorMsg); return new Response( - JSON.stringify({ - chartConfig: null, - findings: resultText.substring(0, 500) || 'No response from AI', - limitations: 'The AI response could not be parsed as JSON.', - stats: {}, - }), - { headers: { 'Content-Type': 'application/json' } } + JSON.stringify({ error: `Analysis failed: unable to interpret dataset. Details: ${errorMsg}` }), + { status: 500, headers: { 'Content-Type': 'application/json' } } ); } @@ -72,6 +68,6 @@ export async function POST(request: NextRequest) { } catch (error) { const msg = error instanceof Error ? error.message : String(error); console.error('Analysis error:', msg); - return new Response(JSON.stringify({ error: msg }), { status: 500 }); + return new Response(JSON.stringify({ error: `Analysis failed: ${msg}` }), { status: 500 }); } } diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index 5fd9e7d..1389d4a 100644 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { parseFile, getDataSample } from '@/lib/data-parser'; +import '@/lib/env-validation'; export const runtime = 'nodejs'; export const maxDuration = 30; @@ -12,9 +13,9 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: 'No file provided' }, { status: 400 }); const ext = file.name.split('.').pop()?.toLowerCase(); - if (!['csv', 'json', 'xlsx', 'xls'].includes(ext || '')) { + if (!['csv', 'json', 'xlsx'].includes(ext || '')) { return NextResponse.json( - { error: 'Unsupported file type. Use CSV, JSON, or Excel.' }, + { error: 'Unsupported file type. Only CSV, JSON, and XLSX files are allowed.' }, { status: 400 } ); } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 9899150..bff3f3c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from 'next'; import { Inter, JetBrains_Mono } from 'next/font/google'; +import '@/lib/env-validation'; import './globals.css'; const inter = Inter({ @@ -14,7 +15,7 @@ const mono = JetBrains_Mono({ }); export const metadata: Metadata = { - title: 'DataLensAI - See your data. Ask anything.', + title: 'AI Data Lens - See your data. Ask anything.', description: 'Drop in a CSV. Ask a business question. Get the full analysis.', }; diff --git a/src/app/page.tsx b/src/app/page.tsx index 8485ca3..1dd55c5 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -4,11 +4,11 @@ import { Hero } from '@/components/Hero'; import { AppShell } from '@/components/AppShell'; export default function Home() { - const data = useSessionStore((s) => s.data); + const currentSession = useSessionStore((s) => s.currentSession); return (
No dataset uploaded yet
++ Please upload a dataset (.csv, .xlsx, or .json) on the home page to start analyzing your data with AI Data Lens. +
+
Conversational analytics
diff --git a/src/components/UploadZone.tsx b/src/components/UploadZone.tsx
index 6af848a..49d21e5 100644
--- a/src/components/UploadZone.tsx
+++ b/src/components/UploadZone.tsx
@@ -15,6 +15,11 @@ export function UploadZone() {
const handleFile = useCallback(
async (file: File) => {
+ const ext = file.name.split('.').pop()?.toLowerCase();
+ if (!ext || !['csv', 'json', 'xlsx'].includes(ext)) {
+ setError(`Unsupported file type: .${ext || ''}. Only .csv, .json, and .xlsx files are allowed.`);
+ return;
+ }
setIsLoading(true);
setError(null);
try {
@@ -71,7 +76,7 @@ export function UploadZone() {
e.target.files?.[0] && handleFile(e.target.files[0])
diff --git a/src/lib/data-parser.ts b/src/lib/data-parser.ts
index 4c198fb..272b0a7 100644
--- a/src/lib/data-parser.ts
+++ b/src/lib/data-parser.ts
@@ -6,11 +6,13 @@ export async function parseFile(
file: File
): Promise<{ data: Record