diff options
| author | Adam <[email protected]> | 2025-12-10 21:16:50 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-12-11 06:48:58 -0600 |
| commit | 3bb546c94d6bb295bfeafdafbb9d34b7cc462560 (patch) | |
| tree | 54ae4fa1b18c68d23ff460caab596e942593a857 /packages/desktop/src/pages | |
| parent | 8e15bcb68e9a8a37bb12afca3984f3967ccb58eb (diff) | |
| download | opencode-3bb546c94d6bb295bfeafdafbb9d34b7cc462560.tar.gz opencode-3bb546c94d6bb295bfeafdafbb9d34b7cc462560.zip | |
wip(desktop): progress
Diffstat (limited to 'packages/desktop/src/pages')
| -rw-r--r-- | packages/desktop/src/pages/layout.tsx | 213 |
1 files changed, 210 insertions, 3 deletions
diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index 10d4cbfda..0ba6c0a2d 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -17,7 +17,7 @@ import { DiffChanges } from "@opencode-ai/ui/diff-changes" import { getFilename } from "@opencode-ai/util/path" import { Select } from "@opencode-ai/ui/select" import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" -import { Session, Project } from "@opencode-ai/sdk/v2/client" +import { Session, Project, ProviderAuthMethod } from "@opencode-ai/sdk/v2/client" import { usePlatform } from "@/context/platform" import { createStore } from "solid-js/store" import { @@ -34,6 +34,11 @@ import { SelectDialog } from "@opencode-ai/ui/select-dialog" import { Tag } from "@opencode-ai/ui/tag" import { IconName } from "@opencode-ai/ui/icons/provider" import { popularProviders, useProviders } from "@/hooks/use-providers" +import { Dialog } from "@opencode-ai/ui/dialog" +import { iife } from "@opencode-ai/util/iife" +import { List, ListRef } from "@opencode-ai/ui/list" +import { Input } from "@opencode-ai/ui/input" +import { useGlobalSDK } from "@/context/global-sdk" export default function Layout(props: ParentProps) { const [store, setStore] = createStore({ @@ -42,6 +47,7 @@ export default function Layout(props: ParentProps) { }) const params = useParams() + const globalSDK = useGlobalSDK() const globalSync = useGlobalSync() const layout = useLayout() const platform = usePlatform() @@ -562,7 +568,6 @@ export default function Layout(props: ParentProps) { activeIcon="plus-small" key={(x) => x?.id} items={providers().all} - // current={local.model.current()} filterKeys={["id", "name"]} groupBy={(x) => (popularProviders.includes(x.id) ? "Popular" : "Other")} sortBy={(a, b) => { @@ -575,7 +580,10 @@ export default function Layout(props: ParentProps) { if (b.category === "Popular" && a.category !== "Popular") return 1 return 0 }} - // onSelect={(x) => } + onSelect={(x) => { + if (!x) return + layout.dialog.connect(x.id) + }} onOpenChange={(open) => { if (open) { layout.dialog.open("provider") @@ -607,6 +615,205 @@ export default function Layout(props: ParentProps) { )} </SelectDialog> </Show> + <Show when={layout.dialog?.opened() === "connect"}> + {iife(() => { + const [store, setStore] = createStore({ + method: undefined as undefined | ProviderAuthMethod, + }) + const providerID = layout.connect.provider()! + const provider = globalSync.data.provider.all.find((x) => x.id === providerID)! + const methods = globalSync.data.provider_auth[providerID] ?? [ + { + type: "api", + label: "API key", + }, + ] + if (methods.length === 1) { + setStore("method", methods[0]) + } + + let listRef: ListRef | undefined + const handleKey = (e: KeyboardEvent) => { + if (e.key === "Escape") return + listRef?.onKeyDown(e) + } + + return ( + <Dialog + modal + defaultOpen + onOpenChange={(open) => { + if (open) { + layout.dialog.open("connect") + } else { + layout.dialog.close("connect") + } + }} + > + <Dialog.Header class="px-4.5"> + <Dialog.Title class="flex items-center"> + <IconButton + tabIndex={-1} + icon="arrow-left" + variant="ghost" + onClick={() => { + if (store.method && methods.length > 1) { + setStore("method", undefined) + return + } + layout.dialog.open("provider") + }} + /> + </Dialog.Title> + <Dialog.CloseButton tabIndex={-1} /> + </Dialog.Header> + <Dialog.Body> + <div class="flex flex-col gap-6 px-2.5 pb-3"> + <div class="px-2.5 flex gap-4 items-center"> + <ProviderIcon id={providerID as IconName} class="size-5 shrink-0 icon-strong-base" /> + <div class="text-16-medium text-text-strong">Connect {provider.name}</div> + </div> + <Show when={store.method === undefined}> + <div class="px-2.5 text-14-regular text-text-base">Select login method for {provider.name}.</div> + <div class=""> + <Input hidden type="text" class="opacity-0 size-0" autofocus onKeyDown={handleKey} /> + <List + ref={(ref) => (listRef = ref)} + items={methods} + key={(m) => m?.label} + onSelect={(method) => { + if (!method) return + setStore("method", method) + + if (method.type === "oauth") { + // const result = await sdk.client.provider.oauth.authorize({ + // providerID: provider.id, + // method: index, + // }) + // if (result.data?.method === "code") { + // dialog.replace(() => ( + // <CodeMethod + // providerID={provider.id} + // title={method.label} + // index={index} + // authorization={result.data!} + // /> + // )) + // } + // if (result.data?.method === "auto") { + // dialog.replace(() => ( + // <AutoMethod + // providerID={provider.id} + // title={method.label} + // index={index} + // authorization={result.data!} + // /> + // )) + // } + } + if (method.type === "api") { + // return dialog.replace(() => <ApiMethod providerID={provider.id} title={method.label} />) + } + }} + > + {(i) => ( + <div class="w-full flex items-center gap-x-2.5"> + {/* TODO: add checkmark thing */} + <span>{i.label}</span> + </div> + )} + </List> + </div> + </Show> + <Show when={store.method?.type === "api"}> + {iife(() => { + const [formStore, setFormStore] = createStore({ + value: "", + error: undefined as string | undefined, + }) + + async function handleSubmit(e: SubmitEvent) { + e.preventDefault() + + const form = e.currentTarget as HTMLFormElement + const formData = new FormData(form) + const apiKey = formData.get("apiKey") as string + + if (!apiKey?.trim()) { + setFormStore("error", "API key is required") + return + } + + setFormStore("error", undefined) + globalSDK.client.auth.set({ + providerID, + auth: { + type: "api", + key: apiKey, + }, + }) + await globalSDK.client.instance.dispose() + } + + return ( + <div class="px-2.5 pb-10 flex flex-col gap-6"> + <Switch> + <Match when={provider.id === "opencode"}> + <div class="flex flex-col gap-4"> + <div class="text-14-regular text-text-base"> + OpenCode Zen gives you access to a curated set of reliable optimized models for + coding agents. + </div> + <div class="text-14-regular text-text-base"> + With a single API key you’ll get access to models such as Claude, GPT, Gemini, GLM + and more. + </div> + <div class="text-14-regular text-text-base"> + Visit{" "} + <button + tabIndex={-1} + class="text-text-strong underline" + onClick={() => platform.openLink("https://opencode.ai/zen")} + > + opencode.ai/zen + </button>{" "} + to collect your API key. + </div> + </div> + </Match> + <Match when={true}> + <div class="text-14-regular text-text-base"> + Enter your {provider.name} API key to connect your account and use {provider.name}{" "} + models in OpenCode. + </div> + </Match> + </Switch> + <form onSubmit={handleSubmit} class="flex flex-col items-start gap-4"> + <Input + autofocus + type="text" + label={`${provider.name} API key`} + placeholder="API key" + name="apiKey" + value={formStore.value} + onChange={setFormStore.bind(null, "value")} + validationState={formStore.error ? "invalid" : undefined} + error={formStore.error} + /> + <Button class="w-auto" type="submit" size="large" variant="primary"> + Submit + </Button> + </form> + </div> + ) + })} + </Show> + </div> + </Dialog.Body> + </Dialog> + ) + })} + </Show> </div> </div> ) |
