fix #2587 - allow for customizable endpoints for all AI providers (#2589)

This commit is contained in:
Amruth Pillai
2026-01-25 18:29:36 +01:00
committed by GitHub
parent 3f55c24e36
commit 982220430e
2 changed files with 59 additions and 30 deletions
+10 -9
View File
@@ -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<typeof aiProviderSchema>;
@@ -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 }) {
+49 -21
View File
@@ -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<AIProvider>[] = [
{ 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<AIProvider> & { 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() {
/>
</div>
{provider === "ollama" && (
<div className="flex flex-col gap-y-2 sm:col-span-2">
<Label htmlFor="base-url">
<Trans>Base URL (Optional)</Trans>
</Label>
<Input
id="base-url"
type="url"
value={baseURL}
disabled={enabled}
placeholder="http://localhost:11434"
onChange={(e) => handleBaseURLChange(e.target.value)}
/>
</div>
)}
<div className="flex flex-col gap-y-2 sm:col-span-2">
<Label htmlFor="base-url">
<Trans>Base URL (Optional)</Trans>
</Label>
<Input
id="base-url"
type="url"
value={baseURL}
disabled={enabled}
placeholder={selectedOption?.defaultBaseURL}
onChange={(e) => handleBaseURLChange(e.target.value)}
/>
</div>
<div>
<Button variant="outline" disabled={isTesting || enabled} onClick={() => testConnection()}>