suna/frontend/src/components/dashboard/sidebar/nav-agents.tsx

164 lines
5.5 KiB
TypeScript
Raw Normal View History

2025-04-16 02:14:58 +08:00
"use client"
import { useEffect, useState } from "react"
import {
ArrowUpRight,
Link as LinkIcon,
MoreHorizontal,
Trash2,
StarOff,
2025-04-16 02:37:56 +08:00
Plus,
2025-04-16 04:45:46 +08:00
MessagesSquare,
2025-04-16 02:14:58 +08:00
} from "lucide-react"
2025-04-16 08:04:04 +08:00
import { toast } from "sonner"
2025-04-16 02:14:58 +08:00
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "@/components/ui/sidebar"
import { getProjects, getThreads } from "@/lib/api"
import Link from "next/link"
export function NavAgents() {
2025-04-16 04:45:46 +08:00
const { isMobile, state } = useSidebar()
2025-04-16 02:37:56 +08:00
const [agents, setAgents] = useState<{name: string, url: string}[]>([])
2025-04-16 02:14:58 +08:00
const [isLoading, setIsLoading] = useState(true)
// Load agents dynamically from the API
useEffect(() => {
async function loadAgents() {
try {
const projectsData = await getProjects()
const agentsList = []
2025-04-16 08:04:04 +08:00
const seenThreadIds = new Set() // Track unique thread IDs
2025-04-16 02:14:58 +08:00
for (const project of projectsData) {
const threads = await getThreads(project.id)
if (threads && threads.length > 0) {
// For each thread in the project, create an agent entry
for (const thread of threads) {
2025-04-16 08:04:04 +08:00
// Only add if we haven't seen this thread ID before
if (!seenThreadIds.has(thread.thread_id)) {
seenThreadIds.add(thread.thread_id)
agentsList.push({
name: `${project.name} - ${thread.thread_id.slice(0, 4)}`,
url: `/dashboard/agents/${thread.thread_id}`
})
}
2025-04-16 02:14:58 +08:00
}
}
}
setAgents(agentsList)
} catch (err) {
console.error("Error loading agents for sidebar:", err)
} finally {
setIsLoading(false)
}
}
loadAgents()
}, [])
return (
2025-04-16 04:45:46 +08:00
<SidebarGroup>
2025-04-16 02:14:58 +08:00
<div className="flex justify-between items-center">
<SidebarGroupLabel>Agents</SidebarGroupLabel>
2025-04-16 02:37:56 +08:00
<Link
2025-04-16 04:45:46 +08:00
href="/dashboard"
2025-04-16 02:37:56 +08:00
className="text-muted-foreground hover:text-foreground h-8 w-8 flex items-center justify-center rounded-md"
title="New Agent"
>
<Plus className="h-4 w-4" />
<span className="sr-only">New Agent</span>
</Link>
2025-04-16 02:14:58 +08:00
</div>
2025-04-16 08:04:04 +08:00
<SidebarMenu className="overflow-y-auto max-h-[calc(100vh-200px)]">
2025-04-16 02:14:58 +08:00
{isLoading ? (
// Show skeleton loaders while loading
Array.from({length: 3}).map((_, index) => (
<SidebarMenuItem key={`skeleton-${index}`}>
<SidebarMenuButton>
<div className="h-4 w-4 bg-sidebar-foreground/10 rounded-md animate-pulse"></div>
<div className="h-3 bg-sidebar-foreground/10 rounded w-3/4 animate-pulse"></div>
</SidebarMenuButton>
</SidebarMenuItem>
))
2025-04-16 08:04:04 +08:00
) : agents.length > 0 ? (
// Show all agents
2025-04-16 02:14:58 +08:00
<>
2025-04-16 08:04:04 +08:00
{agents.map((item, index) => (
2025-04-16 02:14:58 +08:00
<SidebarMenuItem key={`agent-${index}`}>
2025-04-16 04:45:46 +08:00
<SidebarMenuButton
asChild
tooltip={state === "collapsed" ? item.name : undefined}
>
2025-04-16 02:14:58 +08:00
<Link href={item.url} title={item.name}>
2025-04-16 04:45:46 +08:00
<MessagesSquare className="h-4 w-4" />
2025-04-16 02:14:58 +08:00
<span>{item.name}</span>
</Link>
</SidebarMenuButton>
2025-04-16 04:45:46 +08:00
{state !== "collapsed" && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuAction showOnHover>
<MoreHorizontal />
<span className="sr-only">More</span>
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-56 rounded-lg"
side={isMobile ? "bottom" : "right"}
align={isMobile ? "end" : "start"}
>
2025-04-16 08:04:04 +08:00
<DropdownMenuItem onClick={() => {
navigator.clipboard.writeText(window.location.origin + item.url)
toast.success("Link copied to clipboard")
}}>
2025-04-16 04:45:46 +08:00
<LinkIcon className="text-muted-foreground" />
<span>Copy Link</span>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a href={item.url} target="_blank" rel="noopener noreferrer">
<ArrowUpRight className="text-muted-foreground" />
<span>Open in New Tab</span>
</a>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Trash2 className="text-muted-foreground" />
<span>Delete</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
2025-04-16 02:14:58 +08:00
</SidebarMenuItem>
))}
</>
) : (
// Empty state
<SidebarMenuItem>
<SidebarMenuButton className="text-sidebar-foreground/70">
2025-04-16 04:45:46 +08:00
<MessagesSquare className="h-4 w-4" />
2025-04-16 02:14:58 +08:00
<span>No agents yet</span>
</SidebarMenuButton>
</SidebarMenuItem>
)}
</SidebarMenu>
</SidebarGroup>
)
}