cloudaxe-opencode/packages/app/src/components/session-context-usage.tsx

151 lines
5 KiB
TypeScript
Raw Normal View History

2026-01-01 11:23:00 +00:00
import { Match, Show, Switch, createMemo } from "solid-js"
import { Tooltip, type TooltipProps } from "@opencode-ai/ui/tooltip"
import { ProgressCircle } from "@opencode-ai/ui/progress-circle"
import { ProgressCircleV2 } from "@opencode-ai/ui/v2/progress-circle-v2"
2026-01-01 11:23:00 +00:00
import { Button } from "@opencode-ai/ui/button"
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
2026-03-12 16:32:05 +00:00
import { useFile } from "@/context/file"
2026-01-01 11:23:00 +00:00
import { useLayout } from "@/context/layout"
import { useSync } from "@/context/sync"
2026-01-20 16:20:43 +00:00
import { useLanguage } from "@/context/language"
2026-03-26 18:41:22 +00:00
import { useProviders } from "@/hooks/use-providers"
import { useSDK } from "@/context/sdk"
import { getSessionContext, getSessionTokenTotal } from "@/components/session/session-context-metrics"
2026-03-12 13:52:51 +00:00
import { useSessionLayout } from "@/pages/session/session-layout"
2026-03-12 16:32:05 +00:00
import { createSessionTabs } from "@/pages/session/helpers"
2026-01-01 11:23:00 +00:00
interface SessionContextUsageProps {
variant?: "button" | "indicator"
buttonAppearance?: "default" | "v2"
placement?: TooltipProps["placement"]
2026-01-01 11:23:00 +00:00
}
function openSessionContext(args: {
view: ReturnType<ReturnType<typeof useLayout>["view"]>
2026-02-12 20:48:09 +00:00
layout: ReturnType<typeof useLayout>
tabs: ReturnType<ReturnType<typeof useLayout>["tabs"]>
}) {
if (!args.view.reviewPanel.opened()) args.view.reviewPanel.open()
2026-02-12 20:48:09 +00:00
if (args.layout.fileTree.opened() && args.layout.fileTree.tab() !== "all") args.layout.fileTree.setTab("all")
void args.tabs.open("context")
args.tabs.setActive("context")
}
2026-01-01 11:23:00 +00:00
export function SessionContextUsage(props: SessionContextUsageProps) {
const sync = useSync()
2026-03-12 16:32:05 +00:00
const file = useFile()
2026-01-01 11:23:00 +00:00
const layout = useLayout()
2026-01-20 16:20:43 +00:00
const language = useLanguage()
const sdk = useSDK()
const providers = useProviders(() => sdk().directory)
2026-03-12 13:52:51 +00:00
const { params, tabs, view } = useSessionLayout()
2026-01-01 11:23:00 +00:00
const variant = createMemo(() => props.variant ?? "button")
const buttonAppearance = createMemo(() => props.buttonAppearance ?? "default")
2026-03-12 16:32:05 +00:00
const tabState = createSessionTabs({
tabs,
pathFromTab: file.pathFromTab,
normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab),
})
const messages = createMemo(() => (params.id ? (sync().data.message[params.id] ?? []) : []))
const info = createMemo(() => (params.id ? sync().session.get(params.id) : undefined))
2026-01-25 12:42:33 +00:00
const usd = createMemo(
() =>
2026-03-05 14:10:32 +00:00
new Intl.NumberFormat(language.intl(), {
2026-01-25 12:42:33 +00:00
style: "currency",
currency: "USD",
}),
)
const context = createMemo(() => getSessionContext(messages(), [...providers.all().values()]))
const tokens = createMemo(() => info()?.tokens)
const cost = createMemo(() => {
return usd().format(info()?.cost ?? 0)
})
2026-01-01 11:23:00 +00:00
const openContext = () => {
if (!params.id) return
2026-03-12 16:32:05 +00:00
if (tabState.activeTab() === "context") {
tabs().close("context")
return
}
openSessionContext({
view: view(),
2026-02-12 20:48:09 +00:00
layout,
tabs: tabs(),
})
2026-01-01 11:23:00 +00:00
}
const circle = () => (
<div class="flex items-center justify-center">
2026-02-06 15:37:49 +00:00
<ProgressCircle size={16} strokeWidth={2} percentage={context()?.usage ?? 0} />
2026-01-01 11:23:00 +00:00
</div>
)
const circleV2 = () => (
<div class="flex items-center justify-center">
<ProgressCircleV2 percentage={context()?.usage ?? 0} />
</div>
)
2026-01-01 11:23:00 +00:00
const tooltipValue = () => (
<div>
<Show when={tokens()}>
{(value) => (
<div class="flex items-center gap-2">
2026-06-28 23:18:35 +00:00
<span class="text-text-invert-strong">
{getSessionTokenTotal(value())?.toLocaleString(language.intl())}
</span>
<span class="text-text-invert-base">{language.t("context.usage.tokens")}</span>
</div>
)}
</Show>
2026-01-01 11:23:00 +00:00
<Show when={context()}>
{(ctx) => (
<div class="flex items-center gap-2">
<span class="text-text-invert-strong">{ctx().usage ?? 0}%</span>
<span class="text-text-invert-base">{language.t("context.usage.usage")}</span>
</div>
2026-01-01 11:23:00 +00:00
)}
</Show>
<div class="flex items-center gap-2">
<span class="text-text-invert-strong">{cost()}</span>
2026-01-20 16:20:43 +00:00
<span class="text-text-invert-base">{language.t("context.usage.cost")}</span>
2026-01-01 11:23:00 +00:00
</div>
</div>
)
return (
<Show when={params.id}>
<Tooltip value={tooltipValue()} placement={props.placement ?? "top"}>
2026-01-01 11:23:00 +00:00
<Switch>
<Match when={variant() === "indicator"}>{circle()}</Match>
<Match when={buttonAppearance() === "v2"}>
<IconButtonV2
type="button"
variant="ghost-muted"
size="large"
icon={circleV2()}
onClick={openContext}
aria-label={language.t("context.usage.view")}
/>
</Match>
2026-01-01 11:23:00 +00:00
<Match when={true}>
2026-01-22 11:36:28 +00:00
<Button
type="button"
variant="ghost"
class="size-6"
2026-01-22 11:36:28 +00:00
onClick={openContext}
aria-label={language.t("context.usage.view")}
>
2026-01-01 11:23:00 +00:00
{circle()}
</Button>
</Match>
</Switch>
</Tooltip>
</Show>
)
}