From c3a9ec4a99f2636ec032091dd4b18b13ff3e25f3 Mon Sep 17 00:00:00 2001 From: Dax Date: Sat, 28 Mar 2026 01:46:29 -0400 Subject: [PATCH] fix: restore subagent footer and fix style guide violations (#19491) --- .../cli/cmd/tui/component/dialog-model.tsx | 4 +- .../cli/cmd/tui/component/dialog-variant.tsx | 1 - .../src/cli/cmd/tui/routes/session/index.tsx | 5 +- .../tui/routes/session/subagent-footer.tsx | 109 ++++++++++++++++++ 4 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 packages/opencode/src/cli/cmd/tui/routes/session/subagent-footer.tsx diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx index a06e7e0705..ee9fa225ed 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx @@ -138,9 +138,9 @@ export function DialogModel(props: { providerID?: string }) { local.model.set({ providerID, modelID }, { recent: true }) if (local.model.variant.list().length > 0) { dialog.replace(() => ) - } else { - dialog.clear() + return } + dialog.clear() } return ( diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-variant.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-variant.tsx index 8d06ab6411..fd895e0cf6 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-variant.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-variant.tsx @@ -1,6 +1,5 @@ import { createMemo } from "solid-js" import { useLocal } from "@tui/context/local" -import { useSync } from "@tui/context/sync" import { DialogSelect } from "@tui/ui/dialog-select" import { useDialog } from "@tui/ui/dialog" diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 1baa2e9973..2f84f9d7bf 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -61,6 +61,7 @@ import { DialogTimeline } from "./dialog-timeline" import { DialogForkFromTimeline } from "./dialog-fork-from-timeline" import { DialogSessionRename } from "../../component/dialog-session-rename" import { Sidebar } from "./sidebar" +import { SubagentFooter } from "./subagent-footer.tsx" import { Flag } from "@/flag/flag" import { LANGUAGE_EXTENSIONS } from "@/lsp/language" import parsers from "../../../../../../parsers-config.ts" @@ -1054,7 +1055,6 @@ export function Session() { flexGrow={1} scrollAcceleration={scrollAcceleration()} > - {(message, index) => ( @@ -1158,6 +1158,9 @@ export function Session() { 0}> + + + { diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/subagent-footer.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/subagent-footer.tsx new file mode 100644 index 0000000000..315cd1e88c --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/routes/session/subagent-footer.tsx @@ -0,0 +1,109 @@ +import { createMemo, createSignal, Show } from "solid-js" +import { useRouteData } from "@tui/context/route" +import { useSync } from "@tui/context/sync" +import { useTheme } from "@tui/context/theme" +import { SplitBorder } from "@tui/component/border" +import type { AssistantMessage } from "@opencode-ai/sdk/v2" +import { useCommandDialog } from "@tui/component/dialog-command" +import { useKeybind } from "../../context/keybind" +import { Locale } from "@/util/locale" +import { useTerminalDimensions } from "@opentui/solid" + +export function SubagentFooter() { + const route = useRouteData("session") + const sync = useSync() + const messages = createMemo(() => sync.data.message[route.sessionID] ?? []) + + const usage = createMemo(() => { + const msg = messages() + const last = msg.findLast((item): item is AssistantMessage => item.role === "assistant" && item.tokens.output > 0) + if (!last) return + + const tokens = + last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write + if (tokens <= 0) return + + const model = sync.data.provider.find((item) => item.id === last.providerID)?.models[last.modelID] + const pct = model?.limit.context ? `${Math.round((tokens / model.limit.context) * 100)}%` : undefined + const cost = msg.reduce((sum, item) => sum + (item.role === "assistant" ? item.cost : 0), 0) + + const money = new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + }) + + return { + context: pct ? `${Locale.number(tokens)} (${pct})` : Locale.number(tokens), + cost: cost > 0 ? money.format(cost) : undefined, + } + }) + + const { theme } = useTheme() + const keybind = useKeybind() + const command = useCommandDialog() + const [hover, setHover] = createSignal<"parent" | "prev" | "next" | null>(null) + const dimensions = useTerminalDimensions() + + return ( + + + + + + Subagent session + + + {(item) => ( + + {[item().context, item().cost].filter(Boolean).join(" ยท ")} + + )} + + + + setHover("parent")} + onMouseOut={() => setHover(null)} + onMouseUp={() => command.trigger("session.parent")} + backgroundColor={hover() === "parent" ? theme.backgroundElement : theme.backgroundPanel} + > + + Parent {keybind.print("session_parent")} + + + setHover("prev")} + onMouseOut={() => setHover(null)} + onMouseUp={() => command.trigger("session.child.previous")} + backgroundColor={hover() === "prev" ? theme.backgroundElement : theme.backgroundPanel} + > + + Prev {keybind.print("session_child_cycle_reverse")} + + + setHover("next")} + onMouseOut={() => setHover(null)} + onMouseUp={() => command.trigger("session.child.next")} + backgroundColor={hover() === "next" ? theme.backgroundElement : theme.backgroundPanel} + > + + Next {keybind.print("session_child_cycle")} + + + + + + + ) +}