- Fix SQL injection in data.rs by wrapping get_table_data in READ ONLY transaction - Fix SQL injection in docker.rs CREATE DATABASE via escape_ident - Fix command injection in docker.rs by validating pg_version/container_name and escaping shell-interpolated values - Fix UTF-8 panic on stderr truncation with char_indices - Wrap delete_rows in a transaction for atomicity - Replace .expect() with proper error propagation in lib.rs - Cache AI settings in AppState to avoid repeated disk reads - Cap JSONB column discovery at 50 to prevent unbounded queries - Fix ERD colorMode to respect system theme via useTheme() - Extract AppState::get_pool() replacing ~19 inline pool patterns - Extract shared AiSettingsFields component (DRY popover + sheet) - Make get_connections_path pub(crate) and reuse from docker.rs - Deduplicate check_docker by delegating to check_docker_internal Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
67 lines
1.9 KiB
TypeScript
67 lines
1.9 KiB
TypeScript
import { useState } from "react";
|
|
import {
|
|
Popover,
|
|
PopoverContent,
|
|
PopoverTrigger,
|
|
} from "@/components/ui/popover";
|
|
import { Button } from "@/components/ui/button";
|
|
import { useAiSettings, useSaveAiSettings } from "@/hooks/use-ai";
|
|
import { Settings } from "lucide-react";
|
|
import { toast } from "sonner";
|
|
import { AiSettingsFields } from "./AiSettingsFields";
|
|
|
|
export function AiSettingsPopover() {
|
|
const { data: settings } = useAiSettings();
|
|
const saveMutation = useSaveAiSettings();
|
|
|
|
const [url, setUrl] = useState<string | null>(null);
|
|
const [model, setModel] = useState<string | null>(null);
|
|
|
|
const currentUrl = url ?? settings?.ollama_url ?? "http://localhost:11434";
|
|
const currentModel = model ?? settings?.model ?? "";
|
|
|
|
const handleSave = () => {
|
|
saveMutation.mutate(
|
|
{ provider: "ollama", ollama_url: currentUrl, model: currentModel },
|
|
{
|
|
onSuccess: () => toast.success("AI settings saved"),
|
|
onError: (err) =>
|
|
toast.error("Failed to save AI settings", {
|
|
description: String(err),
|
|
}),
|
|
}
|
|
);
|
|
};
|
|
|
|
return (
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
className="h-6 w-6 p-0"
|
|
title="AI Settings"
|
|
>
|
|
<Settings className="h-3 w-3" />
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-80" align="end">
|
|
<div className="flex flex-col gap-3">
|
|
<h4 className="text-sm font-medium">Ollama Settings</h4>
|
|
|
|
<AiSettingsFields
|
|
ollamaUrl={currentUrl}
|
|
onOllamaUrlChange={setUrl}
|
|
model={currentModel}
|
|
onModelChange={setModel}
|
|
/>
|
|
|
|
<Button size="sm" className="h-7 text-xs" onClick={handleSave}>
|
|
Save
|
|
</Button>
|
|
</div>
|
|
</PopoverContent>
|
|
</Popover>
|
|
);
|
|
}
|