Buckets:
| import { createMemo, createEffect, on, onCleanup, For, Show } from "solid-js" | |
| import type { JSX } from "solid-js" | |
| import { useSync } from "@/context/sync" | |
| import { checksum } from "@opencode-ai/core/util/encode" | |
| import { findLast } from "@opencode-ai/core/util/array" | |
| import { same } from "@/utils/same" | |
| import { Icon } from "@opencode-ai/ui/icon" | |
| import { Accordion } from "@opencode-ai/ui/accordion" | |
| import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header" | |
| import { File } from "@opencode-ai/ui/file" | |
| import { Markdown } from "@opencode-ai/ui/markdown" | |
| import { ScrollView } from "@opencode-ai/ui/scroll-view" | |
| import type { Message, Part, UserMessage } from "@opencode-ai/sdk/v2/client" | |
| import { useLanguage } from "@/context/language" | |
| import { useProviders } from "@/hooks/use-providers" | |
| import { useSessionLayout } from "@/pages/session/session-layout" | |
| import { getSessionContextMetrics } from "./session-context-metrics" | |
| import { estimateSessionContextBreakdown, type SessionContextBreakdownKey } from "./session-context-breakdown" | |
| import { createSessionContextFormatter } from "./session-context-format" | |
| const BREAKDOWN_COLOR: Record<SessionContextBreakdownKey, string> = { | |
| system: "var(--syntax-info)", | |
| user: "var(--syntax-success)", | |
| assistant: "var(--syntax-property)", | |
| tool: "var(--syntax-warning)", | |
| other: "var(--syntax-comment)", | |
| } | |
| function Stat(props: { label: string; value: JSX.Element }) { | |
| return ( | |
| <div class="flex flex-col gap-1"> | |
| <div class="text-12-regular text-text-weak">{props.label}</div> | |
| <div class="text-12-medium text-text-strong">{props.value}</div> | |
| </div> | |
| ) | |
| } | |
| function RawMessageContent(props: { message: Message; getParts: (id: string) => Part[]; onRendered: () => void }) { | |
| const file = createMemo(() => { | |
| const parts = props.getParts(props.message.id) | |
| const contents = JSON.stringify({ message: props.message, parts }, null, 2) | |
| return { | |
| name: `${props.message.role}-${props.message.id}.json`, | |
| contents, | |
| cacheKey: checksum(contents), | |
| } | |
| }) | |
| return ( | |
| <File | |
| mode="text" | |
| file={file()} | |
| overflow="wrap" | |
| class="select-text" | |
| onRendered={() => requestAnimationFrame(props.onRendered)} | |
| /> | |
| ) | |
| } | |
| function RawMessage(props: { | |
| message: Message | |
| getParts: (id: string) => Part[] | |
| onRendered: () => void | |
| time: (value: number | undefined) => string | |
| }) { | |
| return ( | |
| <Accordion.Item value={props.message.id}> | |
| <StickyAccordionHeader> | |
| <Accordion.Trigger> | |
| <div class="flex items-center justify-between gap-2 w-full"> | |
| <div class="min-w-0 truncate"> | |
| {props.message.role} <span class="text-text-base">• {props.message.id}</span> | |
| </div> | |
| <div class="flex items-center gap-3"> | |
| <div class="shrink-0 text-12-regular text-text-weak">{props.time(props.message.time.created)}</div> | |
| <Icon name="chevron-grabber-vertical" size="small" class="shrink-0 text-text-weak" /> | |
| </div> | |
| </div> | |
| </Accordion.Trigger> | |
| </StickyAccordionHeader> | |
| <Accordion.Content class="bg-background-base"> | |
| <div class="p-3"> | |
| <RawMessageContent message={props.message} getParts={props.getParts} onRendered={props.onRendered} /> | |
| </div> | |
| </Accordion.Content> | |
| </Accordion.Item> | |
| ) | |
| } | |
| const emptyMessages: Message[] = [] | |
| const emptyUserMessages: UserMessage[] = [] | |
| export function SessionContextTab() { | |
| const sync = useSync() | |
| const language = useLanguage() | |
| const providers = useProviders() | |
| const { params, view } = useSessionLayout() | |
| const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined)) | |
| const messages = createMemo( | |
| () => { | |
| const id = params.id | |
| if (!id) return emptyMessages | |
| return (sync.data.message[id] ?? []) as Message[] | |
| }, | |
| emptyMessages, | |
| { equals: same }, | |
| ) | |
| const userMessages = createMemo( | |
| () => messages().filter((m) => m.role === "user") as UserMessage[], | |
| emptyUserMessages, | |
| { equals: same }, | |
| ) | |
| const visibleUserMessages = createMemo( | |
| () => { | |
| const revert = info()?.revert?.messageID | |
| if (!revert) return userMessages() | |
| return userMessages().filter((m) => m.id < revert) | |
| }, | |
| emptyUserMessages, | |
| { equals: same }, | |
| ) | |
| const usd = createMemo( | |
| () => | |
| new Intl.NumberFormat(language.intl(), { | |
| style: "currency", | |
| currency: "USD", | |
| }), | |
| ) | |
| const metrics = createMemo(() => getSessionContextMetrics(messages(), [...providers.all().values()])) | |
| const ctx = createMemo(() => metrics().context) | |
| const formatter = createMemo(() => createSessionContextFormatter(language.intl())) | |
| const cost = createMemo(() => { | |
| return usd().format(metrics().totalCost) | |
| }) | |
| const counts = createMemo(() => { | |
| const all = messages() | |
| const user = all.reduce((count, x) => count + (x.role === "user" ? 1 : 0), 0) | |
| const assistant = all.reduce((count, x) => count + (x.role === "assistant" ? 1 : 0), 0) | |
| return { | |
| all: all.length, | |
| user, | |
| assistant, | |
| } | |
| }) | |
| const systemPrompt = createMemo(() => { | |
| const msg = findLast(visibleUserMessages(), (m) => !!m.system) | |
| const system = msg?.system | |
| if (!system) return | |
| const trimmed = system.trim() | |
| if (!trimmed) return | |
| return trimmed | |
| }) | |
| const providerLabel = createMemo(() => { | |
| const c = ctx() | |
| if (!c) return "—" | |
| return c.providerLabel | |
| }) | |
| const modelLabel = createMemo(() => { | |
| const c = ctx() | |
| if (!c) return "—" | |
| return c.modelLabel | |
| }) | |
| const breakdown = createMemo( | |
| on( | |
| () => [ctx()?.message.id, ctx()?.input, messages().length, systemPrompt()], | |
| () => { | |
| const c = ctx() | |
| if (!c?.input) return [] | |
| return estimateSessionContextBreakdown({ | |
| messages: messages(), | |
| parts: sync.data.part as Record<string, Part[] | undefined>, | |
| input: c.input, | |
| systemPrompt: systemPrompt(), | |
| }) | |
| }, | |
| ), | |
| ) | |
| const breakdownLabel = (key: SessionContextBreakdownKey) => { | |
| if (key === "system") return language.t("context.breakdown.system") | |
| if (key === "user") return language.t("context.breakdown.user") | |
| if (key === "assistant") return language.t("context.breakdown.assistant") | |
| if (key === "tool") return language.t("context.breakdown.tool") | |
| return language.t("context.breakdown.other") | |
| } | |
| const stats = [ | |
| { label: "context.stats.session", value: () => info()?.title ?? params.id ?? "—" }, | |
| { label: "context.stats.messages", value: () => counts().all.toLocaleString(language.intl()) }, | |
| { label: "context.stats.provider", value: providerLabel }, | |
| { label: "context.stats.model", value: modelLabel }, | |
| { label: "context.stats.limit", value: () => formatter().number(ctx()?.limit) }, | |
| { label: "context.stats.totalTokens", value: () => formatter().number(ctx()?.total) }, | |
| { label: "context.stats.usage", value: () => formatter().percent(ctx()?.usage) }, | |
| { label: "context.stats.inputTokens", value: () => formatter().number(ctx()?.input) }, | |
| { label: "context.stats.outputTokens", value: () => formatter().number(ctx()?.output) }, | |
| { label: "context.stats.reasoningTokens", value: () => formatter().number(ctx()?.reasoning) }, | |
| { | |
| label: "context.stats.cacheTokens", | |
| value: () => `${formatter().number(ctx()?.cacheRead)} / ${formatter().number(ctx()?.cacheWrite)}`, | |
| }, | |
| { label: "context.stats.userMessages", value: () => counts().user.toLocaleString(language.intl()) }, | |
| { label: "context.stats.assistantMessages", value: () => counts().assistant.toLocaleString(language.intl()) }, | |
| { label: "context.stats.totalCost", value: cost }, | |
| { label: "context.stats.sessionCreated", value: () => formatter().time(info()?.time.created) }, | |
| { label: "context.stats.lastActivity", value: () => formatter().time(ctx()?.message.time.created) }, | |
| ] satisfies { label: string; value: () => JSX.Element }[] | |
| let scroll: HTMLDivElement | undefined | |
| let frame: number | undefined | |
| let pending: { x: number; y: number } | undefined | |
| const getParts = (id: string) => (sync.data.part[id] ?? []) as Part[] | |
| const restoreScroll = () => { | |
| const el = scroll | |
| if (!el) return | |
| const s = view().scroll("context") | |
| if (!s) return | |
| if (el.scrollTop !== s.y) el.scrollTop = s.y | |
| if (el.scrollLeft !== s.x) el.scrollLeft = s.x | |
| } | |
| const handleScroll = (event: Event & { currentTarget: HTMLDivElement }) => { | |
| pending = { | |
| x: event.currentTarget.scrollLeft, | |
| y: event.currentTarget.scrollTop, | |
| } | |
| if (frame !== undefined) return | |
| frame = requestAnimationFrame(() => { | |
| frame = undefined | |
| const next = pending | |
| pending = undefined | |
| if (!next) return | |
| view().setScroll("context", next) | |
| }) | |
| } | |
| createEffect( | |
| on( | |
| () => messages().length, | |
| () => { | |
| requestAnimationFrame(restoreScroll) | |
| }, | |
| { defer: true }, | |
| ), | |
| ) | |
| onCleanup(() => { | |
| if (frame === undefined) return | |
| cancelAnimationFrame(frame) | |
| }) | |
| return ( | |
| <ScrollView | |
| class="@container h-full" | |
| viewportRef={(el) => { | |
| scroll = el | |
| restoreScroll() | |
| }} | |
| onScroll={handleScroll} | |
| > | |
| <div class="px-6 pt-4 pb-10 flex flex-col gap-10"> | |
| <div class="grid grid-cols-1 @[32rem]:grid-cols-2 gap-4"> | |
| <For each={stats}> | |
| {(stat) => <Stat label={language.t(stat.label as Parameters<typeof language.t>[0])} value={stat.value()} />} | |
| </For> | |
| </div> | |
| <Show when={breakdown().length > 0}> | |
| <div class="flex flex-col gap-2"> | |
| <div class="text-12-regular text-text-weak">{language.t("context.breakdown.title")}</div> | |
| <div class="h-2 w-full rounded-full bg-surface-base overflow-hidden flex"> | |
| <For each={breakdown()}> | |
| {(segment) => ( | |
| <div | |
| class="h-full" | |
| style={{ | |
| width: `${segment.width}%`, | |
| "background-color": BREAKDOWN_COLOR[segment.key], | |
| }} | |
| /> | |
| )} | |
| </For> | |
| </div> | |
| <div class="flex flex-wrap gap-x-3 gap-y-1"> | |
| <For each={breakdown()}> | |
| {(segment) => ( | |
| <div class="flex items-center gap-1 text-11-regular text-text-weak"> | |
| <div class="size-2 rounded-sm" style={{ "background-color": BREAKDOWN_COLOR[segment.key] }} /> | |
| <div>{breakdownLabel(segment.key)}</div> | |
| <div class="text-text-weaker">{segment.percent.toLocaleString(language.intl())}%</div> | |
| </div> | |
| )} | |
| </For> | |
| </div> | |
| <div class="hidden text-11-regular text-text-weaker">{language.t("context.breakdown.note")}</div> | |
| </div> | |
| </Show> | |
| <Show when={systemPrompt()}> | |
| {(prompt) => ( | |
| <div class="flex flex-col gap-2"> | |
| <div class="text-12-regular text-text-weak">{language.t("context.systemPrompt.title")}</div> | |
| <div class="border border-border-base rounded-md bg-surface-base px-3 py-2"> | |
| <Markdown text={prompt()} class="text-12-regular" /> | |
| </div> | |
| </div> | |
| )} | |
| </Show> | |
| <div class="flex flex-col gap-2"> | |
| <div class="text-12-regular text-text-weak">{language.t("context.rawMessages.title")}</div> | |
| <Accordion multiple> | |
| <For each={messages()}> | |
| {(message) => ( | |
| <RawMessage message={message} getParts={getParts} onRendered={restoreScroll} time={formatter().time} /> | |
| )} | |
| </For> | |
| </Accordion> | |
| </div> | |
| </div> | |
| </ScrollView> | |
| ) | |
| } | |
Xet Storage Details
- Size:
- 11.9 kB
- Xet hash:
- 6ba0f32d7c209be1a7f0c0e38f8b1d03b79c79716b25747bfa199900d988ed1b
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.