Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

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