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.

g1t.sh
133 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={<KeyHelp provider={provider} />}>
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 label="API key" hint={<KeyHelp provider={provider} />}>
84 <Input name="secret" type="password" required placeholder={look.keyPlaceholder} autoComplete="new-password" />
85 </Field>
86 <Field label="Model" hint="Optional. The model your team uses on it. g1t lists the key's models when it checks the key.">
87 <Input name="model" placeholder={look.model} />
88 </Field>
89 {provider === "anthropic" && <ConsoleWorkspaceField />}
90 </>
91 );
92}
93
94/** Where to get a provider's key and how, as the hint under its key. */
95function KeyHelp({ provider }: { provider: AiProvider }) {
96 const { keyUrl, keyHow } = AI_PROVIDER_LOOKS[provider];
97 return (
98 <>
99 {keyUrl && (
100 <>
101 Get a key at{" "}
102 <a href={keyUrl} target="_blank" rel="noreferrer" className="text-accent underline-offset-2 hover:underline">
103 {new URL(keyUrl).host}
104 </a>
105 .{" "}
106 </>
107 )}
108 {keyHow && `${keyHow} `}
109 Sealed when saved: nobody sees it again.
110 </>
111 );
112}
113
114/**
115 * The Console workspace an Anthropic key belongs to: links to the sessions
116 * it starts open there. Sessions don't say which workspace they are in.
117 */
118export function ConsoleWorkspaceField({ defaultValue }: { defaultValue?: string }) {
119 return (
120 <Field
121 label="Console workspace"
122 hint="Optional. Paste any Console address from the workspace the key belongs to, or its wrkspc_ id, so session links open there. Empty means the Default workspace."
123 >
124 <Input
125 name="consoleWorkspace"
126 defaultValue={defaultValue ?? ""}
127 placeholder="https://platform.claude.com/workspaces/wrkspc_…"
128 className="max-w-sm font-mono text-[0.8125rem]"
129 autoComplete="off"
130 />
131 </Field>
132 );
133}