> ## Documentation Index
> Fetch the complete documentation index at: https://www.mintlify.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Mintlify 小组件

> 安装并配置 Mintlify 小组件,在任意网站或 Web 应用中嵌入基于你的内容训练的 AI 助手。

export const AssistantWidgetPlayground = ({children, CodeBlockComponent}) => {
  const EXAMPLE_WIDGET_ID = "YOUR_WIDGET_ID";
  const EMBED_URL = "https://cdn.jsdelivr.net/npm/@mintlify/assistant-widget@0.0/dist/browser/embed.js";
  const PREVIEW_READY_MESSAGE = "mintlify-assistant-playground:ready";
  const PREVIEW_UPDATE_MESSAGE = "mintlify-assistant-playground:update";
  const PREVIEW_STATE_MESSAGE = "mintlify-assistant-playground:state";
  const SUPPORT_EMAIL = "hi@mintlify.com";
  const STARTER_QUESTIONS = ["How do I get started with Mintlify?", "How do I customize my docs?", "How do I deploy my docs?"];
  const VARIANT_OPTIONS = [{
    value: "widget",
    label: "Widget"
  }, {
    value: "modal",
    label: "Modal"
  }, {
    value: "panel",
    label: "Panel"
  }];
  const THEME_OPTIONS = [{
    value: "system",
    label: "System"
  }, {
    value: "light",
    label: "Light"
  }, {
    value: "dark",
    label: "Dark"
  }];
  const SIDE_OPTIONS = [{
    value: "top",
    label: "Top"
  }, {
    value: "bottom",
    label: "Bottom"
  }, {
    value: "left",
    label: "Left"
  }, {
    value: "right",
    label: "Right"
  }, {
    value: "inline-start",
    label: "Inline start"
  }, {
    value: "inline-end",
    label: "Inline end"
  }];
  const ALIGN_OPTIONS = [{
    value: "start",
    label: "Start"
  }, {
    value: "center",
    label: "Center"
  }, {
    value: "end",
    label: "End"
  }];
  const INSTALL_OPTIONS = [{
    value: "html",
    label: "HTML"
  }, {
    value: "next",
    label: "Next.js"
  }];
  const [installTarget, setInstallTarget] = useState("html");
  const [variant, setVariant] = useState("widget");
  const [theme, setTheme] = useState("system");
  const [accent, setAccent] = useState("#16a34a");
  const [radius, setRadius] = useState(18);
  const [side, setSide] = useState("bottom");
  const [align, setAlign] = useState("end");
  const [trackEvents, setTrackEvents] = useState(false);
  const [reportErrors, setReportErrors] = useState(false);
  const [previewHostReady, setPreviewHostReady] = useState(false);
  const [previewUrl, setPreviewUrl] = useState(null);
  const [previewStatus, setPreviewStatus] = useState("loading");
  const previewRef = useRef(null);
  const previewHostRef = useRef(null);
  useEffect(() => {
    const pageMatch = window.location.pathname.replace(/\/$/, "").match(/^(.*?)(\/[a-z]{2}(?:-[A-Za-z]{2,4})?)?\/assistant\/widget$/);
    const basePath = pageMatch?.[1] ?? "";
    const locale = pageMatch?.[2] ?? "";
    const mode = document.documentElement.classList.contains("dark") ? "dark" : "light";
    setPreviewUrl(`${basePath}/_minimal${locale}/assistant/widget-preview?mode=${mode}`);
  }, []);
  useEffect(() => {
    const removeRootWidget = () => {
      const rootWidget = document.querySelector("body > mintlify-assistant");
      if (!rootWidget) return false;
      const destroyPromise = window.MintlifyAssistant?.destroy();
      void destroyPromise?.catch(() => {});
      rootWidget.remove();
      return true;
    };
    const rootWidgetObserver = new MutationObserver(removeRootWidget);
    removeRootWidget();
    rootWidgetObserver.observe(document.body, {
      childList: true
    });
    return () => rootWidgetObserver.disconnect();
  }, []);
  useEffect(() => {
    const host = previewHostRef.current;
    if (!host) return undefined;
    const markReady = height => {
      if (height > 0) setPreviewHostReady(true);
    };
    markReady(host.getBoundingClientRect().height);
    if (typeof ResizeObserver === "undefined") {
      setPreviewHostReady(true);
      return undefined;
    }
    const observer = new ResizeObserver(entries => {
      markReady(entries[0]?.contentRect.height ?? 0);
    });
    observer.observe(host);
    return () => observer.disconnect();
  }, []);
  const classNames = (...classes) => classes.filter(Boolean).join(" ");
  const renderSegmentedControl = ({ariaLabel, onChange, options, threeColumns = false, value}) => <div role="group" aria-label={ariaLabel} className="grid gap-1 rounded-lg bg-gray-100 p-1 dark:bg-white/10" style={{
    gridTemplateColumns: `repeat(${threeColumns ? 3 : options.length}, minmax(0, 1fr))`
  }}>
      {options.map(option => <button key={option.value} type="button" aria-pressed={value === option.value} onClick={() => onChange(option.value)} className={classNames("min-h-8 rounded-md px-2 py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40", value === option.value ? "bg-white text-gray-950 shadow-sm dark:bg-white/15 dark:text-white" : "text-gray-600 hover:text-gray-950 dark:text-gray-400 dark:hover:text-white")}>
          {option.label}
        </button>)}
    </div>;
  const renderSelectField = ({label, onChange, options, value}) => <label className="flex min-w-0 flex-col text-sm font-medium text-gray-700 dark:text-gray-300">
      <span className="mb-2">{label}</span>
      <select value={value} onChange={event => onChange(event.target.value)} className="h-10 w-full rounded-xl border border-gray-950/10 bg-transparent px-3 text-sm font-normal text-gray-950 outline-none transition-shadow focus-visible:ring-2 focus-visible:ring-primary/30 dark:border-white/10 dark:text-white">
        {options.map(option => <option key={option.value} value={option.value}>
            {option.label}
          </option>)}
      </select>
    </label>;
  const renderToggleRow = ({checked, description, label, onChange}) => <label className="flex cursor-pointer items-center justify-between gap-5 py-3">
      <span className="min-w-0">
        <span className="block text-sm font-medium text-gray-950 dark:text-white">
          {label}
        </span>
        <span className="block text-sm text-gray-600 dark:text-gray-400">
          {description}
        </span>
      </span>
      <span className="relative inline-flex shrink-0 rounded-full focus-within:ring-2 focus-within:ring-primary/40">
        <input type="checkbox" role="switch" checked={checked} onChange={event => onChange(event.target.checked)} className="sr-only" />
        <span aria-hidden="true" className={classNames("h-5 w-9 rounded-full transition-colors", checked ? "bg-primary" : "bg-gray-200 dark:bg-white/15")} />
        <span aria-hidden="true" className={classNames("pointer-events-none absolute left-0.5 top-0.5 size-4 rounded-full bg-white shadow-sm transition-all", checked && "ml-4")} />
      </span>
    </label>;
  const appearance = useMemo(() => ({
    variant,
    theme,
    accent,
    radius: `${radius}px`,
    side,
    align
  }), [accent, align, radius, side, theme, variant]);
  const updatePreview = useCallback(() => {
    const previewWindow = previewRef.current?.contentWindow;
    if (!previewWindow) return;
    const liveTheme = appearance.theme === "system" ? document.documentElement.classList.contains("dark") ? "dark" : "light" : appearance.theme;
    previewWindow.postMessage({
      type: PREVIEW_UPDATE_MESSAGE,
      trackEvents,
      reportErrors,
      appearance: {
        ...appearance,
        theme: liveTheme
      }
    }, window.location.origin);
  }, [appearance, reportErrors, trackEvents]);
  useEffect(() => {
    const handlePreviewMessage = event => {
      if (event.source !== previewRef.current?.contentWindow) return;
      if (event.data?.type === PREVIEW_READY_MESSAGE) {
        updatePreview();
        return;
      }
      if (event.data?.type === PREVIEW_STATE_MESSAGE) {
        setPreviewStatus(event.data.state === "error" ? "error" : "ready");
      }
    };
    const themeObserver = new MutationObserver(updatePreview);
    window.addEventListener("message", handlePreviewMessage);
    themeObserver.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    updatePreview();
    return () => {
      window.removeEventListener("message", handlePreviewMessage);
      themeObserver.disconnect();
    };
  }, [updatePreview]);
  useEffect(() => {
    if (!previewHostReady || !previewUrl || previewStatus !== "loading") {
      return undefined;
    }
    const timeout = window.setTimeout(() => {
      setPreviewStatus(status => status === "loading" ? "error" : status);
    }, 20000);
    return () => window.clearTimeout(timeout);
  }, [previewHostReady, previewStatus, previewUrl]);
  const configLines = ["{", `  id: '${EXAMPLE_WIDGET_ID}',`, `  supportEmail: '${SUPPORT_EMAIL}',`, "  starterQuestions: [", ...STARTER_QUESTIONS.map(question => `    '${question}',`), "  ],", "  appearance: {", `    variant: '${variant}',`, `    theme: '${theme}',`, `    accent: '${accent}',`, `    radius: '${radius}px',`, `    side: '${side}',`, `    align: '${align}',`, "  },"];
  if (trackEvents || reportErrors) {
    configLines.push("  hooks: {");
    if (trackEvents) {
      configLines.push("    event(event) {", "      console.log('Assistant event', event);", "    },");
    }
    if (reportErrors) {
      configLines.push("    error(error) {", "      console.error('Assistant error', error.code, error);", "    },");
    }
    configLines.push("  },");
  }
  configLines.push("}");
  const configCode = configLines.join("\n");
  const indentedConfig = configCode.split("\n").join("\n  ");
  const htmlCode = `<script
  type="module"
  src="${EMBED_URL}"
></script>
<script type="module">
  await window.MintlifyAssistant.init(${indentedConfig});
</script>`;
  const nextCode = `'use client';

import Script from 'next/script';

const ASSISTANT_CONFIG = ${configCode};

export const AssistantWidget = () => (
  <Script
    type="module"
    src="${EMBED_URL}"
    onReady={() => {
      void window.MintlifyAssistant.init(ASSISTANT_CONFIG);
    }}
  />
);`;
  const installCode = installTarget === "html" ? htmlCode : nextCode;
  return <div className="my-6 grid gap-8" data-assistant-playground-layout="">
      <div className="min-w-0">
        <div className="not-prose overflow-hidden rounded-xl border border-gray-950/10 dark:border-white/10">
          <div className="px-5 py-4">
            <div className="text-sm font-medium text-gray-950 dark:text-white">
              Widget playground
            </div>
            <p className="mt-0.5 text-sm text-gray-600 dark:text-gray-400">
              Changes apply to the widget preview.
            </p>
          </div>

          <div className="border-t border-gray-950/10 px-5 py-5 dark:border-white/10">
            <div className="space-y-6">
              <fieldset>
                <legend className="mb-2 text-sm font-medium text-gray-950 dark:text-white">
                  Variants
                </legend>
                {renderSegmentedControl({
    ariaLabel: "Variants",
    value: variant,
    options: VARIANT_OPTIONS,
    onChange: setVariant
  })}
              </fieldset>

              <div className="grid gap-4 sm:grid-cols-2">
                {renderSelectField({
    label: "Theme",
    value: theme,
    options: THEME_OPTIONS,
    onChange: setTheme
  })}

                <label className="flex min-w-0 flex-col text-sm font-medium text-gray-700 dark:text-gray-300">
                  <span className="mb-2">Accent</span>
                  <span className="flex h-10 items-center gap-2 rounded-xl border border-gray-950/10 px-2 dark:border-white/10">
                    <input type="color" value={accent} onChange={event => setAccent(event.target.value)} aria-label="Accent color" className="h-7 w-8 cursor-pointer rounded border-0 bg-transparent p-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40" />
                    <span className="font-mono text-xs font-normal text-gray-600 dark:text-gray-400">
                      {accent}
                    </span>
                  </span>
                </label>
              </div>

              <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                <span className="mb-2 flex items-center justify-between">
                  Corner radius
                  <output className="font-mono text-xs font-normal text-gray-500 dark:text-gray-400">
                    {radius}px
                  </output>
                </span>
                <input type="range" min="0" max="32" step="2" value={radius} onChange={event => setRadius(Number.parseInt(event.target.value))} className="block w-full accent-primary" />
              </label>

              <div className="grid gap-4 sm:grid-cols-2">
                <fieldset>
                  <legend className="mb-2 text-sm font-medium text-gray-950 dark:text-white">
                    Trigger side
                  </legend>
                  {renderSegmentedControl({
    ariaLabel: "Trigger side",
    threeColumns: true,
    value: side,
    options: SIDE_OPTIONS,
    onChange: setSide
  })}
                </fieldset>

                <fieldset>
                  <legend className="mb-2 text-sm font-medium text-gray-950 dark:text-white">
                    Trigger alignment
                  </legend>
                  {renderSegmentedControl({
    ariaLabel: "Trigger alignment",
    value: align,
    options: ALIGN_OPTIONS,
    onChange: setAlign
  })}
                </fieldset>
              </div>

              <fieldset>
                <legend className="text-sm font-medium text-gray-950 dark:text-white">
                  Hooks
                </legend>
                <div className="mt-1 divide-y divide-gray-950/10 dark:divide-white/10">
                  {renderToggleRow({
    label: "Lifecycle events",
    description: "Observe open, close, ask, update, and navigation events.",
    checked: trackEvents,
    onChange: setTrackEvents
  })}
                  {renderToggleRow({
    label: "Structured errors",
    description: "Receive stable error codes and retry metadata.",
    checked: reportErrors,
    onChange: setReportErrors
  })}
                </div>
              </fieldset>
            </div>
          </div>

          <div className="border-t border-gray-950/10 p-5 dark:border-white/10">
            <div className="mb-3 flex flex-wrap items-center justify-between gap-3">
              <div>
                <div className="text-sm font-medium text-gray-950 dark:text-white">
                  Install
                </div>
                <div className="mt-0.5 text-sm text-gray-600 dark:text-gray-400">
                  Copy the generated setup for your stack.
                </div>
              </div>
              {renderSegmentedControl({
    ariaLabel: "Installation target",
    value: installTarget,
    options: INSTALL_OPTIONS,
    onChange: setInstallTarget
  })}
            </div>

            <CodeBlockComponent language="jsx" filename={installTarget === "html" ? "index.html" : "assistant-widget.jsx"} wrap>
              {installCode}
            </CodeBlockComponent>
          </div>
        </div>
        {children ? <div className="mt-8">{children}</div> : null}
      </div>

      <aside className="not-prose" data-assistant-preview="">
        <div ref={previewHostRef} className="relative flex h-[42rem] min-h-0 flex-col overflow-hidden rounded-xl border border-gray-950/10 bg-transparent dark:border-white/10 lg:sticky lg:top-20 lg:h-[calc(100vh-6rem)]" data-assistant-preview-card="">
          {previewHostReady && previewUrl ? <iframe ref={previewRef} title="Live Assistant Widget preview" src={previewUrl} onLoad={updatePreview} scrolling="no" className="min-h-0 w-full flex-1 border-0 bg-transparent [color-scheme:light_dark] dark:[color-scheme:dark]" /> : null}
          {previewStatus !== "ready" ? <div aria-live="polite" role="status" className="pointer-events-none absolute inset-0 z-10 flex items-center justify-center gap-3 px-6 text-center text-sm text-gray-600 dark:text-gray-400">
              {previewStatus === "loading" ? <span aria-hidden="true" className="size-[18px] shrink-0 animate-spin rounded-full border-[1.5px] border-gray-300 border-t-gray-600 motion-reduce:animate-none dark:border-gray-600 dark:border-t-gray-300" /> : null}
              <span>
                {previewStatus === "loading" ? "Loading assistant preview..." : "The live preview could not load. It requires a deployed docs site and the browser console may have details."}
              </span>
            </div> : null}
        </div>
      </aside>
    </div>;
};

