From 982220430efa8eed80ff11563a43e4d8a57f1883 Mon Sep 17 00:00:00 2001 From: Amruth Pillai Date: Sun, 25 Jan 2026 18:29:36 +0100 Subject: [PATCH] fix #2587 - allow for customizable endpoints for all AI providers (#2589) --- src/integrations/orpc/router/ai.ts | 19 ++++---- src/routes/dashboard/settings/ai.tsx | 70 +++++++++++++++++++--------- 2 files changed, 59 insertions(+), 30 deletions(-) diff --git a/src/integrations/orpc/router/ai.ts b/src/integrations/orpc/router/ai.ts index dd5bcf6b5..e463da141 100644 --- a/src/integrations/orpc/router/ai.ts +++ b/src/integrations/orpc/router/ai.ts @@ -12,7 +12,7 @@ import pdfParserUserPrompt from "@/integrations/ai/prompts/pdf-parser-user.md?ra import { defaultResumeData, resumeDataSchema } from "@/schema/resume/data"; import { protectedProcedure } from "../context"; -const aiProviderSchema = z.enum(["vercel-ai-gateway", "openai", "anthropic", "gemini", "ollama"]); +const aiProviderSchema = z.enum(["ollama", "openai", "gemini", "anthropic", "vercel-ai-gateway"]); type AIProvider = z.infer; @@ -20,18 +20,19 @@ type GetModelInput = { provider: AIProvider; model: string; apiKey: string; - baseURL?: string; + baseURL: string; }; function getModel(input: GetModelInput) { - const { provider, model, apiKey, baseURL } = input; + const { provider, model, apiKey } = input; + const baseURL = input.baseURL || undefined; return match(provider) - .with("vercel-ai-gateway", () => createGateway({ apiKey }).languageModel(model)) - .with("openai", () => createOpenAI({ apiKey }).languageModel(model)) - .with("anthropic", () => createAnthropic({ apiKey }).languageModel(model)) - .with("gemini", () => createGoogleGenerativeAI({ apiKey }).languageModel(model)) + .with("openai", () => createOpenAI({ apiKey, baseURL }).languageModel(model)) .with("ollama", () => createOllama({ apiKey, baseURL }).languageModel(model)) + .with("anthropic", () => createAnthropic({ apiKey, baseURL }).languageModel(model)) + .with("vercel-ai-gateway", () => createGateway({ apiKey, baseURL }).languageModel(model)) + .with("gemini", () => createGoogleGenerativeAI({ apiKey, baseURL }).languageModel(model)) .exhaustive(); } @@ -39,7 +40,7 @@ const aiCredentialsSchema = z.object({ provider: aiProviderSchema, model: z.string(), apiKey: z.string(), - baseURL: z.string().optional(), + baseURL: z.string(), }); const fileInputSchema = z.object({ @@ -54,7 +55,7 @@ export const aiRouter = { provider: aiProviderSchema, model: z.string(), apiKey: z.string(), - baseURL: z.string().optional(), + baseURL: z.string(), }), ) .handler(async function* ({ input }) { diff --git a/src/routes/dashboard/settings/ai.tsx b/src/routes/dashboard/settings/ai.tsx index 4886b2287..cdc8b33b1 100644 --- a/src/routes/dashboard/settings/ai.tsx +++ b/src/routes/dashboard/settings/ai.tsx @@ -4,6 +4,7 @@ import { BrainIcon, CheckCircleIcon, InfoIcon, XCircleIcon } from "@phosphor-ico import { useMutation } from "@tanstack/react-query"; import { createFileRoute } from "@tanstack/react-router"; import { motion } from "motion/react"; +import { useMemo } from "react"; import { useIsClient } from "usehooks-ts"; import { Button } from "@/components/ui/button"; import { Combobox, type ComboboxOption } from "@/components/ui/combobox"; @@ -21,17 +22,46 @@ export const Route = createFileRoute("/dashboard/settings/ai")({ component: RouteComponent, }); -const providerOptions: ComboboxOption[] = [ - { value: "vercel-ai-gateway", label: "Vercel AI Gateway", keywords: ["vercel", "gateway", "ai"] }, - { value: "openai", label: "OpenAI", keywords: ["openai", "gpt", "chatgpt"] }, - { value: "gemini", label: "Google Gemini", keywords: ["gemini", "google", "bard"] }, - { value: "anthropic", label: "Anthropic Claude", keywords: ["anthropic", "claude", "ai"] }, - { value: "ollama", label: "Ollama", keywords: ["ollama", "ai", "local"] }, +const providerOptions: (ComboboxOption & { defaultBaseURL: string })[] = [ + { + value: "openai", + label: "OpenAI", + keywords: ["openai", "gpt", "chatgpt"], + defaultBaseURL: "https://api.openai.com/v1", + }, + { + value: "ollama", + label: "Ollama", + keywords: ["ollama", "ai", "local"], + defaultBaseURL: "http://localhost:11434", + }, + { + value: "anthropic", + label: "Anthropic Claude", + keywords: ["anthropic", "claude", "ai"], + defaultBaseURL: "https://api.anthropic.com/v1", + }, + { + value: "vercel-ai-gateway", + label: "Vercel AI Gateway", + keywords: ["vercel", "gateway", "ai"], + defaultBaseURL: "https://ai-gateway.vercel.sh/v1/ai", + }, + { + value: "gemini", + label: "Google Gemini", + keywords: ["gemini", "google", "bard"], + defaultBaseURL: "https://generativelanguage.googleapis.com/v1beta", + }, ]; 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({ mutationFn: async () => { if (testStatus === "success") return; @@ -113,21 +143,19 @@ function AIForm() { /> - {provider === "ollama" && ( -
- - handleBaseURLChange(e.target.value)} - /> -
- )} +
+ + handleBaseURLChange(e.target.value)} + /> +