Download apps/vis/web/src/components/context/ContextTab.tsx from SaylorTwift/kimi-code: direct link, hf CLI and curl.
- Browser
- Download file 12.5 kB
-
https://huggingface.co/SaylorTwift/kimi-code/resolve/main/apps/vis/web/src/components/context/ContextTab.tsx
- Command line
-
hf download hf://SaylorTwift/kimi-code/apps/vis/web/src/components/context/ContextTab.tsx
-
curl -L -o ContextTab.tsx https://huggingface.co/SaylorTwift/kimi-code/resolve/main/apps/vis/web/src/components/context/ContextTab.tsx
12.5 kB
| import { useEffect, useState } from 'react'; | |
| import { useContext } from '../../hooks/useContext'; | |
| import { useSession } from '../../hooks/useSession'; | |
| import type { TokenUsage } from '../../types'; | |
| import { Pill } from '../shared/Pill'; | |
| import { ClearRibbon } from './ClearRibbon'; | |
| import { CompactionRibbon } from './CompactionRibbon'; | |
| import { MessageBubble } from './MessageBubble'; | |
| import { UndoRibbon } from './UndoRibbon'; | |
| interface ContextTabProps { | |
| sessionId: string; | |
| /** Override starting agentId; defaults to 'main'. */ | |
| initialAgentId?: string; | |
| } | |
| export function ContextTab({ sessionId, initialAgentId = 'main' }: ContextTabProps) { | |
| const [agentId, setAgentId] = useState<string>(initialAgentId); | |
| const [history, setHistory] = useState<'model' | 'full'>('model'); | |
| // Re-sync on session OR agent id change — see WireTab for the same | |
| // rationale (session navigation must reset a stale subagent pick). | |
| useEffect(() => { | |
| setAgentId(initialAgentId); | |
| }, [sessionId, initialAgentId]); | |
| const { data: detail } = useSession(sessionId); | |
| const { data: ctx, isLoading, error } = useContext(sessionId, agentId, history); | |
| const agents = detail?.agents ?? []; | |
| const messages = ctx?.messages ?? []; | |
| const sessionUsage = ctx?.usage.byScope.session ?? EMPTY_USAGE; | |
| const turnUsage = ctx?.usage.byScope.turn ?? EMPTY_USAGE; | |
| const cumulativeUsage: TokenUsage = { | |
| inputOther: sessionUsage.inputOther + turnUsage.inputOther, | |
| output: sessionUsage.output + turnUsage.output, | |
| inputCacheRead: sessionUsage.inputCacheRead + turnUsage.inputCacheRead, | |
| inputCacheCreation: sessionUsage.inputCacheCreation + turnUsage.inputCacheCreation, | |
| }; | |
| // Live context-window fill (latest step.end usage), distinct from the | |
| // cumulative session-scoped + turn-scoped spend the bar breaks down. | |
| const contextTokens = ctx?.contextTokens ?? 0; | |
| const config = ctx?.config ?? {}; | |
| const permissionMode = ctx?.permission.mode ?? null; | |
| const planActive = ctx?.planMode.active ?? false; | |
| const goal = ctx?.goal ?? null; | |
| const swarmActive = ctx?.swarm.active ?? false; | |
| return ( | |
| <div className="flex min-h-0 flex-1 flex-col"> | |
| {/* Toolbar — agent selector + status pills */} | |
| <div className="flex shrink-0 items-center gap-3 border-b border-border bg-surface-1 px-3 py-2"> | |
| <label className="flex items-center gap-2 font-mono text-[11px] text-fg-2"> | |
| <span className="text-fg-3">agent</span> | |
| <select | |
| value={agentId} | |
| onChange={(e) => { | |
| setAgentId(e.target.value); | |
| }} | |
| className="border border-border bg-surface-0 px-2 py-1 font-mono text-[12px] text-fg-0 focus:border-border-strong focus:outline-none" | |
| > | |
| {agents.length === 0 ? <option value={agentId}>{agentId}</option> : null} | |
| {agents.map((a) => ( | |
| <option key={a.agentId} value={a.agentId}> | |
| {a.agentId} ({a.type} | |
| {a.profileName ? ` · ${a.profileName}` : ''} | |
| {a.parentAgentId ? ` ← ${a.parentAgentId}` : ''}) | |
| </option> | |
| ))} | |
| </select> | |
| </label> | |
| <span className="font-mono text-[11px] text-fg-2"> | |
| <span className="tabular text-fg-0">{messages.length}</span> | |
| <span className="ml-1 text-fg-3">messages</span> | |
| </span> | |
| {config.modelAlias ? ( | |
| <span className="font-mono text-[11px] text-fg-2"> | |
| <span className="text-fg-3">model</span>{' '} | |
| <span className="text-fg-0">{config.modelAlias}</span> | |
| </span> | |
| ) : null} | |
| <div className="ml-auto flex items-center gap-2"> | |
| {/* History view toggle — 'model' (post-compaction, what the model | |
| sees) vs 'full' (full reconstructed history for debugging). */} | |
| <div | |
| role="group" | |
| aria-label="history view" | |
| className="flex items-center overflow-hidden border border-border" | |
| > | |
| <button | |
| type="button" | |
| aria-pressed={history === 'model'} | |
| onClick={() => { | |
| setHistory('model'); | |
| }} | |
| className={[ | |
| 'px-2 py-1 font-mono text-[11px] transition-colors', | |
| history === 'model' | |
| ? 'bg-surface-2 text-fg-0' | |
| : 'bg-surface-0 text-fg-3 hover:text-fg-1', | |
| ].join(' ')} | |
| > | |
| model | |
| </button> | |
| <button | |
| type="button" | |
| aria-pressed={history === 'full'} | |
| onClick={() => { | |
| setHistory('full'); | |
| }} | |
| className={[ | |
| 'border-l border-border px-2 py-1 font-mono text-[11px] transition-colors', | |
| history === 'full' | |
| ? 'bg-surface-2 text-fg-0' | |
| : 'bg-surface-0 text-fg-3 hover:text-fg-1', | |
| ].join(' ')} | |
| > | |
| full history | |
| </button> | |
| </div> | |
| {permissionMode ? ( | |
| <Pill tone="approval" variant="outline">permission: {permissionMode}</Pill> | |
| ) : null} | |
| {planActive ? ( | |
| <Pill tone="info" variant="solid">plan mode</Pill> | |
| ) : null} | |
| {swarmActive ? ( | |
| <Pill tone="subagent" variant="solid">swarm mode</Pill> | |
| ) : null} | |
| </div> | |
| </div> | |
| {/* Full-history hint — clarifies that this is the reconstructed history | |
| and the model itself only sees the compacted view. */} | |
| {history === 'full' ? ( | |
| <div className="shrink-0 border-b border-border bg-surface-1 px-3 py-1 font-mono text-[10.5px] text-fg-3"> | |
| full reconstructed history — the model actually sees the compacted view | |
| </div> | |
| ) : null} | |
| {/* Live context-window fill (contextTokens) + the 4-segment cumulative | |
| session-scoped and turn-scoped usage breakdown. */} | |
| <TokenBar usage={cumulativeUsage} contextTokens={contextTokens} /> | |
| {/* Message stream */} | |
| <div className="min-h-0 flex-1 overflow-y-auto"> | |
| <div className="flex flex-col gap-3 px-3 py-4"> | |
| {goal ? ( | |
| <div className="rounded border border-[var(--color-cat-lifecycle)]/40 bg-surface-0 p-2"> | |
| <div className="mb-1 flex items-center gap-2"> | |
| <Pill tone="lifecycle" variant="soft">goal</Pill> | |
| {goal.status ? <Pill tone="info" variant="outline">{goal.status}</Pill> : null} | |
| </div> | |
| <div className="font-mono text-[12px] text-fg-1">{goal.objective}</div> | |
| {goal.completionCriterion ? ( | |
| <div className="mt-1 font-mono text-[11px] text-fg-3"> | |
| done when: {goal.completionCriterion} | |
| </div> | |
| ) : null} | |
| </div> | |
| ) : null} | |
| {config.systemPrompt ? <SystemPromptBubble text={config.systemPrompt} /> : null} | |
| {isLoading ? ( | |
| <div className="px-3 py-2 font-mono text-[12px] text-fg-3">loading context…</div> | |
| ) : error ? ( | |
| <div className="px-3 py-2 font-mono text-[12px] text-[var(--color-sev-error)]"> | |
| {(error as Error).message} | |
| </div> | |
| ) : messages.length === 0 ? ( | |
| <div className="px-3 py-2 font-mono text-[12px] text-fg-3"> | |
| no messages — session has only lifecycle/config records so far. | |
| </div> | |
| ) : ( | |
| messages.map((m) => { | |
| if (m.source === 'compaction_summary') { | |
| return <CompactionRibbon key={m.lineNo} message={m} />; | |
| } | |
| if (m.source === 'undo') { | |
| return <UndoRibbon key={m.lineNo} message={m} />; | |
| } | |
| if (m.source === 'clear') { | |
| return <ClearRibbon key={m.lineNo} message={m} />; | |
| } | |
| return <MessageBubble key={m.lineNo} message={m} />; | |
| }) | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| const EMPTY_USAGE: TokenUsage = { | |
| inputOther: 0, | |
| output: 0, | |
| inputCacheRead: 0, | |
| inputCacheCreation: 0, | |
| }; | |
| // Colors are chosen from the existing semantic palette so the bar reads | |
| // coherently with the rest of the app: | |
| // inputCacheRead = success (cache hit — the "good" share) | |
| // inputOther = info (billed input) | |
| // output = assistant (what the model produced) | |
| // inputCacheCreation = warning (billed once, amortised next call) | |
| const SEG_COLORS = { | |
| inputCacheRead: 'var(--color-sev-success)', | |
| inputOther: 'var(--color-sev-info)', | |
| output: 'var(--color-assistant)', | |
| inputCacheCreation: 'var(--color-sev-warning)', | |
| } as const; | |
| function TokenBar({ usage, contextTokens }: { usage: TokenUsage; contextTokens: number }) { | |
| const total = | |
| usage.inputOther + usage.output + usage.inputCacheRead + usage.inputCacheCreation; | |
| return ( | |
| <div className="shrink-0"> | |
| {contextTokens > 0 ? ( | |
| <div className="flex items-center justify-end gap-1 border-b border-border bg-surface-1 px-3 py-1 font-mono text-[10px] text-fg-2"> | |
| <span className="text-fg-3">context</span> | |
| <span className="tabular text-fg-0">{contextTokens.toLocaleString()}</span> | |
| <span className="text-fg-3">tok</span> | |
| </div> | |
| ) : null} | |
| {total === 0 ? ( | |
| <div className="h-[2px] bg-border" /> | |
| ) : ( | |
| <div | |
| className="flex h-[3px] w-full" | |
| title={ | |
| `cache_read ${usage.inputCacheRead.toLocaleString()} · ` + | |
| `input ${usage.inputOther.toLocaleString()} · ` + | |
| `output ${usage.output.toLocaleString()} · ` + | |
| `cache_create ${usage.inputCacheCreation.toLocaleString()}` | |
| } | |
| > | |
| {usage.inputCacheRead > 0 ? ( | |
| <div | |
| style={{ | |
| width: `${seg(usage.inputCacheRead, total)}%`, | |
| backgroundColor: SEG_COLORS.inputCacheRead, | |
| }} | |
| /> | |
| ) : null} | |
| {usage.inputOther > 0 ? ( | |
| <div | |
| style={{ | |
| width: `${seg(usage.inputOther, total)}%`, | |
| backgroundColor: SEG_COLORS.inputOther, | |
| }} | |
| /> | |
| ) : null} | |
| {usage.output > 0 ? ( | |
| <div | |
| style={{ width: `${seg(usage.output, total)}%`, backgroundColor: SEG_COLORS.output }} | |
| /> | |
| ) : null} | |
| {usage.inputCacheCreation > 0 ? ( | |
| <div | |
| style={{ | |
| width: `${seg(usage.inputCacheCreation, total)}%`, | |
| backgroundColor: SEG_COLORS.inputCacheCreation, | |
| }} | |
| /> | |
| ) : null} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |
| function seg(n: number, total: number): number { | |
| return (n / total) * 100; | |
| } | |
| function SystemPromptBubble({ text }: { text: string }) { | |
| const [open, setOpen] = useState(false); | |
| return ( | |
| <article | |
| className="relative flex max-w-full min-w-0 flex-col border-l-[3px] bg-surface-1" | |
| style={{ borderLeftColor: 'var(--color-cat-config)' }} | |
| > | |
| <button | |
| type="button" | |
| onClick={() => { | |
| setOpen((v) => !v); | |
| }} | |
| className="flex w-full items-center justify-between gap-3 px-3 py-2 text-left hover:bg-surface-2" | |
| > | |
| <span className="flex items-center gap-2"> | |
| <Pill tone="config" variant="solid">system</Pill> | |
| <span className="font-mono text-[10px] text-fg-3 tabular"> | |
| {text.length.toLocaleString()} chars | |
| </span> | |
| </span> | |
| <span className="font-mono text-[11px] text-fg-1"> | |
| {open ? '▾ collapse' : '▸ show full'} | |
| </span> | |
| </button> | |
| <div className="relative min-w-0 px-3 pb-2"> | |
| <pre | |
| className={[ | |
| 'min-w-0 whitespace-pre-wrap [overflow-wrap:anywhere] font-mono text-[12.5px] text-fg-0', | |
| open ? '' : 'max-h-[9em] overflow-hidden', | |
| ].join(' ')} | |
| > | |
| {text} | |
| </pre> | |
| {!open ? ( | |
| <div | |
| aria-hidden="true" | |
| className="pointer-events-none absolute inset-x-0 bottom-0 h-14" | |
| style={{ | |
| background: 'linear-gradient(to bottom, transparent 0%, var(--color-surface-1) 85%)', | |
| }} | |
| /> | |
| ) : null} | |
| </div> | |
| </article> | |
| ); | |
| } | |