Skip to content

Commit 6e7fc30

Browse files
committed
feat(app): context window window
1 parent 03733b0 commit 6e7fc30

File tree

3 files changed

+502
-57
lines changed

3 files changed

+502
-57
lines changed
Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
1-
import { createMemo, Show } from "solid-js"
1+
import { Match, Show, Switch, createMemo } from "solid-js"
22
import { Tooltip } from "@opencode-ai/ui/tooltip"
33
import { ProgressCircle } from "@opencode-ai/ui/progress-circle"
4-
import { useSync } from "@/context/sync"
4+
import { Button } from "@opencode-ai/ui/button"
55
import { useParams } from "@solidjs/router"
66
import { AssistantMessage } from "@opencode-ai/sdk/v2/client"
77

8-
export function SessionContextUsage() {
8+
import { useLayout } from "@/context/layout"
9+
import { useSync } from "@/context/sync"
10+
11+
interface SessionContextUsageProps {
12+
variant?: "button" | "indicator"
13+
}
14+
15+
export function SessionContextUsage(props: SessionContextUsageProps) {
916
const sync = useSync()
1017
const params = useParams()
18+
const layout = useLayout()
19+
20+
const variant = createMemo(() => props.variant ?? "button")
21+
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
22+
const tabs = createMemo(() => layout.tabs(sessionKey()))
1123
const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
1224

1325
const cost = createMemo(() => {
@@ -19,7 +31,11 @@ export function SessionContextUsage() {
1931
})
2032

2133
const context = createMemo(() => {
22-
const last = messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage
34+
const last = messages().findLast((x) => {
35+
if (x.role !== "assistant") return false
36+
const total = x.tokens.input + x.tokens.output + x.tokens.reasoning + x.tokens.cache.read + x.tokens.cache.write
37+
return total > 0
38+
}) as AssistantMessage
2339
if (!last) return
2440
const total =
2541
last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write
@@ -30,33 +46,57 @@ export function SessionContextUsage() {
3046
}
3147
})
3248

33-
return (
34-
<Show when={context?.()}>
35-
{(ctx) => (
36-
<Tooltip
37-
value={
38-
<div class="">
39-
<div class="flex items-center gap-2">
40-
<span class="text-text-invert-strong">{ctx().tokens}</span>
41-
<span class="text-text-invert-base">Tokens</span>
42-
</div>
43-
<div class="flex items-center gap-2">
44-
<span class="text-text-invert-strong">{ctx().percentage ?? 0}%</span>
45-
<span class="text-text-invert-base">Usage</span>
46-
</div>
47-
<div class="flex items-center gap-2">
48-
<span class="text-text-invert-strong">{cost()}</span>
49-
<span class="text-text-invert-base">Cost</span>
50-
</div>
49+
const openContext = () => {
50+
if (!params.id) return
51+
layout.review.open()
52+
tabs().open("context")
53+
tabs().setActive("context")
54+
}
55+
56+
const circle = () => (
57+
<div class="p-1">
58+
<ProgressCircle size={16} strokeWidth={2} percentage={context()?.percentage ?? 0} />
59+
</div>
60+
)
61+
62+
const tooltipValue = () => (
63+
<div>
64+
<Show when={context()}>
65+
{(ctx) => (
66+
<>
67+
<div class="flex items-center gap-2">
68+
<span class="text-text-invert-strong">{ctx().tokens}</span>
69+
<span class="text-text-invert-base">Tokens</span>
70+
</div>
71+
<div class="flex items-center gap-2">
72+
<span class="text-text-invert-strong">{ctx().percentage ?? 0}%</span>
73+
<span class="text-text-invert-base">Usage</span>
5174
</div>
52-
}
53-
placement="top"
54-
>
55-
<div class="p-1">
56-
<ProgressCircle size={16} strokeWidth={2} percentage={ctx().percentage ?? 0} />
57-
</div>
58-
</Tooltip>
59-
)}
75+
</>
76+
)}
77+
</Show>
78+
<div class="flex items-center gap-2">
79+
<span class="text-text-invert-strong">{cost()}</span>
80+
<span class="text-text-invert-base">Cost</span>
81+
</div>
82+
<Show when={variant() === "button"}>
83+
<div class="text-11-regular text-text-invert-base mt-1">Click to view context</div>
84+
</Show>
85+
</div>
86+
)
87+
88+
return (
89+
<Show when={params.id}>
90+
<Tooltip value={tooltipValue()} placement="top">
91+
<Switch>
92+
<Match when={variant() === "indicator"}>{circle()}</Match>
93+
<Match when={true}>
94+
<Button type="button" variant="ghost" class="size-6" onClick={openContext}>
95+
{circle()}
96+
</Button>
97+
</Match>
98+
</Switch>
99+
</Tooltip>
60100
</Show>
61101
)
62102
}

packages/app/src/context/layout.tsx

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -209,38 +209,58 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
209209
},
210210
async open(tab: string) {
211211
const current = store.sessionTabs[sessionKey] ?? { all: [] }
212-
if (tab !== "review") {
213-
if (!current.all.includes(tab)) {
214-
if (!store.sessionTabs[sessionKey]) {
215-
setStore("sessionTabs", sessionKey, { all: [tab], active: tab })
216-
} else {
217-
setStore("sessionTabs", sessionKey, "all", [...current.all, tab])
218-
setStore("sessionTabs", sessionKey, "active", tab)
219-
}
212+
213+
if (tab === "review") {
214+
if (!store.sessionTabs[sessionKey]) {
215+
setStore("sessionTabs", sessionKey, { all: [], active: tab })
220216
return
221217
}
218+
setStore("sessionTabs", sessionKey, "active", tab)
219+
return
222220
}
223-
if (!store.sessionTabs[sessionKey]) {
224-
setStore("sessionTabs", sessionKey, { all: [], active: tab })
225-
} else {
221+
222+
if (tab === "context") {
223+
const all = [tab, ...current.all.filter((x) => x !== tab)]
224+
if (!store.sessionTabs[sessionKey]) {
225+
setStore("sessionTabs", sessionKey, { all, active: tab })
226+
return
227+
}
228+
setStore("sessionTabs", sessionKey, "all", all)
226229
setStore("sessionTabs", sessionKey, "active", tab)
230+
return
227231
}
232+
233+
if (!current.all.includes(tab)) {
234+
if (!store.sessionTabs[sessionKey]) {
235+
setStore("sessionTabs", sessionKey, { all: [tab], active: tab })
236+
return
237+
}
238+
setStore("sessionTabs", sessionKey, "all", [...current.all, tab])
239+
setStore("sessionTabs", sessionKey, "active", tab)
240+
return
241+
}
242+
243+
if (!store.sessionTabs[sessionKey]) {
244+
setStore("sessionTabs", sessionKey, { all: current.all, active: tab })
245+
return
246+
}
247+
setStore("sessionTabs", sessionKey, "active", tab)
228248
},
229249
close(tab: string) {
230250
const current = store.sessionTabs[sessionKey]
231251
if (!current) return
252+
253+
const all = current.all.filter((x) => x !== tab)
232254
batch(() => {
233-
setStore(
234-
"sessionTabs",
235-
sessionKey,
236-
"all",
237-
current.all.filter((x) => x !== tab),
238-
)
239-
if (current.active === tab) {
240-
const index = current.all.findIndex((f) => f === tab)
241-
const previous = current.all[Math.max(0, index - 1)]
242-
setStore("sessionTabs", sessionKey, "active", previous)
255+
setStore("sessionTabs", sessionKey, "all", all)
256+
if (current.active !== tab) return
257+
258+
const index = current.all.findIndex((f) => f === tab)
259+
if (index <= 0) {
260+
setStore("sessionTabs", sessionKey, "active", undefined)
261+
return
243262
}
263+
setStore("sessionTabs", sessionKey, "active", current.all[index - 1])
244264
})
245265
},
246266
move(tab: string, to: number) {

0 commit comments

Comments
 (0)