DUI
⌘K

Components / AI

AIComposer

Production composer with attachments, modes, plugins, context badges, and send states.

Preview

Website
1"use client"2
3import * as React from "react"4import { AIComposer, type AIComposerMode } from "@/components/ui/ai-composer"5
6export function AIComposerDemo() {7  const [value, setValue] = React.useState("")8  const [mode, setMode] = React.useState<AIComposerMode>("ask")9
10  return (11    <AIComposer12      value={value}13      onChange={setValue}14      onSend={async (message, files) => {15        console.log({ message, files })16      }}17      mode={mode}18      onModeChange={setMode}19      layout="landing"20      21      plugins={[22        { id: "github", label: "GitHub", description: "qentrah/DUI" }23      ]}24    />25  )26}

thread

The thread style of this component.

Website
1"use client"2
3import * as React from "react"4import { AIComposer, type AIComposerMode } from "@/components/ui/ai-composer"5
6export function AIComposerDemo() {7  const [value, setValue] = React.useState("")8  const [mode, setMode] = React.useState<AIComposerMode>("ask")9
10  return (11    <AIComposer12      value={value}13      onChange={setValue}14      onSend={async (message, files) => {15        console.log({ message, files })16      }}17      mode={mode}18      onModeChange={setMode}19      layout="thread"20      21      plugins={[22        { id: "github", label: "GitHub", description: "qentrah/DUI" }23      ]}24    />25  )26}

sending

The sending style of this component.

Website
1"use client"2
3import * as React from "react"4import { AIComposer, type AIComposerMode } from "@/components/ui/ai-composer"5
6export function AIComposerDemo() {7  const [value, setValue] = React.useState("")8  const [mode, setMode] = React.useState<AIComposerMode>("ask")9
10  return (11    <AIComposer12      value={value}13      onChange={setValue}14      onSend={async (message, files) => {15        console.log({ message, files })16      }}17      mode={mode}18      onModeChange={setMode}19      layout="thread"20      sending21      onStop={() => console.log("stop")}22      plugins={[23        { id: "github", label: "GitHub", description: "qentrah/DUI" }24      ]}25    />26  )27}

Installation

Add the source directly to your project with the shadcn CLI.

npx shadcn@latest add qentrah/DUI/ai-composer

Connect it to your product

AIComposer owns presentation and local attachment previews. Your application owns message delivery, cancellation, plugin state, and voice capture through callbacks. This keeps the installed component independent from any backend or AI SDK.

assistant-composer.tsx

tsx
1"use client"2
3import * as React from "react"4import {5  AIComposer,6  type AIComposerMode,7} from "@/components/ui/ai-composer"8
9export function AssistantComposer() {10  const [value, setValue] = React.useState("")11  const [mode, setMode] = React.useState<AIComposerMode>("ask")12  const [sending, setSending] = React.useState(false)13
14  async function send(message: string, files: File[]) {15    setSending(true)16    try {17      await yourApi.send({ message, files, mode })18    } finally {19      setSending(false)20    }21  }22
23  return (24    <AIComposer25      value={value}26      onChange={setValue}27      onSend={send}28      onStop={() => yourApi.stop()}29      sending={sending}30      mode={mode}31      onModeChange={setMode}32      layout="thread"33    />34  )35}

Plugins, context, and voice

Plugin options and badges are plain data. The selector reports user intent through onPluginToggle; it never connects accounts or sends data by itself.

composer-integrations.tsx

tsx
1<AIComposer2  value={value}3  onChange={setValue}4  onSend={send}5  mode={mode}6  onModeChange={setMode}7  badges={[8    { id: "website", label: "Website", tone: "info" },9  ]}10  plugins={[11    { id: "github", label: "GitHub", description: "qentrah/DUI" },12    { id: "sheets", label: "Google Sheets", description: "Planning" },13  ]}14  selectedPluginIds={selectedPluginIds}15  onPluginToggle={togglePlugin}16  onVoiceClick={startVoiceInput}17/>18

Behavior contract

  • Enter sends; Shift + Enter creates a new line.
  • Files can be selected or dropped, are deduplicated, and respect maxAttachments.
  • The component clears text and attachments only after onSend resolves successfully.
  • Use sending with onStop to expose cancellation.

Usage

Install the component, import it from your configured UI directory, and adapt the source to your product. Open the source panel under any example to copy that exact variant.