mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-08 12:34:40 +00:00
fix(tui): scope connect dialog to location
This commit is contained in:
parent
5d73a5789f
commit
815d4ab9b4
2 changed files with 115 additions and 51 deletions
|
|
@ -9,6 +9,7 @@ import type {
|
|||
FormField,
|
||||
FormFields,
|
||||
FormValue,
|
||||
LocationRef,
|
||||
} from "@opencode-ai/client"
|
||||
import open from "open"
|
||||
import { createEffect, createMemo, createSignal, onCleanup, onMount, Show } from "solid-js"
|
||||
|
|
@ -16,6 +17,7 @@ import { useClipboard } from "../context/clipboard"
|
|||
import { useData } from "../context/data"
|
||||
import { useClient } from "../context/client"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { useLocation } from "../context/location"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
import { DialogPrompt } from "../ui/dialog-prompt"
|
||||
|
|
@ -74,10 +76,12 @@ export function DialogIntegration(
|
|||
props: { onConnected?: OnIntegrationConnected; integrationID?: string; autoConnect?: boolean } = {},
|
||||
) {
|
||||
const data = useData()
|
||||
const currentLocation = useLocation()
|
||||
const dialog = useDialog()
|
||||
const theme = useTheme("elevated")
|
||||
const location = currentLocation.ref ?? data.location.default()
|
||||
const integrations = createMemo(() =>
|
||||
integrationOptions(data.location.integration.list() ?? []).filter(
|
||||
integrationOptions(data.location.integration.list(location) ?? []).filter(
|
||||
(integration) => props.integrationID === undefined || integration.id === props.integrationID,
|
||||
),
|
||||
)
|
||||
|
|
@ -88,14 +92,14 @@ export function DialogIntegration(
|
|||
if (!integration) return
|
||||
const methods = connectMethods(integration)
|
||||
if (credentialConnections(integration).length) {
|
||||
manageConnections(integration, methods, dialog, props.onConnected)
|
||||
manageConnections(integration, methods, location, dialog, props.onConnected)
|
||||
return
|
||||
}
|
||||
selectMethod(integration, methods, dialog, props.onConnected)
|
||||
selectMethod(integration, methods, location, dialog, props.onConnected)
|
||||
})
|
||||
|
||||
const options = createMemo(() => {
|
||||
const providers = data.location.websearch.list() ?? []
|
||||
const providers = data.location.websearch.list(location) ?? []
|
||||
const providersByID = new Map(providers.map((provider) => [provider.id, provider]))
|
||||
return integrations().map((integration) => {
|
||||
const methods = connectMethods(integration)
|
||||
|
|
@ -117,8 +121,8 @@ export function DialogIntegration(
|
|||
? () => <text fg={theme.text.feedback.success.default}>✓</text>
|
||||
: undefined,
|
||||
onSelect: () => {
|
||||
if (credentials.length) return manageConnections(integration, methods, dialog, props.onConnected)
|
||||
return selectMethod(integration, methods, dialog, props.onConnected)
|
||||
if (credentials.length) return manageConnections(integration, methods, location, dialog, props.onConnected)
|
||||
return selectMethod(integration, methods, location, dialog, props.onConnected)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
|
@ -145,6 +149,7 @@ export function DialogIntegration(
|
|||
function manageConnections(
|
||||
integration: IntegrationInfo,
|
||||
methods: ConnectMethod[],
|
||||
location: LocationRef,
|
||||
dialog: ReturnType<typeof useDialog>,
|
||||
onConnected?: OnIntegrationConnected,
|
||||
) {
|
||||
|
|
@ -156,7 +161,9 @@ function manageConnections(
|
|||
const shortcuts = Keymap.useShortcuts()
|
||||
const [deleting, setDeleting] = createSignal<string>()
|
||||
const [selected, setSelected] = createSignal(methods.length ? "add" : credentialConnections(integration)[0]?.id)
|
||||
const current = createMemo(() => data.location.integration.list()?.find((item) => item.id === integration.id))
|
||||
const current = createMemo(() =>
|
||||
data.location.integration.list(location)?.find((item) => item.id === integration.id),
|
||||
)
|
||||
|
||||
return (
|
||||
<DialogSelect
|
||||
|
|
@ -174,7 +181,7 @@ function manageConnections(
|
|||
{
|
||||
title: "Add account",
|
||||
value: "add",
|
||||
onSelect: () => selectMethod(current() ?? integration, methods, dialog, onConnected),
|
||||
onSelect: () => selectMethod(current() ?? integration, methods, location, dialog, onConnected),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
|
@ -193,7 +200,7 @@ function manageConnections(
|
|||
onSelect: () => {
|
||||
if (credentialConnections(current() ?? integration)[0]?.id === connection.id) return
|
||||
void client.api.credential
|
||||
.activate({ credentialID: connection.id, location: location(data) })
|
||||
.activate({ credentialID: connection.id, location: locationQuery(location) })
|
||||
.catch(toast.error)
|
||||
},
|
||||
}
|
||||
|
|
@ -217,8 +224,8 @@ function manageConnections(
|
|||
const label = value.trim()
|
||||
if (!label) return
|
||||
void client.api.credential
|
||||
.update({ credentialID: option.value, label, location: location(data) })
|
||||
.then(() => manageConnections(integration, methods, dialog, onConnected))
|
||||
.update({ credentialID: option.value, label, location: locationQuery(location) })
|
||||
.then(() => manageConnections(integration, methods, location, dialog, onConnected))
|
||||
.catch(toast.error)
|
||||
}}
|
||||
/>
|
||||
|
|
@ -234,7 +241,7 @@ function manageConnections(
|
|||
if (deleting() !== option.value) return setDeleting(option.value)
|
||||
const final = credentialConnections(current() ?? integration).length === 1
|
||||
void client.api.credential
|
||||
.remove({ credentialID: option.value, location: location(data) })
|
||||
.remove({ credentialID: option.value, location: locationQuery(location) })
|
||||
.then(() => {
|
||||
setDeleting(undefined)
|
||||
if (!final) return
|
||||
|
|
@ -256,17 +263,18 @@ function manageConnections(
|
|||
function selectMethod(
|
||||
integration: IntegrationInfo,
|
||||
methods: ConnectMethod[],
|
||||
location: LocationRef,
|
||||
dialog: ReturnType<typeof useDialog>,
|
||||
onConnected?: OnIntegrationConnected,
|
||||
) {
|
||||
if (methods.length === 1) return openMethod(integration, methods[0], dialog, onConnected)
|
||||
if (methods.length === 1) return openMethod(integration, methods[0], location, dialog, onConnected)
|
||||
dialog.replace(() => (
|
||||
<DialogSelect
|
||||
title={`Connect ${integration.name}`}
|
||||
options={methods.map((method) => ({
|
||||
title: method.type === "key" ? (method.label ?? "API key") : method.label,
|
||||
value: method.type === "key" ? "key" : method.id,
|
||||
onSelect: () => openMethod(integration, method, dialog, onConnected),
|
||||
onSelect: () => openMethod(integration, method, location, dialog, onConnected),
|
||||
}))}
|
||||
/>
|
||||
))
|
||||
|
|
@ -275,23 +283,27 @@ function selectMethod(
|
|||
function openMethod(
|
||||
integration: IntegrationInfo,
|
||||
method: ConnectMethod,
|
||||
location: LocationRef,
|
||||
dialog: ReturnType<typeof useDialog>,
|
||||
onConnected?: OnIntegrationConnected,
|
||||
) {
|
||||
if (method.type === "key") {
|
||||
void beginKey(integration, method, dialog, onConnected)
|
||||
void beginKey(integration, method, location, dialog, onConnected)
|
||||
return
|
||||
}
|
||||
if (method.type === "command") {
|
||||
dialog.replace(() => <CommandStarting integration={integration} method={method} onConnected={onConnected} />)
|
||||
dialog.replace(() => (
|
||||
<CommandStarting integration={integration} method={method} location={location} onConnected={onConnected} />
|
||||
))
|
||||
return
|
||||
}
|
||||
void beginOAuth(integration, method, dialog, onConnected)
|
||||
void beginOAuth(integration, method, location, dialog, onConnected)
|
||||
}
|
||||
|
||||
async function beginKey(
|
||||
integration: IntegrationInfo,
|
||||
method: Extract<ConnectMethod, { type: "key" }>,
|
||||
location: LocationRef,
|
||||
dialog: ReturnType<typeof useDialog>,
|
||||
onConnected?: OnIntegrationConnected,
|
||||
) {
|
||||
|
|
@ -300,16 +312,16 @@ async function beginKey(
|
|||
: undefined
|
||||
if (answer === null) return
|
||||
dialog.replace(() => (
|
||||
<KeyMethod integration={integration} method={method} answer={answer} onConnected={onConnected} />
|
||||
<KeyMethod integration={integration} method={method} location={location} answer={answer} onConnected={onConnected} />
|
||||
))
|
||||
}
|
||||
|
||||
function CommandStarting(props: {
|
||||
integration: IntegrationInfo
|
||||
method: Extract<ConnectMethod, { type: "command" }>
|
||||
location: LocationRef
|
||||
onConnected?: OnIntegrationConnected
|
||||
}) {
|
||||
const data = useData()
|
||||
const dialog = useDialog()
|
||||
const client = useClient()
|
||||
const toast = useToast()
|
||||
|
|
@ -321,14 +333,14 @@ function CommandStarting(props: {
|
|||
.connect({
|
||||
integrationID: props.integration.id,
|
||||
methodID: props.method.id,
|
||||
location: location(data),
|
||||
location: locationQuery(props.location),
|
||||
})
|
||||
.then((result) => {
|
||||
if (closed) {
|
||||
void client.api.integration.command.cancel({
|
||||
integrationID: props.integration.id,
|
||||
attemptID: result.data.attemptID,
|
||||
location: location(data),
|
||||
location: locationQuery(props.location),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
|
@ -338,6 +350,7 @@ function CommandStarting(props: {
|
|||
integration={props.integration}
|
||||
title={props.method.label}
|
||||
attempt={result.data}
|
||||
location={props.location}
|
||||
onConnected={props.onConnected}
|
||||
/>
|
||||
))
|
||||
|
|
@ -359,6 +372,7 @@ function CommandPending(props: {
|
|||
integration: IntegrationInfo
|
||||
title: string
|
||||
attempt: CommandAttempt
|
||||
location: LocationRef
|
||||
onConnected?: OnIntegrationConnected
|
||||
}) {
|
||||
const data = useData()
|
||||
|
|
@ -374,7 +388,7 @@ function CommandPending(props: {
|
|||
.status({
|
||||
integrationID: props.integration.id,
|
||||
attemptID: props.attempt.attemptID,
|
||||
location: location(data),
|
||||
location: locationQuery(props.location),
|
||||
})
|
||||
.then((result) => {
|
||||
const status = result.data
|
||||
|
|
@ -385,7 +399,7 @@ function CommandPending(props: {
|
|||
}
|
||||
settled = true
|
||||
if (status.status === "complete") {
|
||||
void connected(props.integration, data, dialog, toast, props.onConnected)
|
||||
void connected(props.integration, props.location, data, dialog, toast, props.onConnected)
|
||||
return
|
||||
}
|
||||
toast.show({
|
||||
|
|
@ -408,7 +422,7 @@ function CommandPending(props: {
|
|||
void client.api.integration.command.cancel({
|
||||
integrationID: props.integration.id,
|
||||
attemptID: props.attempt.attemptID,
|
||||
location: location(data),
|
||||
location: locationQuery(props.location),
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -449,6 +463,7 @@ function CommandView(props: { title: string; output: string; message: string })
|
|||
function KeyMethod(props: {
|
||||
integration: IntegrationInfo
|
||||
method: Extract<ConnectMethod, { type: "key" }>
|
||||
location: LocationRef
|
||||
answer?: FormAnswer
|
||||
onConnected?: OnIntegrationConnected
|
||||
}) {
|
||||
|
|
@ -468,11 +483,11 @@ function KeyMethod(props: {
|
|||
void client.api.integration.connect
|
||||
.key({
|
||||
integrationID: props.integration.id,
|
||||
location: location(data),
|
||||
location: locationQuery(props.location),
|
||||
key,
|
||||
...(props.answer ? { answer: props.answer } : {}),
|
||||
})
|
||||
.then(() => connected(props.integration, data, dialog, toast, props.onConnected))
|
||||
.then(() => connected(props.integration, props.location, data, dialog, toast, props.onConnected))
|
||||
.catch((cause) => setError(message(cause)))
|
||||
}}
|
||||
description={() => (
|
||||
|
|
@ -485,23 +500,30 @@ function KeyMethod(props: {
|
|||
async function beginOAuth(
|
||||
integration: IntegrationInfo,
|
||||
method: IntegrationOAuthMethod,
|
||||
location: LocationRef,
|
||||
dialog: ReturnType<typeof useDialog>,
|
||||
onConnected?: OnIntegrationConnected,
|
||||
) {
|
||||
const answer = method.form ? await formAnswer(dialog, method.label, method.form) : undefined
|
||||
if (answer === null) return
|
||||
dialog.replace(() => (
|
||||
<OAuthStarting integration={integration} method={method} answer={answer} onConnected={onConnected} />
|
||||
<OAuthStarting
|
||||
integration={integration}
|
||||
method={method}
|
||||
location={location}
|
||||
answer={answer}
|
||||
onConnected={onConnected}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
||||
function OAuthStarting(props: {
|
||||
integration: IntegrationInfo
|
||||
method: IntegrationOAuthMethod
|
||||
location: LocationRef
|
||||
answer?: FormAnswer
|
||||
onConnected?: OnIntegrationConnected
|
||||
}) {
|
||||
const data = useData()
|
||||
const dialog = useDialog()
|
||||
const client = useClient()
|
||||
const toast = useToast()
|
||||
|
|
@ -510,7 +532,7 @@ function OAuthStarting(props: {
|
|||
void client.api.integration.oauth
|
||||
.connect({
|
||||
integrationID: props.integration.id,
|
||||
location: location(data),
|
||||
location: locationQuery(props.location),
|
||||
methodID: props.method.id,
|
||||
...(props.answer ? { answer: props.answer } : {}),
|
||||
})
|
||||
|
|
@ -521,6 +543,7 @@ function OAuthStarting(props: {
|
|||
integration={props.integration}
|
||||
title={props.method.label}
|
||||
attempt={result.data}
|
||||
location={props.location}
|
||||
onConnected={props.onConnected}
|
||||
/>
|
||||
))
|
||||
|
|
@ -531,6 +554,7 @@ function OAuthStarting(props: {
|
|||
integration={props.integration}
|
||||
title={props.method.label}
|
||||
attempt={result.data}
|
||||
location={props.location}
|
||||
onConnected={props.onConnected}
|
||||
/>
|
||||
))
|
||||
|
|
@ -548,6 +572,7 @@ function OAuthAuto(props: {
|
|||
integration: IntegrationInfo
|
||||
title: string
|
||||
attempt: IntegrationAttempt
|
||||
location: LocationRef
|
||||
onConnected?: OnIntegrationConnected
|
||||
}) {
|
||||
const data = useData()
|
||||
|
|
@ -591,7 +616,11 @@ function OAuthAuto(props: {
|
|||
|
||||
const poll = () => {
|
||||
void client.api.integration.oauth
|
||||
.status({ integrationID: props.integration.id, attemptID: props.attempt.attemptID, location: location(data) })
|
||||
.status({
|
||||
integrationID: props.integration.id,
|
||||
attemptID: props.attempt.attemptID,
|
||||
location: locationQuery(props.location),
|
||||
})
|
||||
.then((result) => {
|
||||
const status = result.data
|
||||
if (status.status === "pending") {
|
||||
|
|
@ -600,7 +629,7 @@ function OAuthAuto(props: {
|
|||
}
|
||||
settled = true
|
||||
if (status.status === "complete") {
|
||||
void connected(props.integration, data, dialog, toast, props.onConnected)
|
||||
void connected(props.integration, props.location, data, dialog, toast, props.onConnected)
|
||||
return
|
||||
}
|
||||
toast.show({ variant: "error", message: status.status === "failed" ? status.message : "Authorization expired" })
|
||||
|
|
@ -620,7 +649,7 @@ function OAuthAuto(props: {
|
|||
void client.api.integration.oauth.cancel({
|
||||
integrationID: props.integration.id,
|
||||
attemptID: props.attempt.attemptID,
|
||||
location: location(data),
|
||||
location: locationQuery(props.location),
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -640,6 +669,7 @@ function OAuthCode(props: {
|
|||
integration: IntegrationInfo
|
||||
title: string
|
||||
attempt: IntegrationAttempt
|
||||
location: LocationRef
|
||||
onConnected?: OnIntegrationConnected
|
||||
}) {
|
||||
const data = useData()
|
||||
|
|
@ -655,7 +685,7 @@ function OAuthCode(props: {
|
|||
void client.api.integration.oauth.cancel({
|
||||
integrationID: props.integration.id,
|
||||
attemptID: props.attempt.attemptID,
|
||||
location: location(data),
|
||||
location: locationQuery(props.location),
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -669,12 +699,12 @@ function OAuthCode(props: {
|
|||
.complete({
|
||||
integrationID: props.integration.id,
|
||||
attemptID: props.attempt.attemptID,
|
||||
location: location(data),
|
||||
location: locationQuery(props.location),
|
||||
code,
|
||||
})
|
||||
.then(() => {
|
||||
settled = true
|
||||
return connected(props.integration, data, dialog, toast, props.onConnected)
|
||||
return connected(props.integration, props.location, data, dialog, toast, props.onConnected)
|
||||
})
|
||||
.catch((cause) => setError(message(cause)))
|
||||
}}
|
||||
|
|
@ -968,26 +998,31 @@ async function externalAnswer(
|
|||
|
||||
async function connected(
|
||||
integration: IntegrationInfo,
|
||||
location: LocationRef,
|
||||
data: ReturnType<typeof useData>,
|
||||
dialog: ReturnType<typeof useDialog>,
|
||||
toast: ReturnType<typeof useToast>,
|
||||
onConnected?: OnIntegrationConnected,
|
||||
) {
|
||||
data.location.integration.invalidate()
|
||||
data.location.model.invalidate()
|
||||
data.location.provider.invalidate()
|
||||
await Promise.all([data.location.integration.sync(), data.location.model.sync(), data.location.provider.sync()])
|
||||
data.location.integration.invalidate(location)
|
||||
data.location.model.invalidate(location)
|
||||
data.location.provider.invalidate(location)
|
||||
await Promise.all([
|
||||
data.location.integration.sync(location),
|
||||
data.location.model.sync(location),
|
||||
data.location.provider.sync(location),
|
||||
])
|
||||
toast.show({ variant: "success", message: `Connected ${integration.name}` })
|
||||
if (onConnected) {
|
||||
onConnected(providerID(data, integration.id))
|
||||
onConnected(providerID(data, location, integration.id))
|
||||
return
|
||||
}
|
||||
dialog.clear()
|
||||
}
|
||||
|
||||
function providerID(data: ReturnType<typeof useData>, integrationID: string) {
|
||||
const models = data.location.model.list() ?? []
|
||||
const matches = (data.location.provider.list() ?? []).filter(
|
||||
function providerID(data: ReturnType<typeof useData>, location: LocationRef, integrationID: string) {
|
||||
const models = data.location.model.list(location) ?? []
|
||||
const matches = (data.location.provider.list(location) ?? []).filter(
|
||||
(provider) => provider.integrationID === integrationID || provider.id === integrationID,
|
||||
)
|
||||
return (
|
||||
|
|
@ -997,9 +1032,8 @@ function providerID(data: ReturnType<typeof useData>, integrationID: string) {
|
|||
)
|
||||
}
|
||||
|
||||
function location(data: ReturnType<typeof useData>) {
|
||||
const current = data.location.default()
|
||||
return { directory: current.directory, workspace: current.workspaceID }
|
||||
function locationQuery(location: LocationRef) {
|
||||
return { directory: location.directory, workspace: location.workspaceID }
|
||||
}
|
||||
|
||||
function message(cause: unknown) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
/** @jsxImportSource @opentui/solid */
|
||||
import { InputRenderable, TextareaRenderable } from "@opentui/core"
|
||||
import type { LocationRef } from "@opencode-ai/client"
|
||||
import { testRender } from "@opentui/solid"
|
||||
import { expect, test } from "bun:test"
|
||||
import { onMount } from "solid-js"
|
||||
|
|
@ -8,7 +9,7 @@ import { ConfigProvider } from "../../../src/config"
|
|||
import { ClientProvider } from "../../../src/context/client"
|
||||
import { DataProvider, useData } from "../../../src/context/data"
|
||||
import { Keymap } from "../../../src/context/keymap"
|
||||
import { LocationProvider } from "../../../src/context/location"
|
||||
import { LocationProvider, useLocation } from "../../../src/context/location"
|
||||
import { ThemeProvider } from "../../../src/context/theme"
|
||||
import { DialogProvider, useDialog } from "../../../src/ui/dialog"
|
||||
import { ToastProvider } from "../../../src/ui/toast"
|
||||
|
|
@ -203,9 +204,27 @@ test("hides account rename and delete actions while the add account row is selec
|
|||
}
|
||||
})
|
||||
|
||||
async function renderIntegration() {
|
||||
test("uses the active location for integration data and credential requests", async () => {
|
||||
const location = { directory: "/remote/project", workspaceID: "workspace_test" }
|
||||
const fixture = await renderIntegration(location)
|
||||
|
||||
try {
|
||||
fixture.app.mockInput.pressArrow("down")
|
||||
fixture.app.mockInput.pressArrow("down")
|
||||
fixture.app.mockInput.pressEnter()
|
||||
|
||||
await fixture.app.waitFor(() => fixture.requests.length === 1)
|
||||
expect(fixture.locations).toContainEqual(location)
|
||||
expect(fixture.locations.at(-1)).toEqual(location)
|
||||
} finally {
|
||||
fixture.app.renderer.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
async function renderIntegration(activeLocation?: LocationRef) {
|
||||
const events = createEventStream()
|
||||
const requests: Array<{ method: string; path: string; body?: { label: string } }> = []
|
||||
const locations: LocationRef[] = []
|
||||
const reads = { integration: 0, model: 0, provider: 0 }
|
||||
let accounts = [
|
||||
{ type: "credential" as const, id: "cred_personal", label: "Personal" },
|
||||
|
|
@ -213,12 +232,19 @@ async function renderIntegration() {
|
|||
]
|
||||
|
||||
const calls = createFetch(async (url, request) => {
|
||||
const directory =
|
||||
url.searchParams.get("location[directory]") ??
|
||||
decodeURIComponent(request.headers.get("x-opencode-directory") ?? process.cwd())
|
||||
const workspaceID =
|
||||
url.searchParams.get("location[workspace]") ?? request.headers.get("x-opencode-workspace") ?? undefined
|
||||
const requestedLocation = { directory, ...(workspaceID ? { workspaceID } : {}) }
|
||||
const location = {
|
||||
directory: process.cwd(),
|
||||
project: { id: "proj_test", directory: process.cwd(), canonical: process.cwd() },
|
||||
...requestedLocation,
|
||||
project: { id: "proj_test", directory, canonical: directory },
|
||||
}
|
||||
|
||||
if (url.pathname === "/api/integration") {
|
||||
locations.push(requestedLocation)
|
||||
reads.integration++
|
||||
return json({
|
||||
location,
|
||||
|
|
@ -244,6 +270,7 @@ async function renderIntegration() {
|
|||
}
|
||||
|
||||
if (request.method === "POST" && /^\/api\/credential\/[^/]+\/activate$/.test(url.pathname)) {
|
||||
locations.push(requestedLocation)
|
||||
const id = url.pathname.split("/")[3]
|
||||
const active = accounts.find((account) => account.id === id)
|
||||
if (!active) throw new Error(`unknown credential: ${id}`)
|
||||
|
|
@ -289,9 +316,11 @@ async function renderIntegration() {
|
|||
function Probe() {
|
||||
const data = useData()
|
||||
const dialog = useDialog()
|
||||
const location = useLocation()
|
||||
onMount(() => {
|
||||
location.set(activeLocation)
|
||||
void data.location.integration
|
||||
.sync()
|
||||
.sync(activeLocation)
|
||||
.then(() => dialog.replace(() => <DialogIntegration integrationID="openai" autoConnect />))
|
||||
})
|
||||
return null
|
||||
|
|
@ -332,6 +361,7 @@ async function renderIntegration() {
|
|||
app,
|
||||
reads,
|
||||
requests,
|
||||
locations,
|
||||
get accounts() {
|
||||
return accounts
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue