11"use client" ;
2- import { useCallback , useEffect , useState } from "react" ;
2+ import { useCallback , useEffect , useRef , useState } from "react" ;
33import { AgentGrid } from "@/components/AgentGrid" ;
44import { AgentListView } from "@/components/AgentListView" ;
55import { Plus , LayoutGrid , List } from "lucide-react" ;
66import KagentLogo from "@/components/kagent-logo" ;
77import Link from "next/link" ;
8+ import { useRouter , useSearchParams } from "next/navigation" ;
89import { ErrorState } from "./ErrorState" ;
910import { Button } from "./ui/button" ;
1011import { LoadingState } from "./LoadingState" ;
11- import { useAgents } from "./AgentsProvider " ;
12+ import { getAgents } from "@/app/actions/agents " ;
1213import { AppPageFrame } from "@/components/layout/AppPageFrame" ;
1314import { PageHeader } from "@/components/layout/PageHeader" ;
1415import { cn } from "@/lib/utils" ;
16+ import { NamespaceCombobox } from "@/components/NamespaceCombobox" ;
17+ import type { AgentResponse } from "@/types" ;
1518
1619const AGENTS_VIEW_KEY = "kagent-agents-view" ;
1720type AgentsView = "grid" | "list" ;
@@ -25,8 +28,41 @@ function readStoredView(): AgentsView {
2528}
2629
2730export default function AgentList ( ) {
28- const { agents , loading, error } = useAgents ( ) ;
31+ const router = useRouter ( ) ;
32+ const searchParams = useSearchParams ( ) ;
33+ const selectedNamespace = searchParams . get ( "namespace" ) ?. trim ( ) || "" ;
34+ const [ agents , setAgents ] = useState < AgentResponse [ ] > ( [ ] ) ;
35+ const [ loading , setLoading ] = useState ( true ) ;
36+ const [ error , setError ] = useState ( "" ) ;
2937 const [ view , setView ] = useState < AgentsView > ( "grid" ) ;
38+ const latestFetchRequestId = useRef ( 0 ) ;
39+
40+ const fetchAgents = useCallback ( async ( ) => {
41+ const requestId = latestFetchRequestId . current + 1 ;
42+ latestFetchRequestId . current = requestId ;
43+
44+ try {
45+ setLoading ( true ) ;
46+ const result = await getAgents ( selectedNamespace ? { namespace : selectedNamespace } : { } ) ;
47+ if ( requestId !== latestFetchRequestId . current ) {
48+ return ;
49+ }
50+ if ( result . error ) {
51+ throw new Error ( result . error || "Failed to fetch agents" ) ;
52+ }
53+ setAgents ( result . data || [ ] ) ;
54+ setError ( "" ) ;
55+ } catch ( err ) {
56+ if ( requestId !== latestFetchRequestId . current ) {
57+ return ;
58+ }
59+ setError ( err instanceof Error ? err . message : "An unexpected error occurred" ) ;
60+ } finally {
61+ if ( requestId === latestFetchRequestId . current ) {
62+ setLoading ( false ) ;
63+ }
64+ }
65+ } , [ selectedNamespace ] ) ;
3066
3167 useEffect ( ( ) => {
3268 const id = requestAnimationFrame ( ( ) => {
@@ -35,6 +71,10 @@ export default function AgentList() {
3571 return ( ) => cancelAnimationFrame ( id ) ;
3672 } , [ ] ) ;
3773
74+ useEffect ( ( ) => {
75+ void fetchAgents ( ) ;
76+ } , [ fetchAgents ] ) ;
77+
3878 const setViewAndPersist = useCallback ( ( next : AgentsView ) => {
3979 setView ( next ) ;
4080 try {
@@ -44,6 +84,22 @@ export default function AgentList() {
4484 }
4585 } , [ ] ) ;
4686
87+ const handleNamespaceChange = useCallback (
88+ ( nextNamespace : string ) => {
89+ const namespace = nextNamespace . trim ( ) ;
90+ if ( ! namespace ) {
91+ router . push ( "/agents" ) ;
92+ return ;
93+ }
94+ router . push ( `/agents?namespace=${ encodeURIComponent ( namespace ) } ` ) ;
95+ } ,
96+ [ router ] ,
97+ ) ;
98+
99+ const createHref = selectedNamespace
100+ ? `/agents/new?namespace=${ encodeURIComponent ( selectedNamespace ) } `
101+ : "/agents/new" ;
102+
47103 if ( error ) {
48104 return < ErrorState message = { error } /> ;
49105 }
@@ -57,69 +113,98 @@ export default function AgentList() {
57113 < PageHeader
58114 titleId = "agents-page-title"
59115 title = "Agents"
116+ description = {
117+ selectedNamespace ? (
118+ < >
119+ Showing agents in namespace < code > { selectedNamespace } </ code > .
120+ </ >
121+ ) : (
122+ "Showing agents across all namespaces."
123+ )
124+ }
60125 className = "mb-8"
61126 end = {
62- agents && agents . length > 0 ? (
63- < div
64- className = "flex w-full min-w-0 items-center justify-end gap-1 rounded-lg border border-border/60 bg-muted/20 p-1"
65- role = "group"
66- aria-label = "Layout"
67- >
68- < Button
69- type = "button"
70- variant = "ghost"
71- size = "sm"
72- className = { cn (
73- "h-8 gap-1.5 px-2.5 text-muted-foreground" ,
74- view === "grid" && "bg-card text-foreground shadow-sm" ,
75- ) }
76- aria-pressed = { view === "grid" }
77- aria-label = "Show agents as cards"
78- onClick = { ( ) => setViewAndPersist ( "grid" ) }
79- >
80- < LayoutGrid className = "h-4 w-4 shrink-0" aria-hidden />
81- < span className = "hidden sm:inline" aria-hidden >
82- Cards
83- </ span >
84- </ Button >
85- < Button
86- type = "button"
87- variant = "ghost"
88- size = "sm"
89- className = { cn (
90- "h-8 gap-1.5 px-2.5 text-muted-foreground" ,
91- view === "list" && "bg-card text-foreground shadow-sm" ,
92- ) }
93- aria-pressed = { view === "list" }
94- aria-label = "Show agents as a list"
95- onClick = { ( ) => setViewAndPersist ( "list" ) }
96- >
97- < List className = "h-4 w-4 shrink-0" aria-hidden />
98- < span className = "hidden sm:inline" aria-hidden >
99- List
100- </ span >
101- </ Button >
127+ < div className = "flex w-full min-w-0 flex-col gap-2 sm:w-auto sm:flex-row sm:items-center sm:justify-end" >
128+ < div className = "w-full sm:w-72" >
129+ < NamespaceCombobox
130+ value = { selectedNamespace }
131+ onValueChange = { handleNamespaceChange }
132+ includeAllNamespaces
133+ autoSelectDefault = { false }
134+ ariaLabel = "Namespace"
135+ placeholder = "All namespaces"
136+ />
102137 </ div >
103- ) : null
138+ { agents && agents . length > 0 ? (
139+ < div
140+ className = "flex w-full min-w-0 items-center justify-end gap-1 rounded-lg border border-border/60 bg-muted/20 p-1 sm:w-auto"
141+ role = "group"
142+ aria-label = "Layout"
143+ >
144+ < Button
145+ type = "button"
146+ variant = "ghost"
147+ size = "sm"
148+ className = { cn (
149+ "h-8 gap-1.5 px-2.5 text-muted-foreground" ,
150+ view === "grid" && "bg-card text-foreground shadow-sm" ,
151+ ) }
152+ aria-pressed = { view === "grid" }
153+ aria-label = "Show agents as cards"
154+ onClick = { ( ) => setViewAndPersist ( "grid" ) }
155+ >
156+ < LayoutGrid className = "h-4 w-4 shrink-0" aria-hidden />
157+ < span className = "hidden sm:inline" aria-hidden >
158+ Cards
159+ </ span >
160+ </ Button >
161+ < Button
162+ type = "button"
163+ variant = "ghost"
164+ size = "sm"
165+ className = { cn (
166+ "h-8 gap-1.5 px-2.5 text-muted-foreground" ,
167+ view === "list" && "bg-card text-foreground shadow-sm" ,
168+ ) }
169+ aria-pressed = { view === "list" }
170+ aria-label = "Show agents as a list"
171+ onClick = { ( ) => setViewAndPersist ( "list" ) }
172+ >
173+ < List className = "h-4 w-4 shrink-0" aria-hidden />
174+ < span className = "hidden sm:inline" aria-hidden >
175+ List
176+ </ span >
177+ </ Button >
178+ </ div >
179+ ) : null }
180+ </ div >
104181 }
105182 />
106183
107184 { agents ?. length === 0 ? (
108185 < div className = "rounded-xl border border-border/60 bg-card/30 py-12 text-center shadow-sm" >
109186 < KagentLogo className = "mx-auto mb-4 h-16 w-16" />
110- < h2 className = "mb-2 text-lg font-medium tracking-tight" > No agents yet</ h2 >
111- < p className = "mb-6 text-pretty text-sm text-muted-foreground" > Create an agent to run it in your cluster and wire models and tools in one place.</ p >
187+ < h2 className = "mb-2 text-lg font-medium tracking-tight" >
188+ { selectedNamespace
189+ ? `No agents found in namespace "${ selectedNamespace } ".`
190+ : "No agents yet" }
191+ </ h2 >
192+ < p className = "mb-6 text-pretty text-sm text-muted-foreground" >
193+ { selectedNamespace
194+ ? "Create an agent in this namespace or switch namespaces."
195+ : "Create an agent to run it in your cluster and wire models and tools in one place." }
196+ </ p >
112197 < Button asChild size = "lg" className = "min-w-[12rem]" >
113- < Link href = "/agents/new" >
198+ < Link href = { createHref } >
114199 < Plus className = "mr-2 h-4 w-4" aria-hidden />
115200 New Agent
116201 </ Link >
117202 </ Button >
118203 </ div >
119204 ) : view === "list" ? (
120- < AgentListView agentResponse = { agents || [ ] } />
205+ < AgentListView agentResponse = { agents || [ ] } onAgentsChanged = { fetchAgents } />
121206 ) : (
122- < AgentGrid agentResponse = { agents || [ ] } />
207+ < AgentGrid agentResponse = { agents || [ ] } onAgentsChanged = { fetchAgents } />
123208 ) }
124209 </ AppPageFrame >
125210 ) ;
0 commit comments