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>