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

@miiflow/assistant-ui

v0.5.1

Published

Headless chat UI primitives with TailwindCSS styling

Readme

@miiflow/assistant-ui

React components and hooks for building custom Miiflow chat interfaces. Install as an npm package for full control over layout, styling, and behavior.

Installation

npm install @miiflow/assistant-ui

Peer dependencies: react >= 18, react-dom >= 18

The styled components use TailwindCSS. If your project doesn't use Tailwind, import the pre-built CSS instead:

import "@miiflow/assistant-ui/styles.css";

If you're embedding inside an existing page and want to avoid Tailwind's preflight (CSS reset) affecting the host page:

import "@miiflow/assistant-ui/styles-no-preflight.css";

Quick Start

import { useMiiflowChat } from "@miiflow/assistant-ui/client";
import {
  ChatProvider,
  ChatLayout,
  ChatHeader,
  MessageList,
  Message,
  MessageComposer,
  WelcomeScreen,
} from "@miiflow/assistant-ui/styled";
import "@miiflow/assistant-ui/styles.css";

function Chat() {
  const {
    messages,
    isStreaming,
    streamingMessageId,
    sendMessage,
    uploadFile,
    startNewThread,
    branding,
    brandingCSSVars,
    loading,
  } = useMiiflowChat({
    // Find these in your Miiflow dashboard under Settings > Embed
    publicKey: "pk_live_...",
    assistantId: "ast_...",
  });

  if (loading) return <div>Loading...</div>;

  const isEmpty = messages.length === 0;

  return (
    <ChatProvider
      messages={messages}
      isStreaming={isStreaming}
      streamingMessageId={streamingMessageId}
      onSendMessage={sendMessage}
    >
      <div style={{ height: "100vh", ...brandingCSSVars }}>
        <ChatLayout
          isEmpty={isEmpty}
          header={
            <ChatHeader
              title={branding?.customName ?? "Assistant"}
              logo={branding?.chatbotLogo}
              actions={[
                { id: "new", label: "New chat", onClick: startNewThread },
              ]}
            />
          }
          welcomeScreen={
            <WelcomeScreen
              welcomeText={branding?.welcomeMessage}
              placeholders={branding?.rotatingPlaceholders}
              suggestions={branding?.presetQuestions}
              onSubmit={sendMessage}
              onSuggestionClick={sendMessage}
            />
          }
          messageList={
            <MessageList>
              {messages.map((msg) => (
                <Message
                  key={msg.id}
                  message={msg}
                  reasoning={msg.reasoning}
                  suggestedActions={msg.suggestedActions}
                  onSuggestedAction={(a) => sendMessage(a.value)}
                />
              ))}
            </MessageList>
          }
          composer={
            <MessageComposer
              onSubmit={sendMessage}
              onUploadFile={uploadFile}
              disabled={isStreaming}
              placeholder={branding?.chatboxPlaceholder}
            />
          }
        />
      </div>
    </ChatProvider>
  );
}

Configuration Reference

Pass a MiiflowChatConfig object to useMiiflowChat:

