tui: hide MCP section when no servers are configured and use cached data

- Only show MCP section in sidebar and status dialog when MCP servers exist
- Use cached MCP data from sync context instead of making separate API calls
- Improves performance by reducing redundant network requests
This commit is contained in:
Dax Raad 2025-10-08 19:52:25 -04:00
parent 113a7b5996
commit d2cf9610e6
3 changed files with 76 additions and 80 deletions

View file

@ -1,57 +1,52 @@
import { TextAttributes } from "@opentui/core"
import { Theme } from "../context/theme"
import { useSDK } from "../context/sdk"
import { useSync } from "@tui/context/sync"
import { createResource, For, Match, Switch } from "solid-js"
import { For, Match, Switch, Show } from "solid-js"
export type DialogStatusProps = {}
export function DialogStatus() {
const sdk = useSDK()
const sync = useSync()
const [mcp] = createResource(async () => {
const result = await sdk.mcp.status()
return result.data
})
return (
<box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
<box flexDirection="row" justifyContent="space-between">
<text attributes={TextAttributes.BOLD}>Status</text>
<text fg={Theme.textMuted}>esc</text>
</box>
<box>
<text>{Object.values(mcp() ?? {}).length} MCP Servers</text>
<For each={Object.entries(mcp() ?? {})}>
{([key, item]) => (
<box flexDirection="row" gap={1}>
<text
flexShrink={0}
style={{
fg: {
connected: Theme.success,
failed: Theme.error,
disabled: Theme.textMuted,
}[item.status],
}}
>
</text>
<text wrapMode="word">
<b>{key}</b>{" "}
<span style={{ fg: Theme.textMuted }}>
<Switch>
<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>
</Switch>
</span>
</text>
</box>
)}
</For>
</box>
<Show when={Object.keys(sync.data.mcp).length > 0}>
<box>
<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,
}[item.status],
}}
>
</text>
<text wrapMode="word">
<b>{key}</b>{" "}
<span style={{ fg: Theme.textMuted }}>
<Switch>
<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>
</Switch>
</span>
</text>
</box>
)}
</For>
</box>
</Show>
{sync.data.lsp.length > 0 && (
<box>
<text>{sync.data.lsp.length} LSP Servers</text>

View file

@ -9,6 +9,7 @@ import type {
Command,
Permission,
LspStatus,
McpStatus,
} from "@opencode-ai/sdk"
import { createStore, produce, reconcile } from "solid-js/store"
import { useSDK } from "@tui/context/sdk"
@ -38,6 +39,9 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
[messageID: string]: Part[]
}
lsp: LspStatus[]
mcp: {
[key: string]: McpStatus
}
}>({
config: {},
ready: false,
@ -50,6 +54,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
message: {},
part: {},
lsp: [],
mcp: {},
})
const sdk = useSDK()
@ -206,6 +211,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
sdk.config.get().then((x) => setStore("config", x.data!)),
sdk.command.list().then((x) => setStore("command", x.data ?? [])),
sdk.lsp.status().then((x) => setStore("lsp", x.data!)),
sdk.mcp.status().then((x) => setStore("mcp", x.data!)),
]).then(() => setStore("ready", true))
const result = {

View file

@ -1,13 +1,11 @@
import { useSync } from "@tui/context/sync"
import { createMemo, For, Show, createResource, Switch, Match } from "solid-js"
import { createMemo, For, Show, Switch, Match } from "solid-js"
import { Theme } from "../../context/theme"
import { useSDK } from "../../context/sdk"
import { Locale } from "@/util/locale"
import type { AssistantMessage } from "@opencode-ai/sdk"
export function Sidebar(props: { sessionID: string }) {
const sync = useSync()
const sdk = useSDK()
const session = createMemo(() => sync.session.get(props.sessionID)!)
const todo = createMemo(() => sync.data.todo[props.sessionID] ?? [])
const messages = createMemo(() => sync.data.message[props.sessionID] ?? [])
@ -26,11 +24,6 @@ export function Sidebar(props: { sessionID: string }) {
return [...result.values()].sort((a, b) => a.length - b.length)
})
const [mcp] = createResource(async () => {
const result = await sdk.mcp.status()
return result.data
})
const cost = createMemo(() => {
const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0)
return new Intl.NumberFormat("en-US", {
@ -70,39 +63,41 @@ export function Sidebar(props: { sessionID: string }) {
<text fg={Theme.textMuted}>{context()?.percentage ?? 0}% used</text>
<text fg={Theme.textMuted}>{cost()} spent</text>
</box>
<box>
<text>
<b>MCP</b>
</text>
<For each={Object.entries(mcp() ?? {})}>
{([key, item]) => (
<box flexDirection="row" gap={1}>
<text
flexShrink={0}
style={{
fg: {
connected: Theme.success,
failed: Theme.error,
disabled: Theme.textMuted,
}[item.status],
}}
>
</text>
<text wrapMode="word">
{key}{" "}
<span style={{ fg: Theme.textMuted }}>
<Switch>
<Match when={item.status === "connected"}>Connected</Match>
<Match when={item.status === "failed" && item}>{(val) => <i>{val().error}</i>}</Match>
<Match when={item.status === "disabled"}>Disabled in configuration</Match>
</Switch>
</span>
</text>
</box>
)}
</For>
</box>
<Show when={Object.keys(sync.data.mcp).length > 0}>
<box>
<text>
<b>MCP</b>
</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,
}[item.status],
}}
>
</text>
<text wrapMode="word">
{key}{" "}
<span style={{ fg: Theme.textMuted }}>
<Switch>
<Match when={item.status === "connected"}>Connected</Match>
<Match when={item.status === "failed" && item}>{(val) => <i>{val().error}</i>}</Match>
<Match when={item.status === "disabled"}>Disabled in configuration</Match>
</Switch>
</span>
</text>
</box>
)}
</For>
</box>
</Show>
<Show when={sync.data.lsp.length > 0}>
<box>
<text>