框架与平台

Minaya 是一个单独的脚本标签,因此无需任何框架集成即可在任意网站上运行。 以下是各平台的具体接入方式——代码片段该放在哪里,以及需要注意的事项。

哪种方式适合你

平台方式
HTML / 任意网站加载 widget.js
Next.js加载 widget.js
React(Vite / CRA)加载 widget.js
Angular加载 widget.js
WordPress加载 widget.js
Shopify加载 widget.js
React NativeWebView
Android(Kotlin)WebView 或 REST API
iOS(Swift)WebView 或 REST API

所有浏览器端平台加载的都是同一个 widget.js。原生移动端没有 DOM, 因此这些平台要么在 WebView 中承载小组件,要么调用公开的 REST API, 并在各自的 UI 中渲染对话内容。

HTML / 任意网站

在结束的 body 标签前添加一个脚本标签即可。

粘贴代码片段

在每个需要显示小组件的页面中,将其添加到 </body> 之前。

<script
  src="https://widget.minaya.ai/widget.js"
  data-site-key="your-site-key"
  data-api-url="https://api.minaya.ai"
  async
></script>

Next.js

使用 next/script,使小组件在页面可交互后再加载。

添加到根布局

App Router —— app/layout.tsx。lazyOnload 策略可以让小组件不占用关键渲染路径。

import Script from "next/script";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          src="https://widget.minaya.ai/widget.js"
          data-site-key="your-site-key"
          data-api-url="https://api.minaya.ai"
          strategy="lazyOnload"
        />
      </body>
    </html>
  );
}

Pages Router 的替代方案

如果你使用的是 pages/_app.tsx,改为在那里渲染同样的 <Script> 组件即可。

提示。不要把脚本放进 next/head——Next 会剥离其中的脚本标签。请使用 next/script。

React(Vite / CRA)

将标签添加到 index.html,或通过一个 hook 注入。

最简单——index.html

将代码片段粘贴到 index.html 中的 </body> 之前。无需其他操作。

<script
  src="https://widget.minaya.ai/widget.js"
  data-site-key="your-site-key"
  data-api-url="https://api.minaya.ai"
  async
></script>

或从组件中挂载

适用于只想在特定路由上显示小组件的场景。清理函数会在组件卸载时移除该标签。

import { useEffect } from "react";

function MinayaWidget() {
  useEffect(() => {
    const script = document.createElement("script");
    script.src = "https://widget.minaya.ai/widget.js";
    script.dataset.siteKey = "your-site-key";
    script.dataset.apiUrl = "https://api.minaya.ai";
    script.async = true;
    document.body.appendChild(script);

    return () => {
      script.remove();
    };
  }, []);

  return null;
}

Angular

将标签添加到 index.html,或通过组件注入。

最简单——src/index.html

将代码片段粘贴到 </body> 之前。

<script
  src="https://widget.minaya.ai/widget.js"
  data-site-key="your-site-key"
  data-api-url="https://api.minaya.ai"
  async
></script>

或从组件中加载

使用 Renderer2 可以让这段 DOM 操作与服务端渲染兼容。

import { Component, OnInit, Renderer2, Inject } from "@angular/core";
import { DOCUMENT } from "@angular/common";

@Component({ selector: "app-minaya", template: "" })
export class MinayaComponent implements OnInit {
  constructor(
    private renderer: Renderer2,
    @Inject(DOCUMENT) private document: Document,
  ) {}

  ngOnInit(): void {
    const script = this.renderer.createElement("script");
    script.src = "https://widget.minaya.ai/widget.js";
    script.setAttribute("data-site-key", "your-site-key");
    script.setAttribute("data-api-url", "https://api.minaya.ai");
    script.async = true;
    this.renderer.appendChild(this.document.body, script);
  }
}

WordPress

粘贴到主题页脚,或使用页眉/页脚脚本插件。

方式 A——使用插件(推荐)

安装 WPCode 或 Insert Headers and Footers,打开其设置,将代码片段粘贴到 Footer 框中。此方式不受主题更新影响。

<script
  src="https://widget.minaya.ai/widget.js"
  data-site-key="your-site-key"
  data-api-url="https://api.minaya.ai"
  async
></script>

方式 B——编辑主题

外观 → 主题文件编辑器 → footer.php。粘贴到 </body> 之前。请使用子主题,否则更新会覆盖你的改动。

验证

在隐私窗口中打开你的网站,启动按钮会在一两秒内出现在右下角。

提示。兼容 Elementor、Divi 及其他页面构建工具——它是纯 JavaScript,并非某个插件的专属集成。

Shopify

添加到 theme.liquid 中结束的 body 标签之前。

打开主题编辑器

在线商店 → 主题 → …… → 编辑代码。

编辑 theme.liquid

在 Layout 下打开 theme.liquid,将代码片段粘贴到 </body> 之前,然后保存。

<script
  src="https://widget.minaya.ai/widget.js"
  data-site-key="your-site-key"
  data-api-url="https://api.minaya.ai"
  async
></script>

允许你的商店域名

在 Minaya 中,将你的 myshopify.com 域名和自定义域名都添加到 Allowed origins。

