This is an automated email from the ASF dual-hosted git repository.

yu199195 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/shenyu-website.git


The following commit(s) were added to refs/heads/main by this push:
     new c415d6f70e4 feat: integrate kapa.ai "Ask AI" widget (#1131)
c415d6f70e4 is described below

commit c415d6f70e462a1baa87b5027863bd9b4d2a7e55
Author: Liming Deng <[email protected]>
AuthorDate: Fri Sep 11 11:43:37 2026 +0800

    feat: integrate kapa.ai "Ask AI" widget (#1131)
    
    Add a local Docusaurus plugin that injects the kapa.ai website widget
    with locale-aware UI copy (en/zh), dark-mode sync with the Docusaurus
    theme toggle, Cmd/Ctrl+K shortcut, and analytics cookies/fingerprinting
    disabled. Styled with the ShenYu brand orange and site favicon.
    
    Add static/.htaccess to override the default ASF Content-Security-Policy
    so the widget script, proxy, and bot-protection domains are allowed.
---
 docusaurus.config.js         |  51 ++++++++++++++++++
 plugins/kapa-widget/index.js | 121 +++++++++++++++++++++++++++++++++++++++++++
 static/.htaccess             |  18 +++++++
 3 files changed, 190 insertions(+)

diff --git a/docusaurus.config.js b/docusaurus.config.js
index 14d666d484b..5f441db55c1 100755
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -317,5 +317,56 @@ module.exports = {
       },
     ],
     "plugin-image-zoom",
+    [
+      require.resolve("./plugins/kapa-widget"),
+      {
+        // Website ID from https://app.kapa.ai -> Integrations -> Website 
Widget.
+        // The KAPA_WEBSITE_ID environment variable overrides this value.
+        websiteId: "b6a69ccf-2d41-41cd-96dd-855ef71b46c7",
+        projectName: "Apache ShenYu",
+        // ShenYu brand orange (same as the logo mark) and the site favicon as 
icon.
+        projectColor: "#FF5C00",
+        projectLogo: "https://shenyu.apache.org/img/favicon.svg";,
+        // The favicon is an orange mark on a transparent background, so the
+        // launcher button and modal header use contrasting backgrounds.
+        extraAttributes: {
+          "data-launcher-button-background-color": "#000033",
+          "data-launcher-button-hover-background-color": "#1a1a4d",
+          "data-launcher-button-color": "#ffffff",
+          "data-modal-header-background-color": "#ffffff",
+          "data-modal-header-color": "#000033",
+          "data-modal-header-background-color-dark": "#17171a",
+          "data-modal-header-color-dark": "#e4e4e7",
+        },
+        i18n: {
+          en: {
+            modalTitle: "Apache ShenYu Docs AI",
+            launcherButtonText: "Ask AI",
+            inputPlaceholder: "Ask a question about Apache ShenYu...",
+            disclaimer:
+              "Answers are generated by AI from the Apache ShenYu 
documentation and may be inaccurate. Please verify against the official docs.",
+            exampleQuestions: [
+              "How do I deploy ShenYu with Docker?",
+              "How do I configure the Divide plugin?",
+              "How does ShenYu integrate with Spring Cloud?",
+              "How do I enable rate limiting?",
+            ],
+          },
+          zh: {
+            modalTitle: "Apache ShenYu 文档 AI 助手",
+            launcherButtonText: "AI 问答",
+            inputPlaceholder: "输入关于 Apache ShenYu 的问题...",
+            disclaimer:
+              "回答由 AI 基于 Apache ShenYu 文档生成,可能存在错误,请以官方文档为准。",
+            exampleQuestions: [
+              "如何使用 Docker 部署 ShenYu?",
+              "如何配置 Divide 插件?",
+              "ShenYu 如何接入 Spring Cloud?",
+              "如何开启限流?",
+            ],
+          },
+        },
+      },
+    ],
   ],
 };
