Skip to content
This repository was archived by the owner on Apr 1, 2026. It is now read-only.

Commit 2613f44

Browse files
committed
wip(desktop): progress
1 parent 62ffeb3 commit 2613f44

8 files changed

Lines changed: 151 additions & 151 deletions

File tree

packages/desktop/src/components/dialog-connect.tsx

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Component, createMemo, Match, onCleanup, onMount, Switch } from "solid-js"
22
import { createStore, produce } from "solid-js/store"
3-
import { useLayout } from "@/context/layout"
3+
import { useDialog } from "@/context/dialog"
44
import { useGlobalSync } from "@/context/global-sync"
55
import { useGlobalSDK } from "@/context/global-sdk"
66
import { usePlatform } from "@/context/platform"
@@ -17,18 +17,19 @@ import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
1717
import { IconName } from "@opencode-ai/ui/icons/provider"
1818
import { iife } from "@opencode-ai/util/iife"
1919
import { Link } from "@/components/link"
20+
import { DialogSelectProvider } from "./dialog-select-provider"
21+
import { DialogModel } from "./dialog-model"
2022

21-
export const DialogConnect: Component = () => {
22-
const layout = useLayout()
23+
export const DialogConnect: Component<{ provider: string }> = (props) => {
24+
const dialog = useDialog()
2325
const globalSync = useGlobalSync()
2426
const globalSDK = useGlobalSDK()
2527
const platform = usePlatform()
2628

27-
const providerID = createMemo(() => layout.connect.provider()!)
28-
const provider = createMemo(() => globalSync.data.provider.all.find((x) => x.id === providerID())!)
29+
const provider = createMemo(() => globalSync.data.provider.all.find((x) => x.id === props.provider)!)
2930
const methods = createMemo(
3031
() =>
31-
globalSync.data.provider_auth[providerID()] ?? [
32+
globalSync.data.provider_auth[props.provider] ?? [
3233
{
3334
type: "api",
3435
label: "API key",
@@ -61,7 +62,7 @@ export const DialogConnect: Component = () => {
6162
await globalSDK.client.provider.oauth
6263
.authorize(
6364
{
64-
providerID: providerID(),
65+
providerID: props.provider,
6566
method: index,
6667
},
6768
{ throwOnError: true },
@@ -116,55 +117,50 @@ export const DialogConnect: Component = () => {
116117
title: `${provider().name} connected`,
117118
description: `${provider().name} models are now available to use.`,
118119
})
119-
layout.connect.complete()
120+
dialog.replace(() => <DialogModel connectedProvider={props.provider} />)
120121
}, 500)
121122
}
122123

124+
function goBack() {
125+
if (methods().length === 1) {
126+
dialog.replace(() => <DialogSelectProvider />)
127+
return
128+
}
129+
if (store.authorization) {
130+
setStore("authorization", undefined)
131+
setStore("method", undefined)
132+
return
133+
}
134+
if (store.method) {
135+
setStore("method", undefined)
136+
return
137+
}
138+
dialog.replace(() => <DialogSelectProvider />)
139+
}
140+
123141
return (
124142
<Dialog
125143
modal
126144
defaultOpen
127145
onOpenChange={(open) => {
128-
if (open) {
129-
layout.dialog.open("connect")
130-
} else {
131-
layout.dialog.close("connect")
146+
if (!open) {
147+
dialog.clear()
132148
}
133149
}}
134150
>
135151
<Dialog.Header class="px-4.5">
136152
<Dialog.Title class="flex items-center">
137-
<IconButton
138-
tabIndex={-1}
139-
icon="arrow-left"
140-
variant="ghost"
141-
onClick={() => {
142-
if (methods().length === 1) {
143-
layout.dialog.open("provider")
144-
return
145-
}
146-
if (store.authorization) {
147-
setStore("authorization", undefined)
148-
setStore("method", undefined)
149-
return
150-
}
151-
if (store.method) {
152-
setStore("method", undefined)
153-
return
154-
}
155-
layout.dialog.open("provider")
156-
}}
157-
/>
153+
<IconButton tabIndex={-1} icon="arrow-left" variant="ghost" onClick={goBack} />
158154
</Dialog.Title>
159155
<Dialog.CloseButton tabIndex={-1} />
160156
</Dialog.Header>
161157
<Dialog.Body>
162158
<div class="flex flex-col gap-6 px-2.5 pb-3">
163159
<div class="px-2.5 flex gap-4 items-center">
164-
<ProviderIcon id={providerID() as IconName} class="size-5 shrink-0 icon-strong-base" />
160+
<ProviderIcon id={props.provider as IconName} class="size-5 shrink-0 icon-strong-base" />
165161
<div class="text-16-medium text-text-strong">
166162
<Switch>
167-
<Match when={providerID() === "anthropic" && store.method?.label?.toLowerCase().includes("max")}>
163+
<Match when={props.provider === "anthropic" && store.method?.label?.toLowerCase().includes("max")}>
168164
Login with Claude Pro/Max
169165
</Match>
170166
<Match when={true}>Connect {provider().name}</Match>
@@ -233,7 +229,7 @@ export const DialogConnect: Component = () => {
233229

234230
setFormStore("error", undefined)
235231
await globalSDK.client.auth.set({
236-
providerID: providerID(),
232+
providerID: props.provider,
237233
auth: {
238234
type: "api",
239235
key: apiKey,
@@ -320,7 +316,7 @@ export const DialogConnect: Component = () => {
320316

321317
setFormStore("error", undefined)
322318
const { error } = await globalSDK.client.provider.oauth.callback({
323-
providerID: providerID(),
319+
providerID: props.provider,
324320
method: methodIndex(),
325321
code,
326322
})
@@ -369,12 +365,12 @@ export const DialogConnect: Component = () => {
369365

370366
onMount(async () => {
371367
const result = await globalSDK.client.provider.oauth.callback({
372-
providerID: providerID(),
368+
providerID: props.provider,
373369
method: methodIndex(),
374370
})
375371
if (result.error) {
376372
// TODO: show error
377-
layout.dialog.close("connect")
373+
dialog.clear()
378374
return
379375
}
380376
await complete()

packages/desktop/src/components/dialog-model.tsx

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Component, createMemo, Match, onCleanup, onMount, Show, Switch } from "solid-js"
22
import { useLocal } from "@/context/local"
3-
import { useLayout } from "@/context/layout"
3+
import { useDialog } from "@/context/dialog"
44
import { popularProviders, useProviders } from "@/hooks/use-providers"
55
import { SelectDialog } from "@opencode-ai/ui/select-dialog"
66
import { Button } from "@opencode-ai/ui/button"
@@ -10,10 +10,12 @@ import { List, ListRef } from "@opencode-ai/ui/list"
1010
import { iife } from "@opencode-ai/util/iife"
1111
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
1212
import { IconName } from "@opencode-ai/ui/icons/provider"
13+
import { DialogSelectProvider } from "./dialog-select-provider"
14+
import { DialogConnect } from "./dialog-connect"
1315

14-
export const DialogModel: Component = () => {
16+
export const DialogModel: Component<{ connectedProvider?: string }> = (props) => {
1517
const local = useLocal()
16-
const layout = useLayout()
18+
const dialog = useDialog()
1719
const providers = useProviders()
1820

1921
return (
@@ -24,18 +26,14 @@ export const DialogModel: Component = () => {
2426
local.model
2527
.list()
2628
.filter((m) => m.visible)
27-
.filter((m) =>
28-
layout.connect.state() === "complete" ? m.provider.id === layout.connect.provider() : true,
29-
),
29+
.filter((m) => (props.connectedProvider ? m.provider.id === props.connectedProvider : true)),
3030
)
3131
return (
3232
<SelectDialog
3333
defaultOpen
3434
onOpenChange={(open) => {
35-
if (open) {
36-
layout.dialog.open("model")
37-
} else {
38-
layout.dialog.close("model")
35+
if (!open) {
36+
dialog.clear()
3937
}
4038
}}
4139
title="Select model"
@@ -66,7 +64,7 @@ export const DialogModel: Component = () => {
6664
class="h-7 -my-1 text-14-medium"
6765
icon="plus-small"
6866
tabIndex={-1}
69-
onClick={() => layout.dialog.open("provider")}
67+
onClick={() => dialog.replace(() => <DialogSelectProvider />)}
7068
>
7169
Connect provider
7270
</Button>
@@ -107,10 +105,8 @@ export const DialogModel: Component = () => {
107105
modal
108106
defaultOpen
109107
onOpenChange={(open) => {
110-
if (open) {
111-
layout.dialog.open("model")
112-
} else {
113-
layout.dialog.close("model")
108+
if (!open) {
109+
dialog.clear()
114110
}
115111
}}
116112
>
@@ -130,7 +126,7 @@ export const DialogModel: Component = () => {
130126
local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
131127
recent: true,
132128
})
133-
layout.dialog.close("model")
129+
dialog.clear()
134130
}}
135131
>
136132
{(i) => (
@@ -163,7 +159,7 @@ export const DialogModel: Component = () => {
163159
}}
164160
onSelect={(x) => {
165161
if (!x) return
166-
layout.dialog.connect(x.id)
162+
dialog.replace(() => <DialogConnect provider={x.id} />)
167163
}}
168164
>
169165
{(i) => (
@@ -193,7 +189,7 @@ export const DialogModel: Component = () => {
193189
class="w-full justify-start px-[11px] py-3.5 gap-4.5 text-14-medium"
194190
icon="dot-grid"
195191
onClick={() => {
196-
layout.dialog.open("provider")
192+
dialog.replace(() => <DialogSelectProvider />)
197193
}}
198194
>
199195
View all providers

packages/desktop/src/components/dialog-provider.tsx renamed to packages/desktop/src/components/dialog-select-provider.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { Component, Show } from "solid-js"
2-
import { useLayout } from "@/context/layout"
2+
import { useDialog } from "@/context/dialog"
33
import { popularProviders, useProviders } from "@/hooks/use-providers"
44
import { SelectDialog } from "@opencode-ai/ui/select-dialog"
55
import { Tag } from "@opencode-ai/ui/tag"
66
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
77
import { IconName } from "@opencode-ai/ui/icons/provider"
8+
import { DialogConnect } from "./dialog-connect"
89

9-
export const DialogProvider: Component = () => {
10-
const layout = useLayout()
10+
export const DialogSelectProvider: Component = () => {
11+
const dialog = useDialog()
1112
const providers = useProviders()
1213

1314
return (
@@ -32,13 +33,11 @@ export const DialogProvider: Component = () => {
3233
}}
3334
onSelect={(x) => {
3435
if (!x) return
35-
layout.dialog.connect(x.id)
36+
dialog.replace(() => <DialogConnect provider={x.id} />)
3637
}}
3738
onOpenChange={(open) => {
38-
if (open) {
39-
layout.dialog.open("provider")
40-
} else {
41-
layout.dialog.close("provider")
39+
if (!open) {
40+
dialog.clear()
4241
}
4342
}}
4443
>

packages/desktop/src/components/prompt-input.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
1515
import { IconButton } from "@opencode-ai/ui/icon-button"
1616
import { Select } from "@opencode-ai/ui/select"
1717
import { getDirectory, getFilename } from "@opencode-ai/util/path"
18-
import { useLayout } from "@/context/layout"
18+
import { useDialog } from "@/context/dialog"
1919
import { DialogModel } from "@/components/dialog-model"
2020

2121
interface PromptInputProps {
@@ -57,7 +57,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
5757
const sync = useSync()
5858
const local = useLocal()
5959
const session = useSession()
60-
const layout = useLayout()
60+
const dialog = useDialog()
6161
let editorRef!: HTMLDivElement
6262

6363
const [store, setStore] = createStore<{
@@ -610,14 +610,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
610610
class="capitalize"
611611
variant="ghost"
612612
/>
613-
<Button as="div" variant="ghost" onClick={() => layout.dialog.open("model")}>
613+
<Button as="div" variant="ghost" onClick={() => dialog.push(() => <DialogModel />)}>
614614
{local.model.current()?.name ?? "Select model"}
615615
<span class="ml-0.5 text-text-weak text-12-regular">{local.model.current()?.provider.name}</span>
616616
<Icon name="chevron-down" size="small" />
617617
</Button>
618-
<Show when={layout.dialog.opened() === "model"}>
619-
<DialogModel />
620-
</Show>
621618
</div>
622619
<Tooltip
623620
placement="top"
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { createEffect, For, onCleanup, Show, type JSX } from "solid-js"
2+
import { createStore } from "solid-js/store"
3+
import { createSimpleContext } from "@opencode-ai/ui/context"
4+
5+
type DialogElement = JSX.Element | (() => JSX.Element)
6+
7+
export const { use: useDialog, provider: DialogProvider } = createSimpleContext({
8+
name: "Dialog",
9+
init: () => {
10+
const [store, setStore] = createStore({
11+
stack: [] as {
12+
element: DialogElement
13+
onClose?: () => void
14+
}[],
15+
})
16+
17+
function handleKeyDown(e: KeyboardEvent) {
18+
if (e.key === "Escape" && store.stack.length > 0) {
19+
const current = store.stack.at(-1)!
20+
current.onClose?.()
21+
setStore("stack", store.stack.slice(0, -1))
22+
e.preventDefault()
23+
e.stopPropagation()
24+
}
25+
}
26+
27+
createEffect(() => {
28+
document.addEventListener("keydown", handleKeyDown, true)
29+
onCleanup(() => {
30+
document.removeEventListener("keydown", handleKeyDown, true)
31+
})
32+
})
33+
34+
return {
35+
get stack() {
36+
return store.stack
37+
},
38+
push(element: DialogElement, onClose?: () => void) {
39+
setStore("stack", (s) => [...s, { element, onClose }])
40+
},
41+
pop() {
42+
const current = store.stack.at(-1)
43+
current?.onClose?.()
44+
setStore("stack", store.stack.slice(0, -1))
45+
},
46+
replace(element: DialogElement, onClose?: () => void) {
47+
for (const item of store.stack) {
48+
item.onClose?.()
49+
}
50+
setStore("stack", [{ element, onClose }])
51+
},
52+
clear() {
53+
for (const item of store.stack) {
54+
item.onClose?.()
55+
}
56+
setStore("stack", [])
57+
},
58+
}
59+
},
60+
})
61+
62+
export function DialogRoot(props: { children?: JSX.Element }) {
63+
const dialog = useDialog()
64+
return (
65+
<>
66+
{props.children}
67+
<Show when={dialog.stack.length > 0}>
68+
<div data-component="dialog-stack">
69+
<For each={dialog.stack}>
70+
{(item, index) => (
71+
<Show when={index() === dialog.stack.length - 1}>
72+
{typeof item.element === "function" ? item.element() : item.element}
73+
</Show>
74+
)}
75+
</For>
76+
</div>
77+
</Show>
78+
</>
79+
)
80+
}

0 commit comments

Comments
 (0)