diff --git a/packages/desktop/src/components/link.tsx b/packages/desktop/src/components/link.tsx new file mode 100644 index 000000000..e13c31330 --- /dev/null +++ b/packages/desktop/src/components/link.tsx @@ -0,0 +1,17 @@ +import { ComponentProps, splitProps } from "solid-js" +import { usePlatform } from "@/context/platform" + +export interface LinkProps extends ComponentProps<"button"> { + href: string +} + +export function Link(props: LinkProps) { + const platform = usePlatform() + const [local, rest] = splitProps(props, ["href", "children"]) + + return ( + + ) +} diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index 4a3fa766b..3086ff2fd 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -36,6 +36,7 @@ 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 { Link } from "@/components/link" import { List, ListRef } from "@opencode-ai/ui/list" import { Input } from "@opencode-ai/ui/input" import { showToast, Toast } from "@opencode-ai/ui/toast" @@ -637,6 +638,8 @@ export default function Layout(props: ParentProps) { error: undefined as string | undefined, }) + const methodIndex = createMemo(() => methods().findIndex((x) => x.label === store.method?.label)) + async function selectMethod(index: number) { const method = methods()[index] setStore( @@ -652,10 +655,13 @@ export default function Layout(props: ParentProps) { setStore("state", "pending") const start = Date.now() await globalSDK.client.provider.oauth - .authorize({ - providerID: providerID(), - method: index, - }) + .authorize( + { + providerID: providerID(), + method: index, + }, + { throwOnError: true }, + ) .then((x) => { const elapsed = Date.now() - start const delay = 1000 - elapsed @@ -731,7 +737,16 @@ export default function Layout(props: ParentProps) {
-
Connect {provider().name}
+
+ + + Login with Claude Pro/Max + + Connect {provider().name} + +
@@ -756,7 +771,6 @@ export default function Layout(props: ParentProps) { data-slot="list-item-extra-icon" />
- {/* TODO: add checkmark thing */} {i.label}
)} @@ -833,13 +847,9 @@ export default function Layout(props: ParentProps) {
Visit{" "} - {" "} + {" "} to collect your API key.
@@ -873,8 +883,88 @@ export default function Layout(props: ParentProps) { - Code {store.authorization?.url} - Auto {store.authorization?.url} + + {iife(() => { + const [formStore, setFormStore] = createStore({ + value: "", + error: undefined as string | undefined, + }) + + onMount(() => { + if (store.authorization?.method === "code" && store.authorization?.url) { + platform.openLink(store.authorization.url) + } + }) + + async function handleSubmit(e: SubmitEvent) { + e.preventDefault() + + const form = e.currentTarget as HTMLFormElement + const formData = new FormData(form) + const code = formData.get("code") as string + + if (!code?.trim()) { + setFormStore("error", "Authorization code is required") + return + } + + setFormStore("error", undefined) + const { error } = await globalSDK.client.provider.oauth.callback({ + providerID: providerID(), + method: methodIndex(), + code, + }) + if (!error) { + 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 + } + setFormStore("error", "Invalid authorization code") + } + + return ( +
+
+ Visit this link to collect your + authorization code to connect your account and use {provider().name} models in + OpenCode. +
+
+ + +
+
+ ) + })} +
+ +
+
+ Visit this link and enter the code below + to connect your account and use {provider().name} models in OpenCode. +
+
+