File size: 3,348 Bytes
23b413b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | /**
* Hook that wraps useAgenticCatalog and provides a unified capabilities view.
*
* Merges catalog.tools + catalog.a2a_agents into a single CapabilityItem[]
* list with search, status, and type filtering.
*/
import { useMemo, useState } from 'react'
import { useAgenticCatalog } from '../../agentic/useAgenticCatalog'
import type { CapabilityItem } from '../../agentic/types'
import { deriveStatus } from './ToolStatusBadge'
type Args = {
backendUrl: string
apiKey?: string
}
export type TypeFilter = 'all' | 'tool' | 'a2a'
export function useToolsInventory({ backendUrl, apiKey }: Args) {
const { catalog, loading, error, refresh } = useAgenticCatalog({
backendUrl,
apiKey,
enabled: true,
})
const [search, setSearch] = useState('')
const [statusFilter, setStatusFilter] = useState<'all' | 'active' | 'inactive'>('all')
const [typeFilter, setTypeFilter] = useState<TypeFilter>('all')
/* Build unified list from both sources */
const allItems: CapabilityItem[] = useMemo(() => {
const toolItems: CapabilityItem[] = (catalog?.tools || [])
.filter((t) => t.id && t.name)
.map((t) => ({ kind: 'tool', data: t }))
const a2aItems: CapabilityItem[] = (catalog?.a2a_agents || [])
.filter((a) => a.id && a.name)
.map((a) => ({ kind: 'a2a_agent', data: a }))
return [...toolItems, ...a2aItems]
}, [catalog?.tools, catalog?.a2a_agents])
/* Filtered list */
const items = useMemo(() => {
let filtered = allItems
// Type filter
if (typeFilter !== 'all') {
const kind = typeFilter === 'a2a' ? 'a2a_agent' : 'tool'
filtered = filtered.filter((item) => item.kind === kind)
}
// Search filter
if (search.trim()) {
const q = search.toLowerCase()
filtered = filtered.filter((item) => {
const d = item.data
const searchable = `${d.name} ${d.description || ''} ${d.id} ${
item.kind === 'a2a_agent' && 'endpoint_url' in d ? d.endpoint_url || '' : ''
}`
return searchable.toLowerCase().includes(q)
})
}
// Status filter
if (statusFilter !== 'all') {
filtered = filtered.filter((item) => deriveStatus(item.data.enabled) === statusFilter)
}
return filtered
}, [allItems, search, statusFilter, typeFilter])
/* Counts */
const counts = useMemo(() => {
const tools = (catalog?.tools || [])
const a2a = (catalog?.a2a_agents || [])
return {
total: tools.length + a2a.length,
tools: {
total: tools.length,
active: tools.filter((t) => t.enabled === true).length,
inactive: tools.filter((t) => t.enabled === false).length,
},
a2a: {
total: a2a.length,
active: a2a.filter((a) => a.enabled === true).length,
inactive: a2a.filter((a) => a.enabled === false).length,
},
active: tools.filter((t) => t.enabled === true).length + a2a.filter((a) => a.enabled === true).length,
inactive: tools.filter((t) => t.enabled === false).length + a2a.filter((a) => a.enabled === false).length,
}
}, [catalog?.tools, catalog?.a2a_agents])
return {
items,
counts,
loading,
error,
refresh,
search,
setSearch,
statusFilter,
setStatusFilter,
typeFilter,
setTypeFilter,
forgeHealthy: catalog?.forge?.healthy ?? null,
}
}
|