Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion web/src/components/CenterPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { Component, Show } from "solid-js";

import {
ctxWindowColorClass,
formatCostUsd,
formatDuration,
formatPercent,
Expand Down Expand Up @@ -181,10 +182,14 @@ const SubagentChip: Component = () => {
);
};

/** Daemon hardcodes Session.CONTEXT_WINDOW = 1_000_000 today. */
const CONTEXT_WINDOW = 1_000_000;

const UsageStrip: Component = () => (
<Show when={focusedSession()?.usage}>
{(u) => (
<div class="grid grid-cols-2 gap-2 text-[11px] sm:grid-cols-4 lg:grid-cols-7">
<div class="grid grid-cols-2 gap-2 text-[11px] sm:grid-cols-4 lg:grid-cols-8">
<CtxStat />
<Stat label="Turns" value={String(u().numTurns)} />
<Stat label="Input" value={formatTokens(u().inputTokens)} />
<Stat label="Output" value={formatTokens(u().outputTokens)} />
Expand Down Expand Up @@ -235,4 +240,47 @@ const Stat: Component<{
</div>
);

/**
* Dedicated context-window stat — value comes with a colored progress
* bar so the user sees their headroom at a glance.
*/
const CtxStat: Component = () => (
<Show
when={focusedSession()?.usage?.lastTurnInputTokens}
fallback={<Stat label="Context" value="—" hint="awaiting first turn" />}
>
{(ctx) => {
const ratio = () => Math.min(ctx() / CONTEXT_WINDOW, 1);
return (
<div
class="flex flex-col gap-0.5 rounded border border-border bg-bg px-2 py-1.5"
title={`Last turn context = ${ctx().toLocaleString()} of ${CONTEXT_WINDOW.toLocaleString()} tokens`}
>
<span class="flex items-center justify-between text-[10px] uppercase tracking-wider text-fg-faint">
<span>Context</span>
<span class={`font-mono ${ctxWindowColorClass(ratio())}`}>
{formatPercent(ratio(), 0)}
</span>
</span>
<span class={`font-mono text-sm ${ctxWindowColorClass(ratio())}`}>
{formatTokens(ctx())}
</span>
<div class="mt-1 h-1 w-full overflow-hidden rounded-full bg-bg-active">
<div
class={`h-full transition-[width] duration-300 ${
ratio() < 0.6
? "bg-success/80"
: ratio() < 0.85
? "bg-warn/80"
: "bg-danger/80"
}`}
style={{ width: `${ratio() * 100}%` }}
/>
</div>
</div>
);
}}
</Show>
);

export default CenterPane;
23 changes: 23 additions & 0 deletions web/src/components/StatusBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
import { Component, Show } from "solid-js";

import {
ctxWindowColorClass,
elapsedSince,
formatCostUsd,
formatDuration,
formatPercent,
formatTokens,
} from "../lib/format";
import { identityLabel, identityColorClass } from "../lib/identity";
Expand Down Expand Up @@ -113,6 +115,7 @@ const SessionMetrics: Component = () => {
<span>{usage()?.numTurns ?? 0}</span>
<span class="text-fg-faint"> turn(s)</span>
</span>
<CtxWindowPill />
<span title="Cumulative input / output tokens">
<span class="text-fg-faint">⇣</span>{" "}
<span>{formatTokens(usage()?.inputTokens)}</span>
Expand Down Expand Up @@ -141,6 +144,26 @@ const SessionMetrics: Component = () => {
);
};

/** Daemon hardcodes Session.CONTEXT_WINDOW = 1_000_000 today. */
const CONTEXT_WINDOW = 1_000_000;

const CtxWindowPill: Component = () => (
<Show when={focusedSession()?.usage?.lastTurnInputTokens}>
{(ctx) => {
const ratio = () => ctx() / CONTEXT_WINDOW;
return (
<span
class={`flex items-center gap-1 ${ctxWindowColorClass(ratio())}`}
title={`Last turn context = ${ctx().toLocaleString()} of ${CONTEXT_WINDOW.toLocaleString()} (${formatPercent(ratio(), 1)})`}
>
<span class="text-fg-faint">ctx</span>
<span>{formatPercent(ratio(), 0)}</span>
</span>
);
}}
</Show>
);

const SearchHotkey: Component = () => (
<button
type="button"
Expand Down
10 changes: 10 additions & 0 deletions web/src/lib/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@ export function formatPercent(ratio: number | null | undefined, digits = 0): str
return `${(ratio * 100).toFixed(digits)}%`;
}

/**
* Context-window utilization color cue. <60% green, 60-85% warn,
* >85% danger. Returns Tailwind text-* classes.
*/
export function ctxWindowColorClass(ratio: number): string {
if (ratio < 0.6) return "text-success";
if (ratio < 0.85) return "text-warn";
return "text-danger";
}

/** HH:MM:SS for a timestamp string/number. Best-effort; falls back to the input. */
export function formatClock(ts: string | number): string {
const t = typeof ts === "number" ? ts : Date.parse(ts);
Expand Down