Cloud agent workspace
Three-pane agent workspace with a preview panel.
Usage
import { AgentWorkspace } from '@/components/blocks/agent-01'
<AgentWorkspace />
// Fill a parent that has a height instead of the viewport
<AgentWorkspace height="100%" />
// Your own tasks, models and agent
<AgentWorkspace
brand={{ name: 'Harbor', logo: <HarborMark /> }}
tasks={[
{
id: 'fix-login',
title: 'Fix the login redirect loop',
repo: 'harbor-web',
branch: 'main',
pr: 41,
adds: 12,
dels: 4,
status: 'open',
files: [{ path: 'src/auth/redirect.ts', diff: '@@ -1,3 +1,3 @@\n-old line\n+new line' }],
thread: [{ id: 'm1', kind: 'user', text: 'The login page loops forever after sign-in.' }],
},
]}
models={[{ id: 'fast', label: 'Harbor Fast', description: 'Quick edits' }]}
defaultPanel="closed"
defaultTab="preview"
onSend={(message, { taskId, model }) => track('send', { taskId, model: model.id })}
generateReply={async function* (message, { task, signal }) {
const response = await fetch('/api/agent', {
method: 'POST',
body: JSON.stringify({ task: task.id, message }),
signal,
})
const reader = response.body!.pipeThrough(new TextDecoderStream()).getReader()
while (true) {
const { value, done } = await reader.read()
if (done) break
yield value
}
}}
/>
Install
pnpm dlx shadcn@latest add @pastaui-pro/agent-01npx shadcn@latest add @pastaui-pro/agent-01yarn dlx shadcn@latest add @pastaui-pro/agent-01bunx --bun shadcn@latest add @pastaui-pro/agent-01Source
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| brand | AgentBrand | = PastaUI and its mark | Name and logo at the top of the sidebar. The logo also sits in the empty state and the desktop taskbar. |
| user | AgentUser | = { name: 'Mira Okafor', plan: 'Team plan' } | Signed-in user at the bottom of the sidebar: name and an optional second line such as the plan. |
| tasks | AgentTask[] | = Five sample tasks | Sidebar tasks. Each: id, title, repo, optional branch, pr, adds and dels, status ('merged', 'open', 'draft' or 'idle'), optional files, previewUrl and the thread. Tasks with a PR show its number, counts and status, the rest show the repo. |
| tasks[].thread | AgentThreadItem[] | = — | Items in order. { kind: 'user', text } is your bubble, { kind: 'agent', text } is a reply where [label](href) becomes a link, and { kind: 'activity', label, icon, commands, files } is a collapsible row. Each command: id, command, status ('running', 'ok' or 'error'), optional duration and output. |
| tasks[].files | AgentFileChange[] | = — | Changed files for the Files tab. Each: path, a unified diff body with @@ hunk headers, and an optional status ('added', 'modified' or 'deleted'). Counts and line numbers come from the diff. |
| models / efforts / permissions | AgentOption[] | = Four current models (Claude Opus 5.5, Claude Sonnet 5, GPT-6 Astra, Gemini 3.1 Pro); Low, Medium, High; Read only, Ask, Full access | Options in the composer menus. Each: id, label, an optional one-line description and an optional icon such as the provider logo. |
| defaultModelId / defaultEffortId / defaultPermissionId | string | = First model / first effort / last permission | Options selected on first render. |
| defaultTaskId | string | = First task | Task open on first render. |
| defaultPanel | 'auto' | 'open' | 'closed' | = 'auto' | Whether the workspace panel starts open. Auto opens it when the block is at least 1200px wide. Below 768px it is always a sheet opened from the header. |
| defaultTab | 'files' | 'preview' | 'desktop' | = 'files' | Workspace tab open on first render. |
| defaultSidebarOpen | boolean | = true | Whether the sidebar starts open on wide layouts. |
| simulateActivity | boolean | = true | After each reply, add a sample activity row whose commands tick through, then a short summary. |
| onSend | (message: string, context: AgentSendContext) => void | = — | Called when a message is sent, with the task id, model, effort and permission. |
| generateReply | (message: string, context: AgentReplyContext) => string | Promise<string> | AsyncIterable<string> | = A canned reply | Produces the reply. Text is revealed word by word as it arrives. The context holds the task with its thread, the model, effort, permission and an AbortSignal that fires when Stop is pressed. |
| height | number | string | = '100dvh' | Height of the shell; the panes scroll inside it. Numbers are pixels and strings take any CSS length: '100%' fills a parent that has a height, such as the area under your own header, and 800 gives a fixed frame. |
| className / style | string / CSSProperties | = — | Classes for the root (use height rather than an h-* class) and inline overrides for the --pp-* theme variables. |