Skip to main content

What you will build

A reusable widget that embeds the assistant directly in your application. The widget provides:
  • A floating button that opens a chat panel when clicked
  • Real-time streaming responses based on information from your documentation
  • Message rendering with Markdown support
Users can use the widget to get help with your product without leaving your application.
Demo of the assistant widget being opened and the user typing in How do I get started? Then the assistant responds.

Prerequisites

  • Mintlify Pro or Custom plan
  • Your domain name, which appears at the end of your dashboard URL. For example, if your dashboard URL is https://dashboard.mintlify.com/org-name/domain-name, your domain name is domain-name
  • An assistant API key
  • Node.js v18 or higher and npm installed
  • Basic React knowledge

Get your assistant API key

  1. Navigate to the API keys page in your dashboard.
  2. Click Create Assistant API Key.
  3. Copy the assistant API key (starts with mint_dsc_) and save it securely.
The assistant API key is a public token that can be used in frontend code. Calls using this token count toward your plan’s message allowance and can incur overages.

Set up the example

The quickest way to get started is to clone the example repository and customize it for your needs.
1

Clone the repository

git clone https://github.com/mintlify/assistant-embed-example.git
cd assistant-embed-example
npm install
2

Configure your project

Open src/config.js and update with your Mintlify project details:
src/config.js
export const ASSISTANT_CONFIG = {
  domain: 'your-domain',
  docsURL: 'https://yourdocs.mintlify.app',
};
Replace:
  • your-domain with your Mintlify project domain found at the end of your dashboard URL.
  • https://yourdocs.mintlify.app with your actual documentation URL.
3

Add your API token

Create a .env file in the project root:
.env
VITE_MINTLIFY_TOKEN=mint_dsc_your_token_here
Replace mint_dsc_your_token_here with your assistant API key.
4

Start the development server

npm run dev
Open your application in a browser and click the Ask button to open the assistant widget.

Project structure

The example uses a component-based architecture.
src/
├── App.css                 # App styles
├── App.jsx                 # Main app component that renders the widget
├── config.js               # Configuration (domain and docsURL)
├── index.css               # Global styles
├── main.jsx                # Entry point
├── utils.js                # Helper functions for parsing suggestions and extracting sources
└── components/
    ├── AssistantWidget.jsx # Main widget component with chat state and API logic
    └── Message.jsx         # Individual message component for rendering user and assistant messages
Key files:
  • src/App.jsx: Main app component. Shows how to import and use the AssistantWidget component.
  • src/config.js: Centralized configuration. Update this file with your domain and docs URL.
  • src/components/AssistantWidget.jsx: The main widget component. Manages the open/close state, chat messages, and API calls.
  • src/utils.js: Contains utility functions for parsing the assistant’s response format and extracting sources.
  • src/components/Message.jsx: Renders individual messages with support for Markdown and suggestion links.

Customization ideas

Source citations

Extract and display sources from assistant responses:
const extractSources = (parts) => {
  return parts
    ?.filter(p => p.type === 'tool-invocation' && p.toolInvocation?.toolName === 'search')
    .flatMap(p => p.toolInvocation?.result || [])
    .map(source => ({
      url: source.url || source.path,
      title: source.metadata?.title || source.path,
    })) || [];
};

// In your message rendering:
{messages.map((message) => {
  const sources = message.role === 'assistant' ? extractSources(message.parts) : [];
  return (
    <div key={message.id}>
      {/* message content */}
      {sources.length > 0 && (
        <div className="mt-2 text-xs">
          <p className="font-semibold">Sources:</p>
          {sources.map((s, i) => (
            <a key={i} href={s.url} target="_blank" rel="noopener noreferrer" className="text-blue-600">
              {s.title}
            </a>
          ))}
        </div>
      )}
    </div>
  );
})}

Track conversation thread IDs

Store thread IDs to maintain conversation history across sessions:
import { useState, useEffect } from 'react';

export function AssistantWidget({ domain, docsURL }) {
  const [threadId, setThreadId] = useState(null);

  useEffect(() => {
    // Retrieve saved thread ID from localStorage
    const saved = localStorage.getItem('assistant-thread-id');
    if (saved) {
      setThreadId(saved);
    }
  }, []);

  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
    api: `https://api-dsc.mintlify.com/v1/assistant/${domain}/message`,
    headers: {
      'Authorization': `Bearer ${import.meta.env.VITE_MINTLIFY_TOKEN}`,
    },
    body: {
      fp: 'anonymous',
      retrievalPageSize: 5,
      ...(threadId && { threadId }), // Include thread ID if available
    },
    streamProtocol: 'data',
    sendExtraMessageFields: true,
    fetch: async (url, options) => {
      const response = await fetch(url, options);
      const newThreadId = response.headers.get('x-thread-id');
      if (newThreadId) {
        setThreadId(newThreadId);
        localStorage.setItem('assistant-thread-id', newThreadId);
      }
      return response;
    },
  });

  // ... rest of component
}

Add keyboard shortcuts

Allow users to open the widget and submit messages with keyboard shortcuts:
useEffect(() => {
  const handleKeyDown = (e) => {
    // Cmd/Ctrl + Shift + I to toggle widget
    if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'I') {
      e.preventDefault();
      setIsOpen((prev) => !prev);
    }

    // Enter (when widget is focused) to submit
    if (e.key === 'Enter' && !e.shiftKey && document.activeElement.id === 'assistant-input') {
      e.preventDefault();
      handleSubmit();
    }
  };

  window.addEventListener('keydown', handleKeyDown);
  return () => window.removeEventListener('keydown', handleKeyDown);
}, [handleSubmit]);