export const WidgetCodeBlock = ({children, ...props}) => <CodeBlock {...props}>{children}</CodeBlock>;

[助手](/docs/zh/assistant)可回答关于你 Mintlify 站点的问题。若要在其他站点或 Web 应用中嵌入相同能力,请使用小组件。借助小组件,你可以在产品仪表板、营销站点、支持门户或其他位置为用户提供基于你内容训练的 AI 聊天服务。

通过一段托管脚本,即可将小组件添加到任何网站或 Web 应用。小组件自带触发器,并在封闭的 Shadow DOM 内渲染,可防止你的应用样式影响小组件。

浏览器端唯一必需的选项是公共小组件 ID。启用状态、允许的来源、附件以及机器人防护均可在仪表板中管理。可在浏览器配置中设置针对该嵌入的起始问题和支持邮箱。

<div id="prerequisites">
  ## 前置条件
</div>

* [Pro 或 Enterprise 套餐](https://mintlify.com/pricing?ref=assistant)。小组件与助手共用同一额度。

<div id="enable-the-widget">
  ## 启用小组件
</div>

1. 前往你部署的 [Widget](https://app.mintlify.com/settings/deployment/widget) 页面。
2. 启用小组件。
3. 添加嵌入小组件的允许来源。
4. 复制小组件 ID。

<div id="install-and-configure">
  ## 安装与配置
</div>

使用交互式面板配置小组件的展示方式、视觉选项和观察者钩子。每次更改选项时,安装代码块都会随之更新。

<Info>
  请将生成代码中的 `YOUR_WIDGET_ID` 替换为你仪表板 [Widget](https://app.mintlify.com/settings/deployment/widget) 页面中的小组件 ID。
</Info>

将生成的代码添加到站点后,重新加载页面。确认触发器已显示,然后点击它并发送一个测试问题,以验证小组件是否已连接。

<AssistantWidgetPlayground CodeBlockComponent={WidgetCodeBlock}>
  <Warning>
    Module 脚本会延迟加载并按文档顺序执行。当你使用 HTML 安装小组件时,请将托管加载器保持在初始化代码块之前,否则小组件将无法挂载。
  </Warning>

  <div id="open-on-initialization">
    ## 初始化时自动打开
  </div>

  将 `defaultOpen` 设为 `true`,可在首次挂载后立即打开小组件:

  ```js theme={null}
  await window.MintlifyAssistant.init({
    id: "YOUR_WIDGET_ID",
    defaultOpen: true,
  });
  ```

  `defaultOpen` 默认为 `false`,且仅在首次初始化时生效。若访客关闭小组件后,再次以相同的小组件 ID 和 API 端点调用 `init()` 并不会重新打开它。初始化之后请使用 `open()` 和 `close()` 来控制它。

  <div id="use-a-custom-trigger">
    ## 使用自定义触发器
  </div>

  在调用其他方法前请先等待 `init()` 完成。你可以保留内置触发器,也可以从应用中任意按钮打开已配置的展示形式。

  ```js theme={null}
  await window.MintlifyAssistant.init({
    id: "YOUR_WIDGET_ID",
    supportEmail: "hi@mintlify.com",
    starterQuestions: [
      "How do I get started with Mintlify?",
      "How do I customize my docs?",
      "How do I deploy my docs?",
    ],
  });

  document.querySelector("#help-button").addEventListener("click", () => {
    void window.MintlifyAssistant.open({
      source: "help-button",
      focus: true,
    });
  });
  ```

  若要打开小组件并立即发送问题,请调用 `ask()`:

  ```js theme={null}
  await window.MintlifyAssistant.ask("How do I authenticate?", {
    source: "authentication-guide",
    open: true,
    focus: true,
  });
  ```

  事件元数据和请求中会包含 `source` 值,便于你区分内置交互与自定义入口。

  <div id="update-a-mounted-widget">
    ## 更新已挂载的小组件
  </div>

  使用 `update()` 可在不清除当前会话的情况下更改外观、文案、支持邮箱、起始问题或钩子。只有传入的字段会被更改。

  ```js theme={null}
  await window.MintlifyAssistant.update({
    appearance: {
      theme: "dark",
      accent: "#7c3aed",
    },
    labels: {
      title: "Docs copilot",
      trigger: "Ask docs",
    },
    supportEmail: "support@example.com",
    starterQuestions: [
      "How do I get started?",
      "How do I manage my account?",
    ],
  });
  ```

  传入 `null` 可将某个字段或分组恢复为默认值、移除支持邮箱,或将起始问题列表恢复为空:

  ```js theme={null}
  await window.MintlifyAssistant.update({
    appearance: {
      accent: null,
    },
    supportEmail: null,
    starterQuestions: null,
    hooks: null,
  });
  ```

  更改 `identity` 会开启新的会话。更改小组件 ID 或 API 端点则需要先调用 `destroy()`,再执行新的 `init()`。

  你可以在初始化时提供 `supportEmail` 和 `starterQuestions`,也可以稍后通过 `update()` 更改。这些值仅作用于当前嵌入,不会继承自你的 Mintlify 仪表板。

  <div id="configuration-reference">
    ## 配置参考
  </div>

  <div id="assistantconfig">
    ### `AssistantConfig`
  </div>

  将该对象传入 `init()`。

  | Option             | Type                                          | Description               |
  | ------------------ | --------------------------------------------- | ------------------------- |
  | `id`               | string                                        | 来自 Mintlify 仪表板的公共小组件 ID。 |
  | `endpoint`         | string                                        | 覆盖托管的小组件 API 端点。          |
  | `identity`         | string                                        | 签名的终端用户身份令牌。匿名访客可省略。      |
  | `nonce`            | string                                        | 复制到小组件所创建资源上的 CSP nonce。  |
  | `defaultOpen`      | boolean                                       | 在首次初始化时打开小组件。默认为 `false`。 |
  | `appearance`       | [`AssistantAppearance`](#assistantappearance) | 视觉和展示相关的覆盖设置。             |
  | `labels`           | [`AssistantLabels`](#assistantlabels)         | 面向客户的文案覆盖设置。              |
  | `supportEmail`     | string                                        | 设置该嵌入在小组件工具栏中显示的支持邮箱。     |
  | `starterQuestions` | string\[]                                     | 为该嵌入设置最多 **三** 条空状态提示。    |
  | `hooks`            | [`AssistantHooks`](#assistanthooks)           | 事件和错误观察者。                 |

  <div id="assistantappearance">
    ### `AssistantAppearance`
  </div>

  | Option                     | Values                                                         | Description                    |
  | -------------------------- | -------------------------------------------------------------- | ------------------------------ |
  | `variant`                  | `widget`, `modal`, `panel`                                     | 控制助手以锚定弹层、居中对话框还是响应式侧边面板的形式打开。 |
  | `theme`                    | `light`, `dark`, `system`                                      | 设置小组件的配色方案。默认为 `system`。       |
  | `accent`                   | CSS color                                                      | 设置主要控件的颜色。                     |
  | `radius`                   | CSS border radius                                              | 设置面板圆角,例如 `18px`。              |
  | `font`                     | CSS font family                                                | 使用你的应用已加载的字体。默认使用内置的 Inter。    |
  | `side`                     | `top`, `bottom`, `left`, `right`, `inline-start`, `inline-end` | 将内置触发器定位到屏幕边缘。                 |
  | `align`                    | `start`, `center`, `end`                                       | 让触发器沿所选边缘对齐。                   |
  | `dismissOnInteractOutside` | boolean                                                        | 控制在助手外部的指针或焦点交互是否将其关闭。         |
  | `logo`                     | URL or `{ light, dark }`                                       | 替换默认的 Mintlify 标识。             |
  | `zIndex`                   | number                                                         | 更改小组件宿主的堆叠顺序。                  |

  不支持任意 CSS 和中性色板覆盖。封闭的 Shadow DOM 可同时保护你的应用与小组件,防止跨站样式回归。

  <div id="assistantlabels">
    ### `AssistantLabels`
  </div>

  | Option        | Values                     | Description                    |
  | ------------- | -------------------------- | ------------------------------ |
  | `title`       | string or `null`           | 设置面板标题。默认为 `Assistant`。        |
  | `trigger`     | string or `null`           | 设置紧凑型小组件和面板触发器的文字。             |
  | `placeholder` | string or `null`           | 设置输入框和模态触发器的占位提示。              |
  | `disclaimer`  | string, `false`, or `null` | 设置空状态免责声明。传入 `false` 可将其隐藏。    |
  | `suggestions` | string or `null`           | 设置起始问题上方的标题。默认为 `Suggestions`。 |

  <div id="assistanthooks">
    ### `AssistantHooks`
  </div>

  ```js theme={null}
  hooks: {
    event(event) {
      console.log(event.type, event.actor, event.source);
    },
    error(error) {
      console.error(error.code, error.retryable, error.status);
    },
  }
  ```

  `event` 钩子会接收 `init`、`open`、`close`、`ask`、`update`、`reset`、`navigate` 和 `destroy` 的生命周期与交互元数据。事件不包含问题文本、身份、会话或 CAPTCHA 令牌。

  `error` 钩子会接收稳定的 `code`、一个 `retryable` 布尔值以及可选的 HTTP `status`。任一钩子抛出的异常都不会中断小组件。

  <div id="assistantopenoptions">
    ### `AssistantOpenOptions`
  </div>

  将该可选对象传入 `open()`。

  | Option   | Type    | Description            |
  | -------- | ------- | ---------------------- |
  | `source` | string  | 由客户定义的归因信息,会包含在事件和请求中。 |
  | `focus`  | boolean | 打开后聚焦输入框。默认为 `true`。   |

  <div id="assistantaskoptions">
    ### `AssistantAskOptions`
  </div>

  将该可选对象作为问题字符串之后的参数传入 `ask()`。

  | Option   | Type    | Description            |
  | -------- | ------- | ---------------------- |
  | `source` | string  | 由客户定义的归因信息,会包含在事件和请求中。 |
  | `open`   | boolean | 在发送前打开面板。默认为 `true`。   |
  | `focus`  | boolean | 打开时聚焦输入框。默认为 `true`。   |

  <div id="assistantupdate">
    ### `AssistantUpdate`
  </div>

  将该对象传入 `update()`。所有字段均为可选,`null` 表示恢复默认值。

  | Option             | Type                                                    | Description                 |
  | ------------------ | ------------------------------------------------------- | --------------------------- |
  | `identity`         | string or `null`                                        | 更改签名身份并开启新的会话。              |
  | `appearance`       | [`AssistantAppearance`](#assistantappearance) or `null` | 深度合并外观设置。                   |
  | `labels`           | [`AssistantLabels`](#assistantlabels) or `null`         | 深度合并面向客户的文案。                |
  | `supportEmail`     | string or `null`                                        | 更改支持邮箱。传入 `null` 可移除。       |
  | `starterQuestions` | string\[] or `null`                                     | 更改最多三条提示。传入 `null` 可恢复为空列表。 |
  | `hooks`            | [`AssistantHooks`](#assistanthooks) or `null`           | 深度合并事件和错误观察者。               |

  <div id="browser-api">
    ## 浏览器 API
  </div>

  | Method                   | Parameter types                                       | Description                   |
  | ------------------------ | ----------------------------------------------------- | ----------------------------- |
  | `init(config)`           | [`AssistantConfig`](#assistantconfig)                 | 加载并挂载小组件。这是所有其他方法的就绪 Promise。 |
  | `open(options)`          | [`AssistantOpenOptions`](#assistantopenoptions)       | 打开已配置的展示形式。                   |
  | `close()`                | —                                                     | 关闭小组件。                        |
  | `ask(question, options)` | string, [`AssistantAskOptions`](#assistantaskoptions) | 按需打开小组件并发送一个问题。               |
  | `update(config)`         | [`AssistantUpdate`](#assistantupdate)                 | 深度合并可变的身份、外观、文案和观察者设置。        |
  | `reset()`                | —                                                     | 开启一次全新的会话。                    |
  | `destroy()`              | —                                                     | 移除小组件并释放其浏览器资源。               |

  会话快照始终保留在小组件内部。每个方法都会 resolve 为 `void`。

  <div id="content-security-policy">
    ## 内容安全策略
  </div>

  如果你的站点使用了内容安全策略(CSP),请为所启用的小组件功能允许以下来源:

  | Directive                                    | Source                              | Required for    |
  | -------------------------------------------- | ----------------------------------- | --------------- |
  | `script-src`                                 | `https://cdn.jsdelivr.net`          | 小组件加载器与运行时      |
  | `connect-src`                                | `https://api.mintlify.com`          | 小组件 API         |
  | `style-src`                                  | `https://cdn.jsdelivr.net`          | 小组件样式表          |
  | `font-src`                                   | `https://cdn.jsdelivr.net`          | 可选的内置 Inter 字体  |
  | `script-src`, `connect-src`, and `frame-src` | `https://challenges.cloudflare.com` | Turnstile 机器人防护 |
  | `script-src`                                 | `https://js.hcaptcha.com`           | hCaptcha 机器人防护  |
  | `connect-src` and `frame-src`                | `https://*.hcaptcha.com`            | hCaptcha 机器人防护  |

  即使采用严格的 `script-src` 策略,也必须同时授权加载器和初始化脚本。向 `init()` 传入 `nonce` 时,仅会将其传播到小组件在初始化之后创建的资源。
</AssistantWidgetPlayground>


## Related topics

- [Front](/docs/zh/integrations/support/front.md)
- [概览](/docs/zh/components/index.md)
- [React](/docs/zh/customize/react-components.md)
