Polish agent activity panel layout

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Thomas Petersen
2026-05-06 22:41:03 -04:00
parent e8126b3d0c
commit 28a68c1787
4 changed files with 52 additions and 12 deletions

View File

@@ -1,5 +1,6 @@
import { Bot, Brain, ChevronDown, Radio, TerminalSquare } from "lucide-react";
import { Brain, ChevronDown, Radio, TerminalSquare } from "lucide-react";
import { ProfileAvatar } from "@/features/profile/ui/ProfileAvatar";
import { cn } from "@/shared/lib/cn";
import { Markdown } from "@/shared/ui/markdown";
import type { TranscriptItem } from "./agentSessionTypes";
@@ -8,10 +9,12 @@ import { formatTranscriptTime } from "./agentSessionUtils";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip";
export function AgentSessionTranscriptList({
agentAvatarUrl,
agentName,
emptyDescription,
items,
}: {
agentAvatarUrl: string | null;
agentName: string;
emptyDescription: string;
items: TranscriptItem[];
@@ -30,12 +33,16 @@ export function AgentSessionTranscriptList({
<div
aria-label="Live ACP transcript"
aria-live="polite"
className="mx-auto w-full max-w-3xl py-1"
className="w-full py-1"
role="log"
>
{items.map((item) => (
<div className="mt-4 first:mt-0" key={item.id}>
<TranscriptItemView agentName={agentName} item={item} />
<TranscriptItemView
agentAvatarUrl={agentAvatarUrl}
agentName={agentName}
item={item}
/>
</div>
))}
</div>
@@ -43,14 +50,22 @@ export function AgentSessionTranscriptList({
}
function TranscriptItemView({
agentAvatarUrl,
agentName,
item,
}: {
agentAvatarUrl: string | null;
agentName: string;
item: TranscriptItem;
}) {
if (item.type === "message") {
return <MessageItem agentName={agentName} item={item} />;
return (
<MessageItem
agentAvatarUrl={agentAvatarUrl}
agentName={agentName}
item={item}
/>
);
}
if (item.type === "tool") {
return <ToolItem item={item} />;
@@ -65,9 +80,11 @@ function TranscriptItemView({
}
function MessageItem({
agentAvatarUrl,
agentName,
item,
}: {
agentAvatarUrl: string | null;
agentName: string;
item: Extract<TranscriptItem, { type: "message" }>;
}) {
@@ -89,9 +106,12 @@ function MessageItem({
>
{isAssistant ? (
<div className="mb-0.5 flex items-center gap-1 text-xs">
<span className="flex h-5 w-5 items-center justify-center">
<Bot className="h-3.5 w-3.5 text-muted-foreground" />
</span>
<ProfileAvatar
avatarUrl={agentAvatarUrl}
className="h-5 w-5 rounded-full text-[9px] shadow-none"
iconClassName="h-3 w-3"
label={agentName}
/>
<span className="font-normal text-foreground">{agentName}</span>
<TranscriptTimestamp timestamp={item.timestamp} />
</div>
@@ -181,7 +201,7 @@ function LifecycleItem({
return (
<div
className={cn(
"flex items-center justify-center gap-1.5 px-4 py-2 text-center text-xs",
"flex items-center justify-start gap-1.5 px-1 py-2 text-left text-xs",
isError ? "text-destructive" : "text-muted-foreground",
)}
>

View File

@@ -25,6 +25,7 @@ import { useObserverEvents } from "./useObserverEvents";
type ManagedAgentSessionPanelProps = {
agent: ManagedAgent;
agentAvatarUrl?: string | null;
channelId?: string | null;
className?: string;
emptyDescription?: string;
@@ -34,6 +35,7 @@ type ManagedAgentSessionPanelProps = {
export function ManagedAgentSessionPanel({
agent,
agentAvatarUrl = null,
channelId = null,
className,
emptyDescription = "Mention this agent in a channel to watch the next turn.",
@@ -78,6 +80,7 @@ export function ManagedAgentSessionPanel({
) : null}
<SessionBody
agentAvatarUrl={agentAvatarUrl}
agentName={agent.name}
connectionState={connectionState}
emptyDescription={emptyDescription}
@@ -127,6 +130,7 @@ function SessionHeader({
}
function SessionBody({
agentAvatarUrl,
agentName,
connectionState,
emptyDescription,
@@ -136,6 +140,7 @@ function SessionBody({
showRaw,
transcript,
}: {
agentAvatarUrl: string | null;
agentName: string;
connectionState: ConnectionState;
emptyDescription: string;
@@ -160,6 +165,7 @@ function SessionBody({
)}
>
<AgentSessionTranscriptList
agentAvatarUrl={agentAvatarUrl}
agentName={agentName}
emptyDescription={emptyDescription}
items={transcript}

View File

@@ -1,8 +1,10 @@
import type * as React from "react";
import { Activity, Bot, CircleDot, Octagon, X } from "lucide-react";
import { Activity, CircleDot, Octagon, X } from "lucide-react";
import { toast } from "sonner";
import { usePersonasQuery } from "@/features/agents/hooks";
import { ManagedAgentSessionPanel } from "@/features/agents/ui/ManagedAgentSessionPanel";
import { ProfileAvatar } from "@/features/profile/ui/ProfileAvatar";
import { cancelManagedAgentTurn } from "@/shared/api/agentControl";
import type { Channel, ManagedAgent } from "@/shared/api/types";
import { useEscapeKey } from "@/shared/hooks/useEscapeKey";
@@ -41,6 +43,11 @@ export function AgentSessionThreadPanel({
}: AgentSessionThreadPanelProps) {
const isLive = agent.status === "running";
const isOverlay = useIsThreadPanelOverlay();
const personasQuery = usePersonasQuery();
const agentPersona = personasQuery.data?.find(
(persona) => persona.id === agent.personaId,
);
const agentAvatarUrl = agentPersona?.avatarUrl ?? null;
useEscapeKey(onClose, isOverlay);
const { ref: scrollRef, onScroll } = useStickToBottom<HTMLDivElement>();
@@ -87,7 +94,13 @@ export function AgentSessionThreadPanel({
)}
<div className="flex items-center gap-3 border-b border-border/70 px-4 py-2.5">
<Bot className="h-4 w-4 shrink-0 text-muted-foreground" />
<ProfileAvatar
avatarUrl={agentAvatarUrl}
className="h-8 w-8 rounded-xl text-xs shadow-none"
iconClassName="h-4 w-4"
label={agent.name}
testId="agent-session-avatar"
/>
<div className="min-w-0 flex-1">
<h2 className="truncate text-sm font-semibold tracking-tight">
{agent.name}
@@ -151,6 +164,7 @@ export function AgentSessionThreadPanel({
>
<ManagedAgentSessionPanel
agent={agent}
agentAvatarUrl={agentAvatarUrl}
channelId={channel.id}
className="border-0 bg-transparent p-0 shadow-none"
emptyDescription={`Mention ${agent.name} in the channel to see its work here.`}

View File

@@ -50,7 +50,7 @@ export function MessageThreadSummaryRow({
);
return (
<div className="relative pb-1">
<div className="relative pb-1 pt-1">
{depthGuideOffsets.length > 0 ? (
<div
aria-hidden
@@ -71,7 +71,7 @@ export function MessageThreadSummaryRow({
) : null}
<button
className="-mt-1 inline-flex w-fit max-w-full items-center gap-1 rounded-full border border-border/70 bg-muted/70 py-0.5 pl-0.5 pr-2 text-left text-xs font-medium text-foreground/90 transition-colors hover:bg-accent hover:text-accent-foreground"
className="inline-flex w-fit max-w-full items-center gap-1 rounded-full border border-border/70 bg-muted/70 py-0.5 pl-0.5 pr-2 text-left text-xs font-medium text-foreground/90 transition-colors hover:bg-accent hover:text-accent-foreground"
data-thread-head-id={message.id}
data-testid="message-thread-summary"
onClick={() => onOpenThread(message)}