From e845eedbc325b05a19679bc439a57cc0fbf23aa3 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:28:34 -0600 Subject: wip(desktop): progress --- packages/desktop/src/pages/layout.tsx | 352 ++++++++++++++++++++-------------- 1 file changed, 203 insertions(+), 149 deletions(-) (limited to 'packages/desktop/src') diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index e9f10e3a2..4a3fa766b 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -1,4 +1,4 @@ -import { createEffect, createMemo, For, Match, ParentProps, Show, Switch, type JSX } from "solid-js" +import { createEffect, createMemo, For, Match, onMount, ParentProps, Show, Switch, type JSX } from "solid-js" import { DateTime } from "luxon" import { A, useNavigate, useParams } from "@solidjs/router" import { useLayout } from "@/context/layout" @@ -17,9 +17,9 @@ 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, ProviderAuthMethod } from "@opencode-ai/sdk/v2/client" +import { Session, Project, ProviderAuthMethod, ProviderAuthAuthorization } from "@opencode-ai/sdk/v2/client" import { usePlatform } from "@/context/platform" -import { createStore } from "solid-js/store" +import { createStore, produce } from "solid-js/store" import { DragDropProvider, DragDropSensors, @@ -40,6 +40,7 @@ import { List, ListRef } from "@opencode-ai/ui/list" import { Input } from "@opencode-ai/ui/input" import { showToast, Toast } from "@opencode-ai/ui/toast" import { useGlobalSDK } from "@/context/global-sdk" +import { Spinner } from "@opencode-ai/ui/spinner" export default function Layout(props: ParentProps) { const [store, setStore] = createStore({ @@ -618,9 +619,6 @@ export default function Layout(props: ParentProps) { {iife(() => { - const [store, setStore] = createStore({ - method: undefined as undefined | ProviderAuthMethod, - }) const providerID = createMemo(() => layout.connect.provider()!) const provider = createMemo(() => globalSync.data.provider.all.find((x) => x.id === providerID())!) const methods = createMemo( @@ -632,12 +630,61 @@ export default function Layout(props: ParentProps) { }, ], ) - if (methods().length === 1) { - setStore("method", methods()[0]) + const [store, setStore] = createStore({ + method: undefined as undefined | ProviderAuthMethod, + authorization: undefined as undefined | ProviderAuthAuthorization, + state: "pending" as undefined | "pending" | "complete" | "error", + error: undefined as string | undefined, + }) + + async function selectMethod(index: number) { + const method = methods()[index] + setStore( + produce((draft) => { + draft.method = method + draft.authorization = undefined + draft.state = undefined + draft.error = undefined + }), + ) + + if (method.type === "oauth") { + setStore("state", "pending") + const start = Date.now() + await globalSDK.client.provider.oauth + .authorize({ + providerID: providerID(), + method: index, + }) + .then((x) => { + const elapsed = Date.now() - start + const delay = 1000 - elapsed + + if (delay > 0) { + setTimeout(() => { + setStore("state", "complete") + setStore("authorization", x.data!) + }, delay) + return + } + setStore("state", "complete") + setStore("authorization", x.data!) + }) + .catch((e) => { + setStore("state", "error") + setStore("error", String(e)) + }) + } } + onMount(() => { + if (methods().length === 1) { + selectMethod(0) + } + }) + let listRef: ListRef | undefined - const handleKey = (e: KeyboardEvent) => { + function handleKey(e: KeyboardEvent) { if (e.key === "Escape") return listRef?.onKeyDown(e) } @@ -661,7 +708,16 @@ export default function Layout(props: ParentProps) { icon="arrow-left" variant="ghost" onClick={() => { - if (store.method && methods.length > 1) { + if (methods().length === 1) { + layout.dialog.open("provider") + return + } + if (store.authorization) { + setStore("authorization", undefined) + setStore("method", undefined) + return + } + if (store.method) { setStore("method", undefined) return } @@ -677,154 +733,152 @@ export default function Layout(props: ParentProps) {
Connect {provider().name}
- - -
- Select login method for {provider().name}. -
-
- - (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(() => ( - // - // )) - // } - // if (result.data?.method === "auto") { - // dialog.replace(() => ( - // - // )) - // } - } - if (method.type === "api") { - // return dialog.replace(() => ) - } - }} - > - {(i) => ( -
- {/* TODO: add checkmark thing */} - {i.label} -
- )} -
-
-
- - {iife(() => { - const [formStore, setFormStore] = createStore({ - value: "", - error: undefined as string | undefined, - }) +
+ + +
Select login method for {provider().name}.
+
+ + (listRef = ref)} + items={methods} + key={(m) => m?.label} + onSelect={async (method, index) => { + if (!method) return + selectMethod(index) + }} + > + {(i) => ( +
+
+ + {/* TODO: add checkmark thing */} + {i.label} +
+ )} + +
+ + +
+
+ + Authorization in progress... +
+
+
+ +
+
+ + Authorization failed: {store.error} +
+
+
+ + {iife(() => { + const [formStore, setFormStore] = createStore({ + value: "", + error: undefined as string | undefined, + }) - async function handleSubmit(e: SubmitEvent) { - e.preventDefault() + 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 + 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 - } + if (!apiKey?.trim()) { + setFormStore("error", "API key is required") + return + } - setFormStore("error", undefined) - await globalSDK.client.auth.set({ - providerID: providerID(), - auth: { - type: "api", - key: apiKey, - }, - }) - await globalSDK.client.global.dispose() - setTimeout(() => { - showToast({ - variant: "success", - icon: "circle-check", - title: `${provider().name} connected`, - description: `${provider().name} models are now available to use.`, + setFormStore("error", undefined) + await globalSDK.client.auth.set({ + providerID: providerID(), + auth: { + type: "api", + key: apiKey, + }, }) - layout.connect.complete() - }, 500) - } + await globalSDK.client.global.dispose() + setTimeout(() => { + showToast({ + variant: "success", + icon: "circle-check", + title: `${provider().name} connected`, + description: `${provider().name} models are now available to use.`, + }) + layout.connect.complete() + }, 500) + } - return ( -
- - -
-
- OpenCode Zen gives you access to a curated set of reliable optimized models for - coding agents. + return ( +
+ + +
+
+ OpenCode Zen gives you access to a curated set of reliable optimized models for + coding agents. +
+
+ With a single API key you’ll get access to models such as Claude, GPT, Gemini, + GLM and more. +
+
+ Visit{" "} + {" "} + to collect your API key. +
+
+
- With a single API key you’ll get access to models such as Claude, GPT, Gemini, GLM - and more. + Enter your {provider().name} API key to connect your account and use{" "} + {provider().name} models in OpenCode.
-
- Visit{" "} - {" "} - to collect your API key. -
-
- - -
- Enter your {provider().name} API key to connect your account and use{" "} - {provider().name} models in OpenCode. -
-
- -
- - -
-
- ) - })} - - + + +
+ + +
+
+ ) + })} +
+ + + Code {store.authorization?.url} + Auto {store.authorization?.url} + + +
+
-- cgit v1.2.3