Extract shared call_ollama_chat helper from generate_sql to reuse settings loading and Ollama API call logic. Add two new AI commands: - explain_sql: explains what a SQL query does in plain language - fix_sql_error: suggests corrected SQL based on the error and schema UI additions: "AI Explain" toolbar button, "Explain" and "Fix with AI" action buttons on query errors, inline explanation display in results. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
143 lines
3.9 KiB
TypeScript
143 lines
3.9 KiB
TypeScript
import { ResultsTable } from "./ResultsTable";
|
|
import { ResultsJsonView } from "./ResultsJsonView";
|
|
import type { QueryResult } from "@/types";
|
|
import { Loader2, AlertCircle, Sparkles, Wand2 } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
interface Props {
|
|
result?: QueryResult | null;
|
|
error?: string | null;
|
|
isLoading?: boolean;
|
|
viewMode?: "table" | "json";
|
|
onCellDoubleClick?: (
|
|
rowIndex: number,
|
|
colIndex: number,
|
|
value: unknown
|
|
) => void;
|
|
highlightedCells?: Set<string>;
|
|
aiExplanation?: string | null;
|
|
isAiLoading?: boolean;
|
|
onExplainError?: () => void;
|
|
onFixError?: () => void;
|
|
}
|
|
|
|
export function ResultsPanel({
|
|
result,
|
|
error,
|
|
isLoading,
|
|
viewMode = "table",
|
|
onCellDoubleClick,
|
|
highlightedCells,
|
|
aiExplanation,
|
|
isAiLoading,
|
|
onExplainError,
|
|
onFixError,
|
|
}: Props) {
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex h-full items-center justify-center text-muted-foreground">
|
|
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
|
|
Executing query...
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (aiExplanation) {
|
|
return (
|
|
<div className="h-full overflow-auto p-4">
|
|
<div className="rounded-md border bg-muted/30 p-4">
|
|
<div className="mb-2 flex items-center gap-2 text-xs font-medium text-muted-foreground">
|
|
<Sparkles className="h-3.5 w-3.5" />
|
|
AI Explanation
|
|
</div>
|
|
<pre className="whitespace-pre-wrap font-sans text-sm leading-relaxed text-foreground">
|
|
{aiExplanation}
|
|
</pre>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="flex h-full flex-col items-center justify-center gap-3 p-4">
|
|
<div className="flex items-start gap-2 rounded-md border border-destructive/50 bg-destructive/10 p-3 text-sm text-destructive">
|
|
<AlertCircle className="mt-0.5 h-4 w-4 shrink-0" />
|
|
<pre className="whitespace-pre-wrap font-mono text-xs">{error}</pre>
|
|
</div>
|
|
{(onExplainError || onFixError) && (
|
|
<div className="flex items-center gap-2">
|
|
{onExplainError && (
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
className="h-7 gap-1.5 text-xs"
|
|
onClick={onExplainError}
|
|
disabled={isAiLoading}
|
|
>
|
|
{isAiLoading ? (
|
|
<Loader2 className="h-3 w-3 animate-spin" />
|
|
) : (
|
|
<Sparkles className="h-3 w-3" />
|
|
)}
|
|
Explain
|
|
</Button>
|
|
)}
|
|
{onFixError && (
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
className="h-7 gap-1.5 text-xs"
|
|
onClick={onFixError}
|
|
disabled={isAiLoading}
|
|
>
|
|
{isAiLoading ? (
|
|
<Loader2 className="h-3 w-3 animate-spin" />
|
|
) : (
|
|
<Wand2 className="h-3 w-3" />
|
|
)}
|
|
Fix with AI
|
|
</Button>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!result) {
|
|
return (
|
|
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
|
|
Press Ctrl+Enter to execute query
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (result.columns.length === 0) {
|
|
return (
|
|
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
|
|
Query executed successfully. {result.row_count} rows affected.
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (viewMode === "json") {
|
|
return (
|
|
<ResultsJsonView
|
|
columns={result.columns}
|
|
rows={result.rows}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<ResultsTable
|
|
columns={result.columns}
|
|
types={result.types}
|
|
rows={result.rows}
|
|
onCellDoubleClick={onCellDoubleClick}
|
|
highlightedCells={highlightedCells}
|
|
/>
|
|
);
|
|
}
|