From 986756865c34b071b2153bf0dadeecd23c775f29 Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Mon, 20 Oct 2025 10:56:21 -0700 Subject: [PATCH 01/36] create guides --- guides/assistant-support-deflection.mdx | 15 +++++++++++++++ guides/assistant-widget.mdx | 15 +++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 guides/assistant-support-deflection.mdx create mode 100644 guides/assistant-widget.mdx diff --git a/guides/assistant-support-deflection.mdx b/guides/assistant-support-deflection.mdx new file mode 100644 index 000000000..01d0cdcb4 --- /dev/null +++ b/guides/assistant-support-deflection.mdx @@ -0,0 +1,15 @@ +--- +title: "Tutorial: Pre-fill support tickets with assistant context" +sidebarTitle: "Pre-fill support tickets" +description: "Reduce support burden by auto-populating tickets with assistant conversations and relevant documentation" +--- + +## What you will build + +## Prerequisites + +## Build the integration + +## Test the integration + +## Troubleshooting diff --git a/guides/assistant-widget.mdx b/guides/assistant-widget.mdx new file mode 100644 index 000000000..210c9bc7f --- /dev/null +++ b/guides/assistant-widget.mdx @@ -0,0 +1,15 @@ +--- +title: "Tutorial: Build an in-app documentation assistant" +sidebarTitle: "Build an in-app assistant" +description: "Embed the Mintlify assistant directly in your application to provide contextual documentation help" +--- + +## What you will build + +## Prerequisites + +## Build the widget + +## Test the widget + +## Troubleshooting From 3e23ba64919cd2e584b049792e85d458f19898ab Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:03:57 -0700 Subject: [PATCH 02/36] add new pages to docs.json --- docs.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs.json b/docs.json index 8688ba60e..a1957f636 100644 --- a/docs.json +++ b/docs.json @@ -233,10 +233,12 @@ "group": "AI", "icon": "bot", "pages": [ + "guides/assistant-support-deflection", + "guides/assistant-widget", "guides/automate-agent", - "guides/geo", "guides/claude-code", "guides/cursor", + "guides/geo", "guides/windsurf" ] }, From e97420a1c27d01a5b22f2f309f0d7dd9805cc0cc Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:04:53 -0700 Subject: [PATCH 03/36] alphabetize sidebar nav --- docs.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs.json b/docs.json index a1957f636..1236bf0bd 100644 --- a/docs.json +++ b/docs.json @@ -233,12 +233,12 @@ "group": "AI", "icon": "bot", "pages": [ - "guides/assistant-support-deflection", - "guides/assistant-widget", "guides/automate-agent", + "guides/assistant-widget", "guides/claude-code", "guides/cursor", "guides/geo", + "guides/assistant-support-deflection", "guides/windsurf" ] }, From 9c051fe35c4edea6a760f3f9be1c67fae45cb5bc Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:17:35 -0700 Subject: [PATCH 04/36] add content for widget guide --- guides/assistant-widget.mdx | 294 +++++++++++++++++++++++++++++++++++- 1 file changed, 293 insertions(+), 1 deletion(-) diff --git a/guides/assistant-widget.mdx b/guides/assistant-widget.mdx index 210c9bc7f..fe0c4d915 100644 --- a/guides/assistant-widget.mdx +++ b/guides/assistant-widget.mdx @@ -1,15 +1,307 @@ --- title: "Tutorial: Build an in-app documentation assistant" sidebarTitle: "Build an in-app assistant" -description: "Embed the Mintlify assistant directly in your application to provide contextual documentation help" +description: "Embed the assistant in your application to provide contextual documentation help" --- ## What you will build +A reusable React component that embeds the Mintlify assistant directly in your application. The component provides: + +- A toggle button that opens a chat panel +- Real-time streaming responses from your documentation +- Conversation history with thread management +- Source citations that link to your documentation +- Copyable code examples from responses + +This allows users to get help with your product without leaving your application. + ## Prerequisites +- Mintlify Pro or Custom plan +- Your documentation domain (for example, `docs.yourcompany.com`) +- Node.js and npm installed +- Basic React knowledge +- Existing React application (Next.js, Vite, Create React App, etc.) + +### Get your assistant API token + +1. Navigate to the [API keys](https://dashboard.mintlify.com/settings/organization/api-keys) page in your dashboard. +2. Select **Create Assistant API Key**. +3. Copy the public token (starts with `mint_dsc_`) and save it. + + + The assistant API token 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. + + ## Build the widget +### Install dependencies + +Install the AI SDK for React and Mintlify's assistant integration: + +```bash +npm install ai @ai-sdk/react +``` + +### Create the assistant component + +Create a new file `AssistantWidget.jsx` (or `.tsx` if using TypeScript) in your components directory: + +```jsx AssistantWidget.jsx expandable +import { useChat } from '@ai-sdk/react'; +import { useState } from 'react'; + +export function AssistantWidget({ domain }) { + const [isOpen, setIsOpen] = useState(false); + const [threadId, setThreadId] = useState(null); + + const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({ + api: `https://api.mintlify.com/v1/assistant/${domain}/message`, + headers: { + 'Authorization': `Bearer ${process.env.REACT_APP_MINTLIFY_TOKEN}`, + }, + body: { + threadId: threadId, + }, + onFinish: (message) => { + // Store thread ID from first response to maintain conversation history + if (!threadId && message.threadId) { + setThreadId(message.threadId); + } + }, + }); + + return ( + <> + {/* Toggle button */} + + + {/* Chat panel */} + {isOpen && ( +
+ {/* Header */} +
+

Documentation Assistant

+

Ask me anything about our docs

