Skip to content

Commit 8446719

Browse files
authored
refactor(session): move context into prompt footer (anomalyco#19486)
1 parent 15a8c22 commit 8446719

4 files changed

Lines changed: 47 additions & 200 deletions

File tree

packages/opencode/src/cli/cmd/tui/app.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,6 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
584584
value: "variant.cycle",
585585
keybind: "variant_cycle",
586586
category: "Agent",
587-
hidden: true,
588587
onSelect: () => {
589588
local.model.variant.cycle()
590589
},

packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import path from "path"
55
import { Filesystem } from "@/util/filesystem"
66
import { useLocal } from "@tui/context/local"
77
import { useTheme } from "@tui/context/theme"
8-
import { EmptyBorder } from "@tui/component/border"
8+
import { EmptyBorder, SplitBorder } from "@tui/component/border"
99
import { useSDK } from "@tui/context/sdk"
1010
import { useRoute } from "@tui/context/route"
1111
import { useSync } from "@tui/context/sync"
@@ -22,7 +22,7 @@ import { useKeyboard, useRenderer } from "@opentui/solid"
2222
import { Editor } from "@tui/util/editor"
2323
import { useExit } from "../../context/exit"
2424
import { Clipboard } from "../../util/clipboard"
25-
import type { FilePart } from "@opencode-ai/sdk/v2"
25+
import type { AssistantMessage, FilePart } from "@opencode-ai/sdk/v2"
2626
import { TuiEvent } from "../../event"
2727
import { iife } from "@/util/iife"
2828
import { Locale } from "@/util/locale"
@@ -59,6 +59,10 @@ export type PromptRef = {
5959

6060
const PLACEHOLDERS = ["Fix a TODO in the codebase", "What is the tech stack of this project?", "Fix broken tests"]
6161
const SHELL_PLACEHOLDERS = ["ls -la", "git status", "pwd"]
62+
const money = new Intl.NumberFormat("en-US", {
63+
style: "currency",
64+
currency: "USD",
65+
})
6266

6367
export function Prompt(props: PromptProps) {
6468
let input: TextareaRenderable
@@ -122,6 +126,25 @@ export function Prompt(props: PromptProps) {
122126
return messages.findLast((m) => m.role === "user")
123127
})
124128

129+
const usage = createMemo(() => {
130+
if (!props.sessionID) return
131+
const msg = sync.data.message[props.sessionID] ?? []
132+
const last = msg.findLast((item): item is AssistantMessage => item.role === "assistant" && item.tokens.output > 0)
133+
if (!last) return
134+
135+
const tokens =
136+
last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write
137+
if (tokens <= 0) return
138+
139+
const model = sync.data.provider.find((item) => item.id === last.providerID)?.models[last.modelID]
140+
const pct = model?.limit.context ? `${Math.round((tokens / model.limit.context) * 100)}%` : undefined
141+
const cost = msg.reduce((sum, item) => sum + (item.role === "assistant" ? item.cost : 0), 0)
142+
return {
143+
context: pct ? `${Locale.number(tokens)} (${pct})` : Locale.number(tokens),
144+
cost: cost > 0 ? money.format(cost) : undefined,
145+
}
146+
})
147+
125148
const [store, setStore] = createStore<{
126149
prompt: PromptInfo
127150
mode: "normal" | "shell"
@@ -833,8 +856,7 @@ export function Prompt(props: PromptProps) {
833856
border={["left"]}
834857
borderColor={highlight()}
835858
customBorderChars={{
836-
...EmptyBorder,
837-
vertical: "┃",
859+
...SplitBorder.customBorderChars,
838860
bottomLeft: "╹",
839861
}}
840862
>
@@ -1158,14 +1180,25 @@ export function Prompt(props: PromptProps) {
11581180
<box gap={2} flexDirection="row">
11591181
<Switch>
11601182
<Match when={store.mode === "normal"}>
1161-
<Show when={local.model.variant.list().length > 0}>
1162-
<text fg={theme.text}>
1163-
{keybind.print("variant_cycle")} <span style={{ fg: theme.textMuted }}>variants</span>
1164-
</text>
1165-
</Show>
1166-
<text fg={theme.text}>
1167-
{keybind.print("agent_cycle")} <span style={{ fg: theme.textMuted }}>agents</span>
1168-
</text>
1183+
<Switch>
1184+
<Match when={usage()}>
1185+
{(item) => (
1186+
<text fg={theme.textMuted} wrapMode="none">
1187+
{[item().context, item().cost].filter(Boolean).join(" · ")}
1188+
</text>
1189+
)}
1190+
</Match>
1191+
<Match when={true}>
1192+
<Show when={local.model.variant.list().length > 0}>
1193+
<text fg={theme.text}>
1194+
{keybind.print("variant_cycle")} <span style={{ fg: theme.textMuted }}>variants</span>
1195+
</text>
1196+
</Show>
1197+
<text fg={theme.text}>
1198+
{keybind.print("agent_cycle")} <span style={{ fg: theme.textMuted }}>agents</span>
1199+
</text>
1200+
</Match>
1201+
</Switch>
11691202
<text fg={theme.text}>
11701203
{keybind.print("command_list")} <span style={{ fg: theme.textMuted }}>commands</span>
11711204
</text>

packages/opencode/src/cli/cmd/tui/routes/session/header.tsx

Lines changed: 0 additions & 172 deletions
This file was deleted.

packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ import { useSDK } from "@tui/context/sdk"
5151
import { useCommandDialog } from "@tui/component/dialog-command"
5252
import type { DialogContext } from "@tui/ui/dialog"
5353
import { useKeybind } from "@tui/context/keybind"
54-
import { Header } from "./header"
5554
import { parsePatch } from "diff"
5655
import { useDialog } from "../../ui/dialog"
5756
import { TodoItem } from "../../component/todo-item"
@@ -154,7 +153,6 @@ export function Session() {
154153
const [showDetails, setShowDetails] = kv.signal("tool_details_visibility", true)
155154
const [showAssistantMetadata, setShowAssistantMetadata] = kv.signal("assistant_metadata_visibility", true)
156155
const [showScrollbar, setShowScrollbar] = kv.signal("scrollbar_visible", true)
157-
const [showHeader, setShowHeader] = kv.signal("header_visible", true)
158156
const [diffWrapMode] = kv.signal<"word" | "none">("diff_wrap_mode", "word")
159157
const [animationsEnabled, setAnimationsEnabled] = kv.signal("animations_enabled", true)
160158
const [showGenericToolOutput, setShowGenericToolOutput] = kv.signal("generic_tool_output_visibility", false)
@@ -635,15 +633,6 @@ export function Session() {
635633
dialog.clear()
636634
},
637635
},
638-
{
639-
title: showHeader() ? "Hide header" : "Show header",
640-
value: "session.toggle.header",
641-
category: "Session",
642-
onSelect: (dialog) => {
643-
setShowHeader((prev) => !prev)
644-
dialog.clear()
645-
},
646-
},
647636
{
648637
title: showGenericToolOutput() ? "Hide generic tool output" : "Show generic tool output",
649638
value: "session.toggle.generic_tool_output",
@@ -1045,11 +1034,8 @@ export function Session() {
10451034
}}
10461035
>
10471036
<box flexDirection="row">
1048-
<box flexGrow={1} paddingBottom={1} paddingTop={1} paddingLeft={2} paddingRight={2} gap={1}>
1037+
<box flexGrow={1} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>
10491038
<Show when={session()}>
1050-
<Show when={showHeader() && (!sidebarVisible() || !wide())}>
1051-
<Header />
1052-
</Show>
10531039
<scrollbox
10541040
ref={(r) => (scroll = r)}
10551041
viewportOptions={{
@@ -1068,6 +1054,7 @@ export function Session() {
10681054
flexGrow={1}
10691055
scrollAcceleration={scrollAcceleration()}
10701056
>
1057+
<box height={1} />
10711058
<For each={messages()}>
10721059
{(message, index) => (
10731060
<Switch>

0 commit comments

Comments
 (0)