diff options
Diffstat (limited to 'packages/desktop/src/context')
| -rw-r--r-- | packages/desktop/src/context/global-sync.tsx | 111 | ||||
| -rw-r--r-- | packages/desktop/src/context/layout.tsx | 50 | ||||
| -rw-r--r-- | packages/desktop/src/context/local.tsx | 24 | ||||
| -rw-r--r-- | packages/desktop/src/context/sync.tsx | 31 |
4 files changed, 149 insertions, 67 deletions
diff --git a/packages/desktop/src/context/global-sync.tsx b/packages/desktop/src/context/global-sync.tsx index 3a6062fb8..2a24a845c 100644 --- a/packages/desktop/src/context/global-sync.tsx +++ b/packages/desktop/src/context/global-sync.tsx @@ -1,22 +1,25 @@ -import type { - Message, - Agent, - Session, - Part, - Config, - Path, - File, - FileNode, - Project, - FileDiff, - Todo, - SessionStatus, - ProviderListResponse, -} from "@opencode-ai/sdk/v2" +import { + type Message, + type Agent, + type Session, + type Part, + type Config, + type Path, + type File, + type FileNode, + type Project, + type FileDiff, + type Todo, + type SessionStatus, + type ProviderListResponse, + type ProviderAuthResponse, + createOpencodeClient, +} from "@opencode-ai/sdk/v2/client" import { createStore, produce, reconcile } from "solid-js/store" import { Binary } from "@opencode-ai/util/binary" import { createSimpleContext } from "@opencode-ai/ui/context" import { useGlobalSDK } from "./global-sdk" +import { onMount } from "solid-js" type State = { ready: boolean @@ -49,19 +52,48 @@ type State = { export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimpleContext({ name: "GlobalSync", init: () => { - const sdk = useGlobalSDK() + const globalSDK = useGlobalSDK() const [globalStore, setGlobalStore] = createStore<{ ready: boolean project: Project[] provider: ProviderListResponse + provider_auth: ProviderAuthResponse children: Record<string, State> }>({ ready: false, project: [], provider: { all: [], connected: [], default: {} }, + provider_auth: {}, children: {}, }) + async function bootstrapInstance(directory: string) { + const [store, setStore] = child(directory) + const sdk = createOpencodeClient({ + baseUrl: globalSDK.url, + directory, + }) + const load = { + project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)), + provider: () => sdk.provider.list().then((x) => setStore("provider", x.data!)), + path: () => sdk.path.get().then((x) => setStore("path", x.data!)), + agent: () => sdk.app.agents().then((x) => setStore("agent", x.data ?? [])), + session: () => + sdk.session.list().then((x) => { + const sessions = (x.data ?? []) + .slice() + .sort((a, b) => a.id.localeCompare(b.id)) + .slice(0, store.limit) + setStore("session", sessions) + }), + status: () => sdk.session.status().then((x) => setStore("session_status", x.data!)), + config: () => sdk.config.get().then((x) => setStore("config", x.data!)), + changes: () => sdk.file.status().then((x) => setStore("changes", x.data!)), + node: () => sdk.file.list({ path: "/" }).then((x) => setStore("node", x.data!)), + } + await Promise.all(Object.values(load).map((p) => p())).then(() => setStore("ready", true)) + } + const children: Record<string, ReturnType<typeof createStore<State>>> = {} function child(directory: string) { if (!children[directory]) { @@ -83,16 +115,21 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple changes: [], }) children[directory] = createStore(globalStore.children[directory]) + bootstrapInstance(directory) } return children[directory] } - sdk.event.listen((e) => { + globalSDK.event.listen((e) => { const directory = e.name const event = e.details if (directory === "global") { switch (event.type) { + case "global.disposed": { + bootstrap() + break + } case "project.updated": { const result = Binary.search(globalStore.project, event.properties.id, (s) => s.id) if (result.found) { @@ -113,6 +150,10 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple const [store, setStore] = child(directory) switch (event.type) { + case "server.instance.disposed": { + bootstrapInstance(directory) + break + } case "session.updated": { const result = Binary.search(store.session, event.properties.info.id, (s) => s.id) if (result.found) { @@ -181,19 +222,28 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple } }) - Promise.all([ - sdk.client.project.list().then(async (x) => { - setGlobalStore( - "project", - x - .data!.filter((p) => !p.worktree.includes("opencode-test") && p.vcs) - .sort((a, b) => a.id.localeCompare(b.id)), - ) - }), - sdk.client.provider.list().then((x) => { - setGlobalStore("provider", x.data ?? {}) - }), - ]).then(() => setGlobalStore("ready", true)) + async function bootstrap() { + return Promise.all([ + globalSDK.client.project.list().then(async (x) => { + setGlobalStore( + "project", + x + .data!.filter((p) => !p.worktree.includes("opencode-test") && p.vcs) + .sort((a, b) => a.id.localeCompare(b.id)), + ) + }), + globalSDK.client.provider.list().then((x) => { + setGlobalStore("provider", x.data ?? {}) + }), + globalSDK.client.provider.auth().then((x) => { + setGlobalStore("provider_auth", x.data ?? {}) + }), + ]).then(() => setGlobalStore("ready", true)) + } + + onMount(() => { + bootstrap() + }) return { data: globalStore, @@ -201,6 +251,7 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple return globalStore.ready }, child, + bootstrap, } }, }) diff --git a/packages/desktop/src/context/layout.tsx b/packages/desktop/src/context/layout.tsx index 5530ad28f..24ba55a53 100644 --- a/packages/desktop/src/context/layout.tsx +++ b/packages/desktop/src/context/layout.tsx @@ -1,5 +1,5 @@ -import { createStore } from "solid-js/store" -import { createMemo, onMount } from "solid-js" +import { createStore, produce } from "solid-js/store" +import { batch, createMemo, onMount } from "solid-js" import { createSimpleContext } from "@opencode-ai/ui/context" import { makePersisted } from "@solid-primitives/storage" import { useGlobalSync } from "./global-sync" @@ -19,6 +19,8 @@ const PASTEL_COLORS = [ "#C1E1C1", // pastel mint ] +type Dialog = "provider" | "model" | "connect" + export const { use: useLayout, provider: LayoutProvider } = createSimpleContext({ name: "Layout", init: () => { @@ -44,8 +46,13 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( }, ) const [ephemeral, setEphemeral] = createStore({ + connect: { + provider: undefined as undefined | string, + state: undefined as undefined | "pending" | "complete" | "error", + error: undefined as undefined | string, + }, dialog: { - open: undefined as undefined | "provider" | "model", + open: undefined as undefined | Dialog, }, }) const usedColors = new Set<string>() @@ -169,14 +176,47 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( }, dialog: { opened: createMemo(() => ephemeral.dialog?.open), - open(dialog: "provider" | "model") { + open(dialog: Dialog) { setEphemeral("dialog", "open", dialog) + if (dialog !== "connect") { + setEphemeral("connect", {}) + } }, - close(dialog: "provider" | "model") { + close(dialog: Dialog) { if (ephemeral.dialog?.open === dialog) { setEphemeral("dialog", "open", undefined) + setEphemeral("connect", {}) } }, + connect(provider: string) { + batch(() => { + setEphemeral("dialog", "open", "connect") + setEphemeral("connect", { provider, state: "pending" }) + }) + }, + }, + connect: { + provider: createMemo(() => ephemeral.connect.provider), + state: createMemo(() => ephemeral.connect.state), + complete() { + setEphemeral( + produce((state) => { + state.dialog.open = "model" + state.connect.state = "complete" + }), + ) + }, + error(message: string) { + setEphemeral( + produce((state) => { + state.connect.state = "error" + state.connect.error = message + }), + ) + }, + clear() { + setEphemeral("connect", {}) + }, }, } }, diff --git a/packages/desktop/src/context/local.tsx b/packages/desktop/src/context/local.tsx index 74d3ac364..d8dfa732a 100644 --- a/packages/desktop/src/context/local.tsx +++ b/packages/desktop/src/context/local.tsx @@ -6,6 +6,7 @@ import { createSimpleContext } from "@opencode-ai/ui/context" import { useSDK } from "./sdk" import { useSync } from "./sync" import { base64Encode } from "@opencode-ai/util/encode" +import { useProviders } from "@/hooks/use-providers" export type LocalFile = FileNode & Partial<{ @@ -37,10 +38,17 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ init: () => { const sdk = useSDK() const sync = useSync() + const providers = useProviders() function isModelValid(model: ModelKey) { - const provider = sync.data.provider?.all.find((x) => x.id === model.providerID) - return !!provider?.models[model.modelID] && sync.data.provider?.connected.includes(model.providerID) + const provider = providers().all.find((x) => x.id === model.providerID) + return ( + !!provider?.models[model.modelID] && + providers() + .connected() + .map((p) => p.id) + .includes(model.providerID) + ) } function getFirstValidModel(...modelFns: (() => ModelKey | undefined)[]) { @@ -115,8 +123,8 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ }) const list = createMemo(() => - sync.data.provider.all - .filter((p) => sync.data.provider.connected.includes(p.id)) + providers() + .connected() .flatMap((p) => Object.values(p.models).map((m) => ({ ...m, @@ -145,11 +153,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ } } - for (const p of sync.data.provider.connected) { - if (p in sync.data.provider.default) { + for (const p of providers().connected()) { + if (p.id in providers().default) { return { - providerID: p, - modelID: sync.data.provider.default[p], + providerID: p.id, + modelID: providers().default[p.id], } } } diff --git a/packages/desktop/src/context/sync.tsx b/packages/desktop/src/context/sync.tsx index 1a11cd599..85758c5b6 100644 --- a/packages/desktop/src/context/sync.tsx +++ b/packages/desktop/src/context/sync.tsx @@ -11,28 +11,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ const globalSync = useGlobalSync() const sdk = useSDK() const [store, setStore] = globalSync.child(sdk.directory) - - const load = { - project: () => sdk.client.project.current().then((x) => setStore("project", x.data!.id)), - provider: () => sdk.client.provider.list().then((x) => setStore("provider", x.data!)), - path: () => sdk.client.path.get().then((x) => setStore("path", x.data!)), - agent: () => sdk.client.app.agents().then((x) => setStore("agent", x.data ?? [])), - session: () => - sdk.client.session.list().then((x) => { - const sessions = (x.data ?? []) - .slice() - .sort((a, b) => a.id.localeCompare(b.id)) - .slice(0, store.limit) - setStore("session", sessions) - }), - status: () => sdk.client.session.status().then((x) => setStore("session_status", x.data!)), - config: () => sdk.client.config.get().then((x) => setStore("config", x.data!)), - changes: () => sdk.client.file.status().then((x) => setStore("changes", x.data!)), - node: () => sdk.client.file.list({ path: "/" }).then((x) => setStore("node", x.data!)), - } - - Promise.all(Object.values(load).map((p) => p())).then(() => setStore("ready", true)) - const absolute = (path: string) => (store.path.directory + "/" + path).replace("//", "/") return { @@ -78,11 +56,16 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ }, fetch: async (count = 10) => { setStore("limit", (x) => x + count) - await load.session() + await sdk.client.session.list().then((x) => { + const sessions = (x.data ?? []) + .slice() + .sort((a, b) => a.id.localeCompare(b.id)) + .slice(0, store.limit) + setStore("session", sessions) + }) }, more: createMemo(() => store.session.length >= store.limit), }, - load, absolute, get directory() { return store.path.directory |
