import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; import { CheckCircleIcon, InfoIcon, XCircleIcon } from "@phosphor-icons/react"; import { useMutation } from "@tanstack/react-query"; import { useMemo } from "react"; import { toast } from "sonner"; import type { AIProvider } from "@/integrations/ai/types"; import { Button } from "@/components/ui/button"; import { Combobox, type ComboboxOption } from "@/components/ui/combobox"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Spinner } from "@/components/ui/spinner"; import { Switch } from "@/components/ui/switch"; import { useAIStore } from "@/integrations/ai/store"; import { orpc } from "@/integrations/orpc/client"; import { getOrpcErrorMessage } from "@/utils/error-message"; import { cn } from "@/utils/style"; type AIProviderOption = ComboboxOption & { defaultBaseURL: string }; const providerOptions: AIProviderOption[] = [ { value: "openai", label: t({ comment: "AI provider option label in dashboard AI settings", message: "OpenAI", }), keywords: ["openai", "gpt", "chatgpt"], defaultBaseURL: "https://api.openai.com/v1", }, { value: "anthropic", label: t({ comment: "AI provider option label in dashboard AI settings", message: "Anthropic Claude", }), keywords: ["anthropic", "claude", "ai"], defaultBaseURL: "https://api.anthropic.com/v1", }, { value: "gemini", label: t({ comment: "AI provider option label in dashboard AI settings", message: "Google Gemini", }), keywords: ["gemini", "google", "bard"], defaultBaseURL: "https://generativelanguage.googleapis.com/v1beta", }, { value: "vercel-ai-gateway", label: t({ comment: "AI provider option label in dashboard AI settings", message: "Vercel AI Gateway", }), keywords: ["vercel", "gateway", "ai"], defaultBaseURL: "https://ai-gateway.vercel.sh/v1/ai", }, { value: "openrouter", label: t({ comment: "AI provider option label in dashboard AI settings", message: "OpenRouter", }), keywords: ["openrouter", "router", "multi", "proxy"], defaultBaseURL: "https://openrouter.ai/api/v1", }, { value: "ollama", label: t({ comment: "AI provider option label in dashboard AI settings", message: "Ollama", }), keywords: ["ollama", "ai", "local"], defaultBaseURL: "https://ollama.com/api", }, ]; function AIForm() { const { set, model, apiKey, baseURL, provider, enabled, testStatus } = useAIStore(); const selectedOption = useMemo(() => { return providerOptions.find((option) => option.value === provider); }, [provider]); const { mutate: testConnection, isPending: isTesting } = useMutation(orpc.ai.testConnection.mutationOptions()); const handleProviderChange = (value: AIProvider | null) => { if (!value) return; set((draft) => { draft.provider = value; }); }; const handleTestConnection = () => { testConnection( { provider, model, apiKey, baseURL }, { onSuccess: (data) => { set((draft) => { draft.testStatus = data ? "success" : "failure"; }); }, onError: (error) => { set((draft) => { draft.testStatus = "failure"; }); toast.error( getOrpcErrorMessage(error, { byCode: { BAD_REQUEST: t({ comment: "Error shown when AI provider credentials or base URL are invalid in AI settings", message: "Invalid AI provider configuration. Please check your settings.", }), BAD_GATEWAY: t({ comment: "Error shown when the configured AI provider cannot be reached during connection test", message: "Could not reach the AI provider. Please try again.", }), }, fallback: t({ comment: "Fallback toast when testing AI provider connection fails", message: "Failed to test AI provider connection. Please try again.", }), }), ); }, }, ); }; return (
set((draft) => { draft.model = e.target.value; }) } placeholder={t({ comment: "Example model-name placeholder in AI settings", message: "e.g., gpt-4, claude-3-opus, gemini-pro", })} autoCorrect="off" autoComplete="off" spellCheck="false" autoCapitalize="off" />
set((draft) => { draft.apiKey = e.target.value; }) } autoCorrect="off" autoComplete="off" spellCheck="false" autoCapitalize="off" data-lpignore="true" data-bwignore="true" data-1p-ignore="true" />
set((draft) => { draft.baseURL = e.target.value; }) } autoCorrect="off" autoComplete="off" spellCheck="false" autoCapitalize="off" />
); } export function AISettingsSection() { const aiEnabled = useAIStore((state) => state.enabled); const canEnableAI = useAIStore((state) => state.canEnable()); const setAIEnabled = useAIStore((state) => state.setEnabled); return (

Artificial Intelligence

Your data is stored locally

Everything entered here is stored locally on your browser. Your data is only sent to the server when making a request to the AI provider, and is never stored or logged on our servers.

{aiEnabled ? : } {aiEnabled ? Enabled : Disabled}

); }