mirror of
https://github.com/block/buzz.git
synced 2026-11-04 00:12:02 +00:00
Polish agent activity panel layout
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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",
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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.`}
|
||||
|
||||
@@ -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)}
|
||||
|
||||
Reference in New Issue
Block a user