npm package: @jazzmine-ui/react
Description: Jazzmine: A customizable React chat UI component library
Author: Abderrazag Boussaid
Latest version: 0.1.10

# jazzmine-react

React chat UI package for the Jazzmine ecosystem.

Package: `@jazzmine-ui/react`  
Version: `0.1.9`  
Runtime: `React 18+`

## Overview

`@jazzmine-ui/react` provides production-ready chat UI building blocks with two integration paths:

- SDK-powered mode via `JazzmineChat` (quickest path).
- Fully controlled mode via `ChatInterface` (your app owns all state and API calls).

The package ships prebuilt styles, markdown rendering support, conversation list/search UX, and context-aware message workflows.

## Install

```bash
npm install @jazzmine-ui/react
```

Install peer dependencies if they are not already present:

```bash
npm install react react-dom react-markdown remark-gfm
```

Import styles once in your app entry point:

```ts
import '@jazzmine-ui/react/styles';
```

## Key features

- Two integration modes: SDK-powered (`JazzmineChat`) and fully controlled (`ChatInterface`).
- Built-in conversation sidebar with create/select/rename/delete actions.
- Infinite conversation loading and conversation search modal hooks.
- Markdown + GFM message rendering (`react-markdown`, `remark-gfm`).
- Context selection from messages and context forwarding on send.
- Empty-state `defaultMessage` support.
- History loading that uses `original_content` for message text.
- Optional mapping of backend `explicit_context` into message contexts.

## Quick navigation

