mirror of
https://gitee.com/zhangsken/opencode.git
synced 2026-10-16 10:30:01 +08:00
fix(app): keep the Console sign-in from suspending the page behind it
Load the integration through a guarded store instead of createResource: the dialog runs under the opener's Suspense boundary, so a pending resource read blanked the whole route while the integration loaded. Also show the API key switch directly instead of behind an Advanced toggle.
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
import type { FormAnswer, IntegrationMethod, IntegrationOauthConnectOutput } from "@opencode/client/promise"
|
||||
import type {
|
||||
FormAnswer,
|
||||
IntegrationInfo,
|
||||
IntegrationMethod,
|
||||
IntegrationOauthConnectOutput,
|
||||
} from "@opencode/client/promise"
|
||||
import { useLanguage } from "@/runtime/i18n/language"
|
||||
import { usePlatform } from "@/runtime/platform/platform"
|
||||
import { useServerSDK } from "@/runtime/server/client"
|
||||
import { useData } from "@/runtime/server/current"
|
||||
import { createEffect, createMemo, createResource, onCleanup } from "solid-js"
|
||||
import { createEffect, createMemo, on, onCleanup } from "solid-js"
|
||||
import { createStore, produce } from "solid-js/store"
|
||||
|
||||
export type ProviderConnectMethod = Extract<IntegrationMethod, { type: "key" | "oauth" }>
|
||||
@@ -42,12 +47,28 @@ export function createProviderConnectionController(options: {
|
||||
const directory = options.directory()
|
||||
return directory ? { directory } : undefined
|
||||
}
|
||||
const [integration] = createResource(
|
||||
() => ({ provider: options.provider(), directory: options.directory() }),
|
||||
(input) =>
|
||||
serverSDK.api.integration
|
||||
.get({ integrationID: input.provider, location: location() })
|
||||
.then((result) => result.data),
|
||||
// Not createResource: the dialog is owned by whichever page opened it, so reading a pending
|
||||
// resource here would suspend that page's <Suspense> and blank the screen behind the dialog.
|
||||
const [integration, setIntegration] = createStore({
|
||||
loading: true,
|
||||
latest: undefined as IntegrationInfo | undefined,
|
||||
})
|
||||
createEffect(
|
||||
on(
|
||||
() => ({ provider: options.provider(), directory: options.directory() }),
|
||||
(input) => {
|
||||
setIntegration({ loading: true, latest: undefined })
|
||||
serverSDK.api.integration
|
||||
.get({ integrationID: input.provider, location: location() })
|
||||
.then((result) => result.data)
|
||||
.catch(() => undefined)
|
||||
.then((latest) => {
|
||||
if (polling.disposed) return
|
||||
if (input.provider !== options.provider() || input.directory !== options.directory()) return
|
||||
setIntegration({ loading: false, latest })
|
||||
})
|
||||
},
|
||||
),
|
||||
)
|
||||
const methods = createMemo<ProviderConnectMethod[]>(() => {
|
||||
const values = integration.latest?.methods.filter(
|
||||
|
||||
@@ -710,30 +710,18 @@ function ProviderConnection(props: {
|
||||
)
|
||||
}
|
||||
|
||||
// Deliberately quiet: most people should never need a key, so it only appears after
|
||||
// opening "Advanced".
|
||||
function ConsoleAdvanced() {
|
||||
const [store, setStore] = createStore({ open: false })
|
||||
// Deliberately quiet: most people should never need a key, so this stays small and at the bottom.
|
||||
function ConsoleApiKeySwitch() {
|
||||
return (
|
||||
<div class="mt-auto flex flex-col items-end gap-1 px-3 pt-4 text-[11px] leading-4 text-v2-text-text-muted">
|
||||
<div class="mt-auto flex justify-end px-3 pt-4 text-[11px] leading-4 text-v2-text-text-muted">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-xs px-1 hover:text-v2-text-text-base focus-visible:outline-2 focus-visible:outline-v2-border-border-focus"
|
||||
aria-expanded={store.open}
|
||||
onClick={() => setStore("open", (open) => !open)}
|
||||
data-action="provider-connect-api-key"
|
||||
class="rounded-xs px-1 underline decoration-v2-border-border-base underline-offset-2 hover:text-v2-text-text-base focus-visible:outline-2 focus-visible:outline-v2-border-border-focus"
|
||||
onClick={() => void controller.auth.select(keyIndex())}
|
||||
>
|
||||
{language.t("provider.connect.console.advanced")}
|
||||
{language.t("provider.connect.console.apiKey.switch")}
|
||||
</button>
|
||||
<Show when={store.open}>
|
||||
<button
|
||||
type="button"
|
||||
data-action="provider-connect-api-key"
|
||||
class="rounded-xs px-1 underline decoration-v2-border-border-base underline-offset-2 hover:text-v2-text-text-base focus-visible:outline-2 focus-visible:outline-v2-border-border-focus"
|
||||
onClick={() => void controller.auth.select(keyIndex())}
|
||||
>
|
||||
{language.t("provider.connect.console.apiKey.switch")}
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -788,7 +776,7 @@ function ProviderConnection(props: {
|
||||
</Match>
|
||||
</Switch>
|
||||
<Show when={isConsole() && controller.currentMethod()?.type !== "key" && keyIndex() !== -1}>
|
||||
<ConsoleAdvanced />
|
||||
<ConsoleApiKeySwitch />
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -237,7 +237,6 @@ export const dict = {
|
||||
"provider.connect.console.description":
|
||||
"Sign in with your OpenCode Console account. Your browser opens to the Console, where you pick a workspace and select Authorize.",
|
||||
"provider.connect.console.opening": "Opening your browser…",
|
||||
"provider.connect.console.advanced": "Advanced",
|
||||
"provider.connect.console.apiKey.switch": "Connect with an API key or service account instead",
|
||||
"provider.connect.console.apiKey.description":
|
||||
"Paste an API key for a service account. You create service accounts in the OpenCode Console under Keys.",
|
||||
|
||||
Reference in New Issue
Block a user