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

@devicai/ui

v0.10.4

Published

React component library for Devic AI assistants

Readme

@devicai/ui

React component library for integrating Devic AI assistants into your application.

Features

  • ChatDrawer - A ready-to-use chat drawer component
  • AICommandBar - A spotlight-style command bar for quick AI interactions
  • AIGenerationButton - A button for triggering AI generation with modal, tooltip, or direct modes
  • useDevicChat - Hook for building custom chat UIs
  • Model Interface Protocol - Support for client-side tool execution
  • Message Feedback - Built-in thumbs up/down feedback with comments
  • CSS Variables - Easy theming with CSS custom properties
  • TypeScript - Full type definitions included
  • React 17+ - Compatible with React 17 and above
  • Minimal Dependencies - Only React as a peer dependency

Installation

npm install @devicai/ui
# or
yarn add @devicai/ui
# or
pnpm add @devicai/ui

Quick Start

Using ChatDrawer (Simplest)

import { DevicProvider, ChatDrawer } from '@devicai/ui';
import '@devicai/ui/dist/esm/styles.css';

function App() {
  return (
    <DevicProvider apiKey="your-api-key">
      <ChatDrawer
        assistantId="my-assistant"
        options={{
          position: 'right',
          welcomeMessage: 'Hello! How can I help you?',
          suggestedMessages: ['Help me with...', 'Tell me about...'],
        }}
      />
    </DevicProvider>
  );
}

Using the Hook (Custom UI)

import { DevicProvider, useDevicChat } from '@devicai/ui';

function CustomChat() {
  const { messages, isLoading, sendMessage } = useDevicChat({
    assistantId: 'my-assistant',
  });

  return (
    <div>
      {messages.map((msg) => (
        <div key={msg.uid}>
          <strong>{msg.role}:</strong> {msg.content.message}
        </div>
      ))}
      {isLoading && <div>Thinking...</div>}
      <button onClick={() => sendMessage('Hello!')}>Send</button>
    </div>
  );
}

function App() {
  return (
    <DevicProvider apiKey="your-api-key">
      <CustomChat />
    </DevicProvider>
  );
}

Components

DevicProvider

Context provider for global configuration.

<DevicProvider
  apiKey="devic-xxx"           // Required
  baseUrl="https://api.devic.ai"
  tenantId="tenant-123"        // Optional global tenant
  tenantMetadata={{ ... }}     // Optional global metadata
>
  <App />
</DevicProvider>

ChatDrawer

A complete chat drawer component.

<ChatDrawer
  assistantId="my-assistant"
  chatUid="optional-existing-chat"
  options={{
    position: 'right',           // 'left' | 'right'
    width: 400,
    defaultOpen: false,
    color: '#1890ff',            // Primary color
    welcomeMessage: 'Hello!',
    suggestedMessages: ['Help me...'],
    enableFileUploads: true,
    allowedFileTypes: { images: true, documents: true },
    inputPlaceholder: 'Type a message...',
    title: 'Chat Assistant',
    showToolTimeline: true,
  }}
  enabledTools={['tool1', 'tool2']}
  modelInterfaceTools={[
    {
      toolName: 'get_user_location',
      schema: {
        type: 'function',
        function: {
          name: 'get_user_location',
          description: 'Get user current location',
          parameters: { type: 'object', properties: {} }
        }
      },
      callback: async () => {
        const pos = await getCurrentPosition();
        return { lat: pos.coords.latitude, lng: pos.coords.longitude };
      }
    }
  ]}
  tenantId="specific-tenant"     // Override provider
  tenantMetadata={{ userId: '123' }}
  apiKey="override-key"          // Override provider

  // Callbacks
  onMessageSent={(message) => {}}
  onMessageReceived={(message) => {}}
  onToolCall={(toolName, params) => {}}
  onError={(error) => {}}
  onChatCreated={(chatUid) => {}}
  onOpen={() => {}}
  onClose={() => {}}

  // Controlled mode
  isOpen={true}
/>

AICommandBar

A floating command bar (similar to Spotlight/Command Palette) for quick AI interactions.

import { AICommandBar } from '@devicai/ui';

