AI suite
The Phase 1 components that turn the conversation primitive into a full chat experience: a status-aware prompt-input, collapsible reasoning, message actions, and prompt suggestions. The demo below wires them all together — try a suggestion, then watch the reasoning stream before the answer.
Pick a suggestion or type below to see the AI suite in action.
Install
npx shadcn@latest add @weaveworm/prompt-input
npx shadcn@latest add @weaveworm/reasoning
npx shadcn@latest add @weaveworm/actions
npx shadcn@latest add @weaveworm/suggestions
npx shadcn@latest add @weaveworm/tool
npx shadcn@latest add @weaveworm/sources
npx shadcn@latest add @weaveworm/branch
npx shadcn@latest add @weaveworm/task
npx shadcn@latest add @weaveworm/image
npx shadcn@latest add @weaveworm/web-preview
npx shadcn@latest add @weaveworm/artifactPrompt input
A composable <form> composer. The textarea grows with its content and submits on Enter (Shift+Enter for a newline); the submit button reflects the generation status — send, spinner, stop, or error.
import {
PromptInput,
PromptInputTextarea,
PromptInputToolbar,
PromptInputTools,
PromptInputSubmit,
} from "@/components/ai/prompt-input";
<PromptInput onSubmit={handleSubmit}>
<PromptInputTextarea value={input} onChange={onChange} />
<PromptInputToolbar>
<PromptInputTools>{/* model picker, attachments… */}</PromptInputTools>
<PromptInputSubmit status={status} />
</PromptInputToolbar>
</PromptInput>Reasoning
Pass isStreaming and the block auto-expands while the model thinks, times the thought, then collapses — or control open yourself.
import { Reasoning, ReasoningTrigger, ReasoningContent } from "@/components/ai/reasoning";
<Reasoning isStreaming={isStreaming}>
<ReasoningTrigger />
<ReasoningContent>{reasoningText}</ReasoningContent>
</Reasoning>Tool
A collapsible card for a single tool call. Pass the state from the Vercel AI SDK's ToolUIPart and the header badge tracks it — pending, running, completed, or error. Drop in ToolConfirmation to gate a call behind human-in-the-loop approval.
{
"service": "web",
"ref": "main@a1b2c3d"
}Deploy web (main@a1b2c3d) to production?
import {
Tool,
ToolHeader,
ToolContent,
ToolInput,
ToolOutput,
ToolConfirmation,
} from "@/components/ai/tool";
<Tool state={part.state}>
<ToolHeader name={part.type} />
<ToolContent>
<ToolInput input={part.input} />
{needsApproval ? (
<ToolConfirmation onApprove={approve} onReject={reject} />
) : (
<ToolOutput output={part.output} errorText={part.errorText} />
)}
</ToolContent>
</Tool>Sources
Ground an answer in its citations: drop InlineCitation markers into the text (they show the source in a tooltip), then list everything in a collapsible Sources block underneath. Map a Vercel AI SDK source-url part straight onto <Source>.
Weaveworm builds its primitives on Base UI1, themes them with Tailwind v4 tokens2, and distributes everything through a shadcn-compatible registry3.
import {
Sources,
SourcesTrigger,
SourcesContent,
Source,
InlineCitation,
} from "@/components/ai/sources";
<p>
The answer text<InlineCitation index={1} href={sources[0].url} title={sources[0].title} />.
</p>
<Sources>
<SourcesTrigger count={sources.length} />
<SourcesContent>
{sources.map((s) => (
<Source key={s.url} href={s.url} title={s.title} />
))}
</SourcesContent>
</Sources>Branch
When a prompt is edited or an answer regenerated, you get more than one version of a turn. Wrap the alternatives in BranchMessages — only the active one renders — and BranchSelector pages through them with wrap-around prev/next controls and a 2 / 3 indicator. Use it controlled (value + onValueChange) or let it manage its own state.
Sure — Weaveworm is an open-source design system for building AI experiences.
import {
Branch,
BranchMessages,
BranchSelector,
BranchPrevious,
BranchPage,
BranchNext,
} from "@/components/ai/branch";
<Branch defaultValue={versions.length - 1}>
<BranchMessages>
{versions.map((text) => (
<Response key={text}>{text}</Response>
))}
</BranchMessages>
<BranchSelector>
<BranchPrevious />
<BranchPage />
<BranchNext />
</BranchSelector>
</Branch>Task
Show what an agent is doing — a collapsible trace of its steps along a single timeline. Pass status and the block auto-expands while the work is in-progress, then collapses once it's completed. Drop TaskItemFile chips into a step to reference the files it touched.
import {
Task,
TaskTrigger,
TaskContent,
TaskItem,
TaskItemFile,
} from "@/components/ai/task";
<Task status={status}>
<TaskTrigger title="Refactoring the auth flow" />
<TaskContent>
{steps.map((step) => (
<TaskItem key={step.id}>{step.text}</TaskItem>
))}
<TaskItem>
Edited <TaskItemFile name="auth.ts" />
</TaskItem>
</TaskContent>
</Task>Image
Render a model-generated image. Pass a src, or hand it the raw base64 + mediaType straight from the Vercel AI SDK's experimental_generateImage and it builds the data URL. A skeleton holds the space while the bitmap decodes, with a graceful fallback if it fails.
import { Image } from "@/components/ai/image";
<Image base64={file.base64} mediaType={file.mediaType} alt="Generated logo" />Web preview
Preview a site the agent generated or linked to, inside faux-browser chrome. Drop a WebPreviewNavigation (back / forward / reload + an editable address bar) above a sandboxed WebPreviewBody iframe. Point the body at a src URL or hand it generated HTML via srcDoc.
import {
WebPreview,
WebPreviewNavigation,
WebPreviewBody,
} from "@/components/ai/web-preview";
<WebPreview defaultUrl="https://example.com" className="h-96">
<WebPreviewNavigation />
<WebPreviewBody />
</WebPreview>Artifact
Lift a discrete piece of generated content — a document, a code file, a chart — out of the message stream into its own framed panel. Pair an ArtifactHeader (title, description, and an ArtifactActions row of icon buttons) above an ArtifactContent body that holds the rendered output.
export function greet(name: string) {
return `Hello, ${name}!`;
}import {
Artifact,
ArtifactHeader,
ArtifactTitleGroup,
ArtifactTitle,
ArtifactDescription,
ArtifactActions,
ArtifactAction,
ArtifactContent,
} from "@/components/ai/artifact";
<Artifact>
<ArtifactHeader>
<ArtifactTitleGroup>
<ArtifactTitle>{file.name}</ArtifactTitle>
<ArtifactDescription>{file.language}</ArtifactDescription>
</ArtifactTitleGroup>
<ArtifactActions>
<ArtifactAction label="Copy" onClick={copy}>
<Copy />
</ArtifactAction>
</ArtifactActions>
</ArtifactHeader>
<ArtifactContent>{/* code, doc, preview… */}</ArtifactContent>
</Artifact>