Skip to content

Latest commit

Β 

History

History
396 lines (337 loc) Β· 9.96 KB

File metadata and controls

396 lines (337 loc) Β· 9.96 KB

Chat Module

A fully modular, reusable chat package that can be integrated into any React application.

πŸ“¦ Installation

Install from the repository using a branch (replace main with your branch name):

# npm
npm install git+https://github.com/devslane/chat-react-module.git#main

# yarn
yarn add git+https://github.com/devslane/chat-react-module.git#main

# pnpm
pnpm add git+https://github.com/devslane/chat-react-module.git#main

Or add to package.json:

{
  "dependencies": {
    "@secondchair/chat-module": "github:devslane/chat-react-module#main"
  }
}

Ensure your bundler (Vite, Webpack, etc.) resolves TypeScript from node_modules or that you alias the package to source. The package exposes ./src/index.ts as the main entry.

πŸš€ Quick Start

The easiest way to integrate the chat module:

import { Chat } from "@secondchair/chat-module";
import "@secondchair/chat-module/index.css";

function App() {
  return (
    <Chat
      sessionId={123}
      onNavigateToSession={(newSessionId) => {
        router.push(`/chat/${newSessionId}`);
      }}
    />
  );
}

That's it! The Chat component includes everything you need.

🎯 Use Cases

1. Simple Chat (No Client/Context)

<Chat
  sessionId={sessionId}
  onNavigateToSession={(id) => router.push(`/chat/${id}`)}
/>

2. With Project/User Context (Instead of ClientId)

<Chat
  sessionId={sessionId}
  contextId={projectId}       // Generic context - can be projectId, userId, etc.
  contextType="project"
  contextName="My Project"
  onNavigateToSession={(id, contextId) =>
    router.push(`/projects/${contextId}/chat/${id}`)
  }
/>

3. Feature Toggles

<Chat
  sessionId={sessionId}
  config={{
    chatPanel: {
      enableToneSelector: false,      // Disable tone selector
      enableStateSelector: false,     // Disable state selector
      enableDocumentSelection: false, // Disable document selection
      enableFileAttachments: false,   // Disable file attachments
    },
    canvas: {
      enableSave: false,              // Disable save button
      enableExport: true,             // Keep export enabled
      exportFormats: {
        pdf: true,
        word: false,
        pleading: false,
        clipboard: true,
      },
    },
  }}
  onNavigateToSession={(id) => router.push(`/chat/${id}`)}
/>

4. Custom Adapters (Full Control)

<Chat
  sessionId={sessionId}
  adapters={{
    // Authentication
    auth: {
      getCurrentUser: () => currentUser,
      isAuthenticated: () => !!currentUser,
    },

    // Toast notifications
    toast: {
      success: (msg) => toast.success(msg),
      error: (msg) => toast.error(msg),
      info: (msg) => toast.info(msg),
      warning: (msg) => toast.warning(msg),
    },

    // Document management
    documents: {
      getDocumentCollections: () => [
        {
          type: "project",
          label: "Project Files",
          documents: projectDocuments,
        },
      ],
      onDocumentsSelected: (type, ids) => {
        console.log(`Selected ${ids.length} ${type} documents`);
      },
    },

    // Navigation
    navigation: {
      navigateToSession: (sessionId, contextId) => {
        router.push(`/chat/${sessionId}`);
      },
    },
  }}
  onNavigateToSession={(id) => router.push(`/chat/${id}`)}
/>

5. Event Tracking

<Chat
  sessionId={sessionId}
  onMessageSent={(message) => {
    analytics.track("message_sent", { messageId: message.id });
  }}
  onSessionCreated={(sessionId) => {
    analytics.track("session_created", { sessionId });
  }}
  onCanvasOpen={(messageId) => {
    analytics.track("canvas_opened", { messageId });
  }}
  onCanvasSave={(messageId, content) => {
    analytics.track("canvas_saved", { messageId });
  }}
  onNavigateToSession={(id) => router.push(`/chat/${id}`)}
/>

πŸ›  Advanced: Custom Layout

For full control over the layout, use the provider and individual components:

import {
  ChatModuleProvider,
  ChatPanel,
  Canvas,
  useCanvasManager,
} from "@secondchair/chat-module";

function MyCustomChat() {
  return (
    <ChatModuleProvider
      config={{ chatPanel: { enableCanvas: true } }}
      adapters={{
        toast: { success: toast.success, error: toast.error },
      }}
      context={{ contextId: projectId, contextType: "project" }}
    >
      <CustomLayout />
    </ChatModuleProvider>
  );
}

function CustomLayout() {
  const canvas = useCanvasManager();

  return (
    <div className="flex h-screen">
      <div className="flex-1">
        <ChatPanel
          chatSession={session}
          isCanvasOpen={canvas.isOpen}
          onOpenCanvas={(msg) => canvas.openCanvas(msg.id)}
        />
      </div>

      {canvas.isOpen && canvas.messageId && (
        <div className="w-1/2">
          <Canvas
            isOpen={canvas.isOpen}
            onClose={canvas.closeCanvas}
            messageId={canvas.messageId}
            selectedMessageIndex={canvas.selectedMessageIndex}
            lastIndex={canvas.totalMessages - 1}
            handleChangeCanvasMessage={(dir) =>
              dir > 0 ? canvas.goToNext() : canvas.goToPrevious()
            }
            isStreaming={canvas.isStreaming}
          />
        </div>
      )}
    </div>
  );
}

