| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- import { TextAttributes } from "@opentui/core"
- import { fileURLToPath } from "bun"
- import { useTheme } from "../context/theme"
- import { useDialog } from "@tui/ui/dialog"
- import { useSync } from "@tui/context/sync"
- import { For, Match, Switch, Show, createMemo } from "solid-js"
- import { Installation } from "@/installation"
- export type DialogStatusProps = {}
- export function DialogStatus() {
- const sync = useSync()
- const { theme } = useTheme()
- const dialog = useDialog()
- const enabledFormatters = createMemo(() => sync.data.formatter.filter((f) => f.enabled))
- const plugins = createMemo(() => {
- const list = sync.data.config.plugin ?? []
- const result = list.map((value) => {
- if (value.startsWith("file://")) {
- const path = fileURLToPath(value)
- const parts = path.split("/")
- const filename = parts.pop() || path
- if (!filename.includes(".")) return { name: filename }
- const basename = filename.split(".")[0]
- if (basename === "index") {
- const dirname = parts.pop()
- const name = dirname || basename
- return { name }
- }
- return { name: basename }
- }
- const index = value.lastIndexOf("@")
- if (index <= 0) return { name: value, version: "latest" }
- const name = value.substring(0, index)
- const version = value.substring(index + 1)
- return { name, version }
- })
- return result.toSorted((a, b) => a.name.localeCompare(b.name))
- })
- return (
- <box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
- <box flexDirection="row" justifyContent="space-between">
- <text fg={theme.text} attributes={TextAttributes.BOLD}>
- Status
- </text>
- <text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
- esc
- </text>
- </box>
- <text fg={theme.textMuted}>OpenCode v{Installation.VERSION}</text>
- <Show when={Object.keys(sync.data.mcp).length > 0} fallback={<text fg={theme.text}>No MCP Servers</text>}>
- <box>
- <text fg={theme.text}>{Object.keys(sync.data.mcp).length} MCP Servers</text>
- <For each={Object.entries(sync.data.mcp)}>
- {([key, item]) => (
- <box flexDirection="row" gap={1}>
- <text
- flexShrink={0}
- style={{
- fg: (
- {
- connected: theme.success,
- failed: theme.error,
- disabled: theme.textMuted,
- needs_auth: theme.warning,
- needs_client_registration: theme.error,
- } as Record<string, typeof theme.success>
- )[item.status],
- }}
- >
- •
- </text>
- <text fg={theme.text} wrapMode="word">
- <b>{key}</b>{" "}
- <span style={{ fg: theme.textMuted }}>
- <Switch fallback={item.status}>
- <Match when={item.status === "connected"}>Connected</Match>
- <Match when={item.status === "failed" && item}>{(val) => val().error}</Match>
- <Match when={item.status === "disabled"}>Disabled in configuration</Match>
- <Match when={(item.status as string) === "needs_auth"}>
- Needs authentication (run: opencode mcp auth {key})
- </Match>
- <Match when={(item.status as string) === "needs_client_registration" && item}>
- {(val) => (val() as { error: string }).error}
- </Match>
- </Switch>
- </span>
- </text>
- </box>
- )}
- </For>
- </box>
- </Show>
- {sync.data.lsp.length > 0 && (
- <box>
- <text fg={theme.text}>{sync.data.lsp.length} LSP Servers</text>
- <For each={sync.data.lsp}>
- {(item) => (
- <box flexDirection="row" gap={1}>
- <text
- flexShrink={0}
- style={{
- fg: {
- connected: theme.success,
- error: theme.error,
- }[item.status],
- }}
- >
- •
- </text>
- <text fg={theme.text} wrapMode="word">
- <b>{item.id}</b> <span style={{ fg: theme.textMuted }}>{item.root}</span>
- </text>
- </box>
- )}
- </For>
- </box>
- )}
- <Show when={enabledFormatters().length > 0} fallback={<text fg={theme.text}>No Formatters</text>}>
- <box>
- <text fg={theme.text}>{enabledFormatters().length} Formatters</text>
- <For each={enabledFormatters()}>
- {(item) => (
- <box flexDirection="row" gap={1}>
- <text
- flexShrink={0}
- style={{
- fg: theme.success,
- }}
- >
- •
- </text>
- <text wrapMode="word" fg={theme.text}>
- <b>{item.name}</b>
- </text>
- </box>
- )}
- </For>
- </box>
- </Show>
- <Show when={plugins().length > 0} fallback={<text fg={theme.text}>No Plugins</text>}>
- <box>
- <text fg={theme.text}>{plugins().length} Plugins</text>
- <For each={plugins()}>
- {(item) => (
- <box flexDirection="row" gap={1}>
- <text
- flexShrink={0}
- style={{
- fg: theme.success,
- }}
- >
- •
- </text>
- <text wrapMode="word" fg={theme.text}>
- <b>{item.name}</b>
- {item.version && <span style={{ fg: theme.textMuted }}> @{item.version}</span>}
- </text>
- </box>
- )}
- </For>
- </box>
- </Show>
- </box>
- )
- }
|