import { useState, useRef, useEffect } from 'react'
import './App.css'
const QUICK_PROMPTS = [
'What were total sales by category?',
'Compare revenue by region last quarter',
'Show monthly sales trend for 2023',
'Top products by revenue'
]
const DEFAULT_AGENT_MESSAGE = {
type: 'agent',
content: 'Hi! Ask me anything about your business data and I will generate SQL, visuals, and insights for you.'
}
function App() {
const sessionIdRef = useRef(null)
if (!sessionIdRef.current) {
sessionIdRef.current = crypto?.randomUUID ? crypto.randomUUID() : `session-${Date.now()}`
}
const sessionId = sessionIdRef.current
const [messages, setMessages] = useState([DEFAULT_AGENT_MESSAGE])
const [input, setInput] = useState('')
const [loading, setLoading] = useState(false)
const [activeResult, setActiveResult] = useState(null)
const [error, setError] = useState(null)
const [datasetCatalog, setDatasetCatalog] = useState([])
const [catalogLoading, setCatalogLoading] = useState(false)
const [tableName, setTableName] = useState('sales')
const [uploading, setUploading] = useState(false)
const [uploadError, setUploadError] = useState(null)
const [clearing, setClearing] = useState(false)
const messagesEndRef = useRef(null)
const inputRef = useRef(null)
const [datasetPreview, setDatasetPreview] = useState(null)
const dataPreview = activeResult?.data ?? []
const previewColumns = dataPreview.length ? Object.keys(dataPreview[0]) : []
const previewRows = dataPreview.slice(0, 5)
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
}
useEffect(() => {
scrollToBottom()
}, [messages])
useEffect(() => {
if (!inputRef.current) return
const el = inputRef.current
el.style.height = 'auto'
const nextHeight = Math.min(el.scrollHeight, 160)
el.style.height = `${nextHeight}px`
}, [input])
const fetchDatasetCatalog = async () => {
setCatalogLoading(true)
try {
const res = await fetch('http://localhost:8000/api/datasets')
const payload = await res.json()
setDatasetCatalog(payload.tables || [])
} catch (err) {
console.error('Failed to fetch dataset catalog', err)
} finally {
setCatalogLoading(false)
}
}
useEffect(() => {
fetchDatasetCatalog()
}, [])
const sendQuery = async (question) => {
const trimmed = question.trim()
if (!trimmed) return
const userMessage = { type: 'user', content: trimmed }
setMessages((prev) => [...prev, userMessage])
setInput('')
setLoading(true)
setError(null)
try {
const response = await fetch('http://localhost:8000/api/analyze', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ query: trimmed, session_id: sessionId })
})
const data = await response.json()
if (!response.ok || data.error) {
const message = data.error || 'Something went wrong.'
setError(message)
setMessages((prev) => [...prev, { type: 'agent', content: `⚠️ ${message}` }])
return
}
const agentPayload = {
type: 'agent',
content: data.insights || 'Analysis complete.',
sql: data.sql_query,
visualization: data.visualization_url,
chartSummary: data.visualization_summary,
trendSummary: data.trend_analysis?.summary,
anomalySummary: data.anomaly_analysis?.summary,
report: data.report_url
}
setMessages((prev) => [...prev, agentPayload])
setActiveResult(data)
} catch (err) {
const fallback = `Network error: ${err.message}`
setError(fallback)
setMessages((prev) => [...prev, { type: 'agent', content: fallback }])
} finally {
setLoading(false)
}
}
const handleSubmit = (e) => {
e.preventDefault()
if (loading) return
sendQuery(input)
}
const handlePromptClick = (prompt) => {
if (loading) return
setInput(prompt)
sendQuery(prompt)
}
const handleDatasetUpload = async (event) => {
const file = event.target.files?.[0]
if (!file || uploading) return
const formData = new FormData()
formData.append('file', file)
formData.append('table_name', tableName || 'sales')
setUploading(true)
setUploadError(null)
try {
const response = await fetch('http://localhost:8000/api/upload-csv', {
method: 'POST',
body: formData
})
const data = await response.json()
if (!response.ok || data.status !== 'success') {
throw new Error(data.detail || 'Upload failed')
}
if (data.preview) {
setDatasetPreview({
table: data.table,
columns: data.columns,
rows: data.preview
})
setActiveResult(null)
}
fetchDatasetCatalog()
} catch (err) {
setUploadError(err.message)
} finally {
setUploading(false)
event.target.value = ''
}
}
const handleClearChat = async () => {
if (loading || clearing) return
setClearing(true)
setMessages([DEFAULT_AGENT_MESSAGE])
setActiveResult(null)
setError(null)
try {
await fetch('http://localhost:8000/api/session/reset', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ session_id: sessionId })
})
} catch (err) {
console.error('Failed to reset session', err)
} finally {
setClearing(false)
}
}
return (
{messages.length === 1 ? (
Autonomous Data Analyst
Ask natural-language questions about your warehouse. The agent will plan the query, validate results, draw charts, and package everything in a PDF.
{QUICK_PROMPTS.map((prompt) => (
))}
) : (
messages.map((msg, index) => (
{msg.type === 'agent' ? '🤖' : '👤'}
{msg.content}
{msg.sql && (
{msg.sql}
)}
{(msg.trendSummary || msg.anomalySummary) && (
{msg.trendSummary && Trend: {msg.trendSummary}}
{msg.anomalySummary && Anomaly: {msg.anomalySummary}}
)}
))
)}
Analysis Results
{datasetPreview && !activeResult && (
Uploaded Data Preview: {datasetPreview.table}
{datasetPreview.columns.map(col => | {col} | )}
{datasetPreview.rows.map((row, i) => (
{datasetPreview.columns.map(col => | {row[col]} | )}
))}
)}
{activeResult ? (
<>
Insight
{activeResult.insights}
{activeResult.visualization_url && (
)}
{activeResult.trend_analysis && (
Trend Analysis
Diagnostic
{activeResult.trend_analysis.summary}
Start
{Number.isFinite(activeResult.trend_analysis.start) ? activeResult.trend_analysis.start.toFixed(2) : '-'}
End
{Number.isFinite(activeResult.trend_analysis.end) ? activeResult.trend_analysis.end.toFixed(2) : '-'}
Change
{activeResult.trend_analysis.change_pct?.toFixed(1)}%
)}
{activeResult.anomaly_analysis && (
Anomalies
Diagnostic
{activeResult.anomaly_analysis.summary}
{activeResult.anomaly_analysis.anomalies?.slice(0, 3).map((a, i) => (
-
{a.period}
z={a.z_score?.toFixed(2)}
))}
)}
{activeResult.forecast_analysis && (
Forecast
Predictive
{activeResult.forecast_analysis.summary}
{activeResult.forecast_analysis.method && (
Method: {activeResult.forecast_analysis.method}
)}
{activeResult.forecast_analysis.forecasts && (
| Period |
Forecast |
Lower Bound |
Upper Bound |
{activeResult.forecast_analysis.forecasts.map((f, i) => (
| {f.period} |
{Number.isFinite(f.value) ? f.value.toFixed(2) : '-'} |
{Number.isFinite(f.lower_bound) ? f.lower_bound.toFixed(2) : '-'} |
{Number.isFinite(f.upper_bound) ? f.upper_bound.toFixed(2) : '-'} |
))}
)}
)}
{activeResult.statistical_tests && (
Statistical Tests
Diagnostic
{activeResult.statistical_tests.summary}
{activeResult.statistical_tests.tests && Object.entries(activeResult.statistical_tests.tests).map(([testName, testData]) => (
{testName.replace(/_/g, ' ')}
{testData.summary || testData.test || '-'}
{testData.p_value !== undefined && (
p-value:
{Number.isFinite(testData.p_value) ? testData.p_value.toFixed(4) : '-'}
{testData.significant !== undefined && (
{testData.significant ? '✓ Significant' : '○ Not Significant'}
)}
{testData.is_stationary !== undefined && (
{testData.is_stationary ? '✓ Stationary' : '○ Non-Stationary'}
)}
)}
))}
)}
{previewColumns.length > 0 && (
Data Preview
{previewColumns.map(col => | {col} | )}
{previewRows.map((row, i) => (
{previewColumns.map(col => | {row[col]} | )}
))}
)}
{activeResult.report_url && (
Download PDF Report
)}
>
) : (
Run a query to see detailed analysis, charts, and data previews here.
)}
)
}
export default App