Buckets:
GHHG10/CodeServer / opencode /packages /console /app /src /routes /workspace /[id] /provider-section.tsx
| import { json, query, action, useParams, createAsync, useSubmission } from "@solidjs/router" | |
| import { createEffect, For, Show } from "solid-js" | |
| import { Provider } from "@opencode-ai/console-core/provider.js" | |
| import { withActor } from "~/context/auth.withActor" | |
| import { createStore } from "solid-js/store" | |
| import styles from "./provider-section.module.css" | |
| import { useI18n } from "~/context/i18n" | |
| import { formError, localizeError } from "~/lib/form-error" | |
| const PROVIDERS = [ | |
| { name: "OpenAI", key: "openai", prefix: "sk-" }, | |
| { name: "Anthropic", key: "anthropic", prefix: "sk-ant-" }, | |
| { name: "Google Gemini", key: "google", prefix: "AI" }, | |
| ] as const | |
| type Provider = (typeof PROVIDERS)[number] | |
| function maskCredentials(credentials: string) { | |
| return `${credentials.slice(0, 8)}...${credentials.slice(-8)}` | |
| } | |
| const removeProvider = action(async (form: FormData) => { | |
| "use server" | |
| const provider = form.get("provider") as string | null | |
| if (!provider) return { error: formError.providerRequired } | |
| const workspaceID = form.get("workspaceID") as string | null | |
| if (!workspaceID) return { error: formError.workspaceRequired } | |
| return json(await withActor(() => Provider.remove({ provider }), workspaceID), { | |
| revalidate: listProviders.key, | |
| }) | |
| }, "provider.remove") | |
| const saveProvider = action(async (form: FormData) => { | |
| "use server" | |
| const provider = form.get("provider") as string | null | |
| const credentials = form.get("credentials") as string | null | |
| if (!provider) return { error: formError.providerRequired } | |
| if (!credentials) return { error: formError.apiKeyRequired } | |
| const workspaceID = form.get("workspaceID") as string | null | |
| if (!workspaceID) return { error: formError.workspaceRequired } | |
| return json( | |
| await withActor( | |
| () => | |
| Provider.create({ provider, credentials }) | |
| .then(() => ({ error: undefined })) | |
| .catch((e) => ({ error: e.message as string })), | |
| workspaceID, | |
| ), | |
| { revalidate: listProviders.key }, | |
| ) | |
| }, "provider.save") | |
| const listProviders = query(async (workspaceID: string) => { | |
| "use server" | |
| return withActor(() => Provider.list(), workspaceID) | |
| }, "provider.list") | |
| function ProviderRow(props: { provider: Provider }) { | |
| const params = useParams() | |
| const i18n = useI18n() | |
| const providers = createAsync(() => listProviders(params.id!)) | |
| const saveSubmission = useSubmission( | |
| saveProvider, | |
| ([fd]) => (fd.get("provider") as string | null) === props.provider.key, | |
| ) | |
| const removeSubmission = useSubmission( | |
| removeProvider, | |
| ([fd]) => (fd.get("provider") as string | null) === props.provider.key, | |
| ) | |
| const [store, setStore] = createStore({ editing: false }) | |
| let input: HTMLInputElement | |
| const providerData = () => providers()?.find((p) => p.provider === props.provider.key) | |
| createEffect(() => { | |
| if (!saveSubmission.pending && saveSubmission.result && !saveSubmission.result.error) { | |
| hide() | |
| } | |
| }) | |
| function show() { | |
| while (true) { | |
| saveSubmission.clear() | |
| if (!saveSubmission.result) break | |
| } | |
| setStore("editing", true) | |
| setTimeout(() => input?.focus(), 0) | |
| } | |
| function hide() { | |
| setStore("editing", false) | |
| } | |
| return ( | |
| <tr data-slot="provider-row"> | |
| <td data-slot="provider-name">{props.provider.name}</td> | |
| <td data-slot="provider-key"> | |
| <Show | |
| when={store.editing} | |
| fallback={<span>{providerData() ? maskCredentials(providerData()!.credentials) : "-"}</span>} | |
| > | |
| <form id={`provider-form-${props.provider.key}`} action={saveProvider} method="post" data-slot="edit-form"> | |
| <div data-slot="input-wrapper"> | |
| <input | |
| ref={(r) => (input = r)} | |
| name="credentials" | |
| type="text" | |
| placeholder={i18n.t("workspace.providers.placeholder", { | |
| provider: props.provider.name, | |
| prefix: props.provider.prefix, | |
| })} | |
| autocomplete="off" | |
| data-form-type="other" | |
| data-lpignore="true" | |
| /> | |
| <Show when={saveSubmission.result && saveSubmission.result.error}> | |
| {(err) => <div data-slot="form-error">{localizeError(i18n.t, err())}</div>} | |
| </Show> | |
| </div> | |
| <input type="hidden" name="provider" value={props.provider.key} /> | |
| <input type="hidden" name="workspaceID" value={params.id} /> | |
| </form> | |
| </Show> | |
| </td> | |
| <td data-slot="provider-action"> | |
| <Show | |
| when={store.editing} | |
| fallback={ | |
| <Show | |
| when={!!providerData()} | |
| fallback={ | |
| <button data-color="ghost" onClick={() => show()}> | |
| {i18n.t("workspace.providers.configure")} | |
| </button> | |
| } | |
| > | |
| <div data-slot="configured-actions"> | |
| <button data-color="ghost" onClick={() => show()}> | |
| {i18n.t("workspace.providers.edit")} | |
| </button> | |
| <form action={removeProvider} method="post" data-slot="delete-form"> | |
| <input type="hidden" name="provider" value={props.provider.key} /> | |
| <input type="hidden" name="workspaceID" value={params.id} /> | |
| <button data-color="ghost" type="submit" disabled={removeSubmission.pending}> | |
| {i18n.t("workspace.providers.delete")} | |
| </button> | |
| </form> | |
| </div> | |
| </Show> | |
| } | |
| > | |
| <div data-slot="form-actions"> | |
| <button | |
| type="submit" | |
| data-color="ghost" | |
| disabled={saveSubmission.pending} | |
| form={`provider-form-${props.provider.key}`} | |
| > | |
| {saveSubmission.pending ? i18n.t("workspace.providers.saving") : i18n.t("workspace.providers.save")} | |
| </button> | |
| <Show when={!saveSubmission.pending}> | |
| <button type="reset" data-color="ghost" onClick={() => hide()}> | |
| {i18n.t("common.cancel")} | |
| </button> | |
| </Show> | |
| </div> | |
| </Show> | |
| </td> | |
| </tr> | |
| ) | |
| } | |
| export function ProviderSection() { | |
| const i18n = useI18n() | |
| return ( | |
| <section class={styles.root}> | |
| <div data-slot="section-title"> | |
| <h2>{i18n.t("workspace.providers.title")}</h2> | |
| <p>{i18n.t("workspace.providers.subtitle")}</p> | |
| </div> | |
| <div data-slot="providers-table"> | |
| <table data-slot="providers-table-element"> | |
| <thead> | |
| <tr> | |
| <th>{i18n.t("workspace.providers.table.provider")}</th> | |
| <th>{i18n.t("workspace.providers.table.apiKey")}</th> | |
| <th></th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <For each={PROVIDERS}>{(provider) => <ProviderRow provider={provider} />}</For> | |
| </tbody> | |
| </table> | |
| </div> | |
| </section> | |
| ) | |
| } | |
Xet Storage Details
- Size:
- 7.04 kB
- Xet hash:
- 23c55663bb156a20a74f62b75e8f3f848c4a2eb8103a0bf6c2c7f56968bbe192
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.