import { useMemo } from 'react'; import type { ImportInfo } from '../../types'; import { formatAbsoluteTime, formatRelativeTime, parseTimestamp } from '../../util/time'; import { CopyButton } from '../shared/CopyButton'; import { JsonViewer } from '../shared/JsonViewer'; import { Pill } from '../shared/Pill'; interface StateTabProps { state: unknown; importMeta?: ImportInfo | null; } interface StateJsonShape { title?: string; isCustomTitle?: boolean; lastPrompt?: string; forkedFrom?: string; createdAt?: string | number; updatedAt?: string | number; agents?: Record; custom?: Record & { imported_from_kimi_cli?: boolean }; } /** State tab — renders the raw `state.json` blob from session detail. * At the top, a handful of highlight cards surface the most-asked fields * (title / lastPrompt / created / updated / agent count). Below that, the * full JSON is shown via the shared JsonViewer so any custom fields the * upstream writer adds remain readable without code changes. */ export function StateTab({ state, importMeta }: StateTabProps) { const s = useMemo(() => { return (state ?? {}) as StateJsonShape; }, [state]); const createdMs = parseTimestamp(s.createdAt); const updatedMs = parseTimestamp(s.updatedAt); const agentIds = s.agents !== undefined ? Object.keys(s.agents) : []; const importedFromKimiCli = s.custom?.imported_from_kimi_cli === true; return (
{importMeta ? : null}
state.json
{importedFromKimiCli ? (
warning · this session is marked imported_from_kimi_cli and would normally be filtered out of the list.
) : null} {/* Highlight cards */}
{s.title !== undefined && s.title !== '' ? ( "{s.title}" ) : ( (none) )} {s.isCustomTitle === true ? ( custom ) : null} {s.forkedFrom !== undefined && s.forkedFrom !== '' ? ( {s.forkedFrom} ) : ( (none) )} {s.lastPrompt !== undefined && s.lastPrompt !== '' ? ( {s.lastPrompt} ) : ( (none) )} {agentIds.length === 0 ? ( (none) ) : ( {agentIds.map((id) => ( {id} ))} )}
{/* Custom blob */}

custom

{s.custom === undefined || Object.keys(s.custom).length === 0 ? ( (empty) ) : ( )}
{/* Raw JSON */}

raw state.json

); } function Card({ label, children }: { label: string; children: import('react').ReactNode }) { return (
{label}
{children}
); } /** Export-bundle provenance, shown above state.json for imported sessions. */ function ManifestCard({ meta }: { meta: ImportInfo }) { const m = meta.manifest; const candidates: [string, string | undefined][] = [ ['original session', m?.sessionId], ['kimi-code version', m?.kimiCodeVersion], ['wire protocol', m?.wireProtocolVersion], ['os', m?.os], ['node', m?.nodejsVersion], ['install source', m?.installSource], ['workspace', m?.workspaceDir], ['exported at', m?.exportedAt ? `${formatAbsoluteTime(Date.parse(m.exportedAt))} (${formatRelativeTime(Date.parse(m.exportedAt))})` : undefined], ['first activity', m?.sessionFirstActivity ? formatAbsoluteTime(Date.parse(m.sessionFirstActivity)) : undefined], ['last activity', m?.sessionLastActivity ? formatAbsoluteTime(Date.parse(m.sessionLastActivity)) : undefined], ['imported at', `${formatAbsoluteTime(Date.parse(meta.importedAt))} (${formatRelativeTime(Date.parse(meta.importedAt))})`], ['original file', meta.originalName ?? undefined], ]; const rows = candidates .filter((r): r is [string, string] => typeof r[1] === 'string' && r[1].length > 0) .map(([label, value]) => ({ label, value })); return (
imported bundle manifest
{rows.map((r) => (
{r.label} {r.value}
))}
{m === null ? (
manifest.json was missing or unreadable in this bundle
) : null}
); } function TsValue({ ms, raw }: { ms: number | null; raw: string | number | undefined }) { if (ms === null) { return raw !== undefined && raw !== '' ? ( {raw} ) : ( (none) ); } return ( {formatAbsoluteTime(ms)} ({formatRelativeTime(ms)}) ); }