Skip to content
99 linesCodeBlameRaw
1/**
2 * A workspace's AI providers: the catalogue people choose from, and the
3 * fields each one asks for when it is added. The marks and tiles are the
4 * integrations' own (components/connectors).
5 */
6import type { ReactNode } from "react";
7
8import type { Provider } from "@g1t/contracts";
9
10import { AI_PROVIDER_GROUPS, AI_PROVIDERS, aiProvidersIn } from "../lib/ai-providers";
11import { ProviderTiles } from "./connectors";
12import { Field, Input } from "./ui";
13import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
14
15/** The AI providers to choose from, grouped. */
16export function ModelCatalog({ slug, adding }: { slug: string; adding: Provider | null }) {
17 return (
18 <div className="mt-4 space-y-4">
19 {AI_PROVIDER_GROUPS.map(({ group, title }) => (
20 <div key={group}>
21 <p className="mb-2 text-xs font-medium tracking-wide text-faint uppercase">{title}</p>
22 <ProviderTiles
23 providers={aiProvidersIn(group)}
24 slug={slug}
25 adding={adding}
26 blurb={(provider) => AI_PROVIDERS[provider]?.blurb ?? ""}
27 />
28 </div>
29 ))}
30 </div>
31 );
32}
33
34/** What adding an AI provider asks for. */
35export function ModelProviderFields({ provider }: { provider: Provider }): ReactNode {
36 const entry = AI_PROVIDERS[provider];
37 if (provider === "azure_openai") {
38 return (
39 <>
40 <Field label="Endpoint" hint="Your resource's endpoint, from the Azure portal: Keys and Endpoint.">
41 <Input name="baseUrl" type="url" required placeholder="https://acme.openai.azure.com" />
42 </Field>
43 <Field label="Key" hint="KEY 1 or KEY 2 of the resource. Sealed when saved: nobody sees it again.">
44 <Input name="secret" type="password" required autoComplete="new-password" />
45 </Field>
46 <Field label="Deployment" hint="The name of the model deployment. Add one provider for each deployment your team uses.">
47 <Input name="model" required placeholder="gpt-5" />
48 </Field>
49 </>
50 );
51 }
52 if (provider === "anthropic_endpoint" || provider === "openai_endpoint") {
53 const anthropic = provider === "anthropic_endpoint";
54 return (
55 <>
56 <Field
57 label="Base URL"
58 hint={anthropic ? "Without /v1, such as https://llm.acme.dev." : "Up to and including the version, such as https://llm.acme.dev/v1."}
59 >
60 <Input name="baseUrl" type="url" required placeholder={anthropic ? "https://llm.acme.dev" : "https://llm.acme.dev/v1"} />
61 </Field>
62 <Field label="Key" hint="Optional, if the endpoint needs one. Sealed when saved: nobody sees it again.">
63 <Input name="secret" type="password" autoComplete="new-password" />
64 </Field>
65 <Field label="Send the key as">
66 <Select name="authHeader" defaultValue={anthropic ? "x-api-key" : "authorization"}>
67 <SelectTrigger aria-label="Send the key as" className="font-mono text-[0.8125rem]">
68 <SelectValue />
69 </SelectTrigger>
70 <SelectContent>
71 <SelectItem value="x-api-key" description="Anthropic's header" className="font-mono text-[0.8125rem]">
72 x-api-key
73 </SelectItem>
74 <SelectItem value="authorization" description="OpenAI's header, and most others" className="font-mono text-[0.8125rem]">
75 Authorization: Bearer
76 </SelectItem>
77 </SelectContent>
78 </Select>
79 </Field>
80 <Field label="Model" hint="Optional. The model your team uses on it.">
81 <Input name="model" placeholder={anthropic ? "claude-sonnet-5-5" : "qwen3-coder"} />
82 </Field>
83 </>
84 );
85 }
86 return (
87 <>
88 <Field
89 label="API key"
90 hint={`${entry?.keyUrl ? `Get one at ${new URL(entry.keyUrl).host}. ` : ""}Sealed when saved: nobody sees it again.`}
91 >
92 <Input name="secret" type="password" required placeholder={entry?.keyPlaceholder} autoComplete="new-password" />
93 </Field>
94 <Field label="Model" hint="Optional. The model your team uses on it. g1t lists the key's models when it checks the key.">
95 <Input name="model" placeholder={entry?.model} />
96 </Field>
97 </>
98 );
99}