<AICommandBar
  assistantId="my-assistant"
  options={{
    shortcut: 'cmd+k',           // Keyboard shortcut to open
    placeholder: 'Ask AI...',
    position: 'fixed',           // 'inline' | 'fixed'
    fixedPlacement: { bottom: 20, right: 20 },
    showResultCard: true,        // Show response in a card
    showShortcutHint: true,      // Show shortcut badge

    // Commands (slash commands)
    commands: [
      {
        keyword: 'summarize',
        description: 'Summarize content',
        message: 'Please summarize this page.',
        icon: <SummarizeIcon />,
      },
    ],

    // History
    enableHistory: true,
    maxHistoryItems: 50,

    // Theming
    backgroundColor: '#ffffff',
    textColor: '#1f2937',
    borderColor: '#e5e7eb',
    borderRadius: 12,
  }}

  // Callbacks
  onResponse={({ message, toolCalls, chatUid }) => {}}
  onSubmit={(message) => {}}
  onToolCall={(toolName, params) => {}}
  onError={(error) => {}}
  onOpen={() => {}}
  onClose={() => {}}

  // Integration with ChatDrawer
  onExecute="openDrawer"         // 'callback' | 'openDrawer'
  chatDrawerRef={drawerRef}      // Required when onExecute="openDrawer"

  // Controlled mode
  isVisible={true}
  onVisibilityChange={(visible) => {}}
/>

AICommandBar with ChatDrawer Integration

import { useRef } from 'react';
import { AICommandBar, ChatDrawer, ChatDrawerHandle } from '@devicai/ui';

function App() {
  const drawerRef = useRef<ChatDrawerHandle>(null);

  return (
    <>
      <AICommandBar
        assistantId="my-assistant"
        onExecute="openDrawer"
        chatDrawerRef={drawerRef}
        options={{
          shortcut: 'cmd+k',
          showResultCard: false,
        }}
      />
      <ChatDrawer ref={drawerRef} assistantId="my-assistant" />
    </>
  );
}

AICommandBar Handle (ref methods)

const commandBarRef = useRef<AICommandBarHandle>(null);

// Methods available via ref
commandBarRef.current?.open();
commandBarRef.current?.close();
commandBarRef.current?.toggle();
commandBarRef.current?.focus();
commandBarRef.current?.submit('Hello!');
commandBarRef.current?.reset();

AIGenerationButton

A button component for triggering AI generation with three interaction modes: direct, modal, or tooltip.

import { AIGenerationButton } from '@devicai/ui';

// Modal mode (default) - opens a modal for user input
<AIGenerationButton
  assistantId="my-assistant"
  options={{
    mode: 'modal',
    modalTitle: 'Generate with AI',
    modalDescription: 'Describe what you want to generate.',
    placeholder: 'E.g., Create a product description...',
    confirmText: 'Generate',
    cancelText: 'Cancel',
  }}
  onResponse={({ message, toolCalls }) => {
    console.log('Generated:', message.content.message);
  }}
/>

// Direct mode - sends predefined prompt immediately
<AIGenerationButton
  assistantId="my-assistant"
  options={{
    mode: 'direct',
    prompt: 'Generate a summary of this content',
    label: 'Summarize',
    loadingLabel: 'Summarizing...',
  }}
  onResponse={({ message }) => setSummary(message.content.message)}
/>

// Tooltip mode - shows inline input
<AIGenerationButton
  assistantId="my-assistant"
  options={{
    mode: 'tooltip',
    tooltipPlacement: 'bottom',  // 'top' | 'bottom' | 'left' | 'right'
    tooltipWidth: 350,
  }}
  onResponse={handleGeneration}
/>

AIGenerationButton Options

<AIGenerationButton
  assistantId="my-assistant"
  options={{
    // Mode
    mode: 'modal',               // 'direct' | 'modal' | 'tooltip'
    prompt: 'Predefined prompt', // Required for direct mode

    // Labels
    label: 'Generate with AI',
    loadingLabel: 'Generating...',
    placeholder: 'Describe what you want...',
    modalTitle: 'Generate with AI',
    modalDescription: 'Optional description',
    confirmText: 'Generate',
    cancelText: 'Cancel',

    // Button styling
    variant: 'primary',          // 'primary' | 'secondary' | 'outline' | 'ghost'
    size: 'medium',              // 'small' | 'medium' | 'large'
    icon: <CustomIcon />,        // Custom icon
    hideIcon: false,
    hideLabel: false,            // Icon-only button

    // Tooltip options
    tooltipPlacement: 'top',
    tooltipWidth: 300,

    // Tool call display
    toolRenderers: {
      search_docs: (input, output) => (
        <div>Found {output.count} results</div>
      ),
    },
    toolIcons: {
      search_docs: <SearchIcon />,
    },
    processingMessage: 'Processing...',

    // Theming
    color: '#3b82f6',
    backgroundColor: '#ffffff',
    textColor: '#1f2937',
    borderColor: '#e5e7eb',
    borderRadius: 8,
    zIndex: 10000,
  }}

  // Callbacks
  onResponse={({ message, toolCalls, chatUid }) => {}}
  onBeforeSend={(prompt) => modifiedPrompt}  // Modify prompt before sending
  onError={(error) => {}}
  onStart={() => {}}
  onOpen={() => {}}
  onClose={() => {}}

  // Other props
  modelInterfaceTools={[...]}
  tenantId="tenant-123"
  tenantMetadata={{ userId: '456' }}
  disabled={false}