diff --git a/plugins/kapa-widget/index.js b/plugins/kapa-widget/index.js
new file mode 100644
index 00000000000..55c074ba056
--- /dev/null
+++ b/plugins/kapa-widget/index.js
@@ -0,0 +1,121 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Docusaurus plugin that injects the kapa.ai "Ask AI" website widget.
+ *
+ * Why a plugin instead of `scripts` in docusaurus.config.js:
+ *   - the site is bilingual (en / zh). `injectHtmlTags` runs once per locale
+ *     build, so we can set `data-language` and the UI copy per locale.
+ *   - the widget is skipped entirely until a real website id is configured,
+ *     so the site keeps building before the kapa project is set up.
+ *
+ * Widget attribute reference:
+ *   https://docs.kapa.ai/integrations/website-widget/configuration
+ */
+
+const WIDGET_SRC = "https://widget.kapa.ai/kapa-widget.bundle.js";;
+const PLACEHOLDER_ID = "REPLACE_WITH_KAPA_WEBSITE_ID";
+
+/** Docusaurus locale -> kapa `data-language` code. */
+const LANGUAGE_MAP = {
+  en: "en",
+  zh: "zh",
+};
+
+module.exports = function kapaWidgetPlugin(context, options = {}) {
+  const {
+    websiteId: configuredWebsiteId,
+    projectName = context.siteConfig.title,
+    projectColor = "#25c2a0",
+    projectLogo,
+    /** Per-locale UI copy. Keys are Docusaurus locales. */
+    i18n = {},
+    /** Extra raw `data-*` attributes merged last (override anything above). */
+    extraAttributes = {},
+  } = options;
+
+  // Environment variable wins so CI / local runs can override the config.
+  const websiteId =
+    process.env.KAPA_WEBSITE_ID || configuredWebsiteId || PLACEHOLDER_ID;
+  const locale = context.i18n.currentLocale;
+  const enabled = websiteId !== PLACEHOLDER_ID;
+
+  return {
+    name: "shenyu-kapa-widget",
+
+    injectHtmlTags() {
+      if (!enabled) {
+        if (locale === context.i18n.defaultLocale) {
+          console.warn(
+            "[kapa-widget] KAPA website id not configured; Ask AI widget is 
disabled."
+          );
+        }
+        return {};
+      }
+
+      const copy = i18n[locale] || i18n[context.i18n.defaultLocale] || {};
+
+      const attributes = {
+        src: WIDGET_SRC,
+        async: true,
+        "data-website-id": websiteId,
+        "data-project-name": projectName,
+        "data-project-color": projectColor,
+        "data-project-logo": projectLogo,
+        "data-language": LANGUAGE_MAP[locale] || "en",
+
+        // Follow the Docusaurus color-mode toggle (<html data-theme="dark">).
+        "data-color-scheme-selector": "html[data-theme=dark]",
+
+        // Keyboard shortcut: Cmd/Ctrl + K opens the Ask AI modal.
+        "data-modal-open-on-command-k": "true",
+
+        // Privacy: no tracking cookies / fingerprinting on an ASF site.
+        "data-user-analytics-cookie-enabled": "false",
+        "data-user-analytics-fingerprint-enabled": "false",
+
+        // Locale-specific copy (falls back to kapa defaults when undefined).
+        "data-modal-title": copy.modalTitle,
+        "data-launcher-button-text": copy.launcherButtonText,
+        "data-ask-ai-input-placeholder": copy.inputPlaceholder,
+        "data-chat-disclaimer": copy.disclaimer,
+        "data-example-questions": Array.isArray(copy.exampleQuestions)
+          ? copy.exampleQuestions.join(",")
+          : copy.exampleQuestions,
+
+        ...extraAttributes,
+      };
+
+      // Drop undefined values so they are not rendered as `attr="undefined"`.
+      Object.keys(attributes).forEach((key) => {
+        if (attributes[key] === undefined || attributes[key] === null) {
+          delete attributes[key];
+        }
+      });
+
+      return {
+        headTags: [
+          {
+            tagName: "script",
+            attributes,
+          },
+        ],
+      };
+    },
+  };
+};
diff --git a/static/.htaccess b/static/.htaccess
new file mode 100644
index 00000000000..8bf0aa0730c
--- /dev/null
+++ b/static/.htaccess
@@ -0,0 +1,18 @@
+# Overrides the default Content-Security-Policy that ASF infra applies to
+# *.apache.org sites. The default only allows scripts from apache.org, which
+# blocks the kapa.ai "Ask AI" widget. This policy keeps the ASF defaults and
+# adds the domains kapa.ai requires (widget, proxy, metrics, reCAPTCHA /
+# hCaptcha bot protection), plus GitHub for avatars and the contributors API.
+# Reference: https://docs.kapa.ai/integrations/faq
+<IfModule mod_headers.c>
+    Header set Content-Security-Policy "default-src 'self' data: blob: 
'unsafe-inline' 'unsafe-eval' https://*.apache.org/ https://apache.org/ 
https://www.apachecon.com/ https://www.communityovercode.org/ 
https://*.scarf.sh/; \
+script-src 'self' data: blob: 'unsafe-inline' 'unsafe-eval' 
https://*.apache.org/ https://apache.org/ https://www.apachecon.com/ 
https://www.communityovercode.org/ https://*.scarf.sh/ https://widget.kapa.ai 
https://www.google.com https://www.gstatic.com https://hcaptcha.com 
https://*.hcaptcha.com; \
+style-src 'self' data: blob: 'unsafe-inline' https://*.apache.org/ 
https://apache.org/ https://hcaptcha.com https://*.hcaptcha.com; \
+connect-src 'self' https://*.apache.org/ https://apache.org/ 
https://*.scarf.sh/ https://api.github.com https://proxy.kapa.ai 
https://kapa-widget-proxy-la7dkmplpq-uc.a.run.app https://metrics.kapa.ai 
https://www.google.com https://hcaptcha.com https://*.hcaptcha.com; \
+img-src 'self' data: blob: https:; \
+font-src 'self' data: blob: https://*.apache.org/; \
+frame-src 'self' data: blob: https://*.apache.org/ https://apache.org/ 
https://www.apachecon.com/ https://www.communityovercode.org/ 
https://www.google.com https://hcaptcha.com https://*.hcaptcha.com; \
+frame-ancestors 'self'; \
+worker-src 'self' data: blob:; \
+object-src 'none'"
+</IfModule>

Reply via email to