@rodgerai/widgets
v2.0.0
Published
Pre-built React widgets for Rodger AI tools
Readme
@rodger/widgets
Beautiful, production-ready React widgets for Rodger AI tools. Seamlessly integrate with @assistant-ui/react to provide rich, interactive UI experiences for tool results.
Features
- 5 Pre-built Widgets - Cart builder, product lookup, quick actions, email signup, CTA buttons
- Built on assistant-ui - Leverages powerful assistant-ui primitives
- Type-Safe - Full TypeScript support with proper interfaces
- Tailwind Styled - Easy customization with Tailwind CSS
- State Management - Handles loading, success, error states automatically
- Responsive Design - Works on mobile, tablet, and desktop
Installation
npm install @rodger/widgets
# or
pnpm add @rodger/widgetsPeer Dependencies
{
"@assistant-ui/react": "^0.11.0",
"react": "^18.0.0 || ^19.0.0"
}These are automatically installed if not present.
Quick Start
1. Set Up Backend with Tools
// app/api/chat/route.ts
import { createAgent } from '@rodger/core';
import { cartBuilder, productLookup } from '@rodger/tools';
const agent = createAgent({
name: 'Shopping Assistant',
llm: { provider: 'openai', model: 'gpt-4o' },
tools: { cartBuilder, productLookup }
});2. Register Widgets in Frontend
// components/Chat.tsx
'use client';
import { Thread } from '@rodger/ui';
import { CartBuilderUI, ProductLookupUI } from '@rodger/widgets';
export default function Chat() {
return (
<Thread
widgets={{
cartBuilder: CartBuilderUI,
productLookup: ProductLookupUI
}}
/>
);
}That's it! When your agent uses these tools, the widgets automatically render the results.
Available Widgets
1. CartBuilderUI
Interactive cart builder with approval workflow, loading states, and checkout links.
import { CartBuilderUI } from '@rodger/widgets';
<Thread
widgets={{
cartBuilder: CartBuilderUI
}}
/>Features:
- Preview state with product details
- User approval workflow
- Loading state during cart creation
- Success state with checkout link
- Error handling with retry options
- Animated transitions
Tool Result Interface:
interface CartBuilderResult {
requiresApproval?: boolean;
preview?: {
products: Array<{
entityId: number;
name: string;
quantity: number;
price?: number;
}>;
protocolName: string;
notes: string | null;
};
message?: string;
}States:
- Preview - Shows cart contents, requires approval
- Loading - Displays spinner during cart creation
- Success - Shows checkout link and confirmation
- Error - Shows error message with retry option
Pairs with: cartBuilder from @rodger/tools
2. ProductLookupUI
Display product search results as interactive cards.
import { ProductLookupUI } from '@rodger/widgets';
<Thread
widgets={{
productLookup: ProductLookupUI
}}
/>Features:
- Responsive card grid layout
- Product metadata display (name, price, size)
- External product page links
- Disclaimer for research products
- Hover effects and animations
Tool Result Format:
ID: 123 | Product Name (Nickname) | $99.99 | 30 servings | https://example.com/product
ID: 456 | Another Product | $79.99 | 60 capsules | https://example.com/product2Component Exports:
import {
ProductLookupUI, // Main widget
ProductCard, // Individual card component
parseProductList // Helper to parse tool results
} from '@rodger/widgets';Pairs with: productLookup from @rodger/tools
3. QuickActionsWidget
Display interactive button choices to guide conversations.
import { QuickActionsWidget } from '@rodger/widgets';
<Thread
widgets={{
quickActions: QuickActionsWidget
}}
/>Features:
- Grid layout for multiple buttons
- Click to send pre-filled prompts
- Hover effects
- Responsive design (1-2 columns)
- Integrated with ThreadPrimitive.Suggestion
Tool Result Interface:
{
message: string; // Message above buttons
actions: Array<{
label: string; // Button text
prompt: string; // Prompt to send when clicked
}>;
}Example Rendering:
What would you like to do?
[Browse Products] [Get Recommendations]Pairs with: quickActions from @rodger/tools
4. EmailSignupWidget
Collect user email addresses with validation and feedback.
import { EmailSignupWidget } from '@rodger/widgets';
<Thread
widgets={{
emailSignup: EmailSignupWidget
}}
/>Features:
- Success/error state handling
- Visual feedback with icons
- Animated transitions
- Clean, minimal design
- Email validation messages
Tool Result Interface:
{
success: boolean;
email: string | null;
message: string; // Feedback message
}States:
- Success - Shows checkmark and confirmation
- Error - Shows error icon and message
Pairs with: emailSignup from @rodger/tools
5. CtaButtonWidget
Display prominent call-to-action buttons for important actions.
import { CtaButtonWidget } from '@rodger/widgets';
<Thread
widgets={{
ctaButton: CtaButtonWidget
}}
/>Features:
- Primary and secondary variants
- Optional description text
- External link with icon
- Hover animations
- Calendar icon for booking actions
- Opens in new window
Tool Result Interface:
{
label: string; // Button text
url: string; // Link destination
description?: string; // Optional description
variant?: 'primary' | 'secondary'; // Button style
}Variants:
- Primary - Navy background with light text
- Secondary - Border style with hover fill
Pairs with: ctaButton from @rodger/tools
Usage Patterns
Register All Widgets
import { Thread } from '@rodger/ui';
import {
CartBuilderUI,
ProductLookupUI,
QuickActionsWidget,
EmailSignupWidget,
CtaButtonWidget
} from '@rodger/widgets';
<Thread
widgets={{
cartBuilder: CartBuilderUI,
productLookup: ProductLookupUI,
quickActions: QuickActionsWidget,
emailSignup: EmailSignupWidget,
ctaButton: CtaButtonWidget
}}
/>Selective Registration
Only register widgets for tools your agent uses:
// Agent only uses cartBuilder and productLookup
<Thread
widgets={{
cartBuilder: CartBuilderUI,
productLookup: ProductLookupUI
}}
/>Using Individual Components
Export and use sub-components directly:
import { ProductCard, parseProductList } from '@rodger/widgets';
function MyCustomProductDisplay({ toolResult }) {
const products = parseProductList(toolResult);
return (
<div className="grid grid-cols-3 gap-4">
{products.map((product, idx) => (
<ProductCard key={idx} product={product} />
))}
</div>
);
}Lazy Loading
Load widgets dynamically:
import { widgets } from '@rodger/widgets';
// Lazy load widgets
const CartBuilderUI = await widgets.cartBuilder();
const ProductLookupUI = await widgets.productLookup();
<Thread
widgets={{
cartBuilder: CartBuilderUI,
productLookup: ProductLookupUI
}}
/>Styling and Theming
Tailwind Configuration
Add @rodger/widgets to your Tailwind config:
// tailwind.config.js
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx}',
'./node_modules/@rodger/widgets/dist/**/*.{js,mjs}'
],
theme: {
extend: {
colors: {
'navy-dark': '#1a202c',
'warm-beige': '#f5f1e8',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
'muted-foreground': 'hsl(var(--muted-foreground))'
}
}
}
};CSS Variables
Define theme colors in your CSS:
/* app/globals.css */
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--muted-foreground: 215.4 16.3% 46.9%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--muted-foreground: 215 20.2% 65.1%;
}Custom Styling
Override widget styles with Tailwind classes:
// Custom cart builder styling
<div className="[&_.cart-preview]:bg-blue-100 [&_.cart-button]:bg-blue-600">
<Thread widgets={{ cartBuilder: CartBuilderUI }} />
</div>Or use custom CSS:
/* Custom widget styles */
.cart-preview {
@apply rounded-xl shadow-lg;
}
.product-card {
@apply hover:scale-105 transition-transform;
}Creating Custom Widgets
Build your own widgets using the assistant-ui patterns:
Basic Widget
// MyWidget.tsx
import { makeAssistantToolUI } from '@assistant-ui/react';
export const MyWidget = makeAssistantToolUI({
toolName: 'myTool',
render: function MyWidgetContent({ result }) {
const data = result as { message: string };
return (
<div className="my-4 rounded-lg bg-background p-4">
<p>{data.message}</p>
</div>
);
}
});Widget with State Management
import { makeAssistantToolUI } from '@assistant-ui/react';
import { useState } from 'react';
export const InteractiveWidget = makeAssistantToolUI({
toolName: 'interactiveTool',
render: function Content({ result }) {
const [isApproved, setIsApproved] = useState(false);
if (isApproved) {
return <div>Approved!</div>;
}
return (
<div>
<button onClick={() => setIsApproved(true)}>
Approve
</button>
</div>
);
}
});Widget with API Calls
import { makeAssistantToolUI } from '@assistant-ui/react';
import { useState } from 'react';
export const ApiWidget = makeAssistantToolUI({
toolName: 'apiTool',
render: function Content({ result }) {
const [status, setStatus] = useState<'idle' | 'loading' | 'success'>('idle');
const handleAction = async () => {
setStatus('loading');
await fetch('/api/action', {
method: 'POST',
body: JSON.stringify(result)
});
setStatus('success');
};
return (
<div>
{status === 'loading' && <div>Loading...</div>}
{status === 'success' && <div>Success!</div>}
{status === 'idle' && (
<button onClick={handleAction}>
Take Action
</button>
)}
</div>
);
}
});Best Practices
- Type Safety - Define interfaces for tool results:
interface MyToolResult {
data: string;
metadata?: Record<string, unknown>;
}
render: ({ result }) => {
const data = result as MyToolResult;
// Full type safety
}- Error Handling - Handle missing or malformed data:
render: ({ result }) => {
if (!result || typeof result !== 'object') {
return <div>Invalid tool result</div>;
}
// Render normally
}- Loading States - Show feedback during async operations:
const [isLoading, setIsLoading] = useState(false);
const handleAction = async () => {
setIsLoading(true);
try {
await performAction();
} finally {
setIsLoading(false);
}
};- Accessibility - Use semantic HTML and ARIA attributes:
<button
onClick={handleAction}
aria-label="Approve cart"
disabled={isLoading}
>
Approve
</button>- Responsive Design - Use Tailwind responsive classes:
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{/* Responsive grid */}
</div>TypeScript Support
Full TypeScript support with exported types:
import type {
CartPreview,
Product
} from '@rodger/widgets';Complete Example
// app/page.tsx
'use client';
import { RodgerProvider, Thread, useRodgerChat } from '@rodger/ui';
import {
CartBuilderUI,
ProductLookupUI,
QuickActionsWidget,
EmailSignupWidget,
CtaButtonWidget
} from '@rodger/widgets';
export default function ShoppingChat() {
const { runtime, isLoadingHistory } = useRodgerChat({
endpoint: '/api/chat',
sessionId: 'shopping-session'
});
if (isLoadingHistory) {
return <div>Loading...</div>;
}
return (
<RodgerProvider runtime={runtime}>
<div className="h-screen">
<Thread
welcomeMessage={
<div>
<h1 className="text-4xl font-bold">Shopping Assistant</h1>
<p className="text-muted-foreground">
Browse products and build your cart
</p>
</div>
}
widgets={{
cartBuilder: CartBuilderUI,
productLookup: ProductLookupUI,
quickActions: QuickActionsWidget,
emailSignup: EmailSignupWidget,
ctaButton: CtaButtonWidget
}}
/>
</div>
</RodgerProvider>
);
}Related Packages
- @rodger/core - Agent framework (required for backend)
- @rodger/tools - Pre-built tools (pairs with widgets)
- @rodger/ui - React components for chat UIs
Documentation
License
MIT