/>

AIGenerationButton Handle (ref methods)

const buttonRef = useRef<AIGenerationButtonHandle>(null);

// Trigger generation programmatically
const result = await buttonRef.current?.generate('Custom prompt');

// Open/close modal or tooltip
buttonRef.current?.open();
buttonRef.current?.close();
buttonRef.current?.reset();

// Check processing state
if (buttonRef.current?.isProcessing) { ... }

Hooks

useDevicChat

Main hook for chat functionality.

const {
  messages,      // ChatMessage[]
  chatUid,       // string | null
  isLoading,     // boolean
  status,        // 'idle' | 'processing' | 'completed' | 'error'
  error,         // Error | null
  sendMessage,   // (message: string, options?: { files?: ChatFile[] }) => Promise<void>
  clearChat,     // () => void
  loadChat,      // (chatUid: string) => Promise<void>
} = useDevicChat({
  assistantId: 'my-assistant',
  chatUid: 'optional-existing-chat',
  apiKey: 'override-key',
  baseUrl: 'https://api.devic.ai',
  tenantId: 'tenant-123',
  tenantMetadata: { userId: '456' },
  enabledTools: ['tool1', 'tool2'],
  modelInterfaceTools: [...],
  pollingInterval: 1000,
  onMessageSent: (message) => {},
  onMessageReceived: (message) => {},
  onToolCall: (toolName, params) => {},
  onError: (error) => {},
  onChatCreated: (chatUid) => {},
});

useAICommandBar

Hook for building custom command bar UIs.

import { useAICommandBar } from '@devicai/ui';

const {
  isVisible,           // boolean
  open,                // () => void
  close,               // () => void
  toggle,              // () => void
  inputValue,          // string
  setInputValue,       // (value: string) => void
  inputRef,            // RefObject<HTMLInputElement>
  focus,               // () => void
  isProcessing,        // boolean
  currentToolSummary,  // string | null
  toolCalls,           // ToolCallSummary[]
  result,              // CommandBarResult | null
  error,               // Error | null
  history,             // string[]
  showingHistory,      // boolean
  showingCommands,     // boolean
  filteredCommands,    // AICommandBarCommand[]
  submit,              // (message?: string) => Promise<void>
  reset,               // () => void
  handleKeyDown,       // (e: KeyboardEvent) => void
} = useAICommandBar({
  assistantId: 'my-assistant',
  options: { shortcut: 'cmd+k' },
  onResponse: (result) => {},
  onError: (error) => {},
});

useAIGenerationButton

Hook for building custom generation button UIs.

import { useAIGenerationButton } from '@devicai/ui';

const {
  isOpen,              // boolean - modal/tooltip open state
  isProcessing,        // boolean
  inputValue,          // string
  setInputValue,       // (value: string) => void
  error,               // Error | null
  result,              // GenerationResult | null
  toolCalls,           // ToolCallSummary[]
  currentToolSummary,  // string | null
  inputRef,            // RefObject<HTMLTextAreaElement>
  open,                // () => void
  close,               // () => void
  generate,            // (prompt?: string) => Promise<GenerationResult | null>
  reset,               // () => void
  handleKeyDown,       // (e: KeyboardEvent) => void
} = useAIGenerationButton({
  assistantId: 'my-assistant',
  options: { mode: 'modal' },
  onResponse: (result) => {},
  onBeforeSend: (prompt) => prompt,
  onError: (error) => {},
  onStart: () => {},
});

useModelInterface

