fix(agents): keep live activity styling in the header

- Remove row-level Live badges and active highlight styling from assistant transcript messages, thought rows, and tool rows
- Stop passing transcript active item state through AgentSessionTranscriptList once row-level activity styling is no longer rendered
- Preserve the panel header Live indicator as the single source of current agent activity state
This commit is contained in:
Taylor Ho
2026-06-15 15:59:29 -07:00
parent 4ad7b624ae
commit c31eaacaeb
2 changed files with 117 additions and 407 deletions
@@ -1,60 +1,27 @@
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { ArrowUpRight, ChevronDown, CircleDot, Wrench } from "lucide-react";
import { ChevronDown } from "lucide-react";
import { useAppNavigation } from "@/app/navigation/useAppNavigation";
import { useUsersBatchQuery } from "@/features/profile/hooks";
import { resolveUserLabel } from "@/features/profile/lib/identity";
import type { Channel, UserProfileSummary } from "@/shared/api/types";
import { useChannelNavigation } from "@/shared/context/ChannelNavigationContext";
import { cn } from "@/shared/lib/cn";
import { rewriteRelayUrl } from "@/shared/lib/mediaUrl";
import { Badge } from "@/shared/ui/badge";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip";
import { UserAvatar } from "@/shared/ui/UserAvatar";
import type { TranscriptItem } from "./agentSessionTypes";
import {
formatToolTitle,
getBuzzToolInfo,
getToolStatusDisplay,
} from "./agentSessionToolCatalog";
import {
buildCompactToolSummary,
isCompactDeveloperTool,
} from "./agentSessionToolSummary";
import {
asRecord,
formatCodeValue,
formatDuration,
formatTranscriptTime,
getResultArray,
getToolString,
getToolStringList,
shortenMiddle,
} from "./agentSessionUtils";
import { getBuzzToolInfo } from "./agentSessionToolCatalog";
import { buildCompactToolSummary } from "./agentSessionToolSummary";
import { asRecord, formatCodeValue, formatDuration } from "./agentSessionUtils";
export function ToolItem({
compact = false,
isActive = false,
item,
}: {
compact?: boolean;
isActive?: boolean;
item: Extract<TranscriptItem, { type: "tool" }>;
}) {
const [isExpanded, setIsExpanded] = React.useState(false);
const status = getToolStatusDisplay(item.status, item.isError);
const hasArgs = Object.keys(item.args).length > 0;
const hasResult = item.result.trim().length > 0;
const canonicalToolName = item.buzzToolName ?? item.toolName;
const buzzTool = getBuzzToolInfo(canonicalToolName);
const ToolIcon = buzzTool?.icon ?? Wrench;
const showStatus = status.state !== "output-available";
const toolTitle = formatToolTitle(canonicalToolName, item.title);
const useCompactSummary = isCompactDeveloperTool(item);
const compactSummary = useCompactSummary
? buildCompactToolSummary(item)
: null;
const compactSummary = buildCompactToolSummary(item);
const duration = getToolDuration(item);
const handleToggle = React.useCallback(
(event: React.SyntheticEvent<HTMLDetailsElement>) => {
@@ -65,12 +32,7 @@ export function ToolItem({
return (
<div
className={cn(
"not-prose w-full",
compact ? "px-0" : "px-1",
isActive &&
"rounded-lg border border-primary/15 bg-primary/3 px-2 py-1",
)}
className={cn("not-prose w-full", compact ? "px-0" : "px-1")}
data-testid="transcript-tool-item"
>
<details
@@ -80,59 +42,16 @@ export function ToolItem({
>
<summary
className={cn(
"inline-flex max-w-full cursor-pointer list-none items-center gap-1.5 py-px",
useCompactSummary && "text-muted-foreground",
"inline-flex max-w-full cursor-pointer list-none items-center gap-5 py-px",
compactSummaryTone(),
)}
>
{compactSummary ? (
<CompactToolSummaryRow
duration={duration}
preview={compactSummary.preview}
thumbnailSrc={compactSummary.thumbnailSrc}
label={compactSummary.label}
/>
) : (
<>
{ToolIcon ? (
<ToolIcon
className={cn(
"h-4 w-4 shrink-0",
buzzTool || isActive
? "text-primary"
: "text-muted-foreground",
)}
/>
) : null}
<span className="min-w-0 truncate text-sm font-medium">
{toolTitle}
</span>
{isActive ? (
<Badge
className="h-4 gap-0.5 px-1 text-xs font-normal"
variant="default"
>
<CircleDot className="h-2 w-2" />
Live
</Badge>
) : null}
{buzzTool ? (
<BuzzToolInlineAction args={item.args} result={item.result} />
) : null}
{showStatus ? (
<span className="flex shrink-0 items-center gap-1 text-xs text-muted-foreground">
<status.Icon
className={cn(
"h-4 w-4",
item.status === "executing" && "animate-pulse",
)}
/>
{status.label}
</span>
) : null}
<ToolTimestamp item={item} duration={duration} />
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform group-open:rotate-180" />
</>
)}
<CompactToolSummaryRow
duration={duration}
preview={compactSummary.preview}
thumbnailSrc={compactSummary.thumbnailSrc}
label={compactSummary.label}
/>
</summary>
<ToolDetailBlocks
@@ -141,7 +60,7 @@ export function ToolItem({
hasArgs={hasArgs}
hasResult={hasResult}
imagePreview={
compactSummary?.kind === "view_image" && isExpanded
compactSummary.kind === "view_image" && isExpanded
? {
src: compactSummary.thumbnailSrc,
title: compactSummary.preview,
@@ -156,6 +75,10 @@ export function ToolItem({
);
}
function compactSummaryTone() {
return "text-muted-foreground/60 group-open:text-muted-foreground";
}
function CompactToolSummaryRow({
duration,
label,
@@ -168,6 +91,7 @@ function CompactToolSummaryRow({
thumbnailSrc: string | null;
}) {
const [thumbnailFailed, setThumbnailFailed] = React.useState(false);
const mutedTone = compactSummaryTone();
const resolvedThumbnail = React.useMemo(() => {
if (!thumbnailSrc || thumbnailFailed) return null;
return resolveImageSrc(thumbnailSrc);
@@ -175,7 +99,9 @@ function CompactToolSummaryRow({
return (
<>
<span className="shrink-0 text-sm font-semibold">{label}</span>
<span className={cn("shrink-0 text-sm font-semibold", mutedTone)}>
{label}
</span>
{resolvedThumbnail ? (
<img
alt=""
@@ -188,18 +114,21 @@ function CompactToolSummaryRow({
/>
) : preview ? (
<span
className="min-w-0 max-w-48 truncate text-sm text-muted-foreground/70"
className={cn("min-w-0 max-w-48 truncate text-sm", mutedTone)}
title={preview}
>
{preview}
</span>
) : null}
{duration ? (
<span className="shrink-0 text-xs text-muted-foreground/70">
{duration}
</span>
<span className={cn("shrink-0 text-xs", mutedTone)}>{duration}</span>
) : null}
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform group-open:rotate-180" />
<ChevronDown
className={cn(
"h-3.5 w-3.5 shrink-0 transition-transform group-open:rotate-180",
mutedTone,
)}
/>
</>
);
}
@@ -433,239 +362,6 @@ function ToolCodeBlock({
);
}
const toolFullDateTimeFormat = new Intl.DateTimeFormat(undefined, {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "2-digit",
second: "2-digit",
});
function ToolTimestamp({
duration,
item,
}: {
duration: string | null;
item: Extract<TranscriptItem, { type: "tool" }>;
}) {
const time = formatTranscriptTime(item.timestamp);
if (!time) return null;
const date = new Date(item.timestamp);
const fullDateTime = Number.isNaN(date.getTime())
? item.timestamp
: toolFullDateTimeFormat.format(date);
return (
<Tooltip>
<TooltipTrigger asChild>
<span className="shrink-0 cursor-default text-xs text-muted-foreground/60">
{time}
{duration ? ` · ${duration}` : null}
</span>
</TooltipTrigger>
<TooltipContent side="top">{fullDateTime}</TooltipContent>
</Tooltip>
);
}
function BuzzToolInlineAction({
args,
result,
}: {
args: Record<string, unknown>;
result: string;
}) {
const { channels } = useChannelNavigation();
const { goChannel } = useAppNavigation();
const resultValue = React.useMemo(
() => parseToolResultValue(result),
[result],
);
const resultRecord = asRecord(resultValue);
const channelId =
getToolString(args, ["channel_id", "channelId"]) ??
getToolString(resultRecord, ["channel_id", "channelId"]);
const pubkeys = React.useMemo(
() => getToolStringList(args, ["pubkeys", "pubkey"]),
[args],
);
const profilesQuery = useUsersBatchQuery(pubkeys, {
enabled: pubkeys.length > 0,
});
const profiles = profilesQuery.data?.profiles;
const openChannel = React.useCallback(
(messageId?: string) => {
if (!channelId) return;
void goChannel(channelId, messageId ? { messageId } : undefined);
},
[channelId, goChannel],
);
const action = React.useMemo(
() =>
getBuzzToolInlineAction({
args,
channelId,
channels,
openChannel,
profiles,
resultValue,
}),
[args, channelId, channels, openChannel, profiles, resultValue],
);
if (!action) {
return null;
}
if (action.onClick) {
return (
<button
className="inline-flex max-w-56 shrink min-w-0 items-center gap-1 rounded-full border border-primary/20 bg-primary/5 px-1.5 py-0.5 text-xs font-normal leading-none text-primary/90 transition-colors hover:border-primary/35 hover:bg-primary/10 hover:text-primary"
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
action.onClick?.();
}}
title={action.title}
type="button"
>
{action.avatar}
<span className="shrink-0">{action.label}</span>
<span className="truncate">{action.value}</span>
<ArrowUpRight className="h-4 w-4 shrink-0" />
</button>
);
}
return (
<span
className="inline-flex max-w-56 shrink min-w-0 items-center gap-1 rounded-full border border-border/60 bg-muted/40 px-1.5 py-0.5 text-xs font-normal leading-none text-muted-foreground"
title={action.title}
>
{action.avatar}
<span className="shrink-0">{action.label}</span>
<span className="truncate">{action.value}</span>
</span>
);
}
type BuzzToolInlineActionModel = {
avatar?: React.ReactNode;
label: string;
value: string;
title: string;
onClick?: () => void;
};
function getBuzzToolInlineAction({
args,
channelId,
channels,
openChannel,
profiles,
resultValue,
}: {
args: Record<string, unknown>;
channelId: string | null;
channels: Channel[];
openChannel: (messageId?: string) => void;
profiles: Record<string, UserProfileSummary> | undefined;
resultValue: unknown;
}): BuzzToolInlineActionModel | null {
const resultRecord = asRecord(resultValue);
const eventId =
getToolString(args, ["event_id", "eventId"]) ??
getToolString(resultRecord, ["event_id", "eventId", "id"]);
if (eventId && channelId) {
return {
label: resultRecord.accepted === true ? "posted" : "event",
onClick: () => openChannel(eventId),
title: eventId,
value: getChannelChipLabel(channels, channelId),
};
}
const messages = getResultArray(resultValue, resultRecord, "messages");
if (messages) {
return {
label: "read",
onClick: channelId ? () => openChannel() : undefined,
title: `${messages.length} messages`,
value: `${messages.length} message${messages.length === 1 ? "" : "s"}`,
};
}
if (channelId) {
return {
label: "channel",
onClick: () => openChannel(),
title: channelId,
value: getChannelChipLabel(channels, channelId),
};
}
const workflowId =
getToolString(args, ["workflow_id", "workflowId"]) ??
getToolString(resultRecord, ["workflow_id", "workflowId"]);
if (workflowId) {
return {
label: "workflow",
title: workflowId,
value: shortenMiddle(workflowId, 26),
};
}
const pubkeys = getToolStringList(args, ["pubkeys", "pubkey"]);
if (pubkeys.length > 0) {
if (pubkeys.length === 1) {
const pk = pubkeys[0];
const displayName = resolveUserLabel({ pubkey: pk, profiles });
const profile = profiles?.[pk.toLowerCase()];
return {
avatar: (
<UserAvatar
avatarUrl={profile?.avatarUrl ?? null}
className="shrink-0"
displayName={displayName}
size="xs"
/>
),
label: "user",
title: pk,
value: displayName,
};
}
return {
label: "users",
title: pubkeys
.map((pk) => resolveUserLabel({ pubkey: pk, profiles }))
.join(", "),
value: `${pubkeys.length} users`,
};
}
const query = getToolString(args, ["query"]);
if (query) {
return {
label: "query",
title: query,
value: shortenMiddle(query, 30),
};
}
if (typeof resultRecord.accepted === "boolean") {
return {
label: "relay",
title: resultRecord.accepted ? "accepted" : "rejected",
value: resultRecord.accepted ? "accepted" : "rejected",
};
}
return null;
}
function parseToolResultValue(result: string): unknown {
const trimmed = result.trim();
if (!trimmed) return null;
@@ -682,8 +378,3 @@ function parseToolResultValue(result: string): unknown {
return null;
}
}
function getChannelChipLabel(channels: Channel[], channelId: string) {
const channel = channels.find((candidate) => candidate.id === channelId);
return channel ? `#${channel.name}` : `#${shortenMiddle(channelId, 22)}`;
}
@@ -15,7 +15,6 @@ import {
} from "@/features/profile/lib/identity";
import { cn } from "@/shared/lib/cn";
import { normalizePubkey } from "@/shared/lib/pubkey";
import { Badge } from "@/shared/ui/badge";
import { Markdown } from "@/shared/ui/markdown";
import { Toggle } from "@/shared/ui/toggle";
import { UserAvatar } from "@/shared/ui/UserAvatar";
@@ -29,12 +28,35 @@ import {
type TranscriptDisplayBlock,
type TranscriptTurnSegment,
} from "./agentSessionTranscriptGrouping";
import { buildTranscriptPresentation } from "./agentSessionTranscriptPresentation";
import { formatTranscriptTime } from "./agentSessionUtils";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip";
/** Dev-only: surface the observer wire label that produced each transcript row. */
const SHOW_TRANSCRIPT_ACP_SOURCE = import.meta.env.DEV;
const TRANSCRIPT_ACP_SOURCE_STORAGE_KEY = "buzz:show-transcript-acp-source";
/**
* Opt-in only: source pills are useful while iterating on observer parsing, but
* they should not appear for every local dev session.
*/
const SHOW_TRANSCRIPT_ACP_SOURCE = shouldShowTranscriptAcpSource();
function shouldShowTranscriptAcpSource() {
const envValue = import.meta.env.VITE_SHOW_TRANSCRIPT_ACP_SOURCE;
if (envValue === "1" || envValue === "true") {
return true;
}
if (typeof window === "undefined") {
return false;
}
try {
return (
window.localStorage.getItem(TRANSCRIPT_ACP_SOURCE_STORAGE_KEY) === "1"
);
} catch {
return false;
}
}
type AgentTranscriptIdentityProps = {
agentAvatarUrl: string | null;
@@ -48,7 +70,6 @@ export function AgentSessionTranscriptList({
agentPubkey,
compact = false,
emptyDescription,
isWorking = false,
items,
profiles,
}: AgentTranscriptIdentityProps & {
@@ -58,10 +79,6 @@ export function AgentSessionTranscriptList({
items: TranscriptItem[];
profiles?: UserProfileLookup;
}) {
const presentation = React.useMemo(
() => buildTranscriptPresentation(items, isWorking),
[items, isWorking],
);
const displayBlocks = React.useMemo(
() => buildTranscriptDisplayBlocks(items),
[items],
@@ -92,7 +109,6 @@ export function AgentSessionTranscriptList({
>
{displayBlocks.map((block) => (
<TranscriptDisplayBlockView
activeItemIds={presentation.activeItemIds}
agentAvatarUrl={agentAvatarUrl}
agentName={agentName}
agentPubkey={agentPubkey}
@@ -127,7 +143,6 @@ function getDisplayBlockKey(block: TranscriptDisplayBlock) {
}
function TranscriptDisplayBlockView({
activeItemIds,
agentAvatarUrl,
agentName,
agentPubkey,
@@ -135,7 +150,6 @@ function TranscriptDisplayBlockView({
compact,
profiles,
}: AgentTranscriptIdentityProps & {
activeItemIds: ReadonlySet<string>;
block: TranscriptDisplayBlock;
compact: boolean;
profiles?: UserProfileLookup;
@@ -143,7 +157,6 @@ function TranscriptDisplayBlockView({
if (block.kind === "single") {
return (
<TranscriptItemRow
activeItemIds={activeItemIds}
agentAvatarUrl={agentAvatarUrl}
agentName={agentName}
agentPubkey={agentPubkey}
@@ -162,7 +175,6 @@ function TranscriptDisplayBlockView({
>
{block.segments.map((segment) => (
<TranscriptTurnSegmentView
activeItemIds={activeItemIds}
agentAvatarUrl={agentAvatarUrl}
agentName={agentName}
agentPubkey={agentPubkey}
@@ -187,7 +199,6 @@ function getTurnSegmentKey(turnId: string, segment: TranscriptTurnSegment) {
}
function TranscriptTurnSegmentView({
activeItemIds,
agentAvatarUrl,
agentName,
agentPubkey,
@@ -195,7 +206,6 @@ function TranscriptTurnSegmentView({
profiles,
segment,
}: AgentTranscriptIdentityProps & {
activeItemIds: ReadonlySet<string>;
compact: boolean;
profiles?: UserProfileLookup;
segment: TranscriptTurnSegment;
@@ -218,7 +228,6 @@ function TranscriptTurnSegmentView({
return (
<TranscriptItemRow
activeItemIds={activeItemIds}
agentAvatarUrl={agentAvatarUrl}
agentName={agentName}
agentPubkey={agentPubkey}
@@ -313,7 +322,7 @@ function PromptUserMessage({
>
<p className="whitespace-pre-wrap wrap-break-word">{text}</p>
{contextOpen && context ? (
<PromptContextSections sections={context.sections} />
<PromptContextSections sections={context.sections} setup={setup} />
) : null}
</div>
<TurnSetupFooter
@@ -328,12 +337,19 @@ function PromptUserMessage({
);
}
function PromptContextSections({ sections }: { sections: PromptSection[] }) {
function PromptContextSections({
sections,
setup,
}: {
sections: PromptSection[];
setup: Extract<TranscriptItem, { type: "lifecycle" }>[];
}) {
return (
<div
className="mt-2 space-y-2 border-t border-border/40 pt-2"
data-testid="transcript-prompt-context-sections"
>
<PromptSetupSummary items={setup} />
{sections.map((section) => (
<details
className="group/section"
@@ -352,6 +368,29 @@ function PromptContextSections({ sections }: { sections: PromptSection[] }) {
);
}
function PromptSetupSummary({
items,
}: {
items: Extract<TranscriptItem, { type: "lifecycle" }>[];
}) {
const label = formatTurnSetupLabel(items);
const detail = turnSetupDetail(items);
const setupText = [label, detail].filter(Boolean).join(" · ");
if (!setupText) {
return null;
}
return (
<p
className="text-xs leading-5 text-muted-foreground"
data-testid="transcript-prompt-setup-summary"
>
{setupText}
</p>
);
}
function TurnSetupFooter({
context = null,
contextOpen = false,
@@ -375,12 +414,35 @@ function TurnSetupFooter({
return <TranscriptTimestamp timestamp={timestamp} />;
}
const contextToggle = showContext ? (
<Toggle
aria-label={`${contextOpen ? "Hide" : "Show"} prompt context`}
data-testid="transcript-prompt-context-toggle"
className="data-[state=on]:bg-primary/10 data-[state=on]:text-primary dark:data-[state=on]:bg-primary/15"
onPressedChange={onContextOpenChange}
pressed={contextOpen}
size="xs"
variant="ghost"
>
{showSetup ? <CheckCheck aria-hidden="true" /> : null}
Context
</Toggle>
) : null;
return (
<div
className="flex items-center gap-1.5 text-muted-foreground/80"
data-testid="transcript-turn-setup"
>
{showSetup ? (
{showContext && showSetup ? (
<Tooltip>
<TooltipTrigger asChild>{contextToggle}</TooltipTrigger>
<TooltipContent side="top">
<p>{tooltipText}</p>
</TooltipContent>
</Tooltip>
) : null}
{!showContext && showSetup ? (
<Tooltip>
<TooltipTrigger asChild>
<button
@@ -396,25 +458,13 @@ function TurnSetupFooter({
</TooltipContent>
</Tooltip>
) : null}
{showContext ? (
<Toggle
aria-label={`${contextOpen ? "Hide" : "Show"} prompt context`}
data-testid="transcript-prompt-context-toggle"
onPressedChange={onContextOpenChange}
pressed={contextOpen}
size="xs"
variant="outline"
>
Context
</Toggle>
) : null}
{showContext && !showSetup ? contextToggle : null}
<TranscriptTimestamp timestamp={timestamp} />
</div>
);
}
function TranscriptItemRow({
activeItemIds,
agentAvatarUrl,
agentName,
agentPubkey,
@@ -422,7 +472,6 @@ function TranscriptItemRow({
item,
profiles,
}: AgentTranscriptIdentityProps & {
activeItemIds: ReadonlySet<string>;
compact: boolean;
item: TranscriptItem;
profiles?: UserProfileLookup;
@@ -444,7 +493,6 @@ function TranscriptItemRow({
agentName={agentName}
agentPubkey={agentPubkey}
compact={compact}
isActive={activeItemIds.has(item.id)}
item={item}
profiles={profiles}
/>
@@ -486,12 +534,10 @@ const TranscriptItemView = React.memo(function TranscriptItemView({
agentName,
agentPubkey,
compact,
isActive,
item,
profiles,
}: AgentTranscriptIdentityProps & {
compact: boolean;
isActive: boolean;
item: TranscriptItem;
profiles?: UserProfileLookup;
}) {
@@ -502,17 +548,16 @@ const TranscriptItemView = React.memo(function TranscriptItemView({
agentName={agentName}
agentPubkey={agentPubkey}
compact={compact}
isActive={isActive}
item={item}
profiles={profiles}
/>
);
}
if (item.type === "tool") {
return <ToolItem compact={compact} isActive={isActive} item={item} />;
return <ToolItem compact={compact} item={item} />;
}
if (item.type === "thought") {
return <ThoughtItem compact={compact} isActive={isActive} item={item} />;
return <ThoughtItem compact={compact} item={item} />;
}
if (item.type === "metadata") {
return <MetadataItem compact={compact} item={item} />;
@@ -525,12 +570,10 @@ function MessageItem({
agentName,
agentPubkey,
compact,
isActive,
item,
profiles,
}: AgentTranscriptIdentityProps & {
compact: boolean;
isActive: boolean;
item: Extract<TranscriptItem, { type: "message" }>;
profiles?: UserProfileLookup;
}) {
@@ -561,9 +604,6 @@ function MessageItem({
"flex animate-in fade-in duration-200 motion-reduce:animate-none",
isAssistant ? "flex-row" : "flex-row items-start justify-end",
compact ? "px-0 py-0.5" : "px-1 py-1",
isAssistant &&
isActive &&
"rounded-lg border border-primary/15 bg-primary/3 px-2 py-1.5",
)}
data-role={isAssistant ? "assistant-message" : "user-message"}
data-testid={
@@ -596,15 +636,6 @@ function MessageItem({
<span className="text-xs font-semibold text-foreground">
{assistantLabel}
</span>
{isActive ? (
<Badge
className="h-4 gap-0.5 px-1 text-xs font-normal"
variant="default"
>
<CircleDot className="h-2 w-2" />
Live
</Badge>
) : null}
<TranscriptTimestamp timestamp={item.timestamp} />
</div>
) : null}
@@ -630,11 +661,9 @@ function MessageItem({
function ThoughtItem({
compact,
isActive,
item,
}: {
compact: boolean;
isActive: boolean;
item: Extract<TranscriptItem, { type: "thought" }>;
}) {
return (
@@ -642,22 +671,12 @@ function ThoughtItem({
className={cn(
"group not-prose w-full rounded-md border border-transparent",
compact ? "px-0" : "px-1",
isActive && "border-primary/15 bg-primary/3 px-2 py-1",
)}
data-testid="transcript-thought-item"
>
<summary className="inline-flex max-w-full cursor-pointer list-none items-center gap-1.5 py-px text-muted-foreground">
<Brain className={cn("h-4 w-4", isActive && "text-primary")} />
<Brain className="h-4 w-4" />
<span className="truncate text-sm font-medium">{item.title}</span>
{isActive ? (
<Badge
className="h-4 gap-0.5 px-1 text-xs font-normal"
variant="default"
>
<CircleDot className="h-2 w-2" />
Live
</Badge>
) : null}
<TranscriptTimestamp timestamp={item.timestamp} />
<ChevronDown className="h-4 w-4 shrink-0 transition-transform group-open:rotate-180" />
</summary>