import { createResource, createEffect, createMemo, onCleanup, Show } from "solid-js" import { createStore, reconcile } from "solid-js/store" import { useDialog } from "@opencode-ai/ui/context/dialog" import { Dialog } from "@opencode-ai/ui/dialog" import { List } from "@opencode-ai/ui/list" import { TextField } from "@opencode-ai/ui/text-field" import { Button } from "@opencode-ai/ui/button" import { IconButton } from "@opencode-ai/ui/icon-button" import { normalizeServerUrl, serverDisplayName, useServer } from "@/context/server" import { usePlatform } from "@/context/platform" import { createOpencodeClient } from "@opencode-ai/sdk/v2/client" import { useNavigate } from "@solidjs/router" type ServerStatus = { healthy: boolean; version?: string } async function checkHealth(url: string, fetch?: typeof globalThis.fetch): Promise { const sdk = createOpencodeClient({ baseUrl: url, fetch, signal: AbortSignal.timeout(3000), }) return sdk.global .health() .then((x) => ({ healthy: x.data?.healthy === true, version: x.data?.version })) .catch(() => ({ healthy: false })) } export function DialogSelectServer() { const navigate = useNavigate() const dialog = useDialog() const server = useServer() const platform = usePlatform() const [store, setStore] = createStore({ url: "", adding: false, error: "", status: {} as Record, }) const [defaultUrl, defaultUrlActions] = createResource(() => platform.getDefaultServerUrl?.()) const isDesktop = platform.platform === "desktop" const items = createMemo(() => { const current = server.url const list = server.list if (!current) return list if (!list.includes(current)) return [current, ...list] return [current, ...list.filter((x) => x !== current)] }) const current = createMemo(() => items().find((x) => x === server.url) ?? items()[0]) const sortedItems = createMemo(() => { const list = items() if (!list.length) return list const active = current() const order = new Map(list.map((url, index) => [url, index] as const)) const rank = (value?: ServerStatus) => { if (value?.healthy === true) return 0 if (value?.healthy === false) return 2 return 1 } return list.slice().sort((a, b) => { if (a === active) return -1 if (b === active) return 1 const diff = rank(store.status[a]) - rank(store.status[b]) if (diff !== 0) return diff return (order.get(a) ?? 0) - (order.get(b) ?? 0) }) }) async function refreshHealth() { const results: Record = {} await Promise.all( items().map(async (url) => { results[url] = await checkHealth(url, platform.fetch) }), ) setStore("status", reconcile(results)) } createEffect(() => { items() refreshHealth() const interval = setInterval(refreshHealth, 10_000) onCleanup(() => clearInterval(interval)) }) function select(value: string, persist?: boolean) { if (!persist && store.status[value]?.healthy === false) return dialog.close() if (persist) { server.add(value) navigate("/") return } server.setActive(value) navigate("/") } async function handleSubmit(e: SubmitEvent) { e.preventDefault() const value = normalizeServerUrl(store.url) if (!value) return setStore("adding", true) setStore("error", "") const result = await checkHealth(value, platform.fetch) setStore("adding", false) if (!result.healthy) { setStore("error", "Could not connect to server") return } setStore("url", "") select(value, true) } async function handleRemove(url: string) { server.remove(url) } return (
x} current={current()} onSelect={(x) => { if (x) select(x) }} > {(i) => (
{serverDisplayName(i)} {store.status[i]?.version}
{ e.stopPropagation() handleRemove(i) }} />
)}

Add a server

{ setStore("url", v) setStore("error", "") }} validationState={store.error ? "invalid" : "valid"} error={store.error} />

Default server

Connect to this server on app launch instead of starting a local server. Requires restart.

No server selected} > } >
{serverDisplayName(defaultUrl()!)}
) }