跳转到主要内容

你将构建的内容

一个可复用的小部件,可将AI 助手直接嵌入到你的应用中。该小部件提供:
  • 浮动按钮,点击即可打开聊天面板
  • 基于你的文档信息的实时流式回复
  • 支持 Markdown 的消息渲染
用户无需离开你的应用即可通过该小部件获取产品帮助。
演示打开助手小部件,用户输入“如何开始?”,随后助手作出回应。

先决条件

  • Mintlify Pro 或 Custom 计划
  • 你的 domain 名称,它位于控制台 URL 的末尾。例如,如果你的控制台 URL 是 https://dashboard.mintlify.com/org-name/domain-name,你的 domain 名称就是 domain-name
  • 一个 AI 助手 API key
  • 已安装 Node.js v18 或更高版本及 npm
  • 基础的 React 知识

获取你的 AI 助手 API key

  1. 在控制台中前往 API keys 页面。
  2. 点击 Create Assistant API Key
  3. 复制 AI 助手 API key(以 mint_dsc_ 开头)并妥善保存。
AI 助手 API key 是一个可在前端代码中使用的公共令牌。使用该令牌的调用将计入你套餐的消息配额,并可能产生超额费用。

设置示例

最快的上手方式是克隆示例存储库,并按需进行自定义。
1

克隆存储库

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

配置你的项目

打开 src/config.js,并填入你的 Mintlify 项目信息:
src/config.js
export const ASSISTANT_CONFIG = {
  domain: 'your-domain',
  docsURL: 'https://yourdocs.mintlify.app',
};
将以下内容替换为你的实际信息:
  • your-domain 替换为你在控制台 URL 末尾看到的 Mintlify 项目 domain。
  • https://yourdocs.mintlify.app 替换为你的文档实际 URL。
3

添加你的 API 令牌

在项目根目录创建一个 .env 文件:
.env
VITE_MINTLIFY_TOKEN=mint_dsc_your_token_here
mint_dsc_your_token_here 替换为你的 AI 助手 API key。
4

启动开发服务器

npm run dev
在浏览器中打开你的应用,点击 Ask 按钮以打开 AI 助手挂件。

项目结构

此示例采用组件化架构。
src/
├── App.css                 # 应用样式
├── App.jsx                 # 渲染小部件的主应用组件
├── config.js               # 配置(domain 和 docsURL)
├── index.css               # 全局样式
├── main.jsx                # 入口文件
├── utils.js                # 用于解析建议和提取源的辅助函数
└── components/
    ├── AssistantWidget.jsx # 包含聊天状态和 API 逻辑的主小部件组件
    └── Message.jsx         # 用于渲染用户和 AI 助手消息的单条消息组件
关键文件:
  • src/App.jsx:主应用组件。演示如何导入并使用 AssistantWidget 组件。
  • src/config.js:集中配置。在此文件中更新你的 domain 和文档 URL。
  • src/components/AssistantWidget.jsx:主要的挂件组件。管理打开/关闭状态、聊天消息和 API 调用。
  • src/utils.js:包含用于解析 AI 助手响应格式并提取来源的实用函数。
  • src/components/Message.jsx:渲染单条消息,支持 Markdown 和建议链接。

自定义思路与示例

来源引注

从 AI 助手的回复中提取并显示出处:
const extractSources = (parts) => {
  return parts
    ?.filter(p => p.type === 'tool-invocation' && p.toolInvocation?.toolName === 'search')
    .flatMap(p => p.toolInvocation?.result || [])
    .map(source => ({
      url: source.url || source.path,
      title: source.metadata?.title || source.path,
    })) || [];
};

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

跟踪会话线程 ID

存储线程 ID,以在不同会话中保留对话历史:
import { useState, useEffect } from 'react';

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

  useEffect(() => {
    // 从 localStorage 中获取已保存的会话 ID
    const saved = localStorage.getItem('assistant-thread-id');
    if (saved) {
      setThreadId(saved);
    }
  }, []);

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

  // ... 组件其余部分
}

添加键盘快捷键

允许用户通过键盘快捷键打开组件并提交消息:
useEffect(() => {
  const handleKeyDown = (e) => {
    // Cmd/Ctrl + Shift + I 切换 AI 助手
    if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'I') {
      e.preventDefault();
      setIsOpen((prev) => !prev);
    }

    // Enter(当 AI 助手获得焦点时)提交
    if (e.key === 'Enter' && !e.shiftKey && document.activeElement.id === 'assistant-input') {
      e.preventDefault();
      handleSubmit();
    }
  };

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