From 4a8e8f537ca688cca52674a619065f577cbd3f9b Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Sun, 14 Dec 2025 05:40:43 -0600 Subject: wip(desktop): progress --- packages/desktop/src/pages/layout.tsx | 480 +--------------------------------- 1 file changed, 8 insertions(+), 472 deletions(-) (limited to 'packages/desktop/src/pages') diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index ef51361ba..63ee5b2aa 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -1,16 +1,4 @@ -import { - createEffect, - createMemo, - createSignal, - For, - Match, - onCleanup, - onMount, - ParentProps, - Show, - Switch, - type JSX, -} from "solid-js" +import { createEffect, createMemo, createSignal, For, Match, ParentProps, Show, Switch, type JSX } from "solid-js" import { DateTime } from "luxon" import { A, useNavigate, useParams } from "@solidjs/router" import { useLayout, getAvatarColors } from "@/context/layout" @@ -20,14 +8,13 @@ import { Avatar } from "@opencode-ai/ui/avatar" import { ResizeHandle } from "@opencode-ai/ui/resize-handle" import { Button } from "@opencode-ai/ui/button" import { Icon } from "@opencode-ai/ui/icon" -import { ProviderIcon } from "@opencode-ai/ui/provider-icon" import { IconButton } from "@opencode-ai/ui/icon-button" import { Tooltip } from "@opencode-ai/ui/tooltip" import { Collapsible } from "@opencode-ai/ui/collapsible" import { DiffChanges } from "@opencode-ai/ui/diff-changes" import { getFilename } from "@opencode-ai/util/path" import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" -import { Session, Project, ProviderAuthMethod, ProviderAuthAuthorization } from "@opencode-ai/sdk/v2/client" +import { Session, Project } from "@opencode-ai/sdk/v2/client" import { usePlatform } from "@/context/platform" import { createStore, produce } from "solid-js/store" import { @@ -40,21 +27,14 @@ import { useDragDropContext, } from "@thisbeyond/solid-dnd" import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd" -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 { Link } from "@/components/link" -import { List, ListRef } from "@opencode-ai/ui/list" -import { TextField } from "@opencode-ai/ui/text-field" -import { showToast, Toast } from "@opencode-ai/ui/toast" +import { useProviders } from "@/hooks/use-providers" +import { Toast } from "@opencode-ai/ui/toast" import { useGlobalSDK } from "@/context/global-sdk" -import { Spinner } from "@opencode-ai/ui/spinner" import { useNotification } from "@/context/notification" import { Binary } from "@opencode-ai/util/binary" import { Header } from "@/components/header" +import { DialogProvider } from "@/components/dialog-provider" +import { DialogConnect } from "@/components/dialog-connect" export default function Layout(props: ParentProps) { const [store, setStore] = createStore({ @@ -576,454 +556,10 @@ export default function Layout(props: ParentProps) { {props.children} - x?.id} - items={providers.all} - filterKeys={["id", "name"]} - groupBy={(x) => (popularProviders.includes(x.id) ? "Popular" : "Other")} - sortBy={(a, b) => { - if (popularProviders.includes(a.id) && popularProviders.includes(b.id)) - return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id) - return a.name.localeCompare(b.name) - }} - sortGroupsBy={(a, b) => { - if (a.category === "Popular" && b.category !== "Popular") return -1 - if (b.category === "Popular" && a.category !== "Popular") return 1 - return 0 - }} - onSelect={(x) => { - if (!x) return - layout.dialog.connect(x.id) - }} - onOpenChange={(open) => { - if (open) { - layout.dialog.open("provider") - } else { - layout.dialog.close("provider") - } - }} - > - {(i) => ( - - - {i.name} - - Recommended - - - Connect with Claude Pro/Max or API key - - - )} - + - {iife(() => { - const providerID = createMemo(() => layout.connect.provider()!) - const provider = createMemo(() => globalSync.data.provider.all.find((x) => x.id === providerID())!) - const methods = createMemo( - () => - globalSync.data.provider_auth[providerID()] ?? [ - { - type: "api", - label: "API key", - }, - ], - ) - 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, - }) - - const methodIndex = createMemo(() => methods().findIndex((x) => x.label === store.method?.label)) - - 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, - }, - { throwOnError: true }, - ) - .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)) - }) - } - } - - let listRef: ListRef | undefined - function handleKey(e: KeyboardEvent) { - if (e.key === "Enter" && e.target instanceof HTMLInputElement) { - return - } - if (e.key === "Escape") return - listRef?.onKeyDown(e) - } - - onMount(() => { - if (methods().length === 1) { - selectMethod(0) - } - - document.addEventListener("keydown", handleKey) - onCleanup(() => { - document.removeEventListener("keydown", handleKey) - }) - }) - - async function complete() { - 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 ( - { - if (open) { - layout.dialog.open("connect") - } else { - layout.dialog.close("connect") - } - }} - > - - - { - 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 - } - layout.dialog.open("provider") - }} - /> - - - - - - - - - - - Login with Claude Pro/Max - - Connect {provider().name} - - - - - - - Select login method for {provider().name}. - - (listRef = ref)} - items={methods} - key={(m) => m?.label} - onSelect={async (method, index) => { - if (!method) return - selectMethod(index) - }} - > - {(i) => ( - - - - - {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() - - 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) - await globalSDK.client.auth.set({ - providerID: providerID(), - auth: { - type: "api", - key: apiKey, - }, - }) - await complete() - } - - 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{" "} - - opencode.ai/zen - {" "} - to collect your API key. - - - - - - Enter your {provider().name} API key to connect your account and use{" "} - {provider().name} models in OpenCode. - - - - - - - Submit - - - - ) - })} - - - - - {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 complete() - 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. - - - - - Submit - - - - ) - })} - - - {iife(() => { - const code = createMemo(() => { - const instructions = store.authorization?.instructions - if (instructions?.includes(":")) { - return instructions?.split(":")[1]?.trim() - } - return instructions - }) - - onMount(async () => { - const result = await globalSDK.client.provider.oauth.callback({ - providerID: providerID(), - method: methodIndex(), - }) - if (result.error) { - // TODO: show error - layout.dialog.close("connect") - return - } - await complete() - }) - - return ( - - - Visit this link and enter the code - below to connect your account and use {provider().name} models in OpenCode. - - - - - Waiting for authorization... - - - ) - })} - - - - - - - - - ) - })} + -- cgit v1.2.3