πŸ“‹ Available Hooks

Hook Purpose
useChatModule() Access all module context (includes config)
useFeatureEnabled(section, feature) Check if a feature is enabled
useMessageSubmission(options) Handle message sending
useDocuments(options) Manage document selection
useCanvasManager(options) Manage canvas state
useCurrentUser() Get current user from adapter
useChatToast() Access toast notifications

βš™οΈ Configuration Options

Canvas Features

canvas: {
  enableTextSelectionPopup: true,   // Text selection popup
  enableNavigation: true,           // Message navigation
  enableExport: true,               // Export/download
  enableSave: true,                 // Save functionality
  saveConfig: {
    showSaveButton: true,
    showSaveAndClose: true,
    enableAutoSave: false,
    autoSaveDelay: 2000,
  },
  exportFormats: {
    pdf: true,
    word: true,
    pleading: true,
    clipboard: true,
    showDownloadMenu: true,
  },
  toolbarFeatures: {
    undoRedo: true,
    fontSize: true,
    heading: true,
    textFormatting: true,
    colors: true,
    lists: true,
    alignment: true,
    insertElements: true,
    collapsible: true,
  },
}

Chat Panel Features

chatPanel: {
  enableDocumentSelection: true,    // Document selection
  enableFileAttachments: true,      // File uploads
  enableChatOptions: true,          // Chat options bar
  enableToneSelector: true,         // Tone selector
  enableStateSelector: true,        // State selector
  enableWelcomeSection: true,       // Welcome section
  enableMessageEditing: true,       // Edit messages
  enableCanvas: true,               // Canvas integration
  enableDocumentPreview: true,      // Document preview
  enableSecureDocumentLinks: true,  // Secure document links
}

Chat Features

chat: {
  enableStreaming: true,            // Streaming support
  enablePagination: true,           // Message pagination
  enableMessageReview: true,        // Message review/status
  enableComparisonMode: true,       // Comparison mode
  maxDocumentSelection: 10,         // Max documents
  maxClientDocumentSelection: 5,    // Max client documents
}

πŸ”Œ Adapter Types

interface ChatModuleAdapters {
  auth?: {
    getCurrentUser: () => ChatUser | null;
    isAuthenticated?: () => boolean;
  };

  documents?: {
    getDocumentCollections: () => DocumentCollection[];
    onDocumentsSelected?: (type: string, ids: (number | string)[]) => void;
    getSecureDocumentUrl?: (id: number | string) => Promise<string>;
    uploadDocument?: (file: File) => Promise<ChatDocument>;
  };

  navigation?: {
    navigateToSession: (sessionId: number, contextId?: number | string) => void;
    navigateToNewChat?: (contextId?: number | string) => void;
  };

  api?: {
    sendMessage: (payload: SendMessagePayload) => Promise<ChatMessage | void>;
    sendStreamingMessage?: (payload: SendMessagePayload) => void;
    fetchMessages?: (sessionId: number, page?: number) => Promise<ChatMessage[]>;
  };

  toast?: {
    success: (message: string) => void;
    error: (message: string) => void;
    info?: (message: string) => void;
    warning?: (message: string) => void;
  };

  storage?: {
    getItem: (key: string) => string | null;
    setItem: (key: string, value: string) => void;
    removeItem: (key: string) => void;
  };
}

πŸ“ Module Structure

chat-react-module/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ Chat.tsx           # All-in-one component
β”‚   β”‚   └── chat/
β”‚   β”‚       β”œβ”€β”€ chat-panel/    # Chat panel components
β”‚   β”‚       └── canvas/        # Canvas components
β”‚   β”œβ”€β”€ context/
β”‚   β”‚   └── ChatModuleProvider.tsx  # Main provider
β”‚   β”œβ”€β”€ hooks/
β”‚   β”‚   β”œβ”€β”€ useMessageSubmission.ts
β”‚   β”‚   β”œβ”€β”€ useDocuments.ts
β”‚   β”‚   └── useCanvasManager.ts
β”‚   β”œβ”€β”€ services/              # API services
β”‚   β”œβ”€β”€ types/                 # TypeScript types
β”‚   └── index.ts               # Main entry
β”œβ”€β”€ assets/                    # Icons and static assets
└── README.md

πŸ”„ Migration from ClientId

If you're migrating from the old clientId pattern:

// Before (with clientId)
<ChatPanel clientId={clientId} sessionId={sessionId} />

// After (generic context)
<Chat
  sessionId={sessionId}
  contextId={clientId}        // Same value, new name
  contextType="client"        // Identifies what kind of context
  onNavigateToSession={(id) => router.push(`/client/${clientId}/chat/${id}`)}
/>

🀝 Contributing

  1. All internal imports use @chatModule/* or relative paths.
  2. Keep components composable and independent.
  3. Run npm run typecheck before committing.