@flanksource/clicky-ui 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai.cjs +15 -0
- package/dist/ai.cjs.map +1 -1
- package/dist/ai.js +15 -0
- package/dist/ai.js.map +1 -1
- package/dist/data/ProgressBars.cjs +194 -0
- package/dist/data/ProgressBars.cjs.map +1 -0
- package/dist/data/ProgressBars.d.ts +85 -0
- package/dist/data/ProgressBars.d.ts.map +1 -0
- package/dist/data/ProgressBars.js +194 -0
- package/dist/data/ProgressBars.js.map +1 -0
- package/dist/data/ProgressBars.model.cjs +27 -0
- package/dist/data/ProgressBars.model.cjs.map +1 -0
- package/dist/data/ProgressBars.model.d.ts +20 -0
- package/dist/data/ProgressBars.model.d.ts.map +1 -0
- package/dist/data/ProgressBars.model.js +27 -0
- package/dist/data/ProgressBars.model.js.map +1 -0
- package/dist/data/TimeseriesCoreBars.cjs +19 -173
- package/dist/data/TimeseriesCoreBars.cjs.map +1 -1
- package/dist/data/TimeseriesCoreBars.d.ts +9 -26
- package/dist/data/TimeseriesCoreBars.d.ts.map +1 -1
- package/dist/data/TimeseriesCoreBars.js +20 -174
- package/dist/data/TimeseriesCoreBars.js.map +1 -1
- package/dist/data/WorkloadCard.model.cjs.map +1 -1
- package/dist/data/WorkloadCard.model.d.ts +2 -2
- package/dist/data/WorkloadCard.model.d.ts.map +1 -1
- package/dist/data/WorkloadCard.model.js.map +1 -1
- package/dist/data/ai/SessionViewer.cjs +275 -0
- package/dist/data/ai/SessionViewer.cjs.map +1 -0
- package/dist/data/ai/SessionViewer.d.ts +33 -0
- package/dist/data/ai/SessionViewer.d.ts.map +1 -0
- package/dist/data/ai/SessionViewer.fixtures.d.ts +4 -0
- package/dist/data/ai/SessionViewer.fixtures.d.ts.map +1 -0
- package/dist/data/ai/SessionViewer.js +275 -0
- package/dist/data/ai/SessionViewer.js.map +1 -0
- package/dist/data/ai/SessionViewer.model.cjs +275 -0
- package/dist/data/ai/SessionViewer.model.cjs.map +1 -0
- package/dist/data/ai/SessionViewer.model.d.ts +87 -0
- package/dist/data/ai/SessionViewer.model.d.ts.map +1 -0
- package/dist/data/ai/SessionViewer.model.js +275 -0
- package/dist/data/ai/SessionViewer.model.js.map +1 -0
- package/dist/data/ai/SessionViewerMenu.cjs +190 -0
- package/dist/data/ai/SessionViewerMenu.cjs.map +1 -0
- package/dist/data/ai/SessionViewerMenu.d.ts +24 -0
- package/dist/data/ai/SessionViewerMenu.d.ts.map +1 -0
- package/dist/data/ai/SessionViewerMenu.js +190 -0
- package/dist/data/ai/SessionViewerMenu.js.map +1 -0
- package/dist/data/ai/index.d.ts +3 -0
- package/dist/data/ai/index.d.ts.map +1 -1
- package/dist/data/ai/session-categories.cjs +219 -0
- package/dist/data/ai/session-categories.cjs.map +1 -0
- package/dist/data/ai/session-categories.d.ts +36 -0
- package/dist/data/ai/session-categories.d.ts.map +1 -0
- package/dist/data/ai/session-categories.js +219 -0
- package/dist/data/ai/session-categories.js.map +1 -0
- package/dist/data/test-runner/TestRunner.fixtures.d.ts.map +1 -1
- package/dist/data/version-info.cjs +3 -3
- package/dist/data/version-info.cjs.map +1 -1
- package/dist/data/version-info.js +3 -3
- package/dist/data/version-info.js.map +1 -1
- package/dist/data.cjs +4 -2
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.ts +3 -2
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +4 -2
- package/dist/data.js.map +1 -1
- package/dist/index.cjs +4 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/dist/data/TimeseriesCoreBars.model.cjs +0 -27
- package/dist/data/TimeseriesCoreBars.model.cjs.map +0 -1
- package/dist/data/TimeseriesCoreBars.model.d.ts +0 -20
- package/dist/data/TimeseriesCoreBars.model.d.ts.map +0 -1
- package/dist/data/TimeseriesCoreBars.model.js +0 -27
- package/dist/data/TimeseriesCoreBars.model.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SessionViewer.cjs","sources":["../../../src/data/ai/SessionViewer.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { Icon, type StaticIconComponent } from \"../Icon\";\nimport { CodeBlock } from \"../CodeBlock\";\nimport { useDensityValue, type Density } from \"../../hooks/use-density\";\nimport { DensityValueProvider } from \"../../hooks/density-provider\";\nimport {\n UiBrain,\n UiChevronDown,\n UiRobotAi,\n UiSparkles,\n UiUserCircle,\n UiWarningTriangle,\n} from \"../../icons\";\nimport {\n getSessionAction,\n normalizeSession,\n summarizeSession,\n summarizeToolInput,\n type SessionEvent,\n type SessionInput,\n type SessionTone,\n} from \"./SessionViewer.model\";\nimport {\n collectSessionFilters,\n isEventVisible,\n type SessionCategory,\n} from \"./session-categories\";\nimport { SessionViewerMenu, type SessionThemeOverride } from \"./SessionViewerMenu\";\n\nexport type {\n SessionEntry,\n SessionEvent,\n SessionInput,\n SessionActionMeta,\n} from \"./SessionViewer.model\";\nexport type { SessionThemeOverride } from \"./SessionViewerMenu\";\n\nexport interface SessionViewerProps {\n /** A captain session: parsed `SessionEntry[]` or raw log text (JSON / JSONL). */\n session: SessionInput;\n className?: string;\n /** Expand each tool call's input/output by default. Defaults to false. */\n defaultExpanded?: boolean;\n /** Render assistant reasoning (\"thinking\") blocks. Defaults to true. */\n showThinking?: boolean;\n /** Show the summary header (model + action counts). Defaults to true. */\n showHeader?: boolean;\n /** Show the 3-dot menu (density + category/tool/source filters). Defaults to true. */\n showMenu?: boolean;\n /** Initial density override; undefined inherits the page/document density. */\n defaultDensity?: Density;\n /** Initial theme override; undefined inherits the page/document `data-theme`. */\n defaultTheme?: SessionThemeOverride;\n}\n\n// Disc colors per tone. The dark variants key off a `[data-theme=\"dark\"]`\n// ancestor (the document attribute set by ThemeProvider, or the component-level\n// override painted on this viewer's root) rather than Tailwind's `dark:` —\n// which this library compiles to `prefers-color-scheme` and so would ignore the\n// `data-theme` attribute. Written as literal class strings so Tailwind scans them.\nconst DISC_TONE: Record<SessionTone, string> = {\n sky: \"bg-sky-100 text-sky-700 [[data-theme=dark]_&]:bg-sky-500/15 [[data-theme=dark]_&]:text-sky-300\",\n amber: \"bg-amber-100 text-amber-700 [[data-theme=dark]_&]:bg-amber-500/15 [[data-theme=dark]_&]:text-amber-300\",\n violet: \"bg-violet-100 text-violet-700 [[data-theme=dark]_&]:bg-violet-500/15 [[data-theme=dark]_&]:text-violet-300\",\n emerald: \"bg-emerald-100 text-emerald-700 [[data-theme=dark]_&]:bg-emerald-500/15 [[data-theme=dark]_&]:text-emerald-300\",\n rose: \"bg-rose-100 text-rose-700 [[data-theme=dark]_&]:bg-rose-500/15 [[data-theme=dark]_&]:text-rose-300\",\n indigo: \"bg-indigo-100 text-indigo-700 [[data-theme=dark]_&]:bg-indigo-500/15 [[data-theme=dark]_&]:text-indigo-300\",\n fuchsia: \"bg-fuchsia-100 text-fuchsia-700 [[data-theme=dark]_&]:bg-fuchsia-500/15 [[data-theme=dark]_&]:text-fuchsia-300\",\n pink: \"bg-pink-100 text-pink-700 [[data-theme=dark]_&]:bg-pink-500/15 [[data-theme=dark]_&]:text-pink-300\",\n slate: \"bg-muted text-muted-foreground\",\n};\n\nfunction toggleInSet<T>(set: ReadonlySet<T>, value: T): Set<T> {\n const next = new Set(set);\n if (next.has(value)) next.delete(value);\n else next.add(value);\n return next;\n}\n\n/**\n * SessionViewer renders a recorded AI coding-agent session (the captain\n * `pkg/ai/history` JSON schema — Claude Code / Codex transcripts) as a vertical\n * action log. Each entry sits on a tone-colored icon disc from the Flanksource\n * \"Agent Action Icons\" set; user prompts/selections are right-aligned like a\n * chat. Tool calls expand to their input and response. The \"3-dot\" menu mirrors\n * `captain history` filtering — toggle density and hide categories, tools or\n * sources.\n */\nexport function SessionViewer({\n session,\n className,\n defaultExpanded = false,\n showThinking = true,\n showHeader = true,\n showMenu = true,\n defaultDensity,\n defaultTheme,\n}: SessionViewerProps) {\n const allEvents = useMemo(() => normalizeSession(session), [session]);\n\n const pageDensity = useDensityValue();\n const [densityOverride, setDensityOverride] = useState<Density | undefined>(defaultDensity);\n const [themeOverride, setThemeOverride] = useState<SessionThemeOverride | undefined>(defaultTheme);\n const [hiddenCategories, setHiddenCategories] = useState<ReadonlySet<SessionCategory>>(\n () => new Set(),\n );\n const [hiddenTools, setHiddenTools] = useState<ReadonlySet<string>>(() => new Set());\n const [hiddenSources, setHiddenSources] = useState<ReadonlySet<string>>(() => new Set());\n // Undefined = follow the `showThinking` prop; the menu toggle sets an override.\n const [showThinkingOverride, setShowThinkingOverride] = useState<boolean | undefined>(undefined);\n const effectiveShowThinking = showThinkingOverride ?? showThinking;\n\n const filters = useMemo(() => collectSessionFilters(allEvents), [allEvents]);\n const hasThinking = useMemo(() => allEvents.some((e) => e.kind === \"thinking\"), [allEvents]);\n\n const visibility = { hiddenCategories, hiddenTools, hiddenSources, showThinking: effectiveShowThinking };\n const events = allEvents.filter((event) => isEventVisible(event, visibility));\n\n if (allEvents.length === 0) {\n return (\n <div\n className={cn(\n \"rounded-md border border-dashed border-border p-density-4 text-center text-sm text-muted-foreground\",\n className,\n )}\n >\n No session activity.\n </div>\n );\n }\n\n const summary = summarizeSession(allEvents);\n const effectiveDensity = densityOverride ?? pageDensity;\n const dataAttrs: Record<string, string> = {\n ...(densityOverride ? { \"data-density\": densityOverride } : {}),\n ...(themeOverride ? { \"data-theme\": themeOverride } : {}),\n };\n\n return (\n <div className={cn(\"text-sm\", className)} {...dataAttrs}>\n <DensityValueProvider density={effectiveDensity}>\n {(showHeader || showMenu) && (\n <div className=\"mb-density-3 flex items-center justify-between gap-density-3\">\n <div className=\"flex flex-wrap items-center gap-x-density-3 gap-y-1 text-xs text-muted-foreground\">\n {showHeader && summary.model && (\n <span className=\"inline-flex items-center gap-1 font-medium text-foreground\">\n <Icon icon={UiRobotAi} className=\"size-3.5\" />\n {summary.model}\n </span>\n )}\n {showHeader && <span>{summary.toolCount} actions</span>}\n {showHeader && <span>{summary.messageCount} messages</span>}\n </div>\n {showMenu && (\n <SessionViewerMenu\n density={densityOverride}\n onDensityChange={setDensityOverride}\n theme={themeOverride}\n onThemeChange={setThemeOverride}\n filters={filters}\n hiddenCategories={hiddenCategories}\n hiddenTools={hiddenTools}\n hiddenSources={hiddenSources}\n onToggleCategory={(category) =>\n setHiddenCategories((set) => toggleInSet(set, category))\n }\n onToggleTool={(tool) => setHiddenTools((set) => toggleInSet(set, tool))}\n onToggleSource={(source) => setHiddenSources((set) => toggleInSet(set, source))}\n showThinking={effectiveShowThinking}\n onToggleThinking={() => setShowThinkingOverride(!effectiveShowThinking)}\n hasThinking={hasThinking}\n />\n )}\n </div>\n )}\n\n {events.length === 0 ? (\n <div className=\"rounded-md border border-dashed border-border p-density-4 text-center text-sm text-muted-foreground\">\n All actions are hidden by the active filters.\n </div>\n ) : (\n <ol className=\"relative\">\n {events.map((event, index) => (\n <SessionRow\n key={event.id}\n event={event}\n last={index === events.length - 1}\n defaultExpanded={defaultExpanded}\n />\n ))}\n </ol>\n )}\n </DensityValueProvider>\n </div>\n );\n}\n\nfunction SessionRow({\n event,\n last,\n defaultExpanded,\n}: {\n event: SessionEvent;\n last: boolean;\n defaultExpanded: boolean;\n}) {\n if (event.kind === \"user\") return <UserRow event={event} />;\n\n const visual = eventVisual(event);\n return (\n <li data-event-kind={event.kind} className=\"relative flex gap-density-3 pb-density-4 last:pb-0\">\n {!last && <span aria-hidden className=\"absolute bottom-0 left-[10px] top-[22px] w-px bg-border\" />}\n <span\n className={cn(\n \"relative z-[1] flex h-[21px] w-[21px] shrink-0 items-center justify-center rounded-full\",\n DISC_TONE[visual.tone],\n )}\n >\n <Icon icon={visual.icon} className=\"h-3 w-3\" />\n </span>\n <div className=\"min-w-0 flex-1 pt-px\">\n <EventBody event={event} visual={visual} defaultExpanded={defaultExpanded} />\n </div>\n </li>\n );\n}\n\n// User prompts and selections sit on the right, like a chat composer turn.\nfunction UserRow({ event }: { event: SessionEvent }) {\n return (\n <li data-event-kind=\"user\" className=\"relative flex justify-end pb-density-4 last:pb-0\">\n <div className=\"flex max-w-[85%] items-start gap-density-3\">\n <div className=\"min-w-0\">\n <div className=\"mb-0.5 text-right text-xs font-medium text-muted-foreground\">You</div>\n <div className=\"whitespace-pre-wrap break-words rounded-lg bg-accent px-density-3 py-density-2 text-right leading-relaxed text-accent-foreground\">\n {event.text}\n </div>\n </div>\n <span\n className={cn(\n \"relative z-[1] flex h-[21px] w-[21px] shrink-0 items-center justify-center rounded-full\",\n DISC_TONE.slate,\n )}\n >\n <Icon icon={UiUserCircle} className=\"h-3 w-3\" />\n </span>\n </div>\n </li>\n );\n}\n\ninterface EventVisual {\n icon: StaticIconComponent;\n tone: SessionTone;\n label: string;\n}\n\nfunction eventVisual(event: SessionEvent): EventVisual {\n switch (event.kind) {\n case \"tool\": {\n const action = getSessionAction(event.tool ?? \"\");\n return { icon: action.icon, tone: action.tone, label: action.label };\n }\n case \"user\":\n return { icon: UiUserCircle, tone: \"slate\", label: \"User\" };\n case \"assistant\":\n return { icon: UiSparkles, tone: \"indigo\", label: \"Assistant\" };\n case \"thinking\":\n return { icon: UiBrain, tone: \"slate\", label: \"Thinking\" };\n case \"error\":\n return { icon: UiWarningTriangle, tone: \"rose\", label: \"Error\" };\n }\n}\n\nfunction EventBody({\n event,\n visual,\n defaultExpanded,\n}: {\n event: SessionEvent;\n visual: EventVisual;\n defaultExpanded: boolean;\n}) {\n if (event.kind === \"tool\") return <ToolBody event={event} label={visual.label} defaultExpanded={defaultExpanded} />;\n if (event.kind === \"thinking\") return <ThinkingBody event={event} />;\n if (event.kind === \"error\") return <ErrorBody event={event} />;\n return <MessageBody event={event} label={visual.label} />;\n}\n\nfunction ToolBody({\n event,\n label,\n defaultExpanded,\n}: {\n event: SessionEvent;\n label: string;\n defaultExpanded: boolean;\n}) {\n const summary = summarizeToolInput(event.tool ?? \"\", event.toolInput);\n const hasDetail = event.toolInput !== undefined || event.toolResponse !== undefined;\n const [open, setOpen] = useState(defaultExpanded);\n\n const header = (\n <>\n <span className=\"font-medium text-foreground\">{label}</span>\n {summary && <span className=\"truncate font-mono text-xs text-muted-foreground\">{summary}</span>}\n </>\n );\n\n return (\n <div className=\"not-prose\">\n {hasDetail ? (\n <button\n type=\"button\"\n aria-expanded={open}\n onClick={() => setOpen((value) => !value)}\n className=\"flex w-full items-center gap-1.5 text-left hover:text-foreground\"\n >\n {header}\n <Icon\n icon={UiChevronDown}\n className={cn(\"ml-auto size-3 shrink-0 text-muted-foreground transition-transform\", open && \"rotate-180\")}\n />\n </button>\n ) : (\n <div className=\"flex items-center gap-1.5\">{header}</div>\n )}\n\n {(event.model || event.source) && (\n <div className=\"mt-0.5 flex gap-density-2 text-[11px] text-muted-foreground\">\n {event.source && <span>{event.source}</span>}\n {event.model && <span>{event.model}</span>}\n {event.reasoningEffort && <span>effort: {event.reasoningEffort}</span>}\n </div>\n )}\n\n {open && hasDetail && (\n <div className=\"mt-1.5 space-y-1.5\">\n {event.toolInput !== undefined && (\n <DetailBlock language=\"json\" source={JSON.stringify(event.toolInput, null, 2)} />\n )}\n {event.toolResponse !== undefined && <ResponseBlock response={event.toolResponse} />}\n </div>\n )}\n </div>\n );\n}\n\nfunction ResponseBlock({ response }: { response: string }) {\n const trimmed = response.trim();\n const isJson = trimmed.startsWith(\"{\") || trimmed.startsWith(\"[\");\n return <DetailBlock language={isJson ? \"json\" : \"text\"} source={response} />;\n}\n\nfunction DetailBlock({ language, source }: { language: string; source: string }): ReactNode {\n return (\n <div className=\"overflow-x-auto text-xs\">\n <CodeBlock language={language} source={source} />\n </div>\n );\n}\n\nfunction MessageBody({ event, label }: { event: SessionEvent; label: string }) {\n return (\n <div>\n <div className=\"mb-0.5 text-xs font-medium text-muted-foreground\">{label}</div>\n <div className=\"whitespace-pre-wrap break-words leading-relaxed text-foreground\">{event.text}</div>\n </div>\n );\n}\n\nfunction ThinkingBody({ event }: { event: SessionEvent }) {\n const [open, setOpen] = useState(false);\n return (\n <div className=\"not-prose\">\n <button\n type=\"button\"\n aria-expanded={open}\n onClick={() => setOpen((value) => !value)}\n className=\"flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground\"\n >\n <span className=\"italic\">Reasoning</span>\n <Icon icon={UiChevronDown} className={cn(\"size-3 transition-transform\", open && \"rotate-180\")} />\n </button>\n {open && (\n <div className=\"mt-1 whitespace-pre-wrap break-words border-l-2 border-border pl-density-3 text-xs italic leading-relaxed text-muted-foreground\">\n {event.text}\n </div>\n )}\n </div>\n );\n}\n\nfunction ErrorBody({ event }: { event: SessionEvent }) {\n return (\n <div className=\"rounded-md border border-rose-200 bg-rose-50 px-density-3 py-1.5 text-xs text-rose-700 [[data-theme=dark]_&]:border-rose-500/30 [[data-theme=dark]_&]:bg-rose-500/10 [[data-theme=dark]_&]:text-rose-300\">\n {event.text}\n </div>\n );\n}\n"],"names":["useMemo","normalizeSession","useDensityValue","useState","collectSessionFilters","isEventVisible","jsx","cn","summarizeSession","jsxs","DensityValueProvider","Icon","UiRobotAi","SessionViewerMenu","UiUserCircle","getSessionAction","UiSparkles","UiBrain","UiWarningTriangle","summarizeToolInput","Fragment","UiChevronDown","CodeBlock"],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Density } from '../../hooks/use-density';
|
|
2
|
+
import { SessionInput } from './SessionViewer.model';
|
|
3
|
+
import { SessionThemeOverride } from './SessionViewerMenu';
|
|
4
|
+
export type { SessionEntry, SessionEvent, SessionInput, SessionActionMeta, } from './SessionViewer.model';
|
|
5
|
+
export type { SessionThemeOverride } from './SessionViewerMenu';
|
|
6
|
+
export interface SessionViewerProps {
|
|
7
|
+
/** A captain session: parsed `SessionEntry[]` or raw log text (JSON / JSONL). */
|
|
8
|
+
session: SessionInput;
|
|
9
|
+
className?: string;
|
|
10
|
+
/** Expand each tool call's input/output by default. Defaults to false. */
|
|
11
|
+
defaultExpanded?: boolean;
|
|
12
|
+
/** Render assistant reasoning ("thinking") blocks. Defaults to true. */
|
|
13
|
+
showThinking?: boolean;
|
|
14
|
+
/** Show the summary header (model + action counts). Defaults to true. */
|
|
15
|
+
showHeader?: boolean;
|
|
16
|
+
/** Show the 3-dot menu (density + category/tool/source filters). Defaults to true. */
|
|
17
|
+
showMenu?: boolean;
|
|
18
|
+
/** Initial density override; undefined inherits the page/document density. */
|
|
19
|
+
defaultDensity?: Density;
|
|
20
|
+
/** Initial theme override; undefined inherits the page/document `data-theme`. */
|
|
21
|
+
defaultTheme?: SessionThemeOverride;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* SessionViewer renders a recorded AI coding-agent session (the captain
|
|
25
|
+
* `pkg/ai/history` JSON schema — Claude Code / Codex transcripts) as a vertical
|
|
26
|
+
* action log. Each entry sits on a tone-colored icon disc from the Flanksource
|
|
27
|
+
* "Agent Action Icons" set; user prompts/selections are right-aligned like a
|
|
28
|
+
* chat. Tool calls expand to their input and response. The "3-dot" menu mirrors
|
|
29
|
+
* `captain history` filtering — toggle density and hide categories, tools or
|
|
30
|
+
* sources.
|
|
31
|
+
*/
|
|
32
|
+
export declare function SessionViewer({ session, className, defaultExpanded, showThinking, showHeader, showMenu, defaultDensity, defaultTheme, }: SessionViewerProps): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
//# sourceMappingURL=SessionViewer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SessionViewer.d.ts","sourceRoot":"","sources":["../../../src/data/ai/SessionViewer.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAmB,KAAK,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAUxE,OAAO,EAML,KAAK,YAAY,EAElB,MAAM,uBAAuB,CAAC;AAM/B,OAAO,EAAqB,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEnF,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEhE,MAAM,WAAW,kBAAkB;IACjC,iFAAiF;IACjF,OAAO,EAAE,YAAY,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,wEAAwE;IACxE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iFAAiF;IACjF,YAAY,CAAC,EAAE,oBAAoB,CAAC;CACrC;AA0BD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,SAAS,EACT,eAAuB,EACvB,YAAmB,EACnB,UAAiB,EACjB,QAAe,EACf,cAAc,EACd,YAAY,GACb,EAAE,kBAAkB,2CAkGpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SessionViewer.fixtures.d.ts","sourceRoot":"","sources":["../../../src/data/ai/SessionViewer.fixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAK1D,eAAO,MAAM,cAAc,EAAE,YAAY,EAyFxC,CAAC;AAIF,eAAO,MAAM,oBAAoB,QAIrB,CAAC"}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import { Icon } from "../Icon.js";
|
|
5
|
+
import { CodeBlock } from "../CodeBlock.js";
|
|
6
|
+
import { useDensityValue } from "../../hooks/use-density.js";
|
|
7
|
+
import { DensityValueProvider } from "../../hooks/density-provider.js";
|
|
8
|
+
import { normalizeSession, summarizeSession, getSessionAction, summarizeToolInput } from "./SessionViewer.model.js";
|
|
9
|
+
import { collectSessionFilters, isEventVisible } from "./session-categories.js";
|
|
10
|
+
import { SessionViewerMenu } from "./SessionViewerMenu.js";
|
|
11
|
+
import { UiRobotAi } from "../../icons/components/UiRobotAi.js";
|
|
12
|
+
import { UiUserCircle } from "../../icons/components/UiUserCircle.js";
|
|
13
|
+
import { UiWarningTriangle } from "../../icons/components/UiWarningTriangle.js";
|
|
14
|
+
import { UiBrain } from "../../icons/components/UiBrain.js";
|
|
15
|
+
import { UiSparkles } from "../../icons/components/UiSparkles.js";
|
|
16
|
+
import { UiChevronDown } from "../../icons/components/UiChevronDown.js";
|
|
17
|
+
const DISC_TONE = {
|
|
18
|
+
sky: "bg-sky-100 text-sky-700 [[data-theme=dark]_&]:bg-sky-500/15 [[data-theme=dark]_&]:text-sky-300",
|
|
19
|
+
amber: "bg-amber-100 text-amber-700 [[data-theme=dark]_&]:bg-amber-500/15 [[data-theme=dark]_&]:text-amber-300",
|
|
20
|
+
violet: "bg-violet-100 text-violet-700 [[data-theme=dark]_&]:bg-violet-500/15 [[data-theme=dark]_&]:text-violet-300",
|
|
21
|
+
emerald: "bg-emerald-100 text-emerald-700 [[data-theme=dark]_&]:bg-emerald-500/15 [[data-theme=dark]_&]:text-emerald-300",
|
|
22
|
+
rose: "bg-rose-100 text-rose-700 [[data-theme=dark]_&]:bg-rose-500/15 [[data-theme=dark]_&]:text-rose-300",
|
|
23
|
+
indigo: "bg-indigo-100 text-indigo-700 [[data-theme=dark]_&]:bg-indigo-500/15 [[data-theme=dark]_&]:text-indigo-300",
|
|
24
|
+
fuchsia: "bg-fuchsia-100 text-fuchsia-700 [[data-theme=dark]_&]:bg-fuchsia-500/15 [[data-theme=dark]_&]:text-fuchsia-300",
|
|
25
|
+
pink: "bg-pink-100 text-pink-700 [[data-theme=dark]_&]:bg-pink-500/15 [[data-theme=dark]_&]:text-pink-300",
|
|
26
|
+
slate: "bg-muted text-muted-foreground"
|
|
27
|
+
};
|
|
28
|
+
function toggleInSet(set, value) {
|
|
29
|
+
const next = new Set(set);
|
|
30
|
+
if (next.has(value)) next.delete(value);
|
|
31
|
+
else next.add(value);
|
|
32
|
+
return next;
|
|
33
|
+
}
|
|
34
|
+
function SessionViewer({
|
|
35
|
+
session,
|
|
36
|
+
className,
|
|
37
|
+
defaultExpanded = false,
|
|
38
|
+
showThinking = true,
|
|
39
|
+
showHeader = true,
|
|
40
|
+
showMenu = true,
|
|
41
|
+
defaultDensity,
|
|
42
|
+
defaultTheme
|
|
43
|
+
}) {
|
|
44
|
+
const allEvents = useMemo(() => normalizeSession(session), [session]);
|
|
45
|
+
const pageDensity = useDensityValue();
|
|
46
|
+
const [densityOverride, setDensityOverride] = useState(defaultDensity);
|
|
47
|
+
const [themeOverride, setThemeOverride] = useState(defaultTheme);
|
|
48
|
+
const [hiddenCategories, setHiddenCategories] = useState(
|
|
49
|
+
() => /* @__PURE__ */ new Set()
|
|
50
|
+
);
|
|
51
|
+
const [hiddenTools, setHiddenTools] = useState(() => /* @__PURE__ */ new Set());
|
|
52
|
+
const [hiddenSources, setHiddenSources] = useState(() => /* @__PURE__ */ new Set());
|
|
53
|
+
const [showThinkingOverride, setShowThinkingOverride] = useState(void 0);
|
|
54
|
+
const effectiveShowThinking = showThinkingOverride ?? showThinking;
|
|
55
|
+
const filters = useMemo(() => collectSessionFilters(allEvents), [allEvents]);
|
|
56
|
+
const hasThinking = useMemo(() => allEvents.some((e) => e.kind === "thinking"), [allEvents]);
|
|
57
|
+
const visibility = { hiddenCategories, hiddenTools, hiddenSources, showThinking: effectiveShowThinking };
|
|
58
|
+
const events = allEvents.filter((event) => isEventVisible(event, visibility));
|
|
59
|
+
if (allEvents.length === 0) {
|
|
60
|
+
return /* @__PURE__ */ jsx(
|
|
61
|
+
"div",
|
|
62
|
+
{
|
|
63
|
+
className: cn(
|
|
64
|
+
"rounded-md border border-dashed border-border p-density-4 text-center text-sm text-muted-foreground",
|
|
65
|
+
className
|
|
66
|
+
),
|
|
67
|
+
children: "No session activity."
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const summary = summarizeSession(allEvents);
|
|
72
|
+
const effectiveDensity = densityOverride ?? pageDensity;
|
|
73
|
+
const dataAttrs = {
|
|
74
|
+
...densityOverride ? { "data-density": densityOverride } : {},
|
|
75
|
+
...themeOverride ? { "data-theme": themeOverride } : {}
|
|
76
|
+
};
|
|
77
|
+
return /* @__PURE__ */ jsx("div", { className: cn("text-sm", className), ...dataAttrs, children: /* @__PURE__ */ jsxs(DensityValueProvider, { density: effectiveDensity, children: [
|
|
78
|
+
(showHeader || showMenu) && /* @__PURE__ */ jsxs("div", { className: "mb-density-3 flex items-center justify-between gap-density-3", children: [
|
|
79
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-density-3 gap-y-1 text-xs text-muted-foreground", children: [
|
|
80
|
+
showHeader && summary.model && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 font-medium text-foreground", children: [
|
|
81
|
+
/* @__PURE__ */ jsx(Icon, { icon: UiRobotAi, className: "size-3.5" }),
|
|
82
|
+
summary.model
|
|
83
|
+
] }),
|
|
84
|
+
showHeader && /* @__PURE__ */ jsxs("span", { children: [
|
|
85
|
+
summary.toolCount,
|
|
86
|
+
" actions"
|
|
87
|
+
] }),
|
|
88
|
+
showHeader && /* @__PURE__ */ jsxs("span", { children: [
|
|
89
|
+
summary.messageCount,
|
|
90
|
+
" messages"
|
|
91
|
+
] })
|
|
92
|
+
] }),
|
|
93
|
+
showMenu && /* @__PURE__ */ jsx(
|
|
94
|
+
SessionViewerMenu,
|
|
95
|
+
{
|
|
96
|
+
density: densityOverride,
|
|
97
|
+
onDensityChange: setDensityOverride,
|
|
98
|
+
theme: themeOverride,
|
|
99
|
+
onThemeChange: setThemeOverride,
|
|
100
|
+
filters,
|
|
101
|
+
hiddenCategories,
|
|
102
|
+
hiddenTools,
|
|
103
|
+
hiddenSources,
|
|
104
|
+
onToggleCategory: (category) => setHiddenCategories((set) => toggleInSet(set, category)),
|
|
105
|
+
onToggleTool: (tool) => setHiddenTools((set) => toggleInSet(set, tool)),
|
|
106
|
+
onToggleSource: (source) => setHiddenSources((set) => toggleInSet(set, source)),
|
|
107
|
+
showThinking: effectiveShowThinking,
|
|
108
|
+
onToggleThinking: () => setShowThinkingOverride(!effectiveShowThinking),
|
|
109
|
+
hasThinking
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
] }),
|
|
113
|
+
events.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed border-border p-density-4 text-center text-sm text-muted-foreground", children: "All actions are hidden by the active filters." }) : /* @__PURE__ */ jsx("ol", { className: "relative", children: events.map((event, index) => /* @__PURE__ */ jsx(
|
|
114
|
+
SessionRow,
|
|
115
|
+
{
|
|
116
|
+
event,
|
|
117
|
+
last: index === events.length - 1,
|
|
118
|
+
defaultExpanded
|
|
119
|
+
},
|
|
120
|
+
event.id
|
|
121
|
+
)) })
|
|
122
|
+
] }) });
|
|
123
|
+
}
|
|
124
|
+
function SessionRow({
|
|
125
|
+
event,
|
|
126
|
+
last,
|
|
127
|
+
defaultExpanded
|
|
128
|
+
}) {
|
|
129
|
+
if (event.kind === "user") return /* @__PURE__ */ jsx(UserRow, { event });
|
|
130
|
+
const visual = eventVisual(event);
|
|
131
|
+
return /* @__PURE__ */ jsxs("li", { "data-event-kind": event.kind, className: "relative flex gap-density-3 pb-density-4 last:pb-0", children: [
|
|
132
|
+
!last && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "absolute bottom-0 left-[10px] top-[22px] w-px bg-border" }),
|
|
133
|
+
/* @__PURE__ */ jsx(
|
|
134
|
+
"span",
|
|
135
|
+
{
|
|
136
|
+
className: cn(
|
|
137
|
+
"relative z-[1] flex h-[21px] w-[21px] shrink-0 items-center justify-center rounded-full",
|
|
138
|
+
DISC_TONE[visual.tone]
|
|
139
|
+
),
|
|
140
|
+
children: /* @__PURE__ */ jsx(Icon, { icon: visual.icon, className: "h-3 w-3" })
|
|
141
|
+
}
|
|
142
|
+
),
|
|
143
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1 pt-px", children: /* @__PURE__ */ jsx(EventBody, { event, visual, defaultExpanded }) })
|
|
144
|
+
] });
|
|
145
|
+
}
|
|
146
|
+
function UserRow({ event }) {
|
|
147
|
+
return /* @__PURE__ */ jsx("li", { "data-event-kind": "user", className: "relative flex justify-end pb-density-4 last:pb-0", children: /* @__PURE__ */ jsxs("div", { className: "flex max-w-[85%] items-start gap-density-3", children: [
|
|
148
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
149
|
+
/* @__PURE__ */ jsx("div", { className: "mb-0.5 text-right text-xs font-medium text-muted-foreground", children: "You" }),
|
|
150
|
+
/* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap break-words rounded-lg bg-accent px-density-3 py-density-2 text-right leading-relaxed text-accent-foreground", children: event.text })
|
|
151
|
+
] }),
|
|
152
|
+
/* @__PURE__ */ jsx(
|
|
153
|
+
"span",
|
|
154
|
+
{
|
|
155
|
+
className: cn(
|
|
156
|
+
"relative z-[1] flex h-[21px] w-[21px] shrink-0 items-center justify-center rounded-full",
|
|
157
|
+
DISC_TONE.slate
|
|
158
|
+
),
|
|
159
|
+
children: /* @__PURE__ */ jsx(Icon, { icon: UiUserCircle, className: "h-3 w-3" })
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
] }) });
|
|
163
|
+
}
|
|
164
|
+
function eventVisual(event) {
|
|
165
|
+
switch (event.kind) {
|
|
166
|
+
case "tool": {
|
|
167
|
+
const action = getSessionAction(event.tool ?? "");
|
|
168
|
+
return { icon: action.icon, tone: action.tone, label: action.label };
|
|
169
|
+
}
|
|
170
|
+
case "user":
|
|
171
|
+
return { icon: UiUserCircle, tone: "slate", label: "User" };
|
|
172
|
+
case "assistant":
|
|
173
|
+
return { icon: UiSparkles, tone: "indigo", label: "Assistant" };
|
|
174
|
+
case "thinking":
|
|
175
|
+
return { icon: UiBrain, tone: "slate", label: "Thinking" };
|
|
176
|
+
case "error":
|
|
177
|
+
return { icon: UiWarningTriangle, tone: "rose", label: "Error" };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function EventBody({
|
|
181
|
+
event,
|
|
182
|
+
visual,
|
|
183
|
+
defaultExpanded
|
|
184
|
+
}) {
|
|
185
|
+
if (event.kind === "tool") return /* @__PURE__ */ jsx(ToolBody, { event, label: visual.label, defaultExpanded });
|
|
186
|
+
if (event.kind === "thinking") return /* @__PURE__ */ jsx(ThinkingBody, { event });
|
|
187
|
+
if (event.kind === "error") return /* @__PURE__ */ jsx(ErrorBody, { event });
|
|
188
|
+
return /* @__PURE__ */ jsx(MessageBody, { event, label: visual.label });
|
|
189
|
+
}
|
|
190
|
+
function ToolBody({
|
|
191
|
+
event,
|
|
192
|
+
label,
|
|
193
|
+
defaultExpanded
|
|
194
|
+
}) {
|
|
195
|
+
const summary = summarizeToolInput(event.tool ?? "", event.toolInput);
|
|
196
|
+
const hasDetail = event.toolInput !== void 0 || event.toolResponse !== void 0;
|
|
197
|
+
const [open, setOpen] = useState(defaultExpanded);
|
|
198
|
+
const header = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
199
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: label }),
|
|
200
|
+
summary && /* @__PURE__ */ jsx("span", { className: "truncate font-mono text-xs text-muted-foreground", children: summary })
|
|
201
|
+
] });
|
|
202
|
+
return /* @__PURE__ */ jsxs("div", { className: "not-prose", children: [
|
|
203
|
+
hasDetail ? /* @__PURE__ */ jsxs(
|
|
204
|
+
"button",
|
|
205
|
+
{
|
|
206
|
+
type: "button",
|
|
207
|
+
"aria-expanded": open,
|
|
208
|
+
onClick: () => setOpen((value) => !value),
|
|
209
|
+
className: "flex w-full items-center gap-1.5 text-left hover:text-foreground",
|
|
210
|
+
children: [
|
|
211
|
+
header,
|
|
212
|
+
/* @__PURE__ */ jsx(
|
|
213
|
+
Icon,
|
|
214
|
+
{
|
|
215
|
+
icon: UiChevronDown,
|
|
216
|
+
className: cn("ml-auto size-3 shrink-0 text-muted-foreground transition-transform", open && "rotate-180")
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
) : /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5", children: header }),
|
|
222
|
+
(event.model || event.source) && /* @__PURE__ */ jsxs("div", { className: "mt-0.5 flex gap-density-2 text-[11px] text-muted-foreground", children: [
|
|
223
|
+
event.source && /* @__PURE__ */ jsx("span", { children: event.source }),
|
|
224
|
+
event.model && /* @__PURE__ */ jsx("span", { children: event.model }),
|
|
225
|
+
event.reasoningEffort && /* @__PURE__ */ jsxs("span", { children: [
|
|
226
|
+
"effort: ",
|
|
227
|
+
event.reasoningEffort
|
|
228
|
+
] })
|
|
229
|
+
] }),
|
|
230
|
+
open && hasDetail && /* @__PURE__ */ jsxs("div", { className: "mt-1.5 space-y-1.5", children: [
|
|
231
|
+
event.toolInput !== void 0 && /* @__PURE__ */ jsx(DetailBlock, { language: "json", source: JSON.stringify(event.toolInput, null, 2) }),
|
|
232
|
+
event.toolResponse !== void 0 && /* @__PURE__ */ jsx(ResponseBlock, { response: event.toolResponse })
|
|
233
|
+
] })
|
|
234
|
+
] });
|
|
235
|
+
}
|
|
236
|
+
function ResponseBlock({ response }) {
|
|
237
|
+
const trimmed = response.trim();
|
|
238
|
+
const isJson = trimmed.startsWith("{") || trimmed.startsWith("[");
|
|
239
|
+
return /* @__PURE__ */ jsx(DetailBlock, { language: isJson ? "json" : "text", source: response });
|
|
240
|
+
}
|
|
241
|
+
function DetailBlock({ language, source }) {
|
|
242
|
+
return /* @__PURE__ */ jsx("div", { className: "overflow-x-auto text-xs", children: /* @__PURE__ */ jsx(CodeBlock, { language, source }) });
|
|
243
|
+
}
|
|
244
|
+
function MessageBody({ event, label }) {
|
|
245
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
246
|
+
/* @__PURE__ */ jsx("div", { className: "mb-0.5 text-xs font-medium text-muted-foreground", children: label }),
|
|
247
|
+
/* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap break-words leading-relaxed text-foreground", children: event.text })
|
|
248
|
+
] });
|
|
249
|
+
}
|
|
250
|
+
function ThinkingBody({ event }) {
|
|
251
|
+
const [open, setOpen] = useState(false);
|
|
252
|
+
return /* @__PURE__ */ jsxs("div", { className: "not-prose", children: [
|
|
253
|
+
/* @__PURE__ */ jsxs(
|
|
254
|
+
"button",
|
|
255
|
+
{
|
|
256
|
+
type: "button",
|
|
257
|
+
"aria-expanded": open,
|
|
258
|
+
onClick: () => setOpen((value) => !value),
|
|
259
|
+
className: "flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground",
|
|
260
|
+
children: [
|
|
261
|
+
/* @__PURE__ */ jsx("span", { className: "italic", children: "Reasoning" }),
|
|
262
|
+
/* @__PURE__ */ jsx(Icon, { icon: UiChevronDown, className: cn("size-3 transition-transform", open && "rotate-180") })
|
|
263
|
+
]
|
|
264
|
+
}
|
|
265
|
+
),
|
|
266
|
+
open && /* @__PURE__ */ jsx("div", { className: "mt-1 whitespace-pre-wrap break-words border-l-2 border-border pl-density-3 text-xs italic leading-relaxed text-muted-foreground", children: event.text })
|
|
267
|
+
] });
|
|
268
|
+
}
|
|
269
|
+
function ErrorBody({ event }) {
|
|
270
|
+
return /* @__PURE__ */ jsx("div", { className: "rounded-md border border-rose-200 bg-rose-50 px-density-3 py-1.5 text-xs text-rose-700 [[data-theme=dark]_&]:border-rose-500/30 [[data-theme=dark]_&]:bg-rose-500/10 [[data-theme=dark]_&]:text-rose-300", children: event.text });
|
|
271
|
+
}
|
|
272
|
+
export {
|
|
273
|
+
SessionViewer
|
|
274
|
+
};
|
|
275
|
+
//# sourceMappingURL=SessionViewer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SessionViewer.js","sources":["../../../src/data/ai/SessionViewer.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { Icon, type StaticIconComponent } from \"../Icon\";\nimport { CodeBlock } from \"../CodeBlock\";\nimport { useDensityValue, type Density } from \"../../hooks/use-density\";\nimport { DensityValueProvider } from \"../../hooks/density-provider\";\nimport {\n UiBrain,\n UiChevronDown,\n UiRobotAi,\n UiSparkles,\n UiUserCircle,\n UiWarningTriangle,\n} from \"../../icons\";\nimport {\n getSessionAction,\n normalizeSession,\n summarizeSession,\n summarizeToolInput,\n type SessionEvent,\n type SessionInput,\n type SessionTone,\n} from \"./SessionViewer.model\";\nimport {\n collectSessionFilters,\n isEventVisible,\n type SessionCategory,\n} from \"./session-categories\";\nimport { SessionViewerMenu, type SessionThemeOverride } from \"./SessionViewerMenu\";\n\nexport type {\n SessionEntry,\n SessionEvent,\n SessionInput,\n SessionActionMeta,\n} from \"./SessionViewer.model\";\nexport type { SessionThemeOverride } from \"./SessionViewerMenu\";\n\nexport interface SessionViewerProps {\n /** A captain session: parsed `SessionEntry[]` or raw log text (JSON / JSONL). */\n session: SessionInput;\n className?: string;\n /** Expand each tool call's input/output by default. Defaults to false. */\n defaultExpanded?: boolean;\n /** Render assistant reasoning (\"thinking\") blocks. Defaults to true. */\n showThinking?: boolean;\n /** Show the summary header (model + action counts). Defaults to true. */\n showHeader?: boolean;\n /** Show the 3-dot menu (density + category/tool/source filters). Defaults to true. */\n showMenu?: boolean;\n /** Initial density override; undefined inherits the page/document density. */\n defaultDensity?: Density;\n /** Initial theme override; undefined inherits the page/document `data-theme`. */\n defaultTheme?: SessionThemeOverride;\n}\n\n// Disc colors per tone. The dark variants key off a `[data-theme=\"dark\"]`\n// ancestor (the document attribute set by ThemeProvider, or the component-level\n// override painted on this viewer's root) rather than Tailwind's `dark:` —\n// which this library compiles to `prefers-color-scheme` and so would ignore the\n// `data-theme` attribute. Written as literal class strings so Tailwind scans them.\nconst DISC_TONE: Record<SessionTone, string> = {\n sky: \"bg-sky-100 text-sky-700 [[data-theme=dark]_&]:bg-sky-500/15 [[data-theme=dark]_&]:text-sky-300\",\n amber: \"bg-amber-100 text-amber-700 [[data-theme=dark]_&]:bg-amber-500/15 [[data-theme=dark]_&]:text-amber-300\",\n violet: \"bg-violet-100 text-violet-700 [[data-theme=dark]_&]:bg-violet-500/15 [[data-theme=dark]_&]:text-violet-300\",\n emerald: \"bg-emerald-100 text-emerald-700 [[data-theme=dark]_&]:bg-emerald-500/15 [[data-theme=dark]_&]:text-emerald-300\",\n rose: \"bg-rose-100 text-rose-700 [[data-theme=dark]_&]:bg-rose-500/15 [[data-theme=dark]_&]:text-rose-300\",\n indigo: \"bg-indigo-100 text-indigo-700 [[data-theme=dark]_&]:bg-indigo-500/15 [[data-theme=dark]_&]:text-indigo-300\",\n fuchsia: \"bg-fuchsia-100 text-fuchsia-700 [[data-theme=dark]_&]:bg-fuchsia-500/15 [[data-theme=dark]_&]:text-fuchsia-300\",\n pink: \"bg-pink-100 text-pink-700 [[data-theme=dark]_&]:bg-pink-500/15 [[data-theme=dark]_&]:text-pink-300\",\n slate: \"bg-muted text-muted-foreground\",\n};\n\nfunction toggleInSet<T>(set: ReadonlySet<T>, value: T): Set<T> {\n const next = new Set(set);\n if (next.has(value)) next.delete(value);\n else next.add(value);\n return next;\n}\n\n/**\n * SessionViewer renders a recorded AI coding-agent session (the captain\n * `pkg/ai/history` JSON schema — Claude Code / Codex transcripts) as a vertical\n * action log. Each entry sits on a tone-colored icon disc from the Flanksource\n * \"Agent Action Icons\" set; user prompts/selections are right-aligned like a\n * chat. Tool calls expand to their input and response. The \"3-dot\" menu mirrors\n * `captain history` filtering — toggle density and hide categories, tools or\n * sources.\n */\nexport function SessionViewer({\n session,\n className,\n defaultExpanded = false,\n showThinking = true,\n showHeader = true,\n showMenu = true,\n defaultDensity,\n defaultTheme,\n}: SessionViewerProps) {\n const allEvents = useMemo(() => normalizeSession(session), [session]);\n\n const pageDensity = useDensityValue();\n const [densityOverride, setDensityOverride] = useState<Density | undefined>(defaultDensity);\n const [themeOverride, setThemeOverride] = useState<SessionThemeOverride | undefined>(defaultTheme);\n const [hiddenCategories, setHiddenCategories] = useState<ReadonlySet<SessionCategory>>(\n () => new Set(),\n );\n const [hiddenTools, setHiddenTools] = useState<ReadonlySet<string>>(() => new Set());\n const [hiddenSources, setHiddenSources] = useState<ReadonlySet<string>>(() => new Set());\n // Undefined = follow the `showThinking` prop; the menu toggle sets an override.\n const [showThinkingOverride, setShowThinkingOverride] = useState<boolean | undefined>(undefined);\n const effectiveShowThinking = showThinkingOverride ?? showThinking;\n\n const filters = useMemo(() => collectSessionFilters(allEvents), [allEvents]);\n const hasThinking = useMemo(() => allEvents.some((e) => e.kind === \"thinking\"), [allEvents]);\n\n const visibility = { hiddenCategories, hiddenTools, hiddenSources, showThinking: effectiveShowThinking };\n const events = allEvents.filter((event) => isEventVisible(event, visibility));\n\n if (allEvents.length === 0) {\n return (\n <div\n className={cn(\n \"rounded-md border border-dashed border-border p-density-4 text-center text-sm text-muted-foreground\",\n className,\n )}\n >\n No session activity.\n </div>\n );\n }\n\n const summary = summarizeSession(allEvents);\n const effectiveDensity = densityOverride ?? pageDensity;\n const dataAttrs: Record<string, string> = {\n ...(densityOverride ? { \"data-density\": densityOverride } : {}),\n ...(themeOverride ? { \"data-theme\": themeOverride } : {}),\n };\n\n return (\n <div className={cn(\"text-sm\", className)} {...dataAttrs}>\n <DensityValueProvider density={effectiveDensity}>\n {(showHeader || showMenu) && (\n <div className=\"mb-density-3 flex items-center justify-between gap-density-3\">\n <div className=\"flex flex-wrap items-center gap-x-density-3 gap-y-1 text-xs text-muted-foreground\">\n {showHeader && summary.model && (\n <span className=\"inline-flex items-center gap-1 font-medium text-foreground\">\n <Icon icon={UiRobotAi} className=\"size-3.5\" />\n {summary.model}\n </span>\n )}\n {showHeader && <span>{summary.toolCount} actions</span>}\n {showHeader && <span>{summary.messageCount} messages</span>}\n </div>\n {showMenu && (\n <SessionViewerMenu\n density={densityOverride}\n onDensityChange={setDensityOverride}\n theme={themeOverride}\n onThemeChange={setThemeOverride}\n filters={filters}\n hiddenCategories={hiddenCategories}\n hiddenTools={hiddenTools}\n hiddenSources={hiddenSources}\n onToggleCategory={(category) =>\n setHiddenCategories((set) => toggleInSet(set, category))\n }\n onToggleTool={(tool) => setHiddenTools((set) => toggleInSet(set, tool))}\n onToggleSource={(source) => setHiddenSources((set) => toggleInSet(set, source))}\n showThinking={effectiveShowThinking}\n onToggleThinking={() => setShowThinkingOverride(!effectiveShowThinking)}\n hasThinking={hasThinking}\n />\n )}\n </div>\n )}\n\n {events.length === 0 ? (\n <div className=\"rounded-md border border-dashed border-border p-density-4 text-center text-sm text-muted-foreground\">\n All actions are hidden by the active filters.\n </div>\n ) : (\n <ol className=\"relative\">\n {events.map((event, index) => (\n <SessionRow\n key={event.id}\n event={event}\n last={index === events.length - 1}\n defaultExpanded={defaultExpanded}\n />\n ))}\n </ol>\n )}\n </DensityValueProvider>\n </div>\n );\n}\n\nfunction SessionRow({\n event,\n last,\n defaultExpanded,\n}: {\n event: SessionEvent;\n last: boolean;\n defaultExpanded: boolean;\n}) {\n if (event.kind === \"user\") return <UserRow event={event} />;\n\n const visual = eventVisual(event);\n return (\n <li data-event-kind={event.kind} className=\"relative flex gap-density-3 pb-density-4 last:pb-0\">\n {!last && <span aria-hidden className=\"absolute bottom-0 left-[10px] top-[22px] w-px bg-border\" />}\n <span\n className={cn(\n \"relative z-[1] flex h-[21px] w-[21px] shrink-0 items-center justify-center rounded-full\",\n DISC_TONE[visual.tone],\n )}\n >\n <Icon icon={visual.icon} className=\"h-3 w-3\" />\n </span>\n <div className=\"min-w-0 flex-1 pt-px\">\n <EventBody event={event} visual={visual} defaultExpanded={defaultExpanded} />\n </div>\n </li>\n );\n}\n\n// User prompts and selections sit on the right, like a chat composer turn.\nfunction UserRow({ event }: { event: SessionEvent }) {\n return (\n <li data-event-kind=\"user\" className=\"relative flex justify-end pb-density-4 last:pb-0\">\n <div className=\"flex max-w-[85%] items-start gap-density-3\">\n <div className=\"min-w-0\">\n <div className=\"mb-0.5 text-right text-xs font-medium text-muted-foreground\">You</div>\n <div className=\"whitespace-pre-wrap break-words rounded-lg bg-accent px-density-3 py-density-2 text-right leading-relaxed text-accent-foreground\">\n {event.text}\n </div>\n </div>\n <span\n className={cn(\n \"relative z-[1] flex h-[21px] w-[21px] shrink-0 items-center justify-center rounded-full\",\n DISC_TONE.slate,\n )}\n >\n <Icon icon={UiUserCircle} className=\"h-3 w-3\" />\n </span>\n </div>\n </li>\n );\n}\n\ninterface EventVisual {\n icon: StaticIconComponent;\n tone: SessionTone;\n label: string;\n}\n\nfunction eventVisual(event: SessionEvent): EventVisual {\n switch (event.kind) {\n case \"tool\": {\n const action = getSessionAction(event.tool ?? \"\");\n return { icon: action.icon, tone: action.tone, label: action.label };\n }\n case \"user\":\n return { icon: UiUserCircle, tone: \"slate\", label: \"User\" };\n case \"assistant\":\n return { icon: UiSparkles, tone: \"indigo\", label: \"Assistant\" };\n case \"thinking\":\n return { icon: UiBrain, tone: \"slate\", label: \"Thinking\" };\n case \"error\":\n return { icon: UiWarningTriangle, tone: \"rose\", label: \"Error\" };\n }\n}\n\nfunction EventBody({\n event,\n visual,\n defaultExpanded,\n}: {\n event: SessionEvent;\n visual: EventVisual;\n defaultExpanded: boolean;\n}) {\n if (event.kind === \"tool\") return <ToolBody event={event} label={visual.label} defaultExpanded={defaultExpanded} />;\n if (event.kind === \"thinking\") return <ThinkingBody event={event} />;\n if (event.kind === \"error\") return <ErrorBody event={event} />;\n return <MessageBody event={event} label={visual.label} />;\n}\n\nfunction ToolBody({\n event,\n label,\n defaultExpanded,\n}: {\n event: SessionEvent;\n label: string;\n defaultExpanded: boolean;\n}) {\n const summary = summarizeToolInput(event.tool ?? \"\", event.toolInput);\n const hasDetail = event.toolInput !== undefined || event.toolResponse !== undefined;\n const [open, setOpen] = useState(defaultExpanded);\n\n const header = (\n <>\n <span className=\"font-medium text-foreground\">{label}</span>\n {summary && <span className=\"truncate font-mono text-xs text-muted-foreground\">{summary}</span>}\n </>\n );\n\n return (\n <div className=\"not-prose\">\n {hasDetail ? (\n <button\n type=\"button\"\n aria-expanded={open}\n onClick={() => setOpen((value) => !value)}\n className=\"flex w-full items-center gap-1.5 text-left hover:text-foreground\"\n >\n {header}\n <Icon\n icon={UiChevronDown}\n className={cn(\"ml-auto size-3 shrink-0 text-muted-foreground transition-transform\", open && \"rotate-180\")}\n />\n </button>\n ) : (\n <div className=\"flex items-center gap-1.5\">{header}</div>\n )}\n\n {(event.model || event.source) && (\n <div className=\"mt-0.5 flex gap-density-2 text-[11px] text-muted-foreground\">\n {event.source && <span>{event.source}</span>}\n {event.model && <span>{event.model}</span>}\n {event.reasoningEffort && <span>effort: {event.reasoningEffort}</span>}\n </div>\n )}\n\n {open && hasDetail && (\n <div className=\"mt-1.5 space-y-1.5\">\n {event.toolInput !== undefined && (\n <DetailBlock language=\"json\" source={JSON.stringify(event.toolInput, null, 2)} />\n )}\n {event.toolResponse !== undefined && <ResponseBlock response={event.toolResponse} />}\n </div>\n )}\n </div>\n );\n}\n\nfunction ResponseBlock({ response }: { response: string }) {\n const trimmed = response.trim();\n const isJson = trimmed.startsWith(\"{\") || trimmed.startsWith(\"[\");\n return <DetailBlock language={isJson ? \"json\" : \"text\"} source={response} />;\n}\n\nfunction DetailBlock({ language, source }: { language: string; source: string }): ReactNode {\n return (\n <div className=\"overflow-x-auto text-xs\">\n <CodeBlock language={language} source={source} />\n </div>\n );\n}\n\nfunction MessageBody({ event, label }: { event: SessionEvent; label: string }) {\n return (\n <div>\n <div className=\"mb-0.5 text-xs font-medium text-muted-foreground\">{label}</div>\n <div className=\"whitespace-pre-wrap break-words leading-relaxed text-foreground\">{event.text}</div>\n </div>\n );\n}\n\nfunction ThinkingBody({ event }: { event: SessionEvent }) {\n const [open, setOpen] = useState(false);\n return (\n <div className=\"not-prose\">\n <button\n type=\"button\"\n aria-expanded={open}\n onClick={() => setOpen((value) => !value)}\n className=\"flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground\"\n >\n <span className=\"italic\">Reasoning</span>\n <Icon icon={UiChevronDown} className={cn(\"size-3 transition-transform\", open && \"rotate-180\")} />\n </button>\n {open && (\n <div className=\"mt-1 whitespace-pre-wrap break-words border-l-2 border-border pl-density-3 text-xs italic leading-relaxed text-muted-foreground\">\n {event.text}\n </div>\n )}\n </div>\n );\n}\n\nfunction ErrorBody({ event }: { event: SessionEvent }) {\n return (\n <div className=\"rounded-md border border-rose-200 bg-rose-50 px-density-3 py-1.5 text-xs text-rose-700 [[data-theme=dark]_&]:border-rose-500/30 [[data-theme=dark]_&]:bg-rose-500/10 [[data-theme=dark]_&]:text-rose-300\">\n {event.text}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|