Getting Started
Components
- Agent Status
- Agent Template Card
- API Key Card
- Audio Player
- Bar Visualizer
- Channel Status Card
- Chat Composer
- Code Editor
- Condition Builder
- Confirm Dialog
- Conversation
- Conversation Bar
- Dataset Card
- Document Status Badge
- Extraction Strategy Select
- File Icon
- File Upload
- Indexing Progress
- Live Waveform
- Matrix
- Message
- Mic Selector
- Model Diff View
- Model Selector
- Model Type Filter
- Node Catalog
- Option Editor
- Orb
- Output Variables View
- Package Card
- Prompt Editor
- Provider Status Card
- Radio Card
- Resource Card
- Resource Sidebar
- Response
- Run Node List
- Scrub Bar
- Segment Card
- Shimmering Text
- Speech Input
- Token Trend Chart
- Tool Call Card
- Transcript Viewer
- Usage Stat Card
- Variable Binding Editor
- Variable Selector
- Voice Button
- Voice Picker
- Waveform
- Workflow Node Card
- Workspace Selector
Node catalog
5AI
Logic
Tools
Selected: llm
"use client"
import { useState } from "react"
import {
BrainCircuitIcon,
Code2Icon,
DatabaseIcon,
GitBranchIcon,
GlobeIcon,
} from "lucide-react"
import {
NodeCatalog,
type NodeCatalogItem,
} from "@/components/ui/node-catalog"
const items: NodeCatalogItem[] = [
{
id: "llm",
label: "LLM",
category: "AI",
description: "Generate structured responses with model routing.",
icon: <BrainCircuitIcon />,
tags: ["model", "prompt"],
},
{
id: "knowledge",
label: "Knowledge retrieval",
category: "AI",
description: "Retrieve relevant chunks from indexed datasets.",
icon: <DatabaseIcon />,
},
{
id: "if-else",
label: "If / Else",
category: "Logic",
description: "Route execution by conditions and confidence thresholds.",
icon: <GitBranchIcon />,
},
{
id: "http",
label: "HTTP request",
category: "Tools",
description: "Call an external API with mapped workflow variables.",
icon: <GlobeIcon />,
},
{
id: "code",
label: "Code",
category: "Tools",
description: "Transform values with a JavaScript or Python snippet.",
icon: <Code2Icon />,
},
]
export function NodeCatalogDemo() {
const [selected, setSelected] = useState("llm")
return (
<div className="w-full max-w-sm space-y-3">
<NodeCatalog
items={items}
onItemSelect={(item) => setSelected(item.id)}
/>
<p className="text-muted-foreground text-sm">Selected: {selected}</p>
</div>
)
}
Installation
pnpm dlx shadcn@latest add https://ui.zgi.ai/r/node-catalog.jsonpnpm dlx shadcn@latest add https://ui.zgi.ai/r/node-catalog.json
yarn dlx shadcn@latest add https://ui.zgi.ai/r/node-catalog.jsonComponent
"use client"
import * as React from "react"
import { SearchIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { Badge } from "@/components/ui/badge"
import { Input } from "@/components/ui/input"
export interface NodeCatalogItem {
id: string
label: React.ReactNode
description?: React.ReactNode
icon?: React.ReactNode
category?: string
tags?: string[]
}
export interface NodeCatalogProps
extends Omit<React.ComponentProps<"div">, "onSelect" | "title"> {
items: NodeCatalogItem[]
title?: React.ReactNode
searchPlaceholder?: string
emptyText?: React.ReactNode
onItemSelect?: (item: NodeCatalogItem) => void
}
export function NodeCatalog({
items,
title = "Node catalog",
searchPlaceholder = "Search nodes...",
emptyText = "No nodes found.",
onItemSelect,
className,
...props
}: NodeCatalogProps) {
const [query, setQuery] = React.useState("")
const filtered = React.useMemo(() => {
const needle = query.trim().toLowerCase()
if (!needle) return items
return items.filter((item) =>
[
item.id,
String(item.label),
String(item.description ?? ""),
item.category ?? "",
...(item.tags ?? []),
]
.join(" ")
.toLowerCase()
.includes(needle)
)
}, [items, query])
const groups = React.useMemo(() => {
const grouped = new Map<string, NodeCatalogItem[]>()
for (const item of filtered) {
const category = item.category ?? "Nodes"
grouped.set(category, [...(grouped.get(category) ?? []), item])
}
return Array.from(grouped.entries())
}, [filtered])
return (
<div
className={cn(
"border-border bg-card text-card-foreground overflow-hidden rounded-lg border shadow-sm",
className
)}
{...props}
>
<div className="border-border border-b p-3">
<div className="mb-3 flex items-center justify-between gap-3">
<p className="text-sm font-medium">{title}</p>
<Badge variant="secondary" className="rounded-md">
{filtered.length}
</Badge>
</div>
<div className="relative">
<SearchIcon className="text-muted-foreground pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2" />
<Input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder={searchPlaceholder}
className="h-9 pl-9"
/>
</div>
</div>
<div className="max-h-96 overflow-auto p-2">
{groups.length ? (
groups.map(([category, categoryItems]) => (
<div key={category} className="py-1">
<p className="text-muted-foreground px-2 py-1.5 text-xs font-medium">
{category}
</p>
<div className="space-y-1">
{categoryItems.map((item) => (
<button
key={item.id}
type="button"
className="hover:bg-accent flex w-full items-start gap-3 rounded-md p-2 text-left transition-colors"
onClick={() => onItemSelect?.(item)}
>
{item.icon ? (
<span className="bg-muted text-muted-foreground flex size-8 shrink-0 items-center justify-center rounded-md [&_svg]:size-4">
{item.icon}
</span>
) : null}
<span className="min-w-0 flex-1">
<span className="block truncate text-sm font-medium">
{item.label}
</span>
{item.description ? (
<span className="text-muted-foreground mt-0.5 line-clamp-2 block text-xs leading-5">
{item.description}
</span>
) : null}
</span>
</button>
))}
</div>
</div>
))
) : (
<div className="text-muted-foreground p-6 text-center text-sm">
{emptyText}
</div>
)}
</div>
</div>
)
}
Usage
import { NodeCatalog } from "@/components/ui/node-catalog"
export function Example() {
return <NodeCatalog items={[{ id: "llm", label: "LLM" }]} />
}Deploy and Scale Agents with ZGI
ZGI delivers the infrastructure and developer experience you need to ship reliable audio & agent applications at scale.
Talk to an expert