| Field | Type | Required | Description | |-------|------|----------|-------------| | publicKey | string | Yes | Public API key from the Miiflow dashboard | | assistantId | string | Yes | Assistant ID from the Miiflow dashboard | | userId | string | No | User ID for identity tracking | | userName | string | No | User display name | | userEmail | string | No | User email | | userMetadata | string | No | JSON string of custom user metadata | | hmac | string | No | HMAC for identity verification | | timestamp | string | No | Timestamp for HMAC verification | | baseUrl | string | No | Override API endpoint (default: https://api.miiflow.ai/api) | | webSocketUrl | string | No | WebSocket URL for tool invocations (auto-derived from baseUrl if not set) | | responseTimeout | number | No | SSE stream timeout in ms (default: 60000) |

Connecting to a Custom Backend

By default, the hook connects to https://api.miiflow.ai. To point to your own backend, pass a baseUrl:

useMiiflowChat({
  publicKey: "pk_live_...",
  assistantId: "ast_...",
  baseUrl: "https://your-server.example.com/api",
  // webSocketUrl is auto-derived from baseUrl; override if needed:
  // webSocketUrl: "wss://your-server.example.com/ws",
});

Your backend must implement the same API contract as the Miiflow platform (session init, SSE streaming, file upload, and tool-result endpoints).

Hook API — useMiiflowChat

import { useMiiflowChat } from "@miiflow/assistant-ui/client";

const result = useMiiflowChat(config);

State

| Property | Type | Description | |----------|------|-------------| | messages | ChatMessage[] | Messages in the conversation | | isStreaming | boolean | Whether a response is currently streaming | | streamingMessageId | string \| null | ID of the message being streamed | | loading | boolean | Whether the session is still initializing | | error | string \| null | Error message if initialization or sending failed | | session | EmbedSession \| null | Current session data | | branding | BrandingData \| null | Branding configuration from the dashboard | | brandingCSSVars | CSSProperties | CSS custom properties derived from branding |

Actions

| Method | Signature | Description | |--------|-----------|-------------| | sendMessage | (content: string, attachmentIds?: string[]) => Promise<void> | Send a message to the assistant | | uploadFile | (file: File) => Promise<string> | Upload a file and get an attachment ID | | startNewThread | () => Promise<string> | Start a new conversation thread | | registerTool | (tool: ClientToolDefinition) => Promise<void> | Register a client-side tool | | registerTools | (tools: ClientToolDefinition[]) => Promise<void> | Register multiple tools | | sendSystemEvent | (event: SystemEvent) => Promise<void> | Send an invisible system event |

Components Reference

ChatProvider

Wraps children and provides chat context via React context.

| Prop | Type | Default | Description | |------|------|---------|-------------| | messages | ChatMessage[] | — | Messages to display | | isStreaming | boolean | false | Whether a response is streaming | | streamingMessageId | string \| null | null | ID of the streaming message | | viewerRole | ParticipantRole | "user" | Viewer's role (determines message alignment) | | onSendMessage | (content: string, attachments?: File[]) => Promise<void> | — | Message send handler | | onStopStreaming | () => void | — | Stop streaming handler | | onRetryLastMessage | () => Promise<void> | — | Retry last message handler | | onVisualizationAction | (event: VisualizationActionEvent) => void | — | Callback for form/card interactions |

ChatLayout

Handles the empty-to-active state transition with crossfade animation. Accepts render slots for each section.

| Prop | Type | Default | Description | |------|------|---------|-------------| | isEmpty | boolean | — | Whether the chat has no messages | | header | ReactNode | — | Header slot (rendered in both states) | | welcomeScreen | ReactNode | — | Content for empty state | | messageList | ReactNode | — | Message list for active state | | composer | ReactNode | — | Composer for active state | | footer | ReactNode | — | Extra content between list and composer | | variant | "standalone" \| "embedded" \| "widget" | "standalone" | Layout variant | | className | string | — | Additional CSS classes |

WelcomeScreen

Empty state with rotating placeholder text and suggestion cards.

| Prop | Type | Default | Description | |------|------|---------|-------------| | placeholders | string[] | [] | Rotating placeholder strings | | suggestions | string[] | [] | Preset suggestion cards | | onSubmit | (message: string) => void | — | Submit handler for built-in input | | onSuggestionClick | (suggestion: string) => void | — | Suggestion card click handler | | welcomeText | string | "How can I help you today?" | Heading text | | composerSlot | ReactNode | — | Override default input with custom composer | | className | string | — | Additional CSS classes |

MessageList

Scrollable message container with auto-scroll.

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | ReactNode | — | Message elements | | autoScroll | boolean | true | Auto-scroll to bottom on new messages | | className | string | — | Additional CSS classes |

Message

Individual message with markdown rendering, reasoning panel, citations, and visualizations.

| Prop | Type | Default | Description | |------|------|---------|-------------| | message | MessageData | — | Message data object | | viewerRole | ParticipantRole | "user" | Viewer's role (determines alignment) | | showAvatar | boolean | true | Show participant avatar | | showTimestamp | boolean | true | Show message timestamp | | renderMarkdown | boolean | true | Render content as markdown | | reasoning | StreamingChunk[] | — | Reasoning/thinking chunks for collapsible panel | | suggestedActions | SuggestedAction[] | — | Suggested follow-up actions | | onSuggestedAction | (action: SuggestedAction) => void | — | Suggested action click handler | | citations | SourceReference[] | — | Citation sources to display | | visualizations | VisualizationChunkData[] | — | Inline visualizations | | className | string | — | Additional CSS classes |

MessageComposer

Rich text editor (Lexical) with file upload, drag-and-drop, and Enter-to-send.

| Prop | Type | Default | Description | |------|------|---------|-------------| | onSubmit | (content: string, attachments?: File[]) => Promise<void> | — | Submit handler | | onUploadFile | (file: File) => Promise<string> | — | File upload handler (returns attachment ID) | | onAttach | (files: File[]) => void | — | Called when files are attached | | disabled | boolean | false | Disable the composer | | supportsAttachments | boolean | true | Enable file attachments | | allowedFileTypes | string[] | images, docs, videos | Allowed MIME types | | maxFileSize | number | 104857600 (100MB) | Max file size in bytes | | placeholder | string | "Type a message..." | Placeholder text | | isSubmitting | boolean | false | Show loading state on send button | | className | string | — | Additional CSS classes |

ChatHeader

Title bar with logo, subtitle, action menu, and close button.

| Prop | Type | Default | Description | |------|------|---------|-------------| | title | string | — | Assistant name | | subtitle | string | — | Description or status text | | logo | string \| ReactNode | — | Logo URL or custom element | | actions | ChatHeaderAction[] | — | Menu items ({ id, label, icon?, onClick, disabled? }) | | showClose | boolean | — | Show close button | | onClose | () => void | — | Close button handler | | loading | boolean | — | Show loading skeleton | | className | string | — | Additional CSS classes | | style | CSSProperties | — | Inline styles |

Styling & Theming

CSS Import

Import the stylesheet to get default styles for all components:

import "@miiflow/assistant-ui/styles.css";

Branding via CSS Variables

The brandingCSSVars object from useMiiflowChat contains CSS custom properties derived from your dashboard branding settings. Spread it onto the container element:

<div style={brandingCSSVars}>
  <ChatLayout ... />
</div>

Available CSS variables:

| Variable | Source | Description | |----------|--------|-------------| | --chat-primary | backgroundBubbleColor | Primary accent color | | --chat-user-message-bg | backgroundBubbleColor | User message bubble background | | --chat-header-bg | headerBackgroundColor | Header background color | | --chat-message-font-size | messageFontSize | Base message font size |

TailwindCSS Customization

All components accept a className prop for Tailwind utility overrides:

<MessageComposer className="rounded-none border-0" />

File Uploads

Pass onUploadFile={uploadFile} to MessageComposer to enable server-side file uploads:

const { sendMessage, uploadFile } = useMiiflowChat(config);

<MessageComposer
  onSubmit={sendMessage}
  onUploadFile={uploadFile}
  supportsAttachments={true}
/>

The composer handles file picking, validation, drag-and-drop, and preview thumbnails. Files are uploaded via uploadFile() which returns an attachment ID. The IDs are passed along when sendMessage() is called.

Client-Side Tools

Register tools that the assistant can invoke on the client:

const { registerTool } = useMiiflowChat(config);

await registerTool({
  name: "get_weather",
  description: "Get current weather for a city",
  parameters: {
    type: "object",
    properties: {
      city: { type: "string", description: "City name" },
    },
    required: ["city"],
  },
  handler: async (params) => {
    const response = await fetch(`/api/weather?city=${params.city}`);
    return response.json();
  },
});

Tools are automatically re-registered when starting a new thread via startNewThread().

The handler function receives the parameters as a Record<string, unknown> and must return a Promise. Results are sent back to the assistant automatically. A 30-second timeout is enforced per invocation.

System Events

Send invisible context events that the assistant can use to inform its responses:

const { sendSystemEvent } = useMiiflowChat(config);

await sendSystemEvent({
  action: "page_navigation",
  description: "User navigated to /pricing",
  followUpInstruction: "If relevant, mention our pricing plans",
});

| Field | Type | Required | Description | |-------|------|----------|-------------| | action | string | Yes | Event identifier | | description | string | Yes | Human-readable description of what happened | | followUpInstruction | string | Yes | Instruction for the assistant | | metadata | Record<string, unknown> | No | Additional structured data |

Identity Verification (HMAC)

For secure identity verification, compute an HMAC on your server and pass it to the config:

useMiiflowChat({
  publicKey: "pk_live_...",
  assistantId: "ast_...",
  userId: "user_123",
  userName: "Jane Doe",
  userEmail: "[email protected]",
  hmac: serverComputedHmac,
  timestamp: serverTimestamp,
});

The hmac and timestamp should be generated server-side using your secret key. See the Miiflow dashboard for your HMAC secret.

Visualizations

Assistant messages can contain rich visualizations (charts, tables, forms, etc.) rendered inline via [VIZ:id] markers. The Message component handles this automatically when you pass the visualizations prop.

Built-in Types

| Type | Component | Description | |------|-----------|-------------| | chart | ChartVisualization | Line, bar, pie, area, scatter charts (Recharts) | | table | TableVisualization | Sortable, paginated data tables | | card | CardVisualization | Structured cards with sections, actions, images | | kpi | KpiVisualization | Key performance indicator metrics with trends | | code_preview | CodePreviewVisualization | Syntax-highlighted code blocks | | form | FormVisualization | Interactive forms with validation |

Visualization Registry

Instead of a hardcoded switch, visualizations are resolved through a registry. You can register custom visualization types that the VisualizationRenderer will render automatically:

import {
  registerVisualization,
  getVisualization,
  getRegisteredTypes,
} from "@miiflow/assistant-ui/styled";

// Register a custom visualization type
registerVisualization("my_widget", {
  component: MyWidgetComponent,
  schema: myWidgetZodSchema, // optional — enables data validation
});

// Check what's registered
console.log(getRegisteredTypes());
// ["chart", "table", "card", "kpi", "code_preview", "form", "my_widget"]

Your component receives these props:

interface VisualizationComponentProps {
  data: any;
  config?: VisualizationConfig;
  isStreaming?: boolean;
  onAction?: (event: VisualizationActionEvent) => void;
}

Overriding built-ins: Call registerVisualization("chart", { component: MyChart }) to replace a built-in type with your own implementation. The last registration wins.

Schema Validation

Each built-in type has a Zod schema registered alongside its component. When a schema is present, VisualizationRenderer validates the data before rendering. Invalid data shows a descriptive error fallback instead of crashing.

You can import the schemas directly for use in your own code:

import {
  chartVisualizationSchema,
  tableVisualizationSchema,
  cardVisualizationSchema,
  kpiVisualizationSchema,
  codePreviewVisualizationSchema,
  formVisualizationSchema,
} from "@miiflow/assistant-ui/styled";

const result = chartVisualizationSchema.safeParse(data);
if (!result.success) {
  console.error("Invalid chart data:", result.error.issues);
}

To add validation to a custom type, pass a schema when registering:

import { z } from "zod";

const mySchema = z.object({
  message: z.string(),
  count: z.number().min(0),
});

registerVisualization("my_widget", {
  component: MyWidget,
  schema: mySchema,
});

Note: zod is a peer dependency (>= 3.0.0). Install it in your project if you haven't already.

Interaction Callbacks

Forms and cards can trigger user interactions (submit, cancel, button click). Instead of listening for global CustomEvents, pass a callback through ChatProvider:

function handleVisualizationAction(event: VisualizationActionEvent) {
  switch (event.type) {
    case "form_submit":
      console.log("Form submitted:", event.action, event.data);
      // Send the form data back to the assistant, save to DB, etc.
      break;
    case "form_cancel":
      console.log("Form cancelled:", event.action);
      break;
    case "card_action":
      console.log("Card action clicked:", event.action);
      break;
  }
}

<ChatProvider
  messages={messages}
  onSendMessage={sendMessage}
  onVisualizationAction={handleVisualizationAction}
>
  ...
</ChatProvider>

The VisualizationActionEvent type is a discriminated union:

type VisualizationActionEvent =
  | { type: "form_submit"; action: string; data: Record<string, unknown> }
  | { type: "form_cancel"; action: string }
  | { type: "card_action"; action: string };

Backward compatibility: If no onVisualizationAction callback is provided, components fall back to dispatching CustomEvents on window (visualization-form-submit, visualization-form-cancel, visualization-action).

Using VisualizationRenderer Standalone

You can render visualizations outside of Message by using VisualizationRenderer directly:

import { VisualizationRenderer } from "@miiflow/assistant-ui/styled";

<VisualizationRenderer
  data={{
    id: "viz-1",
    type: "chart",
    title: "Monthly Revenue",
    data: {
      chartType: "bar",
      series: [{ name: "Revenue", data: [{ x: "Jan", y: 100 }, { x: "Feb", y: 150 }] }],
    },
  }}
  onAction={(event) => console.log(event)}
/>

Package Exports

| Import | Description | |--------|-------------| | @miiflow/assistant-ui | Core types, context, hooks, primitives | | @miiflow/assistant-ui/styled | TailwindCSS-styled components, visualization registry, schemas | | @miiflow/assistant-ui/client | useMiiflowChat hook, session utilities, types | | @miiflow/assistant-ui/primitives | Headless unstyled component primitives | | @miiflow/assistant-ui/styles.css | Full CSS (includes Tailwind preflight) | | @miiflow/assistant-ui/styles-no-preflight.css | CSS without preflight (for embedding in existing pages) |

Key Exports from @miiflow/assistant-ui/styled

Visualization Registry: registerVisualization, getVisualization, getRegisteredTypes, VisualizationEntry

Visualization Schemas: chartVisualizationSchema, tableVisualizationSchema, cardVisualizationSchema, kpiVisualizationSchema, codePreviewVisualizationSchema, formVisualizationSchema

Types: VisualizationActionEvent, VisualizationChunkData, VisualizationConfig, VisualizationType