UI components for the Agenttic framework
npm install @automattic/agenttic-uiReact 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.
``bash`
npm install @automattic/agenttic-ui
- 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
`tsx
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 (
isProcessing={ isProcessing }
error={ error }
onSubmit={ onSubmit }
onStop={ abortCurrentRequest }
suggestions={ suggestions }
clearSuggestions={ clearSuggestions }
messageRenderer={ messageRenderer }
variant="floating"
placeholder="Ask me anything..."
/>
);
}
`
For complete control over layout and component ordering:
`tsx
import { AgentUI } from '@automattic/agenttic-ui';
function CustomChatLayout() {
return (
isProcessing={ isProcessing }
error={ error }
onSubmit={ onSubmit }
onStop={ abortCurrentRequest }
variant="embedded"
>
);
}
`
External control of input value and changes:
`tsx
function ExternallyControlledChat() {
const [ inputValue, setInputValue ] = useState( '' );
return (
isProcessing={ isProcessing }
onSubmit={ onSubmit }
inputValue={ inputValue }
onInputChange={ setInputValue }
variant="embedded"
/>
);
}
`
Track when the user is actively typing in the input field:
`tsx
function ChatWithTypingStatus() {
const [ isTyping, setIsTyping ] = useState( false );
return (
isProcessing={ isProcessing }
onSubmit={ onSubmit }
onTypingStatusChange={ setIsTyping }
variant="embedded"
/>
);
}
`
The onTypingStatusChange callback is triggered when the typing status changes. The user is considered "typing" when:
- The input field is focused
- The browser window is focused
- The input contains text (length > 0)
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
`tsx
interface AgentUIProps {
// Core chat data
messages: Message[];
isProcessing: boolean;
error?: string | null;
onSubmit: ( message: string ) => void;
onStop?: () => void;
// UI configuration
variant?: 'floating' | 'embedded';
placeholder?: string | string[];
triggerIcon?: React.ReactNode;
notice?: NoticeConfig;
emptyView?: React.ReactNode;
// Chat state management (floating variant)
floatingChatState?: ChatState;
onOpen?: () => void;
onExpand?: () => void;
onClose?: () => void;
// Suggestions
suggestions?: Suggestion[];
clearSuggestions?: () => void;
// Message rendering
messageRenderer?: ComponentType< { children: string } >;
// Controlled input (optional)
inputValue?: string;
onInputChange?: ( value: string ) => void;
// Typing status tracking
onTypingStatusChange?: ( isTyping: boolean ) => void;
// Styling
className?: string;
style?: React.CSSProperties;
}
`
Place suggestions anywhere in your layout:
`tsx
// Or below input:
`
Use individual components for complete customization:
`tsx
import {
Messages,
Message,
ChatInput,
Suggestions,
} from '@automattic/agenttic-ui';
function FullyCustomChat() {
return (
$3
Stop button appears automatically during processing:
`tsx
isProcessing={ isProcessing }
onStop={ abortCurrentRequest }
// Submit button becomes stop button when processing
/>
`$3
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`tsx
// Example: Mixing visible and context content
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', // Hidden from UI, sent to agent for 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,
},
]; ;
// Only messages 1 and 3 will be visible (message 2 has only context content)
// Example: Message with both visible and context content
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,
};
// The context content will be filtered out, only "Here are your analytics" is visible
`$3
`tsx
import { ReactMarkdown } from 'react-markdown';const customRenderer = ( { children }: { children: string } ) => (
{ children }
);
;
`$3
For floating variant, control state externally:
`tsx
const [ chatState, setChatState ] = useState< ChatState >( 'collapsed' ); variant="floating"
floatingChatState={ chatState }
onOpen={ () => setChatState( 'compact' ) }
onExpand={ () => setChatState( 'expanded' ) }
onClose={ () => setChatState( 'collapsed' ) }
/>;
`Hooks
$3
Manages floating chat state:
`tsx
const {
state, // 'collapsed' | 'compact' | 'expanded'
setState,
isOpen, // boolean
open, // () => void
close, // () => void
toggle, // () => void
} = useChat( initialState );
`$3
Manages input state with auto-resize:
`tsx
const { value, setValue, clear, textareaRef, handleKeyDown, adjustHeight } =
useInput( {
value: inputValue,
setValue: setInputValue,
onSubmit: handleSubmit,
isProcessing: false,
} );
`Type Definitions
`tsx
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
$3
`tsx
import '@automattic/agenttic-ui/index.css';
`$3
All styles are scoped to
.agenttic class to prevent conflicts.$3
Override CSS custom properties:
`css
.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:
`tsx
import {
ThumbsUpIcon,
ThumbsDownIcon,
CopyIcon,
StopIcon,
ArrowUpIcon,
XIcon,
BigSkyIcon,
StylesIcon,
} from '@automattic/agenttic-ui';
`Development
`bash
Build the package
pnpm buildRun in development mode
pnpm devRun tests
pnpm testType checking
pnpm type-checkStart Storybook
pnpm storybook
`Integration with agenttic-client
`tsx
import { useAgentChat } from '@automattic/agenttic-client';
import { AgentUI } from '@automattic/agenttic-ui';function App() {
const agentProps = useAgentChat( {
agentId: 'big-sky',
} );
return ;
}
`The
useAgentChat hook returns props that match the AgentUI` interface.