提示。除非使用 Shopify Plus,否则 Shopify 会在结账页面屏蔽第三方脚本,因此小组件在结账过程中不会显示。

React Native

React Native 中没有 DOM,因此需要在 WebView 中承载小组件。

安装 react-native-webview

执行 npm install react-native-webview,随后在 iOS 上运行 pod install。

npm install react-native-webview

在 WebView 中渲染小组件

小组件会自动打开,这样访客就不需要在 WebView 内寻找启动按钮。

import { WebView } from "react-native-webview";

const html = `<!doctype html>
<html>
  <head><meta name="viewport" content="width=device-width, initial-scale=1" /></head>
  <body>
    <script
      src="https://widget.minaya.ai/widget.js"
      data-site-key="your-site-key"
      data-api-url="https://api.minaya.ai"
    ></script>
  </body>
</html>`;

function SupportChat() {
  return (
    <WebView
      originWhitelist={["*"]}
      source={{ html, baseUrl: "https://api.minaya.ai" }}
    />
  );
}

开启自动打开

在“小组件外观”中开启“自动打开”,使面板在 WebView 内已经处于打开状态。

提示。必须设置 baseUrl,否则请求来源会是 null,站点密钥校验将拒绝该请求。请将该来源添加到 Allowed origins。

Android(Kotlin)

可以选择在 WebView 中承载小组件,或者调用 REST API 并使用你自己的 UI。

方式 A——WebView

最快的方式。必须启用 JavaScript,否则小组件无法运行。

val webView = findViewById<WebView>(R.id.webView)
webView.settings.javaScriptEnabled = true

val html = """
  <!doctype html>
  <html>
    <head><meta name="viewport" content="width=device-width, initial-scale=1" /></head>
    <body>
      <script src="https://widget.minaya.ai/widget.js"
              data-site-key="your-site-key"
              data-api-url="https://api.minaya.ai"></script>
    </body>
  </html>
""".trimIndent()

webView.loadDataWithBaseURL("https://api.minaya.ai", html, "text/html", "UTF-8", null)

方式 B——基于 REST API 的原生 UI

在 Compose 或 Views 中构建聊天界面,并直接调用 API。保留返回的 sessionId 并在后续请求中回传,以延续同一段对话。

data class ChatRequest(val message: String, val sessionId: String? = null)
data class ChatResponse(val sessionId: String, val reply: String)



// Retrofit setup
val api = Retrofit.Builder()
    .baseUrl("https://api.minaya.ai/")
    .addConverterFactory(GsonConverterFactory.create())
    .build()
    .create(MinayaApi::class.java)

val response = api.chat("your-site-key", ChatRequest("What are your hours?"))

提示。你的站点密钥会打包进应用二进制文件中,因此应将其视为公开信息。Allowed origins 无法保护原生流量——请依赖各计划的消息数量限制。

iOS(Swift)

可以选择在 WKWebView 中承载小组件,或者调用 REST API 并使用你自己的 UI。

方式 A——WKWebView

加载与你网站相同的小组件。

import WebKit

let webView = WKWebView(frame: view.bounds)
view.addSubview(webView)

let html = """
<!doctype html>
<html>
  <head><meta name="viewport" content="width=device-width, initial-scale=1" /></head>
  <body>
    <script src="https://widget.minaya.ai/widget.js"
            data-site-key="your-site-key"
            data-api-url="https://api.minaya.ai"></script>
  </body>
</html>
"""

webView.loadHTMLString(html, baseURL: URL(string: "https://api.minaya.ai"))

方式 B——基于 REST API 的原生 UI

在 SwiftUI 中构建聊天界面,并直接调用 API。存储 sessionId 以便在多条消息之间延续对话。

struct ChatResponse: Decodable {
    let sessionId: String
    let reply: String
}

func sendMessage(_ text: String, sessionId: String?) async throws -> ChatResponse {
    var request = URLRequest(url: URL(string: "https://api.minaya.ai/public/chat")!)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.setValue("your-site-key", forHTTPHeaderField: "X-Site-Key")

    var body: [String: Any] = ["message": text]
    if let sessionId { body["sessionId"] = sessionId }
    request.httpBody = try JSONSerialization.data(withJSONObject: body)

    let (data, _) = try await URLSession.shared.data(for: request)
    return try JSONDecoder().decode(ChatResponse.self, from: data)
}

提示。你的站点密钥会打包进应用二进制文件中,因此应将其视为公开信息。Allowed origins 无法保护原生流量——请依赖各计划的消息数量限制。

故障排查

小组件没有出现

打开浏览器控制台。缺少 data-site-key data-api-url 会记录一条明确的错误信息。如果控制台没有报错, 请检查脚本标签是否位于 <body> 内,以及是否被内容安全策略(CSP)拦截。

每个请求都返回 403

发起请求的来源不在小组件的允许列表中。请在Allowed origins下 添加确切的来源——包括协议部分。没有设置 baseUrl 的 WebView 会发送 null 来源,永远无法匹配。

页面上出现两个小组件

脚本被引入了两次。Minaya 会忽略第二份副本,因此你应当只看到一个启动按钮; 如果看到两个,其中一个来自不同的聊天产品。