+
+ + {/* Messages */} +
+ {messages.length === 0 && ( +
+

How can I help you today?

+
+ )} + + {messages.map((message) => ( +
+
+
+ {message.content} +
+ + {/* Display sources if available */} + {message.sources && message.sources.length > 0 && ( +
+

Sources:

+ +
+ )} +
+
+ ))} + + {isLoading && ( +
+
+
+
+
+
+
+
+
+ )} +
+ + {/* Input form */} +
+
+ + +
+
+
+ )} + + ); +} +``` + +### Configure environment variables + +Add your assistant API token to your environment variables: + +```bash .env +REACT_APP_MINTLIFY_TOKEN=mint_dsc_your_token_here +``` + + + The environment variable name depends on your framework. Common formats include: + - Create React App: `REACT_APP_MINTLIFY_TOKEN` + - Next.js: `NEXT_PUBLIC_MINTLIFY_TOKEN` + - Vite: `VITE_MINTLIFY_TOKEN` + + +### Add the widget to your app + +Import and render the assistant widget in your application. Replace `docs.yourcompany.com` with your documentation domain. + +```jsx App.jsx +import { AssistantWidget } from './components/AssistantWidget'; + +function App() { + return ( +
+ {/* Your existing app content */} + + {/* Add the assistant widget */} + +
+ ); +} + +export default App; +``` + ## Test the widget +1. Start your development server: + ```bash + npm start + ``` +2. Open your application in a browser. +3. Click the assistant button in the bottom-right corner. +4. Ask a question that requires searching your documentation, such as "How do I get started?" + +### Verify the assistant + +Check the following to confirm the widget is working: + +- The chat panel opens when you click the toggle button. +- Your question appears in the chat as a user message. +- The assistant responds with relevant information from your documentation. +- Source links appear below the response and navigate to your docs when clicked. +- Follow-up questions maintain the conversation context. + +## Optional enhancements + +### Filter by documentation section + +Limit the assistant's responses to specific sections of your documentation by adding a `filter` parameter to the request body. For example, to only search the API reference section, add `filter: '/api-reference'`. + +```jsx +const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({ + api: `https://api.mintlify.com/v1/assistant/${domain}/message`, + headers: { + 'Authorization': `Bearer ${process.env.REACT_APP_MINTLIFY_TOKEN}`, + }, + body: { + threadId: threadId, + filter: '/api-reference', // Only search API reference section + }, + // ... rest of config +}); +``` + +### Track user sessions + +Pass a user identifier to track sessions and provide personalized assistance. + +```jsx +body: { + threadId: threadId, + fp: userId, // User fingerprint or ID +}, +``` + +### Customize styling + +The component uses Tailwind CSS classes. Customize the appearance by modifying the class names or replacing them with your own CSS solution. + ## Troubleshooting + +### Widget not appearing + +- Verify the component is imported and rendered in your app. +- Check that Tailwind CSS or your styling solution is properly configured. +- Ensure the z-index values do not conflict with other fixed elements. + +### 401 authentication error + +- Verify your API token starts with `mint_dsc_`. +- Check the token is correctly set in your environment variables. +- Confirm you are using the public assistant API token, not the admin API key. +- Restart your development server after adding environment variables. + +### No responses from assistant + +- Verify your documentation domain is correct (do not include `https://`). +- Check that your Mintlify plan includes the assistant feature. +- Confirm your documentation site is published and accessible. + +### Thread context not maintained + +- Verify the `threadId` is being stored and passed correctly. +- Check that the `onFinish` callback is setting the thread ID from the response. +- Ensure the `threadId` state persists between messages. From 9f98c810ea556bf2795f8d024e16bc15341be342 Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:27:25 -0700 Subject: [PATCH 05/36] review code samples --- guides/assistant-widget.mdx | 103 +++++++++++++++++++++--------------- 1 file changed, 59 insertions(+), 44 deletions(-) diff --git a/guides/assistant-widget.mdx b/guides/assistant-widget.mdx index fe0c4d915..cc32d18f4 100644 --- a/guides/assistant-widget.mdx +++ b/guides/assistant-widget.mdx @@ -52,7 +52,7 @@ Create a new file `AssistantWidget.jsx` (or `.tsx` if using TypeScript) in your import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; -export function AssistantWidget({ domain }) { +export function AssistantWidget({ domain, userId = 'anonymous' }) { const [isOpen, setIsOpen] = useState(false); const [threadId, setThreadId] = useState(null); @@ -62,14 +62,19 @@ export function AssistantWidget({ domain }) { 'Authorization': `Bearer ${process.env.REACT_APP_MINTLIFY_TOKEN}`, }, body: { + fp: userId, threadId: threadId, }, - onFinish: (message) => { - // Store thread ID from first response to maintain conversation history - if (!threadId && message.threadId) { - setThreadId(message.threadId); + fetch: async (url, options) => { + const response = await fetch(url, options); + const tempThreadId = response.headers.get('x-thread-id'); + if (tempThreadId) { + setThreadId(tempThreadId); } + return response; }, + streamProtocol: 'data', + sendExtraMessageFields: true, }); return ( @@ -120,29 +125,42 @@ export function AssistantWidget({ domain }) { : 'bg-gray-100 text-gray-900' }`} > -
+
{message.content}
- {/* Display sources if available */} - {message.sources && message.sources.length > 0 && ( -
-

Sources:

- -
+ {/* Display sources from tool invocation results */} + {message.role === 'assistant' && message.parts && ( + (() => { + const sources = message.parts + .filter(part => + part.type === 'tool-invocation' && + part.toolInvocation?.state === 'result' && + part.toolInvocation?.toolName === 'search' && + Array.isArray(part.toolInvocation?.result) + ) + .flatMap(part => part.toolInvocation.result); + + return sources.length > 0 ? ( +
+

Sources:

+ +
+ ) : null; + })() )}
@@ -248,31 +266,27 @@ Check the following to confirm the widget is working: ### Filter by documentation section -Limit the assistant's responses to specific sections of your documentation by adding a `filter` parameter to the request body. For example, to only search the API reference section, add `filter: '/api-reference'`. +Limit the assistant's responses to specific sections of your documentation by adding a `filter` parameter to the request body: ```jsx -const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({ - api: `https://api.mintlify.com/v1/assistant/${domain}/message`, - headers: { - 'Authorization': `Bearer ${process.env.REACT_APP_MINTLIFY_TOKEN}`, - }, - body: { - threadId: threadId, - filter: '/api-reference', // Only search API reference section - }, - // ... rest of config -}); +body: { + fp: userId, + threadId: threadId, + filter: { + path: '/api-reference' // Only search the API reference section + } +}, ``` ### Track user sessions -Pass a user identifier to track sessions and provide personalized assistance. +The `fp` (fingerprint) parameter is already included in the component to track user sessions. You can pass a unique user identifier to provide personalized assistance and analytics: ```jsx -body: { - threadId: threadId, - fp: userId, // User fingerprint or ID -}, + ``` ### Customize styling @@ -302,6 +316,7 @@ The component uses Tailwind CSS classes. Customize the appearance by modifying t ### Thread context not maintained -- Verify the `threadId` is being stored and passed correctly. -- Check that the `onFinish` callback is setting the thread ID from the response. +- Verify the `threadId` is being extracted from the `x-thread-id` response header. +- Check that the custom `fetch` function is implemented correctly. - Ensure the `threadId` state persists between messages. +- Confirm `streamProtocol: 'data'` is set in the useChat configuration. From 47c11cd8a9273562bc97f8024d5c9f1d7b475fcc Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Mon, 20 Oct 2025 13:29:34 -0700 Subject: [PATCH 06/36] add content to deflection guide --- guides/assistant-support-deflection.mdx | 303 ++++++++++++++++++++++++ 1 file changed, 303 insertions(+) diff --git a/guides/assistant-support-deflection.mdx b/guides/assistant-support-deflection.mdx index 01d0cdcb4..052d6b127 100644 --- a/guides/assistant-support-deflection.mdx +++ b/guides/assistant-support-deflection.mdx @@ -6,10 +6,313 @@ description: "Reduce support burden by auto-populating tickets with assistant co ## What you will build +An integration that captures assistant conversations when users need additional help and automatically creates support tickets with full context. The workflow: + +1. User interacts with the assistant on your documentation site +2. When the assistant can't fully answer their question, user clicks a "Contact Support" link +3. A support ticket is automatically created with: + - User's original questions + - Assistant's responses + - Links to relevant documentation + - Conversation thread ID for reference + +This reduces support burden by giving your team full context before they respond, and helps identify documentation gaps. + ## Prerequisites +- Mintlify Pro or Custom plan +- Support platform account (this guide uses Zendesk as an example, but the approach works for any platform) +- Support platform API credentials +- Basic knowledge of your support platform's API + +### Get your support platform API credentials + +**For Zendesk:** +1. Navigate to Admin Center → Apps and integrations → APIs → Zendesk API +2. Enable token access +3. Click the **Add API token** button +4. Copy and save the token securely +5. Note your Zendesk subdomain (e.g., `yourcompany.zendesk.com`) + +For other platforms (Intercom, Freshdesk, etc.), consult their API documentation for authentication setup. + +## Understand the assistant data + +The Mintlify assistant provides several ways to access conversation data: + +### Dashboard export +Export conversation history as CSV from your [assistant dashboard](https://dashboard.mintlify.com/products/assistant). The export includes: +- User queries +- Assistant responses +- Source citations +- Timestamps +- Thread IDs + +### Built-in deflection +Configure a deflection email in your [assistant settings](https://dashboard.mintlify.com/products/assistant/settings). When the assistant cannot answer a question, it displays this email to users. + +### Custom integration +For programmatic access, you can build a custom contact form that captures the current conversation state and sends it to your support system. + ## Build the integration +This guide shows how to build a serverless function that creates support tickets from assistant conversation data. + +### Create a contact support page + +Add a dedicated contact page to your documentation that captures assistant context when users need help. + +Create `contact-support.mdx` in your docs: + +```mdx contact-support.mdx +--- +title: "Contact Support" +description: "Get help from our support team" +--- + +If the assistant couldn't answer your question, our support team is here to help. + +
+ + + + + + + + + + + +
+ + +``` + +### Create the ticket creation endpoint + +Create a serverless function to handle ticket creation. This example uses Vercel, but you can adapt it for any platform. + +Create `api/create-ticket.js`: + +```javascript api/create-ticket.js +export default async function handler(req, res) { + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + const { email, subject, description, conversation_context } = req.body; + + // Parse conversation context if provided + let contextNote = ''; + if (conversation_context) { + try { + const context = JSON.parse(conversation_context); + contextNote = formatConversationContext(context); + } catch (e) { + console.error('Failed to parse conversation context:', e); + } + } + + // Create Zendesk ticket + const ticketData = { + ticket: { + subject: subject, + comment: { + body: `${description}\n\n---\n\nAssistant Conversation History:\n${contextNote}` + }, + requester: { + email: email, + name: email.split('@')[0] + }, + priority: 'normal', + tags: ['assistant_escalation'] + } + }; + + try { + const response = await fetch( + `https://${process.env.ZENDESK_SUBDOMAIN}.zendesk.com/api/v2/tickets.json`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Basic ${Buffer.from( + `${process.env.ZENDESK_EMAIL}/token:${process.env.ZENDESK_API_TOKEN}` + ).toString('base64')}` + }, + body: JSON.stringify(ticketData) + } + ); + + if (!response.ok) { + throw new Error(`Zendesk API error: ${response.status}`); + } + + const result = await response.json(); + + return res.status(200).json({ + success: true, + ticket_id: result.ticket.id, + ticket_url: `https://${process.env.ZENDESK_SUBDOMAIN}.zendesk.com/agent/tickets/${result.ticket.id}` + }); + + } catch (error) { + console.error('Error creating ticket:', error); + return res.status(500).json({ + error: 'Failed to create support ticket', + message: error.message + }); + } +} + +function formatConversationContext(context) { + if (!context.messages || context.messages.length === 0) { + return 'No conversation history available.'; + } + + return context.messages + .map((msg, index) => { + const role = msg.role === 'user' ? 'User' : 'Assistant'; + const sources = msg.sources + ? `\nSources: ${msg.sources.map(s => s.url).join(', ')}` + : ''; + + return `${index + 1}. ${role}: ${msg.content}${sources}`; + }) + .join('\n\n'); +} +``` + +### Configure environment variables + +Add your support platform credentials to your environment: + +```bash .env +ZENDESK_SUBDOMAIN=yourcompany +ZENDESK_EMAIL=support@yourcompany.com +ZENDESK_API_TOKEN=your_api_token_here +``` + +For Vercel, add these in your project settings under Environment Variables. + +### Link from the assistant + +Update your assistant configuration to direct users to your contact page: + +1. Navigate to your [assistant settings](https://dashboard.mintlify.com/products/assistant/settings) +2. In the deflection email section, you can include a link to your contact page: "Visit [our support page](https://docs.yourcompany.com/contact-support) for help" + ## Test the integration +1. Navigate to your documentation site +2. Open the assistant and ask a question it might not fully answer +3. Click the contact support link +4. Fill out the form and submit +5. Verify the ticket was created in your support system + +### Verify the integration + +Check the following to confirm the workflow is working: + +- Ticket appears in your support platform (Zendesk, etc.) +- Ticket includes the user's description +- Ticket includes formatted conversation context from the assistant +- Ticket is tagged appropriately for tracking +- Support team can access all relevant information + +## Optional enhancements + +### Add user identification + +If users are authenticated on your docs site, include their user ID for better tracking: + +```javascript +const ticketData = { + ticket: { + subject: subject, + comment: { body: description }, + requester: { + email: email, + name: req.body.user_name || email.split('@')[0] + }, + custom_fields: [ + { id: 'user_id_field_id', value: req.body.user_id } + ], + tags: ['assistant_escalation', 'authenticated_user'] + } +}; +``` + +### Analyze deflection patterns + +Use the assistant dashboard to export conversation data and analyze: +- What questions lead to support tickets? +- Which documentation pages are most frequently cited before escalation? +- What topics have low deflection rates (need better docs)? + +Export data from your [assistant dashboard](https://dashboard.mintlify.com/products/assistant) and analyze with your preferred tool. + +### Integrate with multiple platforms + +Adapt the serverless function to support multiple platforms based on configuration: + +```javascript +const PLATFORM_HANDLERS = { + zendesk: createZendeskTicket, + intercom: createIntercomConversation, + freshdesk: createFreshdeskTicket +}; + +const platform = process.env.SUPPORT_PLATFORM || 'zendesk'; +const handler = PLATFORM_HANDLERS[platform]; + +if (!handler) { + throw new Error(`Unsupported platform: ${platform}`); +} + +await handler(ticketData); +``` + ## Troubleshooting + +### Tickets not being created + +- Verify your API credentials are correct and have proper permissions +- Check that your support platform API is accessible from your serverless function +- Review serverless function logs for error messages +- Test API credentials with a simple curl command: + ```bash + curl https://yourcompany.zendesk.com/api/v2/tickets.json \ + -u email/token:api_token \ + -X GET + ``` + +### Conversation context is empty + +- Verify the assistant conversation is being stored in localStorage +- Check browser console for JavaScript errors +- Ensure the contact form is properly capturing the hidden field value +- Test with a fresh conversation in the assistant + +### Tickets missing information + +- Check the `formatConversationContext` function is parsing data correctly +- Verify the conversation context JSON structure matches your expectations +- Add console logging to debug the data being sent +- Review the support platform's field requirements + +### Authentication errors + +- Confirm your API token hasn't expired +- Verify the subdomain is correct (no `https://` prefix) +- Check that email/token format matches platform requirements (e.g., `email/token:api_token` for Zendesk) +- Ensure API token has permission to create tickets From f1fcda8c289e879de6650e36c7dd2536a4e7a2ae Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Tue, 21 Oct 2025 10:57:16 -0700 Subject: [PATCH 07/36] Update assistant-widget.mdx --- guides/assistant-widget.mdx | 275 +++++++++++++++++++++++++++++++----- 1 file changed, 242 insertions(+), 33 deletions(-) diff --git a/guides/assistant-widget.mdx b/guides/assistant-widget.mdx index cc32d18f4..f7b958a7b 100644 --- a/guides/assistant-widget.mdx +++ b/guides/assistant-widget.mdx @@ -19,7 +19,7 @@ This allows users to get help with your product without leaving your application ## Prerequisites - Mintlify Pro or Custom plan -- Your documentation domain (for example, `docs.yourcompany.com`) +- Your Mintlify project identifier (found in your [dashboard](https://dashboard.mintlify.com)) - Node.js and npm installed - Basic React knowledge - Existing React application (Next.js, Vite, Create React App, etc.) @@ -38,10 +38,10 @@ This allows users to get help with your product without leaving your application ### Install dependencies -Install the AI SDK for React and Mintlify's assistant integration: +Install the AI SDK for React and a Markdown renderer: ```bash -npm install ai @ai-sdk/react +npm install ai @ai-sdk/react react-markdown ``` ### Create the assistant component @@ -50,20 +50,37 @@ Create a new file `AssistantWidget.jsx` (or `.tsx` if using TypeScript) in your ```jsx AssistantWidget.jsx expandable import { useChat } from '@ai-sdk/react'; -import { useState } from 'react'; - -export function AssistantWidget({ domain, userId = 'anonymous' }) { +import { useState, useRef, useEffect } from 'react'; +import ReactMarkdown from 'react-markdown'; + +export function AssistantWidget({ + domain, + userId = 'anonymous', + // Customization props + buttonColor = '#2563eb', // blue-600 + accentColor = '#2563eb', + position = 'bottom-right', + headerTitle = 'Documentation Assistant', + headerSubtitle = 'Ask me anything about our docs', + emptyStateText = 'How can I help you today?', + inputPlaceholder = 'Ask a question...', + sendButtonText = 'Send', +}) { const [isOpen, setIsOpen] = useState(false); const [threadId, setThreadId] = useState(null); + const messagesEndRef = useRef(null); + const closeButtonRef = useRef(null); const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({ - api: `https://api.mintlify.com/v1/assistant/${domain}/message`, + api: `https://api-dsc.mintlify.com/v1/assistant/${domain}/message`, headers: { 'Authorization': `Bearer ${process.env.REACT_APP_MINTLIFY_TOKEN}`, }, body: { fp: userId, threadId: threadId, + retrievalPageSize: 5, + filter: null, }, fetch: async (url, options) => { const response = await fetch(url, options); @@ -77,13 +94,63 @@ export function AssistantWidget({ domain, userId = 'anonymous' }) { sendExtraMessageFields: true, }); + // Scroll to latest message + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages]); + + // Handle keyboard shortcuts (Escape to close) + useEffect(() => { + const handleKeyDown = (e) => { + if (e.key === 'Escape' && isOpen) { + setIsOpen(false); + } + }; + if (isOpen) { + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + } + }, [isOpen]); + + // Focus management when opening + useEffect(() => { + if (isOpen && closeButtonRef.current) { + closeButtonRef.current.focus(); + } + }, [isOpen]); + + // Helper function to convert hex to rgb + const hexToRgb = (hex) => { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}` : '37, 99, 235'; + }; + + const getPositionClasses = () => { + const positions = { + 'bottom-right': 'bottom-4 right-4', + 'bottom-left': 'bottom-4 left-4', + 'top-right': 'top-4 right-4', + 'top-left': 'top-4 left-4', + }; + return positions[position] || positions['bottom-right']; + }; + + const panelPosition = { + 'bottom-right': 'bottom-20 right-4', + 'bottom-left': 'bottom-20 left-4', + 'top-right': 'top-20 right-4', + 'top-left': 'top-20 left-4', + }; + return ( <> {/* Toggle button */} {/* Messages */}
{messages.length === 0 && ( -
-

How can I help you today?

+
+

{emptyStateText}

)} @@ -121,15 +204,35 @@ export function AssistantWidget({ domain, userId = 'anonymous' }) {
-
- {message.content} -
+ {message.role === 'user' ? ( +
+ {message.content} +
+ ) : ( +

{children}

, + ul: ({ children }) =>
    {children}
, + ol: ({ children }) =>
    {children}
, + code: ({ inline, children }) => + inline ? ( + {children} + ) : ( + {children} + ), + }} + > + {message.content} +
+ )} - {/* Display sources from tool invocation results */} + {/* Display sources */} {message.role === 'assistant' && message.parts && ( (() => { const sources = message.parts @@ -148,7 +251,7 @@ export function AssistantWidget({ domain, userId = 'anonymous' }) { {sources.map((source, idx) => (
  • -
    -
    +
    +
  • )} +
    {/* Input form */} @@ -186,16 +290,17 @@ export function AssistantWidget({ domain, userId = 'anonymous' }) { type="text" value={input} onChange={handleInputChange} - placeholder="Ask a question..." - className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder={inputPlaceholder} + className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2" disabled={isLoading} />
    @@ -223,7 +328,7 @@ REACT_APP_MINTLIFY_TOKEN=mint_dsc_your_token_here ### Add the widget to your app -Import and render the assistant widget in your application. Replace `docs.yourcompany.com` with your documentation domain. +Import and render the assistant widget in your application. Pass your Mintlify project identifier. ```jsx App.jsx import { AssistantWidget } from './components/AssistantWidget'; @@ -233,8 +338,8 @@ function App() {
    {/* Your existing app content */} - {/* Add the assistant widget */} - + {/* Add the assistant widget - use your project identifier */} +
    ); } @@ -284,14 +389,118 @@ The `fp` (fingerprint) parameter is already included in the component to track u ```jsx ``` ### Customize styling -The component uses Tailwind CSS classes. Customize the appearance by modifying the class names or replacing them with your own CSS solution. +The component accepts several props to match your brand colors and text. You can customize the appearance without modifying the component code. + +#### Change brand colors + +Pass hex color codes to customize the button and accent colors: + +```jsx + +``` + +#### Customize text and labels + +Replace default text with your own: + +```jsx + +``` + +#### Change widget position + +Position the widget in any corner of the screen: + +```jsx + +``` + +#### Advanced styling with CSS + +For more control, modify the Tailwind classes directly in the component. The component uses inline styles for colors, so you can also override specific elements with CSS modules or styled-components if needed. + +## Props reference + +The `AssistantWidget` component accepts the following props: + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `domain` | string | required | Your Mintlify project identifier from the dashboard | +| `userId` | string | `'anonymous'` | Unique identifier for tracking user sessions | +| `buttonColor` | string | `'#2563eb'` | Hex color code for the toggle button and accents | +| `accentColor` | string | `'#2563eb'` | Hex color code for user message bubbles and interactive elements | +| `position` | string | `'bottom-right'` | Widget position: `'bottom-right'`, `'bottom-left'`, `'top-right'`, or `'top-left'` | +| `headerTitle` | string | `'Documentation Assistant'` | Title displayed in the chat panel header | +| `headerSubtitle` | string | `'Ask me anything about our docs'` | Subtitle shown below the title | +| `emptyStateText` | string | `'How can I help you today?'` | Message shown when no conversation history exists | +| `inputPlaceholder` | string | `'Ask a question...'` | Placeholder text for the message input field | +| `sendButtonText` | string | `'Send'` | Label for the send button | + +## Integrate with your design system + +To match your application's existing brand, you can pass theme values from your design system or theme provider: + +```jsx +// Using a theme context +import { useTheme } from './context/ThemeContext'; +import { AssistantWidget } from './components/AssistantWidget'; + +function App() { + const { colors } = useTheme(); + + return ( +
    + {/* Your app content */} + +
    + ); +} +``` + +Or with Tailwind config: + +```jsx +// Extract colors from Tailwind theme +import { AssistantWidget } from './components/AssistantWidget'; + +export default function App() { + const brandColor = '#3b82f6'; // From your Tailwind config + + return ( + + ); +} +``` ## Troubleshooting @@ -310,7 +519,7 @@ The component uses Tailwind CSS classes. Customize the appearance by modifying t ### No responses from assistant -- Verify your documentation domain is correct (do not include `https://`). +- Verify your project identifier is correct.. - Check that your Mintlify plan includes the assistant feature. - Confirm your documentation site is published and accessible. From 0c097bc0205b99a287fb1ac5a9aade455a8c991b Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:00:04 -0700 Subject: [PATCH 08/36] replace `domain` with `project` --- guides/assistant-widget.mdx | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/guides/assistant-widget.mdx b/guides/assistant-widget.mdx index f7b958a7b..7dcdab437 100644 --- a/guides/assistant-widget.mdx +++ b/guides/assistant-widget.mdx @@ -19,7 +19,7 @@ This allows users to get help with your product without leaving your application ## Prerequisites - Mintlify Pro or Custom plan -- Your Mintlify project identifier (found in your [dashboard](https://dashboard.mintlify.com)) +- Your Mintlify project ID (found in your [dashboard](https://dashboard.mintlify.com)) - Node.js and npm installed - Basic React knowledge - Existing React application (Next.js, Vite, Create React App, etc.) @@ -54,7 +54,7 @@ import { useState, useRef, useEffect } from 'react'; import ReactMarkdown from 'react-markdown'; export function AssistantWidget({ - domain, + project, userId = 'anonymous', // Customization props buttonColor = '#2563eb', // blue-600 @@ -72,7 +72,7 @@ export function AssistantWidget({ const closeButtonRef = useRef(null); const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({ - api: `https://api-dsc.mintlify.com/v1/assistant/${domain}/message`, + api: `https://api-dsc.mintlify.com/v1/assistant/${project}/message`, headers: { 'Authorization': `Bearer ${process.env.REACT_APP_MINTLIFY_TOKEN}`, }, @@ -251,7 +251,7 @@ export function AssistantWidget({ {sources.map((source, idx) => (
  • {/* Your existing app content */} - {/* Add the assistant widget - use your project identifier */} - + {/* Add the assistant widget - use your project ID */} + ); } @@ -404,7 +404,7 @@ Pass hex color codes to customize the button and accent colors: ```jsx @@ -416,7 +416,7 @@ Replace default text with your own: ```jsx ``` @@ -446,7 +446,7 @@ The `AssistantWidget` component accepts the following props: | Prop | Type | Default | Description | |------|------|---------|-------------| -| `domain` | string | required | Your Mintlify project identifier from the dashboard | +| `project` | string | required | Your Mintlify project ID from the dashboard | | `userId` | string | `'anonymous'` | Unique identifier for tracking user sessions | | `buttonColor` | string | `'#2563eb'` | Hex color code for the toggle button and accents | | `accentColor` | string | `'#2563eb'` | Hex color code for user message bubbles and interactive elements | @@ -473,7 +473,7 @@ function App() {
    {/* Your app content */} @@ -519,7 +519,7 @@ export default function App() { ### No responses from assistant -- Verify your project identifier is correct.. +- Verify your project ID is correct (find it in your dashboard). - Check that your Mintlify plan includes the assistant feature. - Confirm your documentation site is published and accessible. From fa133a5c7c2189b1490ffa90dd457d31680c3d57 Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:28:15 -0700 Subject: [PATCH 09/36] update reference --- guides/assistant-widget.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/assistant-widget.mdx b/guides/assistant-widget.mdx index 7dcdab437..2522cf185 100644 --- a/guides/assistant-widget.mdx +++ b/guides/assistant-widget.mdx @@ -389,7 +389,7 @@ The `fp` (fingerprint) parameter is already included in the component to track u ```jsx ``` From 79b47c5b72ca5ad43dca3a54522d082e27775603 Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:29:02 -0700 Subject: [PATCH 10/36] update testing info --- guides/assistant-widget.mdx | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/guides/assistant-widget.mdx b/guides/assistant-widget.mdx index 2522cf185..e4e7e4e2a 100644 --- a/guides/assistant-widget.mdx +++ b/guides/assistant-widget.mdx @@ -361,11 +361,24 @@ export default App; Check the following to confirm the widget is working: -- The chat panel opens when you click the toggle button. -- Your question appears in the chat as a user message. -- The assistant responds with relevant information from your documentation. -- Source links appear below the response and navigate to your docs when clicked. -- Follow-up questions maintain the conversation context. +- **Panel opens and closes** - The chat panel appears when you click the toggle button and closes when you click it again or press Escape +- **User messages display** - Your questions appear in the chat as user messages +- **Assistant responds** - The assistant responds with relevant information from your documentation +- **Source citations appear** - Sources are displayed below the response with clickable links +- **Conversation context persists** - Follow-up questions maintain the conversation history (thread ID is preserved across messages) +- **Browser console check** - Open developer tools and verify: + - No authentication errors (look for 401 or 403 in Network tab) + - The `x-thread-id` header is present in API responses + - No CORS errors if embedding on a different domain + +### Common test issues + +If the widget doesn't work as expected: + +- **No responses** - Check that your project ID is correct and your Mintlify plan includes the assistant feature +- **Authentication fails** - Verify the `mint_dsc_` token is correctly set in environment variables and restart your dev server +- **Messages don't show sources** - Ensure `sendExtraMessageFields: true` is set in the useChat configuration +- **Context not maintained** - Verify the `threadId` is being extracted from response headers and passed to subsequent requests ## Optional enhancements From ed3ce97268c6ff6d91b53b5d08b7c027896ebe12 Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:39:22 -0700 Subject: [PATCH 11/36] copy edit --- guides/assistant-widget.mdx | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/guides/assistant-widget.mdx b/guides/assistant-widget.mdx index e4e7e4e2a..e68226444 100644 --- a/guides/assistant-widget.mdx +++ b/guides/assistant-widget.mdx @@ -1,7 +1,7 @@ --- title: "Tutorial: Build an in-app documentation assistant" sidebarTitle: "Build an in-app assistant" -description: "Embed the assistant in your application to provide contextual documentation help" +description: "Embed the assistant in your application to answer questions with information from your documentation" --- ## What you will build @@ -9,29 +9,28 @@ description: "Embed the assistant in your application to provide contextual docu A reusable React component that embeds the Mintlify assistant directly in your application. The component provides: - A toggle button that opens a chat panel -- Real-time streaming responses from your documentation -- Conversation history with thread management +- Real-time streaming responses based on information from your documentation - Source citations that link to your documentation -- Copyable code examples from responses -This allows users to get help with your product without leaving your application. +Users can get help with your product without leaving your application. ## Prerequisites - Mintlify Pro or Custom plan - Your Mintlify project ID (found in your [dashboard](https://dashboard.mintlify.com)) +- An assistant API key - Node.js and npm installed - Basic React knowledge - Existing React application (Next.js, Vite, Create React App, etc.) -### Get your assistant API token +### Get your assistant API key 1. Navigate to the [API keys](https://dashboard.mintlify.com/settings/organization/api-keys) page in your dashboard. 2. Select **Create Assistant API Key**. -3. Copy the public token (starts with `mint_dsc_`) and save it. +3. Copy the assistant API key (starts with `mint_dsc_`) and save it securely. - The assistant API token 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. + 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. ## Build the widget @@ -56,11 +55,11 @@ import ReactMarkdown from 'react-markdown'; export function AssistantWidget({ project, userId = 'anonymous', - // Customization props - buttonColor = '#2563eb', // blue-600 + // Customize any of these default values to match your style and tone + buttonColor = '#2563eb', accentColor = '#2563eb', position = 'bottom-right', - headerTitle = 'Documentation Assistant', + headerTitle = 'Documentation assistant', headerSubtitle = 'Ask me anything about our docs', emptyStateText = 'How can I help you today?', inputPlaceholder = 'Ask a question...', @@ -99,7 +98,7 @@ export function AssistantWidget({ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); - // Handle keyboard shortcuts (Escape to close) + // Handle keyboard shortcuts useEffect(() => { const handleKeyDown = (e) => { if (e.key === 'Escape' && isOpen) { @@ -112,7 +111,7 @@ export function AssistantWidget({ } }, [isOpen]); - // Focus management when opening + // Focus management useEffect(() => { if (isOpen && closeButtonRef.current) { closeButtonRef.current.focus(); From 68f29f36876291c009ee9d85f506b9714c638cee Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:47:25 -0700 Subject: [PATCH 12/36] =?UTF-8?q?=F0=9F=92=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- guides/assistant-widget.mdx | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/guides/assistant-widget.mdx b/guides/assistant-widget.mdx index e68226444..ea4df5ca5 100644 --- a/guides/assistant-widget.mdx +++ b/guides/assistant-widget.mdx @@ -118,12 +118,6 @@ export function AssistantWidget({ } }, [isOpen]); - // Helper function to convert hex to rgb - const hexToRgb = (hex) => { - const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); - return result ? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}` : '37, 99, 235'; - }; - const getPositionClasses = () => { const positions = { 'bottom-right': 'bottom-4 right-4', @@ -337,7 +331,7 @@ function App() {
    {/* Your existing app content */} - {/* Add the assistant widget - use your project ID */} + {/* Add the assistant widget. Use your project ID */}
    ); @@ -360,24 +354,21 @@ export default App; Check the following to confirm the widget is working: -- **Panel opens and closes** - The chat panel appears when you click the toggle button and closes when you click it again or press Escape -- **User messages display** - Your questions appear in the chat as user messages -- **Assistant responds** - The assistant responds with relevant information from your documentation -- **Source citations appear** - Sources are displayed below the response with clickable links -- **Conversation context persists** - Follow-up questions maintain the conversation history (thread ID is preserved across messages) -- **Browser console check** - Open developer tools and verify: +- **Panel opens and closes**: The chat panel appears when you click the toggle button and closes when you click it again or press Escape +- **User messages display**: Your questions appear in the chat as user messages +- **Assistant responds**: The assistant responds with relevant information from your documentation +- **Source citations appear**: Sources are displayed below the response +- **Conversation context persists**: Follow-up questions maintain the conversation history +- **Browser console**: Open developer tools and verify: - No authentication errors (look for 401 or 403 in Network tab) - The `x-thread-id` header is present in API responses - - No CORS errors if embedding on a different domain - -### Common test issues If the widget doesn't work as expected: -- **No responses** - Check that your project ID is correct and your Mintlify plan includes the assistant feature -- **Authentication fails** - Verify the `mint_dsc_` token is correctly set in environment variables and restart your dev server -- **Messages don't show sources** - Ensure `sendExtraMessageFields: true` is set in the useChat configuration -- **Context not maintained** - Verify the `threadId` is being extracted from response headers and passed to subsequent requests +- **No responses**: Check that your project ID is correct and your Mintlify plan includes the assistant feature. +- **Authentication fails**: Verify the `mint_dsc_` key is correctly set in environment variables and restart your dev server. +- **Messages don't show sources**: Ensure `sendExtraMessageFields: true` is set in the useChat configuration. +- **Context not maintained**: Verify the `threadId` is being extracted from response headers and passed to subsequent requests. ## Optional enhancements @@ -408,9 +399,9 @@ The `fp` (fingerprint) parameter is already included in the component to track u ### Customize styling -The component accepts several props to match your brand colors and text. You can customize the appearance without modifying the component code. +The component accepts several props to customize the appearance. -#### Change brand colors +#### Change colors Pass hex color codes to customize the button and accent colors: From b0b9e7ba435672e62ae2042fdfb6a85efc0017a0 Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:48:08 -0700 Subject: [PATCH 13/36] replace table with parafields --- guides/assistant-widget.mdx | 51 ++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/guides/assistant-widget.mdx b/guides/assistant-widget.mdx index ea4df5ca5..d393e075b 100644 --- a/guides/assistant-widget.mdx +++ b/guides/assistant-widget.mdx @@ -447,18 +447,45 @@ For more control, modify the Tailwind classes directly in the component. The com The `AssistantWidget` component accepts the following props: -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `project` | string | required | Your Mintlify project ID from the dashboard | -| `userId` | string | `'anonymous'` | Unique identifier for tracking user sessions | -| `buttonColor` | string | `'#2563eb'` | Hex color code for the toggle button and accents | -| `accentColor` | string | `'#2563eb'` | Hex color code for user message bubbles and interactive elements | -| `position` | string | `'bottom-right'` | Widget position: `'bottom-right'`, `'bottom-left'`, `'top-right'`, or `'top-left'` | -| `headerTitle` | string | `'Documentation Assistant'` | Title displayed in the chat panel header | -| `headerSubtitle` | string | `'Ask me anything about our docs'` | Subtitle shown below the title | -| `emptyStateText` | string | `'How can I help you today?'` | Message shown when no conversation history exists | -| `inputPlaceholder` | string | `'Ask a question...'` | Placeholder text for the message input field | -| `sendButtonText` | string | `'Send'` | Label for the send button | + + Your Mintlify project ID from the dashboard + + + + Unique identifier for tracking user sessions + + + + Hex color code for the toggle button and accents + + + + Hex color code for user message bubbles and interactive elements + + + + Widget position: `'bottom-right'`, `'bottom-left'`, `'top-right'`, or `'top-left'` + + + + Title displayed in the chat panel header + + + + Subtitle shown below the title + + + + Message shown when no conversation history exists + + + + Placeholder text for the message input field + + + + Label for the send button + ## Integrate with your design system From a784dc2cc3c067a10c1882dfc5bcf1cd355ae3a3 Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:48:47 -0700 Subject: [PATCH 14/36] concision --- guides/assistant-widget.mdx | 45 ------------------------------------- 1 file changed, 45 deletions(-) diff --git a/guides/assistant-widget.mdx b/guides/assistant-widget.mdx index d393e075b..1a0c583f5 100644 --- a/guides/assistant-widget.mdx +++ b/guides/assistant-widget.mdx @@ -487,51 +487,6 @@ The `AssistantWidget` component accepts the following props: Label for the send button -## Integrate with your design system - -To match your application's existing brand, you can pass theme values from your design system or theme provider: - -```jsx -// Using a theme context -import { useTheme } from './context/ThemeContext'; -import { AssistantWidget } from './components/AssistantWidget'; - -function App() { - const { colors } = useTheme(); - - return ( -
    - {/* Your app content */} - -
    - ); -} -``` - -Or with Tailwind config: - -```jsx -// Extract colors from Tailwind theme -import { AssistantWidget } from './components/AssistantWidget'; - -export default function App() { - const brandColor = '#3b82f6'; // From your Tailwind config - - return ( - - ); -} -``` - ## Troubleshooting ### Widget not appearing From d84450ec0876b91549f11be6d0e1e0d441d5418b Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:49:14 -0700 Subject: [PATCH 15/36] use accordion for troubleshooting --- guides/assistant-widget.mdx | 52 +++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/guides/assistant-widget.mdx b/guides/assistant-widget.mdx index 1a0c583f5..d59c1446c 100644 --- a/guides/assistant-widget.mdx +++ b/guides/assistant-widget.mdx @@ -489,28 +489,30 @@ The `AssistantWidget` component accepts the following props: ## Troubleshooting -### Widget not appearing - -- Verify the component is imported and rendered in your app. -- Check that Tailwind CSS or your styling solution is properly configured. -- Ensure the z-index values do not conflict with other fixed elements. - -### 401 authentication error - -- Verify your API token starts with `mint_dsc_`. -- Check the token is correctly set in your environment variables. -- Confirm you are using the public assistant API token, not the admin API key. -- Restart your development server after adding environment variables. - -### No responses from assistant - -- Verify your project ID is correct (find it in your dashboard). -- Check that your Mintlify plan includes the assistant feature. -- Confirm your documentation site is published and accessible. - -### Thread context not maintained - -- Verify the `threadId` is being extracted from the `x-thread-id` response header. -- Check that the custom `fetch` function is implemented correctly. -- Ensure the `threadId` state persists between messages. -- Confirm `streamProtocol: 'data'` is set in the useChat configuration. + + + - Verify the component is imported and rendered in your app. + - Check that Tailwind CSS or your styling solution is properly configured. + - Ensure the z-index values do not conflict with other fixed elements. + + + + - Verify your API token starts with `mint_dsc_`. + - Check the token is correctly set in your environment variables. + - Confirm you are using the public assistant API token, not the admin API key. + - Restart your development server after adding environment variables. + + + + - Verify your project ID is correct (find it in your dashboard). + - Check that your Mintlify plan includes the assistant feature. + - Confirm your documentation site is published and accessible. + + + + - Verify the `threadId` is being extracted from the `x-thread-id` response header. + - Check that the custom `fetch` function is implemented correctly. + - Ensure the `threadId` state persists between messages. + - Confirm `streamProtocol: 'data'` is set in the useChat configuration. + + From 0fb6ffe842d97daa486e39a8c27319003b0fe01f Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Thu, 23 Oct 2025 14:02:34 -0700 Subject: [PATCH 16/36] fix parameter names --- guides/assistant-widget.mdx | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/guides/assistant-widget.mdx b/guides/assistant-widget.mdx index d59c1446c..bfa9a568a 100644 --- a/guides/assistant-widget.mdx +++ b/guides/assistant-widget.mdx @@ -53,7 +53,7 @@ import { useState, useRef, useEffect } from 'react'; import ReactMarkdown from 'react-markdown'; export function AssistantWidget({ - project, + domain, userId = 'anonymous', // Customize any of these default values to match your style and tone buttonColor = '#2563eb', @@ -71,7 +71,7 @@ export function AssistantWidget({ const closeButtonRef = useRef(null); const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({ - api: `https://api-dsc.mintlify.com/v1/assistant/${project}/message`, + api: `https://api-dsc.mintlify.com/v1/assistant/${domain}/message`, headers: { 'Authorization': `Bearer ${process.env.REACT_APP_MINTLIFY_TOKEN}`, }, @@ -244,7 +244,7 @@ export function AssistantWidget({ {sources.map((source, idx) => (
  • - The environment variable name depends on your framework. Common formats include: - - Create React App: `REACT_APP_MINTLIFY_TOKEN` - - Next.js: `NEXT_PUBLIC_MINTLIFY_TOKEN` - - Vite: `VITE_MINTLIFY_TOKEN` + Create React App requires environment variables to be prefixed with `REACT_APP_`. Make sure to restart your development server after adding the variable. ### Add the widget to your app -Import and render the assistant widget in your application. Pass your Mintlify project ID. +Import and render the assistant widget in your application. Pass your Mintlify project domain (the subdomain from your documentation URL, e.g., "yourcompany" from "yourcompany.mintlify.app"). ```jsx App.jsx import { AssistantWidget } from './components/AssistantWidget'; @@ -331,8 +328,8 @@ function App() {
    {/* Your existing app content */} - {/* Add the assistant widget. Use your project ID */} - + {/* Add the assistant widget. Use your project domain */} +
    ); } @@ -407,7 +404,7 @@ Pass hex color codes to customize the button and accent colors: ```jsx @@ -419,7 +416,7 @@ Replace default text with your own: ```jsx ``` @@ -447,8 +444,8 @@ For more control, modify the Tailwind classes directly in the component. The com The `AssistantWidget` component accepts the following props: - - Your Mintlify project ID from the dashboard + + Your Mintlify project domain (subdomain from your documentation site URL) From a352ac3ed4fea2973f3739eb1afdce779fd82601 Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Thu, 23 Oct 2025 14:04:07 -0700 Subject: [PATCH 17/36] improve code sample --- guides/assistant-widget.mdx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/guides/assistant-widget.mdx b/guides/assistant-widget.mdx index bfa9a568a..664bf8ff6 100644 --- a/guides/assistant-widget.mdx +++ b/guides/assistant-widget.mdx @@ -162,6 +162,7 @@ export function AssistantWidget({ className={`fixed ${panelPosition[position]} w-full sm:w-96 max-h-[85vh] sm:h-[600px] bg-white rounded-lg shadow-2xl flex flex-col z-50 border border-gray-200 m-2 sm:m-0`} role="region" aria-label="Documentation assistant chat panel" + aria-modal="true" > {/* Header */}
    @@ -182,7 +183,7 @@ export function AssistantWidget({
    {/* Messages */} -
    +
    {messages.length === 0 && (

    {emptyStateText}

    @@ -208,11 +209,11 @@ export function AssistantWidget({
    ) : (

    {children}

    , - ul: ({ children }) =>
      {children}
    , - ol: ({ children }) =>
      {children}
    , + p: ({ children }) =>

    {children}

    , + ul: ({ children }) =>
      {children}
    , + ol: ({ children }) =>
      {children}
    , code: ({ inline, children }) => inline ? ( {children} @@ -284,14 +285,16 @@ export function AssistantWidget({ value={input} onChange={handleInputChange} placeholder={inputPlaceholder} - className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2" + className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-offset-0" disabled={isLoading} + aria-label="Message input" /> From 9f8c80c7706e974f35498b25f7dce07ff44fbb58 Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:39:13 -0700 Subject: [PATCH 18/36] remove deflection guide no way to test right now --- docs.json | 1 - guides/assistant-support-deflection.mdx | 318 ------------------------ 2 files changed, 319 deletions(-) delete mode 100644 guides/assistant-support-deflection.mdx diff --git a/docs.json b/docs.json index c045e4dea..400d09b6c 100644 --- a/docs.json +++ b/docs.json @@ -238,7 +238,6 @@ "guides/claude-code", "guides/cursor", "guides/geo", - "guides/assistant-support-deflection", "guides/windsurf" ] }, diff --git a/guides/assistant-support-deflection.mdx b/guides/assistant-support-deflection.mdx deleted file mode 100644 index 052d6b127..000000000 --- a/guides/assistant-support-deflection.mdx +++ /dev/null @@ -1,318 +0,0 @@ ---- -title: "Tutorial: Pre-fill support tickets with assistant context" -sidebarTitle: "Pre-fill support tickets" -description: "Reduce support burden by auto-populating tickets with assistant conversations and relevant documentation" ---- - -## What you will build - -An integration that captures assistant conversations when users need additional help and automatically creates support tickets with full context. The workflow: - -1. User interacts with the assistant on your documentation site -2. When the assistant can't fully answer their question, user clicks a "Contact Support" link -3. A support ticket is automatically created with: - - User's original questions - - Assistant's responses - - Links to relevant documentation - - Conversation thread ID for reference - -This reduces support burden by giving your team full context before they respond, and helps identify documentation gaps. - -## Prerequisites - -- Mintlify Pro or Custom plan -- Support platform account (this guide uses Zendesk as an example, but the approach works for any platform) -- Support platform API credentials -- Basic knowledge of your support platform's API - -### Get your support platform API credentials - -**For Zendesk:** -1. Navigate to Admin Center → Apps and integrations → APIs → Zendesk API -2. Enable token access -3. Click the **Add API token** button -4. Copy and save the token securely -5. Note your Zendesk subdomain (e.g., `yourcompany.zendesk.com`) - -For other platforms (Intercom, Freshdesk, etc.), consult their API documentation for authentication setup. - -## Understand the assistant data - -The Mintlify assistant provides several ways to access conversation data: - -### Dashboard export -Export conversation history as CSV from your [assistant dashboard](https://dashboard.mintlify.com/products/assistant). The export includes: -- User queries -- Assistant responses -- Source citations -- Timestamps -- Thread IDs - -### Built-in deflection -Configure a deflection email in your [assistant settings](https://dashboard.mintlify.com/products/assistant/settings). When the assistant cannot answer a question, it displays this email to users. - -### Custom integration -For programmatic access, you can build a custom contact form that captures the current conversation state and sends it to your support system. - -## Build the integration - -This guide shows how to build a serverless function that creates support tickets from assistant conversation data. - -### Create a contact support page - -Add a dedicated contact page to your documentation that captures assistant context when users need help. - -Create `contact-support.mdx` in your docs: - -```mdx contact-support.mdx ---- -title: "Contact Support" -description: "Get help from our support team" ---- - -If the assistant couldn't answer your question, our support team is here to help. - -
    - - - - - - - - - - - -
    - - -``` - -### Create the ticket creation endpoint - -Create a serverless function to handle ticket creation. This example uses Vercel, but you can adapt it for any platform. - -Create `api/create-ticket.js`: - -```javascript api/create-ticket.js -export default async function handler(req, res) { - if (req.method !== 'POST') { - return res.status(405).json({ error: 'Method not allowed' }); - } - - const { email, subject, description, conversation_context } = req.body; - - // Parse conversation context if provided - let contextNote = ''; - if (conversation_context) { - try { - const context = JSON.parse(conversation_context); - contextNote = formatConversationContext(context); - } catch (e) { - console.error('Failed to parse conversation context:', e); - } - } - - // Create Zendesk ticket - const ticketData = { - ticket: { - subject: subject, - comment: { - body: `${description}\n\n---\n\nAssistant Conversation History:\n${contextNote}` - }, - requester: { - email: email, - name: email.split('@')[0] - }, - priority: 'normal', - tags: ['assistant_escalation'] - } - }; - - try { - const response = await fetch( - `https://${process.env.ZENDESK_SUBDOMAIN}.zendesk.com/api/v2/tickets.json`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Basic ${Buffer.from( - `${process.env.ZENDESK_EMAIL}/token:${process.env.ZENDESK_API_TOKEN}` - ).toString('base64')}` - }, - body: JSON.stringify(ticketData) - } - ); - - if (!response.ok) { - throw new Error(`Zendesk API error: ${response.status}`); - } - - const result = await response.json(); - - return res.status(200).json({ - success: true, - ticket_id: result.ticket.id, - ticket_url: `https://${process.env.ZENDESK_SUBDOMAIN}.zendesk.com/agent/tickets/${result.ticket.id}` - }); - - } catch (error) { - console.error('Error creating ticket:', error); - return res.status(500).json({ - error: 'Failed to create support ticket', - message: error.message - }); - } -} - -function formatConversationContext(context) { - if (!context.messages || context.messages.length === 0) { - return 'No conversation history available.'; - } - - return context.messages - .map((msg, index) => { - const role = msg.role === 'user' ? 'User' : 'Assistant'; - const sources = msg.sources - ? `\nSources: ${msg.sources.map(s => s.url).join(', ')}` - : ''; - - return `${index + 1}. ${role}: ${msg.content}${sources}`; - }) - .join('\n\n'); -} -``` - -### Configure environment variables - -Add your support platform credentials to your environment: - -```bash .env -ZENDESK_SUBDOMAIN=yourcompany -ZENDESK_EMAIL=support@yourcompany.com -ZENDESK_API_TOKEN=your_api_token_here -``` - -For Vercel, add these in your project settings under Environment Variables. - -### Link from the assistant - -Update your assistant configuration to direct users to your contact page: - -1. Navigate to your [assistant settings](https://dashboard.mintlify.com/products/assistant/settings) -2. In the deflection email section, you can include a link to your contact page: "Visit [our support page](https://docs.yourcompany.com/contact-support) for help" - -## Test the integration - -1. Navigate to your documentation site -2. Open the assistant and ask a question it might not fully answer -3. Click the contact support link -4. Fill out the form and submit -5. Verify the ticket was created in your support system - -### Verify the integration - -Check the following to confirm the workflow is working: - -- Ticket appears in your support platform (Zendesk, etc.) -- Ticket includes the user's description -- Ticket includes formatted conversation context from the assistant -- Ticket is tagged appropriately for tracking -- Support team can access all relevant information - -## Optional enhancements - -### Add user identification - -If users are authenticated on your docs site, include their user ID for better tracking: - -```javascript -const ticketData = { - ticket: { - subject: subject, - comment: { body: description }, - requester: { - email: email, - name: req.body.user_name || email.split('@')[0] - }, - custom_fields: [ - { id: 'user_id_field_id', value: req.body.user_id } - ], - tags: ['assistant_escalation', 'authenticated_user'] - } -}; -``` - -### Analyze deflection patterns - -Use the assistant dashboard to export conversation data and analyze: -- What questions lead to support tickets? -- Which documentation pages are most frequently cited before escalation? -- What topics have low deflection rates (need better docs)? - -Export data from your [assistant dashboard](https://dashboard.mintlify.com/products/assistant) and analyze with your preferred tool. - -### Integrate with multiple platforms - -Adapt the serverless function to support multiple platforms based on configuration: - -```javascript -const PLATFORM_HANDLERS = { - zendesk: createZendeskTicket, - intercom: createIntercomConversation, - freshdesk: createFreshdeskTicket -}; - -const platform = process.env.SUPPORT_PLATFORM || 'zendesk'; -const handler = PLATFORM_HANDLERS[platform]; - -if (!handler) { - throw new Error(`Unsupported platform: ${platform}`); -} - -await handler(ticketData); -``` - -## Troubleshooting - -### Tickets not being created - -- Verify your API credentials are correct and have proper permissions -- Check that your support platform API is accessible from your serverless function -- Review serverless function logs for error messages -- Test API credentials with a simple curl command: - ```bash - curl https://yourcompany.zendesk.com/api/v2/tickets.json \ - -u email/token:api_token \ - -X GET - ``` - -### Conversation context is empty - -- Verify the assistant conversation is being stored in localStorage -- Check browser console for JavaScript errors -- Ensure the contact form is properly capturing the hidden field value -- Test with a fresh conversation in the assistant - -### Tickets missing information - -- Check the `formatConversationContext` function is parsing data correctly -- Verify the conversation context JSON structure matches your expectations -- Add console logging to debug the data being sent -- Review the support platform's field requirements - -### Authentication errors - -- Confirm your API token hasn't expired -- Verify the subdomain is correct (no `https://` prefix) -- Check that email/token format matches platform requirements (e.g., `email/token:api_token` for Zendesk) -- Ensure API token has permission to create tickets From 595fde4a69eacdb70ea8d8e26114111939d4b2af Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:33:27 -0700 Subject: [PATCH 19/36] improve UX of sample code --- guides/assistant-widget.mdx | 330 ++++++++++++++++++++++++------------ 1 file changed, 222 insertions(+), 108 deletions(-) diff --git a/guides/assistant-widget.mdx b/guides/assistant-widget.mdx index 664bf8ff6..9d18c4385 100644 --- a/guides/assistant-widget.mdx +++ b/guides/assistant-widget.mdx @@ -49,13 +49,30 @@ Create a new file `AssistantWidget.jsx` (or `.tsx` if using TypeScript) in your ```jsx AssistantWidget.jsx expandable import { useChat } from '@ai-sdk/react'; -import { useState, useRef, useEffect } from 'react'; +import { useState, useRef, useEffect, useCallback } from 'react'; import ReactMarkdown from 'react-markdown'; +// Helper function to extract sources from message parts +const extractSources = (parts, domain) => { + if (!parts) return []; + + return parts + .filter(part => + part.type === 'tool-invocation' && + part.toolInvocation?.state === 'result' && + part.toolInvocation?.toolName === 'search' && + Array.isArray(part.toolInvocation?.result) + ) + .flatMap(part => part.toolInvocation.result) + .map(source => ({ + url: source.url || `https://${domain}.mintlify.app/${source.path}`, + title: source.metadata?.title || source.path, + })); +}; + export function AssistantWidget({ domain, userId = 'anonymous', - // Customize any of these default values to match your style and tone buttonColor = '#2563eb', accentColor = '#2563eb', position = 'bottom-right', @@ -64,13 +81,21 @@ export function AssistantWidget({ emptyStateText = 'How can I help you today?', inputPlaceholder = 'Ask a question...', sendButtonText = 'Send', + persistThread = true, // Persist thread ID to localStorage }) { const [isOpen, setIsOpen] = useState(false); - const [threadId, setThreadId] = useState(null); + const [threadId, setThreadId] = useState(() => { + // Restore thread from localStorage if enabled + if (persistThread && typeof window !== 'undefined') { + return localStorage.getItem(`mintlify-thread-${domain}`) || null; + } + return null; + }); const messagesEndRef = useRef(null); - const closeButtonRef = useRef(null); + const inputRef = useRef(null); + const chatPanelRef = useRef(null); - const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({ + const { messages, input, handleInputChange, handleSubmit, isLoading, error } = useChat({ api: `https://api-dsc.mintlify.com/v1/assistant/${domain}/message`, headers: { 'Authorization': `Bearer ${process.env.REACT_APP_MINTLIFY_TOKEN}`, @@ -84,13 +109,20 @@ export function AssistantWidget({ fetch: async (url, options) => { const response = await fetch(url, options); const tempThreadId = response.headers.get('x-thread-id'); - if (tempThreadId) { + if (tempThreadId && tempThreadId !== threadId) { setThreadId(tempThreadId); + // Persist thread ID if enabled + if (persistThread && typeof window !== 'undefined') { + localStorage.setItem(`mintlify-thread-${domain}`, tempThreadId); + } } return response; }, streamProtocol: 'data', sendExtraMessageFields: true, + onError: (err) => { + console.error('Chat error:', err); + }, }); // Scroll to latest message @@ -98,23 +130,43 @@ export function AssistantWidget({ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); - // Handle keyboard shortcuts + // Handle keyboard shortcuts and focus trap useEffect(() => { const handleKeyDown = (e) => { + // Close on Escape if (e.key === 'Escape' && isOpen) { setIsOpen(false); + return; + } + + // Focus trap - keep Tab navigation within modal + if (e.key === 'Tab' && isOpen && chatPanelRef.current) { + const focusableElements = chatPanelRef.current.querySelectorAll( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ); + const firstElement = focusableElements[0]; + const lastElement = focusableElements[focusableElements.length - 1]; + + if (e.shiftKey && document.activeElement === firstElement) { + lastElement?.focus(); + e.preventDefault(); + } else if (!e.shiftKey && document.activeElement === lastElement) { + firstElement?.focus(); + e.preventDefault(); + } } }; + if (isOpen) { document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); } }, [isOpen]); - // Focus management + // Focus input when chat opens useEffect(() => { - if (isOpen && closeButtonRef.current) { - closeButtonRef.current.focus(); + if (isOpen && inputRef.current) { + setTimeout(() => inputRef.current?.focus(), 100); } }, [isOpen]); @@ -135,13 +187,26 @@ export function AssistantWidget({ 'top-left': 'top-20 left-4', }; + const clearConversation = useCallback(() => { + if (window.confirm('Clear this conversation? This cannot be undone.')) { + setThreadId(null); + if (persistThread && typeof window !== 'undefined') { + localStorage.removeItem(`mintlify-thread-${domain}`); + } + window.location.reload(); // Simple way to reset chat state + } + }, [domain, persistThread]); + return ( <> {/* Toggle button */} + )} +
    -
    + {/* Error banner */} + {error && ( +
    + + + +
    +

    Something went wrong

    +

    {error.message || 'Please try again'}

    +
    +
    + )} + {/* Messages */} -
    - {messages.length === 0 && ( -
    -

    {emptyStateText}

    +
    + {messages.length === 0 && !isLoading && ( +
    +
    + + + +
    +

    {emptyStateText}

    )} - {messages.map((message) => ( -
    + {messages.map((message) => { + const sources = message.role === 'assistant' ? extractSources(message.parts, domain) : []; + + return (
    - {message.role === 'user' ? ( -
    - {message.content} -
    - ) : ( -

    {children}

    , - ul: ({ children }) =>
      {children}
    , - ol: ({ children }) =>
      {children}
    , - code: ({ inline, children }) => - inline ? ( - {children} - ) : ( - {children} +
    + {message.role === 'user' ? ( +
    + {message.content} +
    + ) : ( +

    {children}

    , + ul: ({ children }) =>
      {children}
    , + ol: ({ children }) =>
      {children}
    , + li: ({ children }) =>
  • {children}
  • , + code: ({ inline, children }) => + inline ? ( + {children} + ) : ( + {children} + ), + a: ({ href, children }) => ( + + {children} + ), - }} - > - {message.content} - - )} - - {/* Display sources */} - {message.role === 'assistant' && message.parts && ( - (() => { - const sources = message.parts - .filter(part => - part.type === 'tool-invocation' && - part.toolInvocation?.state === 'result' && - part.toolInvocation?.toolName === 'search' && - Array.isArray(part.toolInvocation?.result) - ) - .flatMap(part => part.toolInvocation.result); - - return sources.length > 0 ? ( -
    -

    Sources:

    - -
    - ) : null; - })() - )} + }} + > + {message.content || + message.parts + ?.filter(p => p.type === 'text') + .map(p => p.text) + .join('') || ''} + + )} + + {/* Display sources */} + {sources.length > 0 && ( +
    +

    Sources:

    + +
    + )} + - - ))} + ); + })} {isLoading && (
    -
    +
    @@ -278,25 +380,37 @@ export function AssistantWidget({
    {/* Input form */} -
    -
    + +
    From dfb124ed985781bb6e816a0e00594e71d388666a Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:36:04 -0700 Subject: [PATCH 20/36] tweak code sample --- guides/assistant-widget.mdx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/guides/assistant-widget.mdx b/guides/assistant-widget.mdx index 9d18c4385..ecd151022 100644 --- a/guides/assistant-widget.mdx +++ b/guides/assistant-widget.mdx @@ -49,12 +49,15 @@ Create a new file `AssistantWidget.jsx` (or `.tsx` if using TypeScript) in your ```jsx AssistantWidget.jsx expandable import { useChat } from '@ai-sdk/react'; -import { useState, useRef, useEffect, useCallback } from 'react'; +import { useState, useRef, useEffect, useCallback, useMemo } from 'react'; import ReactMarkdown from 'react-markdown'; -// Helper function to extract sources from message parts +/** + * Extracts source citations from message parts returned by the assistant API. + * The Vercel data stream protocol returns search results as tool invocation parts. + */ const extractSources = (parts, domain) => { - if (!parts) return []; + if (!parts || !Array.isArray(parts)) return []; return parts .filter(part => @@ -506,7 +509,7 @@ The `fp` (fingerprint) parameter is already included in the component to track u ```jsx ``` From 0fda0ce0d1c2998dcc1dc0bef28aa8bce6d75209 Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Mon, 27 Oct 2025 13:44:57 -0700 Subject: [PATCH 21/36] simplify sample code --- guides/assistant-widget.mdx | 556 +++++++++++------------------------- 1 file changed, 166 insertions(+), 390 deletions(-) diff --git a/guides/assistant-widget.mdx b/guides/assistant-widget.mdx index ecd151022..2f7c41b80 100644 --- a/guides/assistant-widget.mdx +++ b/guides/assistant-widget.mdx @@ -49,114 +49,42 @@ Create a new file `AssistantWidget.jsx` (or `.tsx` if using TypeScript) in your ```jsx AssistantWidget.jsx expandable import { useChat } from '@ai-sdk/react'; -import { useState, useRef, useEffect, useCallback, useMemo } from 'react'; +import { useState, useRef, useEffect } from 'react'; import ReactMarkdown from 'react-markdown'; -/** - * Extracts source citations from message parts returned by the assistant API. - * The Vercel data stream protocol returns search results as tool invocation parts. - */ -const extractSources = (parts, domain) => { - if (!parts || !Array.isArray(parts)) return []; - - return parts - .filter(part => - part.type === 'tool-invocation' && - part.toolInvocation?.state === 'result' && - part.toolInvocation?.toolName === 'search' && - Array.isArray(part.toolInvocation?.result) - ) - .flatMap(part => part.toolInvocation.result) - .map(source => ({ - url: source.url || `https://${domain}.mintlify.app/${source.path}`, - title: source.metadata?.title || source.path, - })); -}; - -export function AssistantWidget({ - domain, - userId = 'anonymous', - buttonColor = '#2563eb', - accentColor = '#2563eb', - position = 'bottom-right', - headerTitle = 'Documentation assistant', - headerSubtitle = 'Ask me anything about our docs', - emptyStateText = 'How can I help you today?', - inputPlaceholder = 'Ask a question...', - sendButtonText = 'Send', - persistThread = true, // Persist thread ID to localStorage -}) { +export function AssistantWidget({ domain }) { const [isOpen, setIsOpen] = useState(false); - const [threadId, setThreadId] = useState(() => { - // Restore thread from localStorage if enabled - if (persistThread && typeof window !== 'undefined') { - return localStorage.getItem(`mintlify-thread-${domain}`) || null; - } - return null; - }); const messagesEndRef = useRef(null); const inputRef = useRef(null); - const chatPanelRef = useRef(null); - const { messages, input, handleInputChange, handleSubmit, isLoading, error } = useChat({ + const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({ api: `https://api-dsc.mintlify.com/v1/assistant/${domain}/message`, headers: { 'Authorization': `Bearer ${process.env.REACT_APP_MINTLIFY_TOKEN}`, }, body: { - fp: userId, - threadId: threadId, retrievalPageSize: 5, - filter: null, - }, - fetch: async (url, options) => { - const response = await fetch(url, options); - const tempThreadId = response.headers.get('x-thread-id'); - if (tempThreadId && tempThreadId !== threadId) { - setThreadId(tempThreadId); - // Persist thread ID if enabled - if (persistThread && typeof window !== 'undefined') { - localStorage.setItem(`mintlify-thread-${domain}`, tempThreadId); - } - } - return response; }, streamProtocol: 'data', - sendExtraMessageFields: true, - onError: (err) => { - console.error('Chat error:', err); - }, }); - // Scroll to latest message + // Auto-scroll to latest message useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); - // Handle keyboard shortcuts and focus trap + // Focus input when chat opens + useEffect(() => { + if (isOpen && inputRef.current) { + inputRef.current.focus(); + } + }, [isOpen]); + + // Close on Escape key useEffect(() => { const handleKeyDown = (e) => { - // Close on Escape if (e.key === 'Escape' && isOpen) { setIsOpen(false); - return; - } - - // Focus trap - keep Tab navigation within modal - if (e.key === 'Tab' && isOpen && chatPanelRef.current) { - const focusableElements = chatPanelRef.current.querySelectorAll( - 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' - ); - const firstElement = focusableElements[0]; - const lastElement = focusableElements[focusableElements.length - 1]; - - if (e.shiftKey && document.activeElement === firstElement) { - lastElement?.focus(); - e.preventDefault(); - } else if (!e.shiftKey && document.activeElement === lastElement) { - firstElement?.focus(); - e.preventDefault(); - } } }; @@ -166,51 +94,13 @@ export function AssistantWidget({ } }, [isOpen]); - // Focus input when chat opens - useEffect(() => { - if (isOpen && inputRef.current) { - setTimeout(() => inputRef.current?.focus(), 100); - } - }, [isOpen]); - - const getPositionClasses = () => { - const positions = { - 'bottom-right': 'bottom-4 right-4', - 'bottom-left': 'bottom-4 left-4', - 'top-right': 'top-4 right-4', - 'top-left': 'top-4 left-4', - }; - return positions[position] || positions['bottom-right']; - }; - - const panelPosition = { - 'bottom-right': 'bottom-20 right-4', - 'bottom-left': 'bottom-20 left-4', - 'top-right': 'top-20 right-4', - 'top-left': 'top-20 left-4', - }; - - const clearConversation = useCallback(() => { - if (window.confirm('Clear this conversation? This cannot be undone.')) { - setThreadId(null); - if (persistThread && typeof window !== 'undefined') { - localStorage.removeItem(`mintlify-thread-${domain}`); - } - window.location.reload(); // Simple way to reset chat state - } - }, [domain, persistThread]); - return ( <> {/* Toggle button */} - )} - +
    +

    Documentation assistant

    +

    Ask me anything

    -
    - - {/* Error banner */} - {error && ( -
    - - +
    -

    Something went wrong

    -

    {error.message || 'Please try again'}

    -
    -
    - )} + +
    {/* Messages */} -
    - {messages.length === 0 && !isLoading && ( -
    -
    - - - -
    -

    {emptyStateText}

    -
    - )} - - {messages.map((message) => { - const sources = message.role === 'assistant' ? extractSources(message.parts, domain) : []; - - return ( +
    + {messages.map((message) => ( +
    -
    - {message.role === 'user' ? ( -
    - {message.content} -
    - ) : ( -

    {children}

    , - ul: ({ children }) =>
      {children}
    , - ol: ({ children }) =>
      {children}
    , - li: ({ children }) =>
  • {children}
  • , - code: ({ inline, children }) => - inline ? ( - {children} - ) : ( - {children} - ), - a: ({ href, children }) => ( - - {children} - - ), - }} - > - {message.content || - message.parts - ?.filter(p => p.type === 'text') - .map(p => p.text) - .join('') || ''} -
    - )} - - {/* Display sources */} - {sources.length > 0 && ( -
    -

    Sources:

    - -
    - )} -
    + {message.role === 'user' ? ( +
    + {message.content} +
    + ) : ( + + {message.content} + + )}
    - ); - })} +
    + ))} {isLoading && (
    -
    -
    -
    -
    -
    -
    -
    +
    Loading...
    )} +
    @@ -390,30 +178,16 @@ export function AssistantWidget({ type="text" value={input} onChange={handleInputChange} - placeholder={inputPlaceholder} - className="flex-1 px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-offset-0 focus:border-transparent disabled:bg-gray-50 disabled:text-gray-500" - style={{ '--tw-ring-color': accentColor }} + placeholder="Ask a question..." + className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm" disabled={isLoading} - aria-label="Message input" />
    @@ -438,7 +212,7 @@ REACT_APP_MINTLIFY_TOKEN=mint_dsc_your_token_here ### Add the widget to your app -Import and render the assistant widget in your application. Pass your Mintlify project domain (the subdomain from your documentation URL, e.g., "yourcompany" from "yourcompany.mintlify.app"). +Import and render the component in your application. Pass your Mintlify project domain (the subdomain from your documentation URL, e.g., "yourcompany" from "yourcompany.mintlify.app"). ```jsx App.jsx import { AssistantWidget } from './components/AssistantWidget'; @@ -447,8 +221,6 @@ function App() { return (
    {/* Your existing app content */} - - {/* Add the assistant widget. Use your project domain */}
    ); @@ -474,135 +246,145 @@ Check the following to confirm the widget is working: - **Panel opens and closes**: The chat panel appears when you click the toggle button and closes when you click it again or press Escape - **User messages display**: Your questions appear in the chat as user messages - **Assistant responds**: The assistant responds with relevant information from your documentation -- **Source citations appear**: Sources are displayed below the response -- **Conversation context persists**: Follow-up questions maintain the conversation history -- **Browser console**: Open developer tools and verify: - - No authentication errors (look for 401 or 403 in Network tab) - - The `x-thread-id` header is present in API responses - -If the widget doesn't work as expected: +- **Markdown renders**: Markdown formatting (bold, code blocks, links) appears correctly in responses +- **Loading state shows**: You see "Loading..." text while waiting for a response +- **Browser console**: Open developer tools and check the Network tab: + - No authentication errors (look for 401 or 403 errors) + - API requests go to `https://api-dsc.mintlify.com/v1/assistant/{domain}/message` -- **No responses**: Check that your project ID is correct and your Mintlify plan includes the assistant feature. -- **Authentication fails**: Verify the `mint_dsc_` key is correctly set in environment variables and restart your dev server. -- **Messages don't show sources**: Ensure `sendExtraMessageFields: true` is set in the useChat configuration. -- **Context not maintained**: Verify the `threadId` is being extracted from response headers and passed to subsequent requests. +## Customization ideas -## Optional enhancements +This example shows the simplest implementation. Here are common ways to extend it: -### Filter by documentation section +### Add color customization -Limit the assistant's responses to specific sections of your documentation by adding a `filter` parameter to the request body: +Accept color props and use them for theming: ```jsx -body: { - fp: userId, - threadId: threadId, - filter: { - path: '/api-reference' // Only search the API reference section - } -}, +export function AssistantWidget({ domain, buttonColor = '#2563eb' }) { + // ... rest of component + return ( + <> + - {/* Chat panel */} {isOpen && ( -
    - {/* Header */} -
    -
    -

    Documentation assistant

    -

    Ask me anything

    -
    - +
    +
    +

    Documentation assistant

    - {/* Messages */}
    {messages.map((message) => ( -
    -
    +
    +
    {message.role === 'user' ? ( -
    - {message.content} -
    +
    {message.content}
    ) : ( - - {message.content} - + {message.content} )}
    ))} - {isLoading && ( -
    -
    Loading...
    -
    - )} - -
    + {isLoading &&
    Loading...
    }
    - {/* Input form */} -
    +
    Send From 9f0893e780ae993c43225061d6657e4bd315442f Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Fri, 31 Oct 2025 16:37:03 -0700 Subject: [PATCH 30/36] update the verify steps --- guides/assistant-embed.mdx | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/guides/assistant-embed.mdx b/guides/assistant-embed.mdx index 90a757f65..3d8443885 100644 --- a/guides/assistant-embed.mdx +++ b/guides/assistant-embed.mdx @@ -159,24 +159,22 @@ export default App; 1. Start your development server: ```bash - npm start + npm run dev ``` 2. Open your application in a browser. -3. Click the assistant widget in the bottom-right corner. -4. Ask a question that requires searching your documentation. +3. Click the "Ask" button in the bottom-right corner. +4. Ask a question about your documentation. ### Verify the assistant -TODO: + Check the following to confirm the widget is working: -- **Panel opens and closes**: The chat panel appears when you click the toggle button and closes when you click it again or press Escape -- **User messages display**: Your questions appear in the chat as user messages +- **Button opens the widget**: Click "Ask" and the chat panel appears +- **Button closes the widget**: Click "Close" and the panel disappears +- **User messages display**: Your questions appear in the chat on the right side - **Assistant responds**: The assistant responds with relevant information from your documentation - **Markdown renders**: Markdown formatting (bold, code blocks, links) appears correctly in responses - **Loading state shows**: You see "Loading..." text while waiting for a response -- **Browser console**: Open developer tools and check the Network tab: - - No authentication errors (look for 401 or 403 errors) - - API requests go to `https://api-dsc.mintlify.com/v1/assistant/{domain}/message` ## Customization ideas @@ -273,4 +271,11 @@ const extractSources = (parts) => { - Confirm your documentation site is published and accessible. - Verify the assistant API token is correctly set in your environment variables. + + + Open developer tools and check the Network tab to troubleshoot: + - Look for 401 or 403 authentication errors + - Verify API requests are reaching `https://api-dsc.mintlify.com/v1/assistant/{domain}/message` + - Check the response status and error messages + From 38a4c31536a93450ff073964f9e13ab4ce76720f Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Fri, 31 Oct 2025 17:10:43 -0700 Subject: [PATCH 31/36] Update assistant-embed.mdx --- guides/assistant-embed.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/guides/assistant-embed.mdx b/guides/assistant-embed.mdx index 3d8443885..663b813a2 100644 --- a/guides/assistant-embed.mdx +++ b/guides/assistant-embed.mdx @@ -60,6 +60,7 @@ export function AssistantWidget({ domain }) { 'Authorization': `Bearer ${process.env.VITE_MINTLIFY_TOKEN}`, }, body: { + fp: 'anonymous', retrievalPageSize: 5, }, streamProtocol: 'data', From 80eb859b9a5df056964bc41d2910f615a895885a Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Tue, 4 Nov 2025 16:10:17 -0800 Subject: [PATCH 32/36] add missing option --- guides/assistant-embed.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/guides/assistant-embed.mdx b/guides/assistant-embed.mdx index 663b813a2..b85a4cbbe 100644 --- a/guides/assistant-embed.mdx +++ b/guides/assistant-embed.mdx @@ -64,6 +64,7 @@ export function AssistantWidget({ domain }) { retrievalPageSize: 5, }, streamProtocol: 'data', + sendExtraMessageFields: true, }); return ( From 0ee197b09059ff2331f763920c205e3676b7fec9 Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Tue, 4 Nov 2025 16:28:55 -0800 Subject: [PATCH 33/36] review intro --- guides/assistant-embed.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/assistant-embed.mdx b/guides/assistant-embed.mdx index b85a4cbbe..b00acddd8 100644 --- a/guides/assistant-embed.mdx +++ b/guides/assistant-embed.mdx @@ -11,7 +11,7 @@ A reusable React component that embeds the [assistant](/ai/assistant) directly i - A floating widget that opens a chat panel when clicked - Real-time streaming responses based on information from your documentation -Users can get help with your product without leaving your application. +Users can use the widget to get help with your product without leaving your application. ## Prerequisites From 209700ca538c79dabbec990c300c74475d7d4153 Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Tue, 4 Nov 2025 16:32:56 -0800 Subject: [PATCH 34/36] add preview button --- guides/assistant-embed.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/guides/assistant-embed.mdx b/guides/assistant-embed.mdx index b00acddd8..cce17d22b 100644 --- a/guides/assistant-embed.mdx +++ b/guides/assistant-embed.mdx @@ -4,6 +4,8 @@ sidebarTitle: "Build an in-app assistant" description: "Embed the assistant in your application to answer questions with information from your documentation" --- +import { PreviewButton } from "/snippets/previewbutton.jsx" + ## What you will build A reusable React component that embeds the [assistant](/ai/assistant) directly in your application. The component provides: @@ -13,6 +15,8 @@ A reusable React component that embeds the [assistant](/ai/assistant) directly i Users can use the widget to get help with your product without leaving your application. +Clone the repository to start working with a complete example locally. + ## Prerequisites - [Mintlify Pro or Custom plan](https://mintlify.com/pricing) From 3f9273f347d0aa86cc314ff1a7e92e8edc4f9678 Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Tue, 4 Nov 2025 17:03:32 -0800 Subject: [PATCH 35/36] use steps --- guides/assistant-embed.mdx | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/guides/assistant-embed.mdx b/guides/assistant-embed.mdx index cce17d22b..4fe0b0b7d 100644 --- a/guides/assistant-embed.mdx +++ b/guides/assistant-embed.mdx @@ -29,7 +29,7 @@ Users can use the widget to get help with your product without leaving your appl ### Get your assistant API key 1. Navigate to the [API keys](https://dashboard.mintlify.com/settings/organization/api-keys) page in your dashboard. -2. Select **Create Assistant API Key**. +2. Click **Create Assistant API Key**. 3. Copy the assistant API key (starts with `mint_dsc_`) and save it securely. @@ -38,19 +38,20 @@ Users can use the widget to get help with your product without leaving your appl ## Build the widget -### Install dependencies + + Install the AI SDK for React and a Markdown renderer: ```bash npm install ai @ai-sdk/react react-markdown ``` - -### Create the assistant component + + Create a new file `AssistantWidget.jsx` (or `.tsx` if using TypeScript) in your components directory: -```jsx AssistantWidget.jsx +```jsx AssistantWidget.jsx expandable import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; import ReactMarkdown from 'react-markdown'; @@ -128,7 +129,8 @@ export function AssistantWidget({ domain }) { } ``` -### Configure environment variables + + Add your assistant API token to your `.env` file in your project root: @@ -142,7 +144,8 @@ Restart your development server to apply the changes. Vite requires environment variables to be prefixed with `VITE_`. Use the appropriate prefix for your framework. -### Add the widget to your app + + Import and render the component in your application. Pass your Mintlify project `domain` (the end of your dashboard URL. For example, `domain-name` from `https://dashboard.mintlify.com/org-name/domain-name`). @@ -161,6 +164,9 @@ function App() { export default App; ``` + + + ## Test the widget 1. Start your development server: From cbcc7b8345b5953a58d107289ae62fb81a8e5064 Mon Sep 17 00:00:00 2001 From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com> Date: Tue, 4 Nov 2025 17:07:16 -0800 Subject: [PATCH 36/36] move verify section --- guides/assistant-embed.mdx | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/guides/assistant-embed.mdx b/guides/assistant-embed.mdx index 4fe0b0b7d..e964bff5a 100644 --- a/guides/assistant-embed.mdx +++ b/guides/assistant-embed.mdx @@ -165,33 +165,21 @@ export default App; ``` - - -## Test the widget - -1. Start your development server: + + 1. Start your development server: ```bash npm run dev ``` -2. Open your application in a browser. -3. Click the "Ask" button in the bottom-right corner. -4. Ask a question about your documentation. - -### Verify the assistant - -Check the following to confirm the widget is working: - -- **Button opens the widget**: Click "Ask" and the chat panel appears -- **Button closes the widget**: Click "Close" and the panel disappears -- **User messages display**: Your questions appear in the chat on the right side -- **Assistant responds**: The assistant responds with relevant information from your documentation -- **Markdown renders**: Markdown formatting (bold, code blocks, links) appears correctly in responses -- **Loading state shows**: You see "Loading..." text while waiting for a response + 2. Open your application in a browser. + 3. Click the "Ask" button in the bottom-right corner. + 4. Ask a question about your documentation. + + ## Customization ideas -This example shows a simple implementation of the assistant API. Here are common ways to customize the widget: -TODO: +This example shows a simple implementation of the assistant API. Here are some ways to begin customizing the widget. + ### Add color customization Accept color props and use them for theming: