npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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/widgets

Peer 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:

  1. Preview - Shows cart contents, requires approval
  2. Loading - Displays spinner during cart creation
  3. Success - Shows checkout link and confirmation
  4. 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/product2

Component 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:

  1. Success - Shows checkmark and confirmation
  2. 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

  1. 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
}
  1. Error Handling - Handle missing or malformed data:
render: ({ result }) => {
  if (!result || typeof result !== 'object') {
    return <div>Invalid tool result</div>;
  }
  // Render normally
}
  1. Loading States - Show feedback during async operations:
const [isLoading, setIsLoading] = useState(false);

const handleAction = async () => {
  setIsLoading(true);
  try {
    await performAction();
  } finally {
    setIsLoading(false);
  }
};
  1. Accessibility - Use semantic HTML and ARIA attributes:
<button
  onClick={handleAction}
  aria-label="Approve cart"
  disabled={isLoading}
>
  Approve
</button>
  1. 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

Documentation

License

MIT