mirror of
https://github.com/sst/opencode.git
synced 2025-12-23 10:11:41 +00:00
tui: return to model selection after OAuth or API key setup
Improves the provider auth flow by restoring the model selection screen after successful provider connection, allowing users to immediately select a model without seeing intermediate dialogs. Also removes redundant dialog close calls in the form component since navigation is now handled at the dialog provider level.
This commit is contained in:
parent
ffe42bdc8a
commit
8eedd6aa07
3 changed files with 51 additions and 17 deletions
|
|
@ -2,11 +2,9 @@ import { createMemo, createSignal } from "solid-js"
|
|||
import { useLocal } from "@tui/context/local"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { map, pipe, flatMap, entries, filter, isDeepEqual, sortBy } from "remeda"
|
||||
import { DialogSelect, type DialogSelectOption, type DialogSelectRef } from "@tui/ui/dialog-select"
|
||||
import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select"
|
||||
import { useDialog } from "@tui/ui/dialog"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { DialogPrompt } from "../ui/dialog-prompt"
|
||||
import { useSDK } from "../context/sdk"
|
||||
import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
|
||||
|
||||
function Free() {
|
||||
|
|
@ -27,7 +25,6 @@ export function DialogModel() {
|
|||
const local = useLocal()
|
||||
const sync = useSync()
|
||||
const dialog = useDialog()
|
||||
const sdk = useSDK()
|
||||
const [ref, setRef] = createSignal<DialogSelectRef<unknown>>()
|
||||
|
||||
const connected = createMemo(() =>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { createMemo, onMount } from "solid-js"
|
||||
import { createMemo, createSignal, onMount, Show } from "solid-js"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { map, pipe, sortBy } from "remeda"
|
||||
import { DialogSelect } from "@tui/ui/dialog-select"
|
||||
|
|
@ -7,7 +7,8 @@ import { useSDK } from "../context/sdk"
|
|||
import { DialogPrompt } from "../ui/dialog-prompt"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import type { ProviderAuthAuthorization, ProviderAuthMethod } from "@opencode-ai/sdk"
|
||||
import type { ProviderAuthAuthorization } from "@opencode-ai/sdk"
|
||||
import { DialogModel } from "./dialog-model"
|
||||
|
||||
const PROVIDER_PRIORITY: Record<string, number> = {
|
||||
opencode: 0,
|
||||
|
|
@ -23,19 +24,16 @@ export function createDialogProviderOptions() {
|
|||
const sync = useSync()
|
||||
const dialog = useDialog()
|
||||
const sdk = useSDK()
|
||||
const { theme } = useTheme()
|
||||
const options = createMemo(() => {
|
||||
return pipe(
|
||||
sync.data.provider_next.all,
|
||||
map((provider) => ({
|
||||
title: provider.name,
|
||||
value: provider.id,
|
||||
footer: sync.data.provider_next.connected.includes(provider.id)
|
||||
? "Connected"
|
||||
: {
|
||||
opencode: "Recommended",
|
||||
anthropic: "Claude Max or API key",
|
||||
}[provider.id],
|
||||
footer: {
|
||||
opencode: "Recommended",
|
||||
anthropic: "Claude Max or API key",
|
||||
}[provider.id],
|
||||
async onSelect() {
|
||||
const methods = sync.data.provider_auth[provider.id] ?? [
|
||||
{
|
||||
|
|
@ -78,13 +76,15 @@ export function createDialogProviderOptions() {
|
|||
<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} />)
|
||||
}
|
||||
},
|
||||
})),
|
||||
sortBy((x) => PROVIDER_PRIORITY[x.value] ?? 99),
|
||||
|
|
@ -125,7 +125,7 @@ function AutoMethod(props: AutoMethodProps) {
|
|||
}
|
||||
await sdk.client.instance.dispose()
|
||||
await sync.bootstrap()
|
||||
dialog.clear()
|
||||
dialog.replace(() => <DialogModel />)
|
||||
})
|
||||
|
||||
return (
|
||||
|
|
@ -153,6 +153,8 @@ function CodeMethod(props: CodeMethodProps) {
|
|||
const { theme } = useTheme()
|
||||
const sdk = useSDK()
|
||||
const sync = useSync()
|
||||
const dialog = useDialog()
|
||||
const [error, setError] = createSignal(false)
|
||||
|
||||
return (
|
||||
<DialogPrompt
|
||||
|
|
@ -171,15 +173,52 @@ function CodeMethod(props: CodeMethodProps) {
|
|||
if (!error) {
|
||||
await sdk.client.instance.dispose()
|
||||
await sync.bootstrap()
|
||||
dialog.replace(() => <DialogModel />)
|
||||
return
|
||||
}
|
||||
setError(true)
|
||||
}}
|
||||
description={() => (
|
||||
<box gap={1}>
|
||||
<text fg={theme.textMuted}>{props.authorization.instructions}</text>
|
||||
<text fg={theme.primary}>{props.authorization.url}</text>
|
||||
<Show when={error()}>
|
||||
<text fg={theme.error}>Invalid code</text>
|
||||
</Show>
|
||||
</box>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
interface ApiMethodProps {
|
||||
providerID: string
|
||||
title: string
|
||||
}
|
||||
function ApiMethod(props: ApiMethodProps) {
|
||||
const dialog = useDialog()
|
||||
const sdk = useSDK()
|
||||
const sync = useSync()
|
||||
|
||||
return (
|
||||
<DialogPrompt
|
||||
title={props.title}
|
||||
placeholder="API key"
|
||||
onConfirm={async (value) => {
|
||||
if (!value) return
|
||||
sdk.client.auth.set({
|
||||
path: {
|
||||
id: props.providerID,
|
||||
},
|
||||
body: {
|
||||
type: "api",
|
||||
key: value,
|
||||
},
|
||||
})
|
||||
await sdk.client.instance.dispose()
|
||||
await sync.bootstrap()
|
||||
dialog.replace(() => <DialogModel />)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ export function DialogPrompt(props: DialogPromptProps) {
|
|||
useKeyboard((evt) => {
|
||||
if (evt.name === "return") {
|
||||
props.onConfirm?.(textarea.plainText)
|
||||
dialog.clear()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -44,7 +43,6 @@ export function DialogPrompt(props: DialogPromptProps) {
|
|||
<textarea
|
||||
onSubmit={() => {
|
||||
props.onConfirm?.(textarea.plainText)
|
||||
dialog.clear()
|
||||
}}
|
||||
height={3}
|
||||
keyBindings={[{ name: "return", action: "submit" }]}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue