dialog-status.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import { TextAttributes } from "@opentui/core"
  2. import { useTheme } from "../context/theme"
  3. import { useSync } from "@tui/context/sync"
  4. import { For, Match, Switch, Show, createMemo } from "solid-js"
  5. export type DialogStatusProps = {}
  6. export function DialogStatus() {
  7. const sync = useSync()
  8. const { theme } = useTheme()
  9. const enabledFormatters = createMemo(() => sync.data.formatter.filter((f) => f.enabled))
  10. const plugins = createMemo(() => {
  11. const list = sync.data.config.plugin ?? []
  12. const result = list.map((value) => {
  13. if (value.startsWith("file://")) {
  14. const path = value.substring("file://".length)
  15. const parts = path.split("/")
  16. const filename = parts.pop() || path
  17. if (!filename.includes(".")) return { name: filename }
  18. const basename = filename.split(".")[0]
  19. if (basename === "index") {
  20. const dirname = parts.pop()
  21. const name = dirname || basename
  22. return { name }
  23. }
  24. return { name: basename }
  25. }
  26. const index = value.lastIndexOf("@")
  27. if (index <= 0) return { name: value, version: "latest" }
  28. const name = value.substring(0, index)
  29. const version = value.substring(index + 1)
  30. return { name, version }
  31. })
  32. return result.toSorted((a, b) => a.name.localeCompare(b.name))
  33. })
  34. return (
  35. <box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
  36. <box flexDirection="row" justifyContent="space-between">
  37. <text fg={theme.text} attributes={TextAttributes.BOLD}>
  38. Status
  39. </text>
  40. <text fg={theme.textMuted}>esc</text>
  41. </box>
  42. <Show when={Object.keys(sync.data.mcp).length > 0} fallback={<text fg={theme.text}>No MCP Servers</text>}>
  43. <box>
  44. <text fg={theme.text}>{Object.keys(sync.data.mcp).length} MCP Servers</text>
  45. <For each={Object.entries(sync.data.mcp)}>
  46. {([key, item]) => (
  47. <box flexDirection="row" gap={1}>
  48. <text
  49. flexShrink={0}
  50. style={{
  51. fg: (
  52. {
  53. connected: theme.success,
  54. failed: theme.error,
  55. disabled: theme.textMuted,
  56. needs_auth: theme.warning,
  57. needs_client_registration: theme.error,
  58. } as Record<string, typeof theme.success>
  59. )[item.status],
  60. }}
  61. >
  62. </text>
  63. <text fg={theme.text} wrapMode="word">
  64. <b>{key}</b>{" "}
  65. <span style={{ fg: theme.textMuted }}>
  66. <Switch fallback={item.status}>
  67. <Match when={item.status === "connected"}>Connected</Match>
  68. <Match when={item.status === "failed" && item}>{(val) => val().error}</Match>
  69. <Match when={item.status === "disabled"}>Disabled in configuration</Match>
  70. <Match when={(item.status as string) === "needs_auth"}>
  71. Needs authentication (run: opencode mcp auth {key})
  72. </Match>
  73. <Match when={(item.status as string) === "needs_client_registration" && item}>
  74. {(val) => (val() as { error: string }).error}
  75. </Match>
  76. </Switch>
  77. </span>
  78. </text>
  79. </box>
  80. )}
  81. </For>
  82. </box>
  83. </Show>
  84. {sync.data.lsp.length > 0 && (
  85. <box>
  86. <text fg={theme.text}>{sync.data.lsp.length} LSP Servers</text>
  87. <For each={sync.data.lsp}>
  88. {(item) => (
  89. <box flexDirection="row" gap={1}>
  90. <text
  91. flexShrink={0}
  92. style={{
  93. fg: {
  94. connected: theme.success,
  95. error: theme.error,
  96. }[item.status],
  97. }}
  98. >
  99. </text>
  100. <text fg={theme.text} wrapMode="word">
  101. <b>{item.id}</b> <span style={{ fg: theme.textMuted }}>{item.root}</span>
  102. </text>
  103. </box>
  104. )}
  105. </For>
  106. </box>
  107. )}
  108. <Show when={enabledFormatters().length > 0} fallback={<text fg={theme.text}>No Formatters</text>}>
  109. <box>
  110. <text fg={theme.text}>{enabledFormatters().length} Formatters</text>
  111. <For each={enabledFormatters()}>
  112. {(item) => (
  113. <box flexDirection="row" gap={1}>
  114. <text
  115. flexShrink={0}
  116. style={{
  117. fg: theme.success,
  118. }}
  119. >
  120. </text>
  121. <text wrapMode="word" fg={theme.text}>
  122. <b>{item.name}</b>
  123. </text>
  124. </box>
  125. )}
  126. </For>
  127. </box>
  128. </Show>
  129. <Show when={plugins().length > 0} fallback={<text fg={theme.text}>No Plugins</text>}>
  130. <box>
  131. <text fg={theme.text}>{plugins().length} Plugins</text>
  132. <For each={plugins()}>
  133. {(item) => (
  134. <box flexDirection="row" gap={1}>
  135. <text
  136. flexShrink={0}
  137. style={{
  138. fg: theme.success,
  139. }}
  140. >
  141. </text>
  142. <text wrapMode="word" fg={theme.text}>
  143. <b>{item.name}</b>
  144. {item.version && <span style={{ fg: theme.textMuted }}> @{item.version}</span>}
  145. </text>
  146. </box>
  147. )}
  148. </For>
  149. </box>
  150. </Show>
  151. </box>
  152. )
  153. }