@automattic/agenttic-ui
React UI components for AI agent chat interfaces. A pure UI layer designed to work seamlessly with @automattic/agenttic-client hooks or any agent communication system.
Installation
npm install @automattic/agenttic-ui
Key Features
- Pure UI components with no agent communication logic
- Composable architecture for complete layout flexibility
- Floating and embedded chat variants
- Controlled input support for external value management
- Smooth animations and drag-and-drop positioning
- Message actions and markdown rendering
- Request cancellation UI with stop button functionality
- TypeScript support with comprehensive types
- Storybook component documentation
Quick Start
Complete Chat Interface (Convenience API)
import { useAgentChat } from '@automattic/agenttic-client';
import { AgentUI } from '@automattic/agenttic-ui';
function ChatApplication() {
const {
messages,
isProcessing,
error,
onSubmit,
abortCurrentRequest,
suggestions,
clearSuggestions,
messageRenderer,
} = useAgentChat( {
agentId: 'big-sky',
sessionId: 'my-session',
} );
return (
<AgentUI
messages={ messages }
isProcessing={ isProcessing }
error={ error }
onSubmit={ onSubmit }
onStop={ abortCurrentRequest }
suggestions={ suggestions }
clearSuggestions={ clearSuggestions }
messageRenderer={ messageRenderer }
variant="floating"
placeholder="Ask me anything..."
/>
);
}
Composable Architecture
For complete control over layout and component ordering:
import { AgentUI } from '@automattic/agenttic-ui';
function CustomChatLayout() {
return (
<AgentUI.Container
messages={ messages }
isProcessing={ isProcessing }
error={ error }
onSubmit={ onSubmit }
onStop={ abortCurrentRequest }
variant="embedded"
>
<AgentUI.ConversationView>
<AgentUI.Header />
<AgentUI.Messages />
<AgentUI.Footer>
<AgentUI.Notice />
<AgentUI.Input />
</AgentUI.Footer>
<AgentUI.Suggestions />
</AgentUI.ConversationView>
</AgentUI.Container>
);
}
Controlled Input
External control of input value and changes:
function ExternallyControlledChat() {
const [ inputValue, setInputValue ] = useState( '' );
return (
<AgentUI
messages={ messages }
isProcessing={ isProcessing }
onSubmit={ onSubmit }
inputValue={ inputValue }
onInputChange={ setInputValue }
variant="embedded"
/>
);
}
Architecture
AgentUI Components
AgentUI - Convenience wrapper with default layout
AgentUI.Container - Root container with state management and context
AgentUI.ConversationView - Conversation layout wrapper
AgentUI.Header - Chat header with close/expand buttons
AgentUI.Messages - Message history display
AgentUI.Footer - Footer wrapper for input and notice
AgentUI.Input - Text input with auto-resize
AgentUI.Notice - Error and notification display
AgentUI.Suggestions - Quick action suggestions
AgentUIProps Interface
interface AgentUIProps {
messages: Message[];
isProcessing: boolean;
error?: string | null;
onSubmit: ( message: string ) => void;
onStop?: () => void;
variant?: 'floating' | 'embedded';
placeholder?: string | string[];
triggerIcon?: React.ReactNode;
notice?: NoticeConfig;
emptyView?: React.ReactNode;
floatingChatState?: ChatState;
onOpen?: () => void;
onExpand?: () => void;
onClose?: () => void;
suggestions?: Suggestion[];
clearSuggestions?: () => void;
messageRenderer?: ComponentType< { children: string } >;
inputValue?: string;
onInputChange?: ( value: string ) => void;
className?: string;
style?: React.CSSProperties;
}
Usage Patterns
Flexible Component Ordering
Place suggestions anywhere in your layout:
<AgentUI.Container {...props}>
<AgentUI.ConversationView>
<AgentUI.Messages />
<AgentUI.Suggestions /> {/* Above input */}
<AgentUI.Footer>
<AgentUI.Input />
</AgentUI.Footer>
</AgentUI.ConversationView>
</AgentUI.Container>
<AgentUI.Container {...props}>
<AgentUI.ConversationView>
<AgentUI.Messages />
<AgentUI.Footer>
<AgentUI.Input />
</AgentUI.Footer>
<AgentUI.Suggestions /> {/* Below input */}
</AgentUI.ConversationView>
</AgentUI.Container>
Individual Components
Use individual components for complete customization:
import {
Messages,
Message,
ChatInput,
Suggestions,
} from '@automattic/agenttic-ui';
function FullyCustomChat() {
return (
<div className="my-chat-container">
<Messages
messages={ messages }
messageRenderer={ messageRenderer }
/>
<ChatInput
value={ inputValue }
onChange={ setInputValue }
onSubmit={ onSubmit }
placeholder="Type a message..."
isProcessing={ isProcessing }
/>
<Suggestions
suggestions={ suggestions }
onSuggestionClick={ onSubmit }
onClear={ clearSuggestions }
/>
</div>
);
}
Request Cancellation
Stop button appears automatically during processing:
<AgentUI
isProcessing={ isProcessing }
onStop={ abortCurrentRequest }
/>
Content Types
Content items can have different types that determine how they're displayed:
type: 'text' - Normal text content (visible)
type: 'image_url' - Image content (visible)
type: 'component' - React component (visible)
type: 'context' - Context information sent as text to the agent but hidden from UI
const messages = [
{
id: '1',
role: 'user',
content: [ { type: 'text', text: 'Take me to the dashboard' } ],
timestamp: Date.now(),
archived: false,
showIcon: true,
},
{
id: '2',
role: 'user',
content: [
{
type: 'context',
text: 'Navigation completed. Dashboard loaded successfully.',
},
],
timestamp: Date.now(),
archived: false,
showIcon: true,
},
{
id: '3',
role: 'agent',
content: [
{ type: 'text', text: "I've taken you to the dashboard." },
],
timestamp: Date.now(),
archived: false,
showIcon: true,
},
];
<AgentUI messages={ messages } />;
const mixedMessage = {
id: '4',
role: 'user',
content: [
{ type: 'text', text: 'Here are your analytics' },
{ type: 'context', text: 'page: /analytics, loaded: true' },
],
timestamp: Date.now(),
archived: false,
showIcon: true,
};
Custom Message Renderer
import { ReactMarkdown } from 'react-markdown';
const customRenderer = ( { children }: { children: string } ) => (
<ReactMarkdown remarkPlugins={ [ remarkGfm ] }>{ children }</ReactMarkdown>
);
<AgentUI messageRenderer={ customRenderer } />;
Chat State Control
For floating variant, control state externally:
const [ chatState, setChatState ] = useState< ChatState >( 'collapsed' );
<AgentUI
variant="floating"
floatingChatState={ chatState }
onOpen={ () => setChatState( 'compact' ) }
onExpand={ () => setChatState( 'expanded' ) }
onClose={ () => setChatState( 'collapsed' ) }
/>;
Hooks
useChat
Manages floating chat state:
const {
state,
setState,
isOpen,
open,
close,
toggle,
} = useChat( initialState );
useInput
Manages input state with auto-resize:
const { value, setValue, clear, textareaRef, handleKeyDown, adjustHeight } =
useInput( {
value: inputValue,
setValue: setInputValue,
onSubmit: handleSubmit,
isProcessing: false,
} );
Type Definitions
interface Message {
id: string;
role: 'user' | 'agent';
content: Array< {
type: 'text' | 'image_url' | 'component' | 'context';
text?: string;
image_url?: string;
component?: React.ComponentType;
componentProps?: any;
} >;
timestamp: number;
archived: boolean;
showIcon: boolean;
icon?: string;
actions?: MessageAction[];
disabled?: boolean;
}
interface MessageAction {
id: string;
icon?: React.ReactNode;
label: string;
onClick: ( message: Message ) => void | Promise< void >;
tooltip?: string;
disabled?: boolean;
pressed?: boolean;
showLabel?: boolean;
}
interface Suggestion {
id: string;
label: string;
prompt: string;
}
interface NoticeConfig {
icon?: React.ReactNode | null | false;
message: string;
action?: {
label: string;
onClick: () => void;
};
dismissible?: boolean;
onDismiss?: () => void;
}
type ChatState = 'collapsed' | 'compact' | 'expanded';
Styling
CSS Import
import '@automattic/agenttic-ui/index.css';
CSS Scoping
All styles are scoped to .agenttic class to prevent conflicts.
Customization
Override CSS custom properties:
.agenttic {
--color-primary: #your-brand-color;
--color-background: #ffffff;
--color-foreground: #000000;
}
.agenttic [data-slot='chat-footer'] {
--color-background: oklch( 1 0 0 );
--color-primary: #your-brand-color;
}
Icons
Pre-built icon components:
import {
ThumbsUpIcon,
ThumbsDownIcon,
CopyIcon,
StopIcon,
ArrowUpIcon,
XIcon,
BigSkyIcon,
StylesIcon,
} from '@automattic/agenttic-ui';
Development
pnpm build
pnpm dev
pnpm test
pnpm type-check
pnpm storybook
Integration with agenttic-client
import { useAgentChat } from '@automattic/agenttic-client';
import { AgentUI } from '@automattic/agenttic-ui';
function App() {
const agentProps = useAgentChat( {
agentId: 'big-sky',
} );
return <AgentUI { ...agentProps } variant="floating" />;
}
The useAgentChat hook returns props that match the AgentUI interface.