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
workflow-brief.mdcustomer-feedback.csv
Shift + Enter for newline
"use client"
import { useState } from "react"
import { Badge } from "@/components/ui/badge"
import { ChatComposer } from "@/components/ui/chat-composer"
import {
ModelSelector,
type ModelSelectorModel,
type ModelSelectorValue,
} from "@/components/ui/model-selector"
const models: ModelSelectorModel[] = [
{
provider: "zgi",
providerLabel: "ZGI",
model: "zgi-agent-pro",
label: "ZGI Agent Pro",
capabilities: ["tools", "json"],
},
{
provider: "zgi",
providerLabel: "ZGI",
model: "zgi-agent-fast",
label: "ZGI Agent Fast",
capabilities: ["streaming"],
},
]
export function ChatComposerDemo() {
const [value, setValue] = useState("")
const [model, setModel] = useState<ModelSelectorValue>({
provider: "zgi",
model: "zgi-agent-pro",
})
return (
<ChatComposer
value={value}
onValueChange={setValue}
onSubmit={({ message }) => {
setValue(`Review this next: ${message}`)
}}
modelSelector={
<ModelSelector
models={models}
value={model}
onValueChange={setModel}
showCapabilities={false}
className="h-9 w-44"
/>
}
attachments={
<div className="flex flex-wrap gap-2">
<Badge variant="secondary" className="rounded-sm">
workflow-brief.md
</Badge>
<Badge variant="secondary" className="rounded-sm">
customer-feedback.csv
</Badge>
</div>
}
helperText="Shift + Enter for newline"
className="w-full max-w-2xl"
/>
)
}
Installation
pnpm dlx shadcn@latest add https://ui.zgi.ai/r/chat-composer.jsonpnpm dlx shadcn@latest add https://ui.zgi.ai/r/chat-composer.json
yarn dlx shadcn@latest add https://ui.zgi.ai/r/chat-composer.jsonComponent
"use client"
import * as React from "react"
import { PaperclipIcon, SendIcon, SquareIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Textarea } from "@/components/ui/textarea"
export interface ChatComposerSubmitData {
message: string
}
export interface ChatComposerProps
extends Omit<React.ComponentProps<"div">, "onSubmit"> {
value?: string
defaultValue?: string
onValueChange?: (value: string) => void
onSubmit?: (data: ChatComposerSubmitData) => void | Promise<void>
onStop?: () => void
placeholder?: string
disabled?: boolean
loading?: boolean
attachments?: React.ReactNode
leadingActions?: React.ReactNode
trailingActions?: React.ReactNode
modelSelector?: React.ReactNode
submitLabel?: string
stopLabel?: string
helperText?: React.ReactNode
}
export function ChatComposer({
value,
defaultValue = "",
onValueChange,
onSubmit,
onStop,
placeholder = "Ask ZGI to review, transform, or run a workflow...",
disabled = false,
loading = false,
attachments,
leadingActions,
trailingActions,
modelSelector,
submitLabel = "Send",
stopLabel = "Stop",
helperText,
className,
...props
}: ChatComposerProps) {
const [internalValue, setInternalValue] = React.useState(defaultValue)
const message = value ?? internalValue
const canSubmit = message.trim().length > 0 && !disabled && !loading
function setMessage(nextValue: string) {
onValueChange?.(nextValue)
if (value === undefined) setInternalValue(nextValue)
}
async function submit() {
if (!canSubmit) return
await onSubmit?.({ message: message.trim() })
if (value === undefined) setInternalValue("")
}
return (
<div
className={cn(
"border-border bg-card text-card-foreground rounded-lg border p-3 shadow-sm",
className
)}
{...props}
>
{attachments ? <div className="mb-3">{attachments}</div> : null}
<Textarea
value={message}
onChange={(event) => setMessage(event.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault()
submit()
}
}}
placeholder={placeholder}
disabled={disabled}
className="min-h-24 resize-none border-0 bg-transparent p-0 shadow-none focus-visible:ring-0"
/>
<div className="mt-3 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div className="flex min-w-0 flex-wrap items-center gap-2">
{modelSelector}
{leadingActions}
{helperText ? (
<Badge variant="secondary" className="rounded-sm font-normal">
{helperText}
</Badge>
) : null}
</div>
<div className="flex shrink-0 items-center justify-end gap-2">
{trailingActions}
<Button
type="button"
variant="outline"
size="icon"
className="size-9 rounded-md"
disabled={disabled || loading}
aria-label="Attach file"
>
<PaperclipIcon className="size-4" />
</Button>
{loading ? (
<Button
type="button"
variant="outline"
className="h-9 rounded-md"
onClick={onStop}
>
<SquareIcon className="size-4 fill-current" />
{stopLabel}
</Button>
) : (
<Button
type="button"
className="h-9 rounded-md"
disabled={!canSubmit}
onClick={submit}
>
<SendIcon className="size-4" />
{submitLabel}
</Button>
)}
</div>
</div>
</div>
)
}
Usage
import { ChatComposer } from "@/components/ui/chat-composer"
export function Example() {
return (
<ChatComposer
onSubmit={({ message }) => {
console.log(message)
}}
/>
)
}Props
| Prop | Type | Default |
|---|---|---|
| value | string | - |
| defaultValue | string | "" |
| onValueChange | (value: string) => void | - |
| onSubmit | ({ message }) => void | Promise<void> | - |
| onStop | () => void | - |
| loading | boolean | false |
| attachments | ReactNode | - |
| modelSelector | ReactNode | - |
| helperText | ReactNode | - |
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