import { ContextMenu } from "@base-ui/react/context-menu" import { Dialog } from "@base-ui/react/dialog" import { Link } from "@tanstack/react-router" import { CalendarBlankIcon, CaretDownIcon, CaretRightIcon, ChartLineUpIcon, ChatCircleIcon, CircleNotchIcon, GitMergeIcon, GitPullRequestIcon, LightningIcon, PlusIcon, TrashIcon, TreeStructureIcon, XIcon, } from "@phosphor-icons/react" import { IoLogoGithub, IoLogoSlack } from "react-icons/io5" import { SiLinear } from "react-icons/si" import { useState } from "react" import type { ComponentType, SVGProps } from "react" import type { SessionUser } from "@/lib/api" import type { AgentSource, AgentThread } from "@/lib/agents/types" import { SidebarUserMenu } from "@/components/SidebarUserMenu" import { ReviewFileTree, ReviewSidebarProvider, useReviewSidebarData, } from "@/components/agents/ReviewSidebar" import { Button } from "@/components/ui/button" import { SidebarCollapseButton, SidebarFrame, useSidebarLayout, } from "@/components/sidebar-layout" import { groupThreads } from "@/lib/agents/api" import { useAgentThreads, useDeleteAgentThread, useSeedAgentThreadDetails, } from "@/lib/agents/queries" import { cn } from "@/lib/utils" type SourceIcon = ComponentType> const SOURCE_META: Record = { dashboard: { icon: ChatCircleIcon, label: "Started from the dashboard" }, github: { icon: IoLogoGithub, label: "Triggered from GitHub" }, slack: { icon: IoLogoSlack, label: "Triggered from Slack" }, linear: { icon: SiLinear, label: "Triggered from Linear" }, schedule: { icon: CalendarBlankIcon, label: "Triggered from a schedule" }, } type PrState = NonNullable["state"] const PR_STATE_META: Record< PrState, { icon: SourceIcon; label: string; className: string } > = { draft: { icon: GitPullRequestIcon, label: "Draft pull request", className: "text-[var(--ui-text-dim)]", }, open: { icon: GitPullRequestIcon, label: "Open pull request", className: "text-[var(--ui-success)]", }, merged: { icon: GitMergeIcon, label: "Merged pull request", className: "text-[var(--ui-accent)]", }, closed: { icon: GitPullRequestIcon, label: "Closed pull request", className: "text-[var(--ui-danger)]", }, } interface AgentsSidebarProps { user: SessionUser activeThreadId?: string } const NAV = [ { to: "/agents/automations", label: "Automations", icon: LightningIcon }, { to: "/my-settings", label: "Dashboard", icon: ChartLineUpIcon }, { to: "/agents/reviews", label: "Reviews", icon: GitPullRequestIcon }, ] as const export function AgentsSidebar({ user, activeThreadId }: AgentsSidebarProps) { const threadsQuery = useAgentThreads() const threads = threadsQuery.data ?? [] useSeedAgentThreadDetails(threads, activeThreadId) const groups = groupThreads(threads) const layout = useSidebarLayout() const reviewSidebar = useReviewSidebarData() return (
open-swe
New Agent
{reviewSidebar ? ( ) : (
)}
) } function ThreadGroup({ label, threads, activeThreadId, onNavigate, defaultCollapsed = false, }: { label: string threads: Array activeThreadId?: string onNavigate?: () => void defaultCollapsed?: boolean }) { const [collapsed, setCollapsed] = useState(defaultCollapsed) if (threads.length === 0) return null const ToggleIcon = collapsed ? CaretRightIcon : CaretDownIcon return (
{!collapsed && threads.map((thread) => ( ))}
) } function ThreadRow({ thread, isActive, onNavigate, }: { thread: AgentThread isActive: boolean onNavigate?: () => void }) { const deleteThread = useDeleteAgentThread() const [deleteOpen, setDeleteOpen] = useState(false) const badge = thread.diffStats && thread.diffStats.additions > 0 ? `+${thread.diffStats.additions}` : null const isDeleting = deleteThread.isPending && deleteThread.variables === thread.id const onDelete = (e: React.MouseEvent) => { e.preventDefault() e.stopPropagation() if (isDeleting) return setDeleteOpen(true) } const onConfirmDelete = () => { if (isDeleting) return deleteThread.mutate(thread.id, { onSuccess: () => setDeleteOpen(false), }) } const source = thread.source && thread.source !== "dashboard" ? SOURCE_META[thread.source] : null const SourceIcon = source?.icon const prMeta = thread.pr ? PR_STATE_META[thread.pr.state] : null const PrIcon = prMeta?.icon const showFinishedIndicator = thread.status === "finished" && !thread.viewed const openTrace = () => { if (!thread.traceUrl) return window.open(thread.traceUrl, "_blank", "noopener,noreferrer") } return ( <> } > {thread.status === "running" ? ( ) : ( )} {source && SourceIcon && ( {source.label} )} {thread.title} {prMeta && PrIcon && ( {prMeta.label} )} {badge && ( {badge} )} Open trace Delete thread
Delete thread Delete "{thread.title}"? This cannot be undone.
) } export function AgentsShell({ user, activeThreadId, children, }: { user: SessionUser activeThreadId?: string children: React.ReactNode }) { return (
{children}
) }