Hook for implementing the Model Interface Protocol.

const {
  toolSchemas,           // Tool schemas to send to API
  isClientTool,          // (name: string) => boolean
  handleToolCalls,       // (toolCalls: ToolCall[]) => Promise<ToolCallResponse[]>
  extractPendingToolCalls, // (messages: ChatMessage[]) => ToolCall[]
} = useModelInterface({
  tools: [
    {
      toolName: 'get_user_location',
      schema: {
        type: 'function',
        function: {
          name: 'get_user_location',
          description: 'Get user location',
          parameters: { type: 'object', properties: {} }
        }
      },
      callback: async () => ({ lat: 40.7, lng: -74.0 })
    }
  ],
  onToolExecute: (toolName, params) => {},
  onToolComplete: (toolName, result) => {},
  onToolError: (toolName, error) => {},
});

usePolling

Hook for polling real-time chat history.

const {
  data,       // RealtimeChatHistory | null
  isPolling,  // boolean
  error,      // Error | null
  start,      // () => void
  stop,       // () => void
  refetch,    // () => Promise<void>
} = usePolling(
  chatUid,
  async () => client.getRealtimeHistory(assistantId, chatUid),
  {
    interval: 1000,
    enabled: true,
    stopStatuses: ['completed', 'error'],
    onUpdate: (data) => {},
    onStop: (data) => {},
    onError: (error) => {},
  }
);

API Client

Use the API client directly for advanced use cases.

import { DevicApiClient } from '@devicai/ui';

const client = new DevicApiClient({
  apiKey: 'your-api-key',
  baseUrl: 'https://api.devic.ai',
});

// Get assistants
const assistants = await client.getAssistants();

// Send message (async mode)
const { chatUid } = await client.sendMessageAsync('my-assistant', {
  message: 'Hello!',
  tenantId: 'tenant-123',
});

// Poll for results
const result = await client.getRealtimeHistory('my-assistant', chatUid);

// Send tool responses
await client.sendToolResponses('my-assistant', chatUid, [
  { tool_call_id: 'call_123', content: { result: 'data' }, role: 'tool' }
]);

Theming

Customize appearance with CSS variables:

.devic-chat-drawer {
  --devic-primary: #1890ff;
  --devic-primary-hover: #40a9ff;
  --devic-primary-light: #e6f7ff;
  --devic-bg: #ffffff;
  --devic-bg-secondary: #f5f5f5;
  --devic-text: #333333;
  --devic-text-secondary: #666666;
  --devic-text-muted: #999999;
  --devic-border: #e8e8e8;
  --devic-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  --devic-radius: 8px;
  --devic-radius-sm: 4px;
  --devic-radius-lg: 16px;
}

Or use the color option in ChatDrawer:

<ChatDrawer
  options={{ color: '#ff4081' }}
/>

Model Interface Protocol

The Model Interface Protocol allows you to define client-side tools that the assistant can call during a conversation.

const locationTool: ModelInterfaceTool = {
  toolName: 'get_user_location',
  schema: {
    type: 'function',
    function: {
      name: 'get_user_location',
      description: 'Get the user current geographic location',
      parameters: {
        type: 'object',
        properties: {},
      }
    }
  },
  callback: async () => {
    return new Promise((resolve, reject) => {
      navigator.geolocation.getCurrentPosition(
        (pos) => resolve({
          latitude: pos.coords.latitude,
          longitude: pos.coords.longitude,
        }),
        (err) => reject(err)
      );
    });
  }
};

<ChatDrawer
  assistantId="my-assistant"
  modelInterfaceTools={[locationTool]}
/>

TypeScript

All types are exported:

import type {
  // Chat types
  ChatMessage,
  ChatFile,
  ChatDrawerOptions,
  ChatDrawerHandle,

  // AICommandBar types
  AICommandBarOptions,
  AICommandBarHandle,
  AICommandBarCommand,
  CommandBarResult,
  ToolCallSummary,

  // AIGenerationButton types
  AIGenerationButtonOptions,
  AIGenerationButtonHandle,
  AIGenerationButtonMode,
  GenerationResult,

  // Tool types
  ModelInterfaceTool,
  ModelInterfaceToolSchema,
  ToolCall,
  ToolCallResponse,

  // API types
  RealtimeChatHistory,

  // Hook types
  UseDevicChatOptions,
} from '@devicai/ui';

License

MIT