- [Usage](#usage)
- [JazzmineChat (SDK-powered)](#jazzminechat-sdk-powered)
- [ChatInterface (fully controlled)](#chatinterface-fully-controlled)
- [Standalone components](#standalone-components)
- [API reference](#api-reference)
- [Exports](#exports)
- [Core types](#core-types)
- [Changelog](#changelog)

## Usage

### JazzmineChat (SDK-powered)

Use this when you already have a backend client matching `IJazzmineClient`.

```tsx
'use client';

import React from 'react';
import { JazzmineChat } from '@jazzmine-ui/react';
import type { IJazzmineClient } from '@jazzmine-ui/react';
import '@jazzmine-ui/react/styles';

// Any client with compatible method signatures works.
declare const client: IJazzmineClient;

export function App() {
  return (
    <div style={{ height: '100vh' }}>
      <JazzmineChat
        client={client}
        placeholder="Ask anything..."
        defaultMessage="Welcome! Ask me anything to get started."
        onError={(error) => {
          console.error('JazzmineChat error:', error);
        }}
      />
    </div>
  );
}
```

What `JazzmineChat` handles for you:

- Conversation create/select/delete/rename wiring.
- Conversation pagination and sidebar updates.
- Search modal query and result flow.
- Sending messages with optional explicit context.
- Loading history for selected conversation.
- Mapping backend history to UI message objects.

### ChatInterface (fully controlled)

Use this when your application should control every piece of chat state.

```tsx
import React from 'react';
import { ChatInterface } from '@jazzmine-ui/react';
import type { Chat, Message } from '@jazzmine-ui/react';
import '@jazzmine-ui/react/styles';

export function App() {
  const [messages, setMessages] = React.useState<Message[]>([]);
  const [chats, setChats] = React.useState<Chat[]>([]);
  const [activeChatId, setActiveChatId] = React.useState<string>();
  const [isLoading, setIsLoading] = React.useState(false);

  const onSend = async (text: string) => {
    setMessages((prev) => [
      ...prev,
      { id: crypto.randomUUID(), text, sender: 'user', timestamp: new Date().toISOString() },
    ]);

    setIsLoading(true);
    try {
      const reply = `Echo: ${text}`;
      setMessages((prev) => [
        ...prev,
        {
          id: crypto.randomUUID(),
          text: reply,
          sender: 'assistant',
          timestamp: new Date().toISOString(),
        },
      ]);
    } finally {
      setIsLoading(false);
    }
  };

  const onNewChat = () => {
    const id = crypto.randomUUID();
    setChats((prev) => [{ id, title: 'New Chat', timestamp: 'Now' }, ...prev]);
    setActiveChatId(id);
    setMessages([]);
  };

  const onSelectChat = (chatId: string) => {
    setActiveChatId(chatId);
    setMessages([]);
  };

  const onDeleteChat = (chatId: string) => {
    setChats((prev) => prev.filter((chat) => chat.id !== chatId));
    if (activeChatId === chatId) {
      setActiveChatId(undefined);
      setMessages([]);
    }
  };

  return (
    <div style={{ height: '100vh' }}>
      <ChatInterface
        chats={chats}
        activeChatId={activeChatId}
        messages={messages}
        assistantName="Jazzmine AI"
        isLoading={isLoading}
        onSend={onSend}
        onNewChat={onNewChat}
        onSelectChat={onSelectChat}
        onDeleteChat={onDeleteChat}
        placeholder="Type your message..."
        defaultMessage="Welcome! Ask me anything to get started."
      />
    </div>
  );
}
```

### Standalone components

All major UI blocks are exported and can be used independently:

- `Sidebar`
- `MessageList`
- `FloatingChatWidget`
- `SearchModal`

Minimal examples:

```tsx
import React from 'react';
import {
  FloatingChatWidget,
  MessageList,
  SearchModal,
  Sidebar,
} from '@jazzmine-ui/react';
import type { Chat, Message } from '@jazzmine-ui/react';

export function StandaloneExamples() {
  const chats: Chat[] = [{ id: '1', title: 'General', timestamp: 'Today' }];
  const messages: Message[] = [
    { id: 'm1', text: 'Hello', sender: 'user' },
    { id: 'm2', text: 'Hi, how can I help?', sender: 'assistant' },
  ];

  return (
    <>
      <Sidebar
        chats={chats}
        activeChatId="1"
        onNewChat={() => {}}
        onSelectChat={() => {}}
      />

      <MessageList messages={messages} assistantName="Jazzmine AI" />

      <FloatingChatWidget
        messages={messages}
        onSend={(text) => {
          console.log(text);
        }}
      />

      <SearchModal
        isOpen={false}
        onClose={() => {}}
        onSelectConversation={() => {}}
        searchQuery=""
        onSearchQueryChange={() => {}}
        results={[]}
        isSearching={false}
      />
    </>
  );
}
```

## API reference

### JazzmineChatProps

- `client`: `IJazzmineClient` (required)
- `userId`: `string` (default: `'user'`)
- `assistantName`: `string`
- `placeholder`: `string`
- `className`: `string`
- `defaultMessage`: `string` (shown when there are no messages)
- `onError`: `(error: Error) => void`

### ChatInterfaceProps

- `messages`: `Message[]`
- `chats`: `Chat[]`
- `activeChatId`: `string`
- `assistantName`: `string`
- `onSend`: `(text: string, contexts?: Context[]) => void`
- `onNewChat`: `() => void`
- `onSelectChat`: `(chatId: string) => void`
- `onRenameChat`: `(chatId: string, newTitle?: string) => void`
- `onDeleteChat`: `(chatId: string) => void`
- `onSearchClick`: `() => void`
- `onLoadMore`: `() => void`
- `hasMore`: `boolean`
- `isLoadingChats`: `boolean`
- `isSearchOpen`: `boolean`
- `searchQuery`: `string`
- `onSearchQueryChange`: `(query: string) => void`
- `loadingText`: `string`
- `loadingVariant`: `'text-and-dots' | 'dots-only'`
- `isLoading`: `boolean`
- `placeholder`: `string`
- `className`: `string`
- `defaultMessage`: `string` (shown when there are no messages)

### SidebarProps

- `chats`: `Chat[]`
- `activeChatId`: `string`
- `assistantName`: `string`
- `logo`: `string`
- `logoAlt`: `string`
- `chatActions`: `ChatAction[]`
- `onNewChat`: `() => void`
- `onSelectChat`: `(chatId: string) => void`
- `onRenameChat`: `(chatId: string, newTitle?: string) => void`
- `onDeleteChat`: `(chatId: string) => void`
- `onArchiveChat`: `(chatId: string) => void`
- `onSearchClick`: `() => void`
- `onLoadMore`: `() => void`
- `hasMore`: `boolean`
- `isLoadingChats`: `boolean`
- `isSearchOpen`: `boolean`
- `searchQuery`: `string`
- `onSearchQueryChange`: `(query: string) => void`
- `className`: `string`
- `collapsed`: `boolean`
- `onToggleCollapse`: `(value?: boolean) => void`

### MessageListProps

- `messages`: `Message[]`
- `assistantName`: `string`
- `defaultMessage`: `string`
- `isLoading`: `boolean`
- `loadingText`: `string`
- `loadingVariant`: `'text-and-dots' | 'dots-only'`
- `className`: `string`
- `searchQuery`: `string`
- `activeSearchMessageId`: `string`
- `onAddContext`: `(context: Context) => void`
- `scrollRef`: `React.RefObject<HTMLDivElement | null> | ((node: HTMLDivElement | null) => void)`

### FloatingChatWidgetProps

- `messages`: `Message[]`
- `assistantName`: `string`
- `isLoading`: `boolean`
- `loadingText`: `string`
- `loadingVariant`: `'text-and-dots' | 'dots-only'`
- `initialOpen`: `boolean`
- `onOpen`: `(open: boolean) => void`
- `onSend`: `(text: string) => void`
- `logo`: `string`
- `className`: `string`

### SearchModalProps

- `isOpen`: `boolean`
- `onClose`: `() => void`
- `onSelectConversation`: `(conversationId: string) => void`
- `searchQuery`: `string`
- `onSearchQueryChange`: `(query: string) => void`
- `results`: `Chat[]`
- `isSearching`: `boolean`

### IJazzmineClient contract

```ts
interface IJazzmineClient {
  sendMessage(
    message: string,
    options?: {
      conversationId?: string;
      autoCreateConversation?: boolean;
      conversationTitle?: string;
      explicitContext?: string[];
    }
  ): Promise<{
    response: {
      response: string;
      response_markdown: string;
      conversation_id: string;
      message_id: string;
      is_blocked: boolean;
      errors: string[];
    };
    conversationId: string;
  }>;

  createConversation(payload?: {
    title?: string;
    user_id?: string;
  }): Promise<{
    conversation_id: string;
    title: string;
  }>;

  deleteConversation(conversationId: string): Promise<{
    conversation_id: string;
    deleted: boolean;
  }>;

  listConversations(params: {
    userId: string;
    limit?: number;
    offset?: number;
  }): Promise<{
    conversations: Array<{
      conversation_id: string;
      title: string;
      last_updated_at: number;
    }>;
    total: number;
    hasMore: boolean;
    offset: number;
    limit: number;
  }>;

  searchConversations(params: {
    userId: string;
    query: string;
    limit?: number;
    offset?: number;
  }): Promise<{
    conversations: Array<{
      conversation_id: string;
      title: string;
      last_updated_at: number;
    }>;
    total: number;
    hasMore: boolean;
    offset: number;
    limit: number;
    query?: string;
  }>;

  updateConversation(
    conversationId: string,
    payload: { title: string }
  ): Promise<{
    conversation_id: string;
    title: string;
    last_updated_at: number;
  }>;

  getConversationMessages(params: {
    conversationId: string;
    limit?: number;
    offset?: number;
  }): Promise<{
    conversation_id: string;
    messages: Array<{
      id: string;
      role: 'user' | 'assistant' | 'colleague_assistant' | 'system';
      original_content: string;
      enhanced_message: string;
      explicit_context?: string[] | null;
      timestamp: number;
    }>;
    total: number;
    hasMore: boolean;
  }>;

  getHealth(): Promise<{
    status: string;
    agent_name: string;
  }>;
}
```

## Exports

Components:

- `ChatInterface`
- `Sidebar`
- `MessageList`
- `FloatingChatWidget`
- `JazzmineChat`
- `SearchModal`

Types:

- `ChatInterfaceProps`
- `SidebarProps`
- `MessageListProps`
- `FloatingChatWidgetProps`
- `JazzmineChatProps`
- `SearchModalProps`
- `IJazzmineClient`
- `Context`
- `Message`
- `Chat`

## Core types

```ts
interface Context {
  id: string;
  messageId: string;
  text: string;
  sender: 'user' | 'assistant';
  isPartial: boolean;
}

interface Message {
  id: string;
  text: string;
  sender: 'user' | 'assistant';
  timestamp?: string;
  avatar?: string;
  contexts?: Context[];
}

interface Chat {
  id: string;
  title: string;
  timestamp?: string;
}
```

## Notes

- The package does not import `@jazzmine-ui/sdk` directly.
- Any client that structurally matches `IJazzmineClient` can be used.
- `JazzmineChat` is client-side and includes a `use client` directive.
- Markdown peers (`react-markdown` and `remark-gfm`) are required.
- Styles are distributed as `@jazzmine-ui/react/styles`.

## Development

```bash
npm install
npm run lint
npm run build
```

## Changelog

### 0.1.9

- Added empty-state `defaultMessage` support in chat flow.
- Improved history mapping to use `original_content` as message text.
- Added support for mapping backend `explicit_context` to message contexts.
- Expanded README structure for ecosystem docs pages.

### 0.1.8

- Added `defaultMessage` prop to `JazzmineChat` and `ChatInterface` pathways.
- Updated docs and usage examples.

### 0.1.7

- Added conversation search modal and sidebar search integration.
- Added pagination/load-more behavior in sidebar.
- Added rename and delete flows for conversations.

## License

MIT