|
|
@@ -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 (
|
|
|
+ <box flexShrink={0}>
|
|
|
+ <box
|
|
|
+ paddingTop={1}
|
|
|
+ paddingBottom={1}
|
|
|
+ paddingLeft={2}
|
|
|
+ paddingRight={1}
|
|
|
+ {...SplitBorder}
|
|
|
+ border={["left"]}
|
|
|
+ borderColor={theme.border}
|
|
|
+ flexShrink={0}
|
|
|
+ backgroundColor={theme.backgroundPanel}
|
|
|
+ >
|
|
|
+ <box flexDirection="row" justifyContent="space-between" gap={1}>
|
|
|
+ <box flexDirection="row" gap={2}>
|
|
|
+ <text fg={theme.text}>
|
|
|
+ <b>Subagent session</b>
|
|
|
+ </text>
|
|
|
+ <Show when={usage()}>
|
|
|
+ {(item) => (
|
|
|
+ <text fg={theme.textMuted} wrapMode="none">
|
|
|
+ {[item().context, item().cost].filter(Boolean).join(" · ")}
|
|
|
+ </text>
|
|
|
+ )}
|
|
|
+ </Show>
|
|
|
+ </box>
|
|
|
+ <box flexDirection="row" gap={2}>
|
|
|
+ <box
|
|
|
+ onMouseOver={() => setHover("parent")}
|
|
|
+ onMouseOut={() => setHover(null)}
|
|
|
+ onMouseUp={() => command.trigger("session.parent")}
|
|
|
+ backgroundColor={hover() === "parent" ? theme.backgroundElement : theme.backgroundPanel}
|
|
|
+ >
|
|
|
+ <text fg={theme.text}>
|
|
|
+ Parent <span style={{ fg: theme.textMuted }}>{keybind.print("session_parent")}</span>
|
|
|
+ </text>
|
|
|
+ </box>
|
|
|
+ <box
|
|
|
+ onMouseOver={() => setHover("prev")}
|
|
|
+ onMouseOut={() => setHover(null)}
|
|
|
+ onMouseUp={() => command.trigger("session.child.previous")}
|
|
|
+ backgroundColor={hover() === "prev" ? theme.backgroundElement : theme.backgroundPanel}
|
|
|
+ >
|
|
|
+ <text fg={theme.text}>
|
|
|
+ Prev <span style={{ fg: theme.textMuted }}>{keybind.print("session_child_cycle_reverse")}</span>
|
|
|
+ </text>
|
|
|
+ </box>
|
|
|
+ <box
|
|
|
+ onMouseOver={() => setHover("next")}
|
|
|
+ onMouseOut={() => setHover(null)}
|
|
|
+ onMouseUp={() => command.trigger("session.child.next")}
|
|
|
+ backgroundColor={hover() === "next" ? theme.backgroundElement : theme.backgroundPanel}
|
|
|
+ >
|
|
|
+ <text fg={theme.text}>
|
|
|
+ Next <span style={{ fg: theme.textMuted }}>{keybind.print("session_child_cycle")}</span>
|
|
|
+ </text>
|
|
|
+ </box>
|
|
|
+ </box>
|
|
|
+ </box>
|
|
|
+ </box>
|
|
|
+ </box>
|
|
|
+ )
|
|
|
+}
|