Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions packages/opencode/src/cli/cmd/tui/component/dialog-status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ 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 { useLanguage } from "@tui/context/language"

export type DialogStatusProps = {}

export function DialogStatus() {
const sync = useSync()
const { theme } = useTheme()
const dialog = useDialog()
const { t } = useLanguage()

const enabledFormatters = createMemo(() => sync.data.formatter.filter((f) => f.enabled))

Expand Down Expand Up @@ -44,15 +46,15 @@ export function DialogStatus() {
<box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
<box flexDirection="row" justifyContent="space-between">
<text fg={theme.text} attributes={TextAttributes.BOLD}>
Status
{t("tui.status.title")}
</text>
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
esc
</text>
</box>
<Show when={Object.keys(sync.data.mcp).length > 0} fallback={<text fg={theme.text}>No MCP Servers</text>}>
<Show when={Object.keys(sync.data.mcp).length > 0} fallback={<text fg={theme.text}>{t("tui.status.no_mcp")}</text>}>
<box>
<text fg={theme.text}>{Object.keys(sync.data.mcp).length} MCP Servers</text>
<text fg={theme.text}>{t("tui.status.mcp_servers", { count: Object.keys(sync.data.mcp).length })}</text>
<For each={Object.entries(sync.data.mcp)}>
{([key, item]) => (
<box flexDirection="row" gap={1}>
Expand All @@ -77,12 +79,12 @@ export function DialogStatus() {
<b>{key}</b>{" "}
<span style={{ fg: theme.textMuted }}>
<Switch fallback={item.status}>
<Match when={item.status === "connected"}>Connected</Match>
<Match when={item.status === "connected"}>{t("tui.status.connected")}</Match>
<Match when={item.status === "failed" && item}>{(val) => val().error}</Match>
<Match when={(item.status as string) === "pending"}>Pending approval</Match>
<Match when={item.status === "disabled"}>Disabled in configuration</Match>
<Match when={(item.status as string) === "pending"}>{t("tui.status.pending_approval")}</Match>
<Match when={item.status === "disabled"}>{t("tui.status.disabled_config")}</Match>
<Match when={(item.status as string) === "needs_auth"}>
Needs authentication (run: opencode mcp auth {key})
{t("tui.status.needs_auth", { name: key })}
</Match>
<Match when={(item.status as string) === "needs_client_registration" && item}>
{(val) => (val() as { error: string }).error}
Expand All @@ -97,7 +99,7 @@ export function DialogStatus() {
</Show>
{sync.data.lsp.length > 0 && (
<box>
<text fg={theme.text}>{sync.data.lsp.length} LSP Servers</text>
<text fg={theme.text}>{t("tui.status.lsp_servers", { count: sync.data.lsp.length })}</text>
<For each={sync.data.lsp}>
{(item) => (
<box flexDirection="row" gap={1}>
Expand All @@ -120,9 +122,9 @@ export function DialogStatus() {
</For>
</box>
)}
<Show when={enabledFormatters().length > 0} fallback={<text fg={theme.text}>No Formatters</text>}>
<Show when={enabledFormatters().length > 0} fallback={<text fg={theme.text}>{t("tui.status.no_formatters")}</text>}>
<box>
<text fg={theme.text}>{enabledFormatters().length} Formatters</text>
<text fg={theme.text}>{t("tui.status.formatters", { count: enabledFormatters().length })}</text>
<For each={enabledFormatters()}>
{(item) => (
<box flexDirection="row" gap={1}>
Expand All @@ -142,9 +144,9 @@ export function DialogStatus() {
</For>
</box>
</Show>
<Show when={plugins().length > 0} fallback={<text fg={theme.text}>No Plugins</text>}>
<Show when={plugins().length > 0} fallback={<text fg={theme.text}>{t("tui.status.no_plugins")}</text>}>
<box>
<text fg={theme.text}>{plugins().length} Plugins</text>
<text fg={theme.text}>{t("tui.status.plugins", { count: plugins().length })}</text>
<For each={plugins()}>
{(item) => (
<box flexDirection="row" gap={1}>
Expand Down
16 changes: 15 additions & 1 deletion packages/opencode/src/cli/cmd/tui/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,4 +428,18 @@ export const dict: Record<string, string> = {
"trust.dangerous.advice_root": "Unless you have a very specific reason, DO NOT trust the filesystem root.",
"trust.dangerous.option.yes": "I understand the risks, trust for this session",
"trust.dangerous.option.no": "Exit (recommended)",
}

// Status dialog
"tui.status.title": "Status",
"tui.status.no_mcp": "No MCP Servers",
"tui.status.mcp_servers": "{{count}} MCP Servers",
"tui.status.connected": "Connected",
"tui.status.pending_approval": "Pending approval",
"tui.status.disabled_config": "Disabled in configuration",
"tui.status.needs_auth": "Needs authentication (run: mimo mcp auth {{name}})",
"tui.status.lsp_servers": "{{count}} LSP Servers",
"tui.status.no_formatters": "No Formatters",
"tui.status.formatters": "{{count}} Formatters",
"tui.status.no_plugins": "No Plugins",
"tui.status.plugins": "{{count}} Plugins",
}
15 changes: 15 additions & 0 deletions packages/opencode/src/cli/cmd/tui/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,4 +421,19 @@ export const dict = {
"trust.dangerous.advice_root": "除非有明确的理由,否则不要信任文件系统根目录。",
"trust.dangerous.option.yes": "我了解风险,仅本次信任",
"trust.dangerous.option.no": "退出(推荐)",

// Status dialog
"tui.status.title": "状态",
"tui.status.no_mcp": "无 MCP 服务器",
"tui.status.mcp_servers": "{{count}} 个 MCP 服务器",
"tui.status.connected": "已连接",
"tui.status.pending_approval": "等待授权",
"tui.status.disabled_config": "已在配置中禁用",
"tui.status.needs_auth": "需要认证(运行:mimo mcp auth {{name}})",
"tui.status.lsp_servers": "{{count}} 个 LSP 服务器",
"tui.status.no_formatters": "无格式化工具",
"tui.status.formatters": "{{count}} 个格式化工具",
"tui.status.no_plugins": "无插件",
"tui.status.plugins": "{{count}} 个插件",

} satisfies Partial<Record<Keys, string>>
6 changes: 4 additions & 2 deletions packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import { TuiPluginRuntime } from "../../plugin"
import { DialogGoUpsell } from "../../component/dialog-go-upsell"
import { SessionRetry } from "@/session/retry"
import { getRevertDiffFiles } from "../../util/revert-diff"
import { Log } from "@/util"

addDefaultParsers(parsers.parsers)

Expand Down Expand Up @@ -129,6 +130,7 @@ export function Session() {
const tuiConfig = useTuiConfig()
const kv = useKV()
const { theme } = useTheme()
const log = Log.create({ service: "tui.session" })
const promptRef = usePromptRef()
const session = createMemo(() => sync.session.get(route.sessionID))
const currentAgentID = useCurrentAgentID()
Expand Down Expand Up @@ -295,7 +297,7 @@ export function Session() {
if (keybind.match("app_exit", evt)) {
const status = sync.data.session_status?.[route.sessionID]
if (status && status.type !== "idle") {
void sdk.client.session.abort({ sessionID: route.sessionID }).catch(() => {})
void sdk.client.session.abort({ sessionID: route.sessionID }).catch((err: any) => log.warn("Failed to abort session on exit", { error: String(err) }))
return
}
void exit()
Expand Down Expand Up @@ -544,7 +546,7 @@ export function Session() {
},
onSelect: async (dialog) => {
const status = sync.data.session_status?.[route.sessionID]
if (status?.type !== "idle") await sdk.client.session.abort({ sessionID: route.sessionID }).catch(() => {})
if (status?.type !== "idle") await sdk.client.session.abort({ sessionID: route.sessionID }).catch((err: any) => log.warn("Failed to abort session before undo", { error: String(err) }))
const revert = session()?.revert?.messageID
const message = messages().findLast((x) => (!revert || x.id < revert) && x.role === "user")
if (!message) return
Expand Down