Reusable feature components that provide business functionality.
File: components/schema-explorer.tsx
Interactive schema browser with editing capabilities.
Props:
interface SchemaExplorerProps {
schema: Schema;
onSchemaUpdate: (schema: Schema) => void;
readOnly?: boolean;
}Features:
- Collapsible table list
- Column details (type, nullable, keys)
- Inline description editing
- AI description generation
- Hide/show tables and columns
- Visual indicators for new/modified items
Usage:
<SchemaExplorer
schema={currentSchema}
onSchemaUpdate={(updatedSchema) => setSchema(updatedSchema)}
/>Key Methods:
// Toggle table expansion
const toggleTable = (tableName: string) => {
setExpandedTables(prev =>
prev.includes(tableName)
? prev.filter(t => t !== tableName)
: [...prev, tableName]
);
};
// Update description
const updateDescription = (tableName: string, columnName?: string, description: string) => {
const updatedSchema = { ...schema };
// Update logic...
onSchemaUpdate(updatedSchema);
};
// Toggle visibility
const toggleHidden = (tableName: string, columnName?: string) => {
const updatedSchema = { ...schema };
// Toggle logic...
onSchemaUpdate(updatedSchema);
};File: components/schema-update-modal.tsx
Modal that summarizes detected schema changes (new tables, new/modified columns) after re-introspection and asks the user to confirm applying the update.
Props:
interface SchemaUpdateModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
schema: Schema | null; // schema with isNew/isModified flags
onConfirm: () => void;
onCancel: () => void;
}Counts are derived from isNew / isModified flags on tables and columns
(see Schema change detection).
File: components/query-results-display.tsx
Displays query execution results.
Props:
interface QueryResultsProps {
data: {
columns: string[];
rows: DataRows;
rowCount: number;
executionTime: number;
};
/** Seed a saved chart (e.g. a report's persisted visualization) so it renders immediately, no AI call. */
initialChartConfig?: ChartConfig;
/** Notified whenever the currently displayed chart config changes (or null when viewing the table). */
onChartConfigChange?: (config: ChartConfig | null) => void;
/** When provided (results belong to a saved report), renders a "Save chart to report" action. */
onSaveChart?: (config: ChartConfig) => void;
}Features:
- Sortable columns
- Filterable data
- Pagination
- Row search
- CSV export
- Switch to chart view
- Automatic column type detection (text, number, currency, date, URL)
- Manual column type override via dropdown on column header badges
Column Type Detection:
The component auto-detects column types by sampling the first 10 rows:
| Type | Detection Method | Formatting |
|---|---|---|
text |
Default fallback | Plain string |
number |
All values numeric | Locale-formatted (e.g., 1,234) |
currency |
Column name matches currency keywords (e.g., price, revenue, cost) AND values are numeric |
USD formatted (e.g., $1,234.56) |
date |
All values match ISO date format | Localized date/time |
url |
All values match URL pattern (http://, https://, www.) |
Clickable link with external link icon |
empty |
All values null/undefined/empty | No formatting |
Users can override the auto-detected type by clicking the type badge on any column header, which opens a dropdown with all available types. The auto-detected type is marked with "(detected)" in the dropdown. Overrides reset when new query results are loaded.
View Modes:
- Table - Standard data table with sorting and type-aware formatting
- Chart - Visualization (via ChartDisplay)
Export Function:
const exportToCSV = () => {
const csvContent = [
columns.join(','),
...rows.map(row => row.map(cell => `"${cell}"`).join(','))
].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv' });
// Trigger download
};File: components/chart-display.tsx
Main chart renderer. Switches over config.type to render the matching chart
component, transforming the columns/rows result shape into Recharts row objects.
Props:
interface ChartDisplayProps {
config: ChartConfig; // discriminated union, see models/chart-config.interface.ts
columns: string[];
rows: DataRows;
}config.type is one of "bar" | "line" | "pie" | "area" | "scatter" | "composed";
an unknown type renders an "Unknown chart type" alert.
Usage:
<ChartDisplay
config={{
type: 'bar',
title: 'Monthly Sales',
xAxisColumn: 'month',
yAxisColumns: ['revenue', 'profit'],
}}
columns={columns}
rows={rows}
/>Located in components/charts/:
- bar-chart.tsx - Vertical/horizontal bar charts
- line-chart.tsx - Line charts with optional area fill
- pie-chart.tsx - Pie/donut charts
- area-chart.tsx - Stacked area charts
- scatter-chart.tsx - Scatter plots
- composed-chart.tsx - Mixed bars/lines/areas on shared axes (see below)
All use Recharts library and follow consistent prop patterns.
File: components/charts/composed-chart.tsx
Renders multiple numeric series of different kinds (bars, lines, and/or areas)
on a single shared X/Y axis pair, driven by ComposedChartConfig
(models/chart-config.interface.ts):
interface ComposedChartConfig extends BaseChartConfig {
type: "composed";
xAxisColumn: string; // shared X axis for all series
bars?: string[]; // columns rendered as bars
lines?: string[]; // columns rendered as lines
areas?: string[]; // columns rendered as filled areas
xAxisLabel?: string;
yAxisLabel?: string;
colors?: string[];
}Behavior:
- Render order is bars → areas → lines, so lines draw on top of the filled areas and bars.
- Colors come from a single running color index across all three series
groups (bars, then areas, then lines), cycling through
config.colors(default palette#3b82f6, #10b981, #f59e0b, #ef4444, #8b5cf6, #ec4899). - All series columns are numerically coerced (same guard as the other renderers — non-numeric / empty values are left untouched).
- The legend shows only when there is more than one numeric series.
When to use: comparing series that read better with different visual emphasis
on the same axis — e.g. totals/counts as bars alongside a trend, average, or rate
as a line. The AI can select it via the create_composed_chart tool
(CHART_TOOLS), mapped to "composed" by chartTypeMap in
app/api/chart/generate/route.ts.
File: components/chart-customizer.tsx
A live-editing panel for chart configuration. Lets users change the chart type,
remap columns, set axis labels and display toggles, and pick colors — previewing
changes immediately. Exports ChartCustomizer, the reshapeChartConfig helper,
and the CHART_PALETTE constant.
Props:
interface ChartCustomizerProps {
config: ChartConfig;
columns: string[];
onChange: (config: ChartConfig) => void;
}Per-type column mapping — the rendered fields depend on config.type:
bar/line/area: single X-Axis select + multi-select Y-Axis columns (series) checklist.pie: Category (name) and Value column selects.scatter: X, Y, and optional Point label column selects.composed: X-Axis select plus three checklists — Bars, Lines, Areas — so any column can be assigned to a series kind.
Axis-label inputs show for everything except pie; display toggles are type-specific
(Stacked for bar/area, Smooth + Show dots for line, Show labels for pie). Colors use
a palette of swatches plus a native <input type="color"> picker; for scatter it is
a single point color, otherwise one color per series (in the renderer's color order).
reshapeChartConfig(prev, newType, columns) rebuilds a config of newType
while carrying column choices across chart shapes — it preserves
title/description/colors and maps the previous X column and series columns into the
new shape's fields (filling sensible defaults from columns when needed). This is
what runs when the user switches the chart type in the dropdown, so selections
survive the switch.
Build-manually flow: the chart view dropdown in
QueryResultsDisplay offers "Build manually (no AI)",
which calls reshapeChartConfig(undefined, "bar", columns) to seed a default bar
chart and opens the customizer — no AI call. A "Customize" toggle reveals the
panel for any displayed chart (AI-generated or manual).
Persistence: the customized config is lifted out of QueryResultsDisplay via
onChartConfigChange into the query tab's chartConfig
(models/query-tab.interface.ts). When the results belong to a saved report, a
"Save chart to report" action (onSaveChart) writes the config to the report's
SavedReport.visualization (app/query/page.tsx, saveChartToReport). On reopening
the report, the tab's chart is seeded from report.visualization so it renders
without an AI call.
File: components/save-report-dialog.tsx
Dialog for saving queries as reports.
Props:
interface SaveReportDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
query: string;
sql: string;
explanation: string;
confidence: number;
warnings: string[];
connectionId: string;
onSave: (report: SavedReport) => void;
}Fields Captured:
- Report name
- Description
- Parameters (optional)
File: components/edit-report-dialog.tsx
Dialog for editing existing report metadata.
Props:
interface EditReportDialogProps {
report: SavedReport;
open: boolean;
onOpenChange: (open: boolean) => void;
onSave: (report: SavedReport) => void;
}File: components/parameter-config.tsx
Configure parameters for parameterized reports.
Props:
interface ParameterConfigProps {
parameters: ReportParameter[];
onChange: (parameters: ReportParameter[]) => void;
}Parameter Types:
text- Text inputnumber- Numeric inputdate- Date pickerdatetime- DateTime pickerboolean- Checkbox
File: components/parameter-input-dialog.tsx
Dialog for entering parameter values when running reports.
Props:
interface ParameterInputDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
parameters: ReportParameter[];
onSubmit: (values: Record<string, any>) => void;
}File: components/saved-reports.tsx
List component for displaying saved reports.
Props:
interface SavedReportsProps {
reports: SavedReport[];
onRun: (report: SavedReport) => void;
onEdit: (report: SavedReport) => void;
onDelete: (reportId: string) => void;
connectionFilter?: string;
}File: components/export-reports-dialog.tsx
Dialog for exporting saved reports as a JSON file (the Reports page "Export
Reports" action). Reports are grouped by connection (checking a connection toggles
all its reports); reports whose connection no longer exists fall under an "Unknown
connection" group so they can still be exported. The downloaded file
(reports-export-<date>.json, shape { version, exportDate, reports }) can be
placed at config/reports.json to share reports with everyone using the app.
Props:
interface ExportReportsDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}File: components/import-reports-dialog.tsx
Dialog for importing reports from a JSON file (the Reports page "Import Reports"
action — complements ExportReportsDialog). Accepts a reports export
({ reports: [...] }), a bare array, or a full database export
({ savedReports: [...] }). Parsed reports are classified into three buckets:
those whose original connection exists (auto-imported), those whose connection is
missing (the user picks a connection per report), and those already present (skipped
by id). Imports run through the context saveReport, so it works in both
localStorage and auth modes; runtime/sharing fields (source, accessLevel,
sharedByEmail, sharedByName) are stripped so each report is created fresh and
owned.
Props:
interface ImportReportsDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}The dashboard surfaces Dashboard Widgets — pinned saved reports whose SQL is executed live on dashboard load and rendered as KPI cards or a trend chart. See the Dashboard Widgets section below for the full flow.
Users pin a saved report to the dashboard from the report's actions dropdown in
SavedReports — "Pin as Dashboard Metric" or "Pin as Dashboard
Chart" (and the corresponding "Unpin …"). Pinning sets a dashboardWidget config
on the SavedReport (components/saved-reports.tsx, setDashboardWidget) and
persists it via the storage layer; unpinning deletes the config.
DashboardWidgetConfig (models/saved-report.interface.ts):
interface DashboardWidgetConfig {
kind: 'metric' | 'chart';
// metric only — the KPI's value is rows[0][0] of the report's result
target?: number;
unit?: 'number' | 'currency' | 'percent';
higherIsBetter?: boolean; // default true
// chart only — optional cached config; generated on the fly when absent
chartConfig?: ChartConfig;
}The companion SavedReport.visualization?: ChartConfig field stores the report's
saved chart; when present, the dashboard chart widget uses it directly (no AI call).
Load flow (loadDashboardWidgets in app/page.tsx):
- Filters the active connection's reports to those with a
dashboardWidget. - Caps the dashboard at 4 metric reports (
.slice(0, 4)) plus 1 chart report (.find(...)). - Runs each report's SQL via
POST /api/query/execute(params substituted byutils/substitute-params.ts; queries with an unresolved{{placeholder}}are skipped so the dashboard never blocks on required parameters). - Metric queries run together under
Promise.allSettled, so a single failing query is isolated and never breaks the rest of the dashboard strip. The whole load is fire-and-forget. - For the chart widget, config resolution prefers
report.visualization, thendashboardWidget.chartConfig, and only callsPOST /api/chart/generatewhen neither exists.
Metric status logic (utils/metric-status.ts):
formatMetricValue(value, unit?)coerces to a number (stripping non-numeric characters) and formats by unit —currency→ USD,percent→N%, default → locale number. Non-numeric values fall back to their string form (or an em dash).computeStatus(value, target?, higherIsBetter = true)returns"exceeding" | "on-track" | "behind" | "neutral". WhenhigherIsBetterthe ratio isvalue/target; otherwise it inverts totarget/valueso a lower value scores better. Ratio>= 1→ exceeding,>= 0.9→ on-track, below → behind. Returnsneutralwhen there is no target or the value isn't numeric. Edge cases:higherIsBetterwith target0→ neutral;!higherIsBetterwith value0→ exceeding.
File: components/executive-metrics.tsx
Renders the pinned-report KPI strip. Each KpiMetric shows the report name,
optional description, formatted value, an optional "Target: …" badge, and a
status icon/color keyed off KpiStatus (exceeding / on-track / behind /
neutral). Returns null when there are no metrics. When onRemove is provided,
each card gets an unpin (X) button.
function ExecutiveMetrics({
metrics: KpiMetric[];
onRemove?: (reportId: string) => void;
}): JSX.Element | nullFile: components/performance-chart.tsx
Thin wrapper around ChartDisplay for the pinned-report trend
chart. title/description override the chart config's own header so the widget
reflects the report's name; when onRemove is supplied, an unpin button is
overlaid in the card's top-right corner.
interface PerformanceChartProps {
config: ChartConfig;
columns: string[];
rows: DataRows;
title?: string;
description?: string;
onRemove?: () => void;
}File: components/quick-actions.tsx
Quick action card with 4 color-coded buttons:
- New Query (blue)
- Generate Report (green)
- Connect Database (purple)
- View Analytics (orange)
File: components/recent-reports.tsx
Shows mock recent reports with type badges, timestamps, and download options.
File: components/scheduled-reports.tsx
Demo UI listing scheduled report deliveries (frequency, next run, recipients, format)
with active/pause toggles. Uses a hardcoded scheduledReports array — no scheduling
backend exists yet.
File: components/report-templates.tsx
Demo gallery of report template cards (Executive Summary, Sales Performance, etc.)
with category and query-count badges. Uses a hardcoded templates array.
File: components/query-tab-content.tsx
Displays an individual query tab with generated SQL, explanation, warnings, and results.
Features:
- Editable SQL textarea (dark background, monospace)
- Execute and Save as Report buttons
- Revision button for failed queries
- Follow-up question button
- Shows execution results or errors
- Displays explanation responses (alternative to SQL queries)
- Smooth scroll to results
File: components/followup-dialog.tsx
Dialog for asking follow-up questions about query results.
Features:
- Text input for follow-up question
- Row limit selector: None (schema only), 25, 50, 100, or All
- Warning when "All" exceeds 500 rows
- Ctrl+Enter keyboard shortcut
- Disabled state during processing
Infrastructure, navigation, theme, auth, sharing, and modal components are documented separately in Infrastructure Components.
{isLoading ? (
<div className="flex items-center justify-center p-4">
<Loader2 className="h-6 w-6 animate-spin" />
</div>
) : (
<Content />
)}{items.length === 0 ? (
<EmptyState
icon={<DatabaseIcon />}
title="No items found"
description="Create your first item to get started"
action={<Button onClick={onCreate}>Create Item</Button>}
/>
) : (
<ItemList items={items} />
)}{error && (
<Alert variant="destructive">
<AlertDescription>{error.message}</AlertDescription>
</Alert>
)}- Components Overview - Component categories
- Infrastructure Components - Providers, navigation, theme, auth, modals
- Page Components - App pages
- Models Overview - TypeScript interfaces