import { AgentAvatar } from "./AgentAvatar"; import { Link } from "@/lib/router"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { deriveInitials } from "./Identity"; import { IssueReferenceActivitySummary } from "./IssueReferenceActivitySummary"; import { timeAgo } from "../lib/timeAgo"; import { cn } from "../lib/utils"; import { formatActivityVerb } from "../lib/activity-format"; import { deriveProjectUrlKey, type ActivityEvent, type Agent } from "@paperclipai/shared"; import type { CompanyUserProfile } from "../lib/company-members"; import { useState, type ReactNode } from "react"; import { CellMarquee, HoveredCellContext, useCellMarqueeHover } from "./CellMarquee"; function entityLink(entityType: string, entityId: string, name?: string | null): string | null { switch (entityType) { case "issue": return `/issues/${name ?? entityId}`; case "agent": return `/agents/${entityId}`; case "project": return `/projects/${deriveProjectUrlKey(name, entityId)}`; case "goal": return `/goals/${entityId}`; case "approval": return `/approvals/${entityId}`; default: return null; } } export interface ActivityRowProps { event: ActivityEvent; agentMap: Map; userProfileMap?: Map; entityNameMap: Map; entityTitleMap?: Map; className?: string; appearance?: "default" | "refreshed"; density?: "comfortable" | "compact"; } export function ActivityRow({ event, agentMap, userProfileMap, entityNameMap, entityTitleMap, className, appearance = "default", density = "comfortable" }: ActivityRowProps) { const verb = formatActivityVerb(event.action, event.details, { agentMap, userProfileMap }); const isHeartbeatEvent = event.entityType === "heartbeat_run"; const heartbeatAgentId = isHeartbeatEvent ? (event.details as Record | null)?.agentId as string | undefined : undefined; const name = isHeartbeatEvent ? (heartbeatAgentId ? entityNameMap.get(`agent:${heartbeatAgentId}`) : null) : entityNameMap.get(`${event.entityType}:${event.entityId}`); const entityTitle = entityTitleMap?.get(`${event.entityType}:${event.entityId}`) ?? (event.entityType === "issue" && typeof event.details?.issueTitle === "string" ? event.details.issueTitle : undefined); const link = isHeartbeatEvent && heartbeatAgentId ? `/agents/${heartbeatAgentId}/runs/${event.entityId}` : entityLink(event.entityType, event.entityId, name); const actor = event.actorType === "agent" ? agentMap.get(event.actorId) : null; const userProfile = event.actorType === "user" ? userProfileMap?.get(event.actorId) : null; const actorName = actor?.name ?? (event.actorType === "system" ? "System" : userProfile?.label ?? (event.actorType === "user" ? "Board" : event.actorId || "Unknown")); const actorAvatarUrl = userProfile?.image ?? null; const avatar = event.actorType === "agent" ? ( ) : ( ); if (appearance === "refreshed") { const subject = event.entityType === "issue" ? entityTitle || name || "Task" : [name, entityTitle].filter(Boolean).join(" — "); return ; } const inner = (
{avatar}

{actorName}{" "} {verb} {event.entityType === "issue" ? ( {entityTitle} ) : ( {name && {name}} {entityTitle && — {entityTitle}} )}

{event.entityType === "issue" ? name : null}
{timeAgo(event.createdAt)}
); const classes = cn( "dashboard-list-row text-sm", link && "cursor-pointer hover:bg-accent/50 transition-colors", className, ); if (link) { return ( {inner} ); } return (
{inner}
); } function RefreshedActivityRow({ event, avatar, actorName, verb, subject, identifier, link, density, className }: { event: ActivityEvent; avatar: ReactNode; actorName: string; verb: string; subject: string; identifier?: string; link: string | null; density: "comfortable" | "compact"; className?: string; }) { const { hoveredCell, onMouseMove, clearHoveredCell } = useCellMarqueeHover(); const [focused, setFocused] = useState(false); const rowProps = { "data-slot": "activity-row", "data-density": density, className: cn("task-row-refreshed task-summary-row activity-row-refreshed", className), onMouseMove, onMouseLeave: () => { clearHoveredCell(); setFocused(false); }, onFocus: () => setFocused(true), onBlur: () => setFocused(false), }; const content = <> {avatar} {actorName} {verb}{subject ? ` ${subject}` : ""} {identifier ? {identifier} : null} {timeAgo(event.createdAt)} ; return {link ? {content} :
{content}
}
; }