From 3a3d94f1fb957abf8ce80583792a4dfcfc19a48a Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 17:19:39 +0000 Subject: [PATCH 1/3] Enhance Umami tracking implementation - Standardize Hero events to `navigation_click` - Add `search_result_click` tracking in search dialog - Track `ai_assistant_query` with query length - Track 404 errors with referrer - Add tracking for "Mission Brief" and "Network Status" in footer - Implement `CopyTracking` for code/text copy events - Implement `PageFeedback` for helpful/not helpful votes - Update documentation in `docs/umami_tracking.md` Co-authored-by: longsizhuo <114939201+longsizhuo@users.noreply.github.com> --- app/components/CopyTracking.tsx | 54 ++++++++++++++++++++++++ app/components/CustomSearchDialog.tsx | 56 +++++++++++++++++++++--- app/components/DocsAssistant.tsx | 25 ++++++++--- app/components/Footer.tsx | 6 +++ app/components/Hero.tsx | 10 ++--- app/components/PageFeedback.tsx | 59 ++++++++++++++++++++++++++ app/docs/[...slug]/page.tsx | 2 + app/docs/layout.tsx | 2 + app/not-found.tsx | 5 ++- docs/umami_tracking.md | 61 ++++++++++++++++----------- 10 files changed, 235 insertions(+), 45 deletions(-) create mode 100644 app/components/CopyTracking.tsx create mode 100644 app/components/PageFeedback.tsx diff --git a/app/components/CopyTracking.tsx b/app/components/CopyTracking.tsx new file mode 100644 index 00000000..255293ba --- /dev/null +++ b/app/components/CopyTracking.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { useEffect } from "react"; + +export function CopyTracking() { + useEffect(() => { + const handleCopy = () => { + const selection = window.getSelection(); + if (!selection || selection.isCollapsed) return; + + const text = selection.toString(); + if (!text) return; + + // Determine if it's code or text + let type = "text"; + const anchorNode = selection.anchorNode; + const focusNode = selection.focusNode; + + const isCode = (node: Node | null) => { + let current = node; + while (current) { + if ( + current.nodeName === "PRE" || + current.nodeName === "CODE" || + (current instanceof Element && + (current.classList.contains("code-block") || + current.tagName === "PRE" || + current.tagName === "CODE")) + ) { + return true; + } + current = current.parentNode; + } + return false; + }; + + if (isCode(anchorNode) || isCode(focusNode)) { + type = "code"; + } + + if (window.umami) { + window.umami.track("prose_copy", { + type, + content_length: text.length, + }); + } + }; + + document.addEventListener("copy", handleCopy); + return () => document.removeEventListener("copy", handleCopy); + }, []); + + return null; +} diff --git a/app/components/CustomSearchDialog.tsx b/app/components/CustomSearchDialog.tsx index c7a4bd3e..1a0ded86 100644 --- a/app/components/CustomSearchDialog.tsx +++ b/app/components/CustomSearchDialog.tsx @@ -17,6 +17,7 @@ import { TagsListItem, type SharedProps, } from "fumadocs-ui/components/dialog/search"; +import { useRouter } from "next/navigation"; interface TagItem { name: string; @@ -34,6 +35,12 @@ interface DefaultSearchDialogProps extends SharedProps { allowClear?: boolean; } +interface SearchItem { + url?: string; + onSelect?: (value: string) => void; + [key: string]: unknown; +} + export function CustomSearchDialog({ defaultTag, tags = [], @@ -46,7 +53,12 @@ export function CustomSearchDialog({ ...props }: DefaultSearchDialogProps) { const { locale } = useI18n(); + const router = useRouter(); const [tag, setTag] = useState(defaultTag); + + // Extract onOpenChange to use in dependency array cleanly + const { onOpenChange, ...otherProps } = props; + const { search, setSearch, query } = useDocsSearch( type === "fetch" ? { @@ -65,7 +77,7 @@ export function CustomSearchDialog({ }, ); - // Tracking logic + // Tracking logic for queries useEffect(() => { if (!search) return; @@ -88,12 +100,45 @@ export function CustomSearchDialog({ })); }, [links]); + const trackedItems = useMemo(() => { + const data = query.data !== "empty" && query.data ? query.data : defaultItems; + if (!data) return []; + + return data.map((item: unknown, index: number) => { + const searchItem = item as SearchItem; + return { + ...searchItem, + onSelect: (value: string) => { + if (window.umami) { + window.umami.track("search_result_click", { + query: search, + rank: index + 1, + url: searchItem.url, + }); + } + + // Call original onSelect if it exists + if (searchItem.onSelect) searchItem.onSelect(value); + + // Handle navigation if URL exists + if (searchItem.url) { + router.push(searchItem.url); + if (onOpenChange) { + onOpenChange(false); + } + } + }, + }; + }); + }, [query.data, defaultItems, search, router, onOpenChange]); + return ( @@ -102,11 +147,8 @@ export function CustomSearchDialog({ - + {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} + {tags.length > 0 && ( diff --git a/app/components/DocsAssistant.tsx b/app/components/DocsAssistant.tsx index 60e2af6e..cb0223ee 100644 --- a/app/components/DocsAssistant.tsx +++ b/app/components/DocsAssistant.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect, useMemo } from "react"; +import { useCallback, useEffect, useMemo, useRef } from "react"; import { AssistantRuntimeProvider } from "@assistant-ui/react"; import { useAISDKRuntime } from "@assistant-ui/react-ai-sdk"; @@ -54,20 +54,31 @@ function DocsAssistantInner({ pageContext }: DocsAssistantProps) { const chat = useChat({ transport, - onFinish: () => { - // 当对话结束时(流式传输完成),记录一次查询行为 - if (window.umami) { - window.umami.track("ai_assistant_query"); - } - }, }); const { + messages, error: chatError, status: chatStatus, clearError: clearChatError, } = chat; + // Track assistant query completion + const prevStatus = useRef(chatStatus); + + useEffect(() => { + if (prevStatus.current === "streaming" && chatStatus === "ready") { + // Streaming finished successfully + const lastUserMessage = messages.filter((m) => m.role === "user").pop(); + if (window.umami) { + window.umami.track("ai_assistant_query", { + length: (lastUserMessage as any)?.content?.length ?? 0, // eslint-disable-line @typescript-eslint/no-explicit-any + }); + } + } + prevStatus.current = chatStatus; + }, [chatStatus, messages]); + useEffect(() => { if (chatStatus === "submitted" || chatStatus === "streaming") { clearChatError(); diff --git a/app/components/Footer.tsx b/app/components/Footer.tsx index 61336dae..021d322e 100644 --- a/app/components/Footer.tsx +++ b/app/components/Footer.tsx @@ -128,6 +128,9 @@ export function Footer() {
  • Mission Brief @@ -136,6 +139,9 @@ export function Footer() {
  • Network Status diff --git a/app/components/Hero.tsx b/app/components/Hero.tsx index b2831a85..c957040e 100644 --- a/app/components/Hero.tsx +++ b/app/components/Hero.tsx @@ -83,9 +83,9 @@ export function Hero() { + + + + ); +} diff --git a/app/docs/[...slug]/page.tsx b/app/docs/[...slug]/page.tsx index e982a4c5..7964685c 100644 --- a/app/docs/[...slug]/page.tsx +++ b/app/docs/[...slug]/page.tsx @@ -13,6 +13,7 @@ import { import { Contributors } from "@/app/components/Contributors"; import { DocsAssistant } from "@/app/components/DocsAssistant"; import { LicenseNotice } from "@/app/components/LicenseNotice"; +import { PageFeedback } from "@/app/components/PageFeedback"; import fs from "fs/promises"; import path from "path"; @@ -92,6 +93,7 @@ export default async function DocPage({ params }: Param) { +
    diff --git a/app/docs/layout.tsx b/app/docs/layout.tsx index 8e0d46ab..404848db 100644 --- a/app/docs/layout.tsx +++ b/app/docs/layout.tsx @@ -4,6 +4,7 @@ import { baseOptions } from "@/lib/layout.shared"; import type { ReactNode } from "react"; import { DocsRouteFlag } from "@/app/components/RouteFlags"; import type { PageTree } from "fumadocs-core/server"; +import { CopyTracking } from "@/app/components/CopyTracking"; function pruneEmptyFolders(root: PageTree.Root): PageTree.Root { const transformNode = (node: PageTree.Node): PageTree.Node | null => { @@ -68,6 +69,7 @@ export default async function Layout({ children }: { children: ReactNode }) { return ( <> {/* Add a class on while in docs to adjust global backgrounds */} + { if (window.umami) { - window.umami.track("404", { path: pathname }); + window.umami.track("error_404", { + path: pathname, + referrer: document.referrer || "direct", + }); } }, [pathname]); diff --git a/docs/umami_tracking.md b/docs/umami_tracking.md index ffeb9a37..1b53a8bd 100644 --- a/docs/umami_tracking.md +++ b/docs/umami_tracking.md @@ -6,13 +6,15 @@ **内部导航 (Internal Navigation)**: 仅用于网站内部页面的跳转。 -| 区域 (Region) | 元素说明 | 触发行为 | 埋点事件名 (Event Name) | 埋点传参 (Event Data) | -| :------------- | :---------------------------- | :------- | :---------------------- | :------------------------------------------------------------------------------------ | -| **header** | 导航链接 (特点, 社区等) | 点击 | `navigation_click` | `region`: "header", `label`: 链接名 (e.g., "features"), `path`: 目标路径 | -| **footer** | 归档/资源链接 | 点击 | `navigation_click` | `region`: "footer", `label`: 链接名 (e.g., "AI & Mathematics"), `category`: "archive" | -| **sidebar** | 文档侧边栏链接 | 点击 | `navigation_click` | `region`: "sidebar", `label`: 页面标题, `path`: 目标路径 | -| **toc** | 目录 (Table of Contents) 链接 | 点击 | `navigation_click` | `region`: "toc", `label`: 章节标题 | -| **pagination** | 上一篇/下一篇 | 点击 | `navigation_click` | `region`: "pagination", `label`: "prev" / "next", `path`: 目标路径 | +| 区域 (Region) | 元素说明 | 触发行为 | 埋点事件名 (Event Name) | 埋点传参 (Event Data) | +| :------------------- | :---------------------------- | :------- | :---------------------- | :------------------------------------------------------------------------------------ | +| **header** | 导航链接 (特点, 社区等) | 点击 | `navigation_click` | `region`: "header", `label`: 链接名 (e.g., "features"), `path`: 目标路径 | +| **footer** | 归档/资源链接 | 点击 | `navigation_click` | `region`: "footer", `label`: 链接名 (e.g., "Mission Brief"), `category`: "archive" | +| **sidebar** | 文档侧边栏链接 | 点击 | `navigation_click` | `region`: "sidebar", `label`: 页面标题, `path`: 目标路径 | +| **toc** | 目录 (Table of Contents) 链接 | 点击 | `navigation_click` | `region`: "toc", `label`: 章节标题 | +| **pagination** | 上一篇/下一篇 | 点击 | `navigation_click` | `region`: "pagination", `label`: "prev" / "next", `path`: 目标路径 | +| **hero_cta** | 首页 Hero 按钮 | 点击 | `navigation_click` | `region`: "hero_cta", `label`: "Access Articles" | +| **home_categories** | 首页分类卡片 | 点击 | `navigation_click` | `region`: "home_categories", `label`: 分类标题 (e.g., "AI") | **资源与外部链接 (Resources & External Links)**: 用于追踪外部资源、工具或社交媒体的点击。 @@ -35,18 +37,19 @@ ## 3. 内容互动与反馈 (Content Interaction & Feedback) -| 区域 | 元素说明 | 触发行为 | 埋点事件名 (Event Name) | 埋点传参 (Event Data) | -| :--------------- | :----------------- | :------- | :---------------------- | :------------------------------------------------------------------- | -| **Content** | 复制生词/代码块 | 点击 | `prose_copy` | `type`: "code"/"text", `content_length`: 字符数范围 | -| **Content** | 页面反馈 (Helpful) | 点击 | `feedback_submit` | `page`: 当前页面路径, `vote`: "helpful" / "not_helpful" | -| **Feature** | 投稿 (Contribute) | 点击 | `contribute_trigger` | `location`: "hero" / "docs" | -| **AI Assistant** | 提问 | 发送 | `ai_assistant_query` | `length`: 字符数范围 (e.g., "0-50") _注意:不记录具体内容以保护隐私_ | +| 区域 | 元素说明 | 触发行为 | 埋点事件名 (Event Name) | 埋点传参 (Event Data) | +| :--------------- | :------------------------- | :------- | :--------------------------- | :----------------------------------------------------------- | +| **Content** | 复制生词/代码块 | 复制 | `prose_copy` | `type`: "code"/"text", `content_length`: 字符数 (number) | +| **Content** | 页面反馈 (Helpful) | 点击 | `feedback_submit` | `page`: 当前页面路径, `vote`: "helpful" / "not_helpful" | +| **Feature** | 投稿 (Contribute) | 点击 | `contribute_trigger` | `location`: "hero" / "docs" | +| **Feature** | 投稿跳转 (Github Redirect) | 跳转 | `contribute_github_redirect` | `dir`: 目标目录, `filename`: 文件名 | +| **AI Assistant** | 提问 | 完成 | `ai_assistant_query` | `length`: 查询字符数 (number) _注意:不记录具体内容以保护隐私_ | ## 4. 异常与错误 (Errors) -| 场景 | 说明 | 触发条件 | 埋点事件名 (Event Name) | 埋点传参 (Event Data) | -| :------ | :--------- | :------------ | :---------------------- | :------------------------------------- | -| **404** | 页面未找到 | 访问 404 页面 | `error_404` | `path`: 访问路径, `referrer`: 来源页面 | +| 场景 | 说明 | 触发条件 | 埋点事件名 (Event Name) | 埋点传参 (Event Data) | +| :------ | :--------- | :------------ | :---------------------- | :----------------------------------------------- | +| **404** | 页面未找到 | 访问 404 页面 | `error_404` | `path`: 访问路径, `referrer`: 来源页面 (document.referrer) | ## 实施指南 @@ -68,15 +71,23 @@ >Artificial Intelligence ``` -### 搜索埋点示例 (配合 cmdk / fumadocs) +### 搜索埋点示例 (CustomSearchDialog) ```tsx -// 在搜索组件中 -const onSelectResult = (result, index) => { - umami.track("search_result_click", { - query: searchQuery, - rank: index + 1, - url: result.url, - }); -}; +// 在搜索结果点击时 +umami.track("search_result_click", { + query: searchQuery, + rank: index + 1, + url: result.url, +}); +``` + +### 复制监听示例 (CopyTracking) + +```tsx +// 自动监听 document copy 事件 +umami.track("prose_copy", { + type: isCode ? "code" : "text", + content_length: selection.toString().length, +}); ``` From 41e51e50cd9987d1480e5d4a84e363d2ac86012a Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 18:01:23 +0000 Subject: [PATCH 2/3] Enhance Umami tracking implementation with Chinese comments - Standardize Hero events to `navigation_click` with Chinese comments - Add `search_result_click` tracking in search dialog with Chinese comments - Track `ai_assistant_query` with query length with Chinese comments - Track 404 errors with referrer with Chinese comments - Add tracking for "Mission Brief" and "Network Status" in footer with Chinese comments - Implement `CopyTracking` for code/text copy events with Chinese comments - Implement `PageFeedback` for helpful/not helpful votes with Chinese comments - Update documentation in `docs/umami_tracking.md` Co-authored-by: longsizhuo <114939201+longsizhuo@users.noreply.github.com> --- app/components/CopyTracking.tsx | 3 +++ app/components/CustomSearchDialog.tsx | 4 ++++ app/components/DocsAssistant.tsx | 1 + app/components/Footer.tsx | 6 ++++++ app/components/Hero.tsx | 2 ++ app/components/PageFeedback.tsx | 1 + app/not-found.tsx | 1 + 7 files changed, 18 insertions(+) diff --git a/app/components/CopyTracking.tsx b/app/components/CopyTracking.tsx index 255293ba..6c268e9e 100644 --- a/app/components/CopyTracking.tsx +++ b/app/components/CopyTracking.tsx @@ -4,6 +4,7 @@ import { useEffect } from "react"; export function CopyTracking() { useEffect(() => { + // 监听全局 Copy 事件 const handleCopy = () => { const selection = window.getSelection(); if (!selection || selection.isCollapsed) return; @@ -35,9 +36,11 @@ export function CopyTracking() { }; if (isCode(anchorNode) || isCode(focusNode)) { + // 判断选中节点是否包含在
     标签内,区分代码块复制
             type = "code";
           }
     
    +      // Umami 埋点: 记录复制行为,区分文本/代码类型和复制长度
           if (window.umami) {
             window.umami.track("prose_copy", {
               type,
    diff --git a/app/components/CustomSearchDialog.tsx b/app/components/CustomSearchDialog.tsx
    index 1a0ded86..c155a586 100644
    --- a/app/components/CustomSearchDialog.tsx
    +++ b/app/components/CustomSearchDialog.tsx
    @@ -82,6 +82,7 @@ export function CustomSearchDialog({
         if (!search) return;
     
         const timer = setTimeout(() => {
    +            // Umami 埋点: 搜索结果点击
           if (window.umami) {
             window.umami.track("search_query", { query: search });
           }
    @@ -100,6 +101,7 @@ export function CustomSearchDialog({
         }));
       }, [links]);
     
    +  // 使用 useMemo 劫持 search items,注入埋点逻辑
       const trackedItems = useMemo(() => {
         const data = query.data !== "empty" && query.data ? query.data : defaultItems;
         if (!data) return [];
    @@ -109,6 +111,7 @@ export function CustomSearchDialog({
             return {
               ...searchItem,
               onSelect: (value: string) => {
    +            // Umami 埋点: 搜索结果点击
                 if (window.umami) {
                   window.umami.track("search_result_click", {
                     query: search,
    @@ -122,6 +125,7 @@ export function CustomSearchDialog({
     
                 // Handle navigation if URL exists
                 if (searchItem.url) {
    +                // 显式执行路由跳转和关闭弹窗,确保点击行为能够同时触发埋点和导航
                     router.push(searchItem.url);
                     if (onOpenChange) {
                         onOpenChange(false);
    diff --git a/app/components/DocsAssistant.tsx b/app/components/DocsAssistant.tsx
    index cb0223ee..5b52a58e 100644
    --- a/app/components/DocsAssistant.tsx
    +++ b/app/components/DocsAssistant.tsx
    @@ -70,6 +70,7 @@ function DocsAssistantInner({ pageContext }: DocsAssistantProps) {
         if (prevStatus.current === "streaming" && chatStatus === "ready") {
           // Streaming finished successfully
           const lastUserMessage = messages.filter((m) => m.role === "user").pop();
    +      // Umami 埋点: AI 对话结束,记录查询长度(保护隐私,不记录具体内容)
           if (window.umami) {
             window.umami.track("ai_assistant_query", {
               length: (lastUserMessage as any)?.content?.length ?? 0, // eslint-disable-line @typescript-eslint/no-explicit-any
    diff --git a/app/components/Footer.tsx b/app/components/Footer.tsx
    index 021d322e..14e6ae1d 100644
    --- a/app/components/Footer.tsx
    +++ b/app/components/Footer.tsx
    @@ -63,6 +63,7 @@ export function Footer() {
                       
                       
                       
    diff --git a/app/components/PageFeedback.tsx b/app/components/PageFeedback.tsx
    index d9d8928f..2b613584 100644
    --- a/app/components/PageFeedback.tsx
    +++ b/app/components/PageFeedback.tsx
    @@ -12,6 +12,7 @@ export function PageFeedback() {
       const handleVote = (vote: "helpful" | "not_helpful") => {
         if (voted) return;
     
    +    // Umami 埋点: 记录用户是否有帮助的投票
         if (window.umami) {
           window.umami.track("feedback_submit", {
             page: pathname,
    diff --git a/app/not-found.tsx b/app/not-found.tsx
    index 7eeaae64..0053a3b7 100644
    --- a/app/not-found.tsx
    +++ b/app/not-found.tsx
    @@ -9,6 +9,7 @@ export default function NotFound() {
       const pathname = usePathname();
     
       useEffect(() => {
    +      // Umami 埋点: 记录 404 错误和来源页面 (Referrer)
         if (window.umami) {
           window.umami.track("error_404", {
             path: pathname,
    
    From 713d1263a7416c39f7313569b0b91ff029e7030a Mon Sep 17 00:00:00 2001
    From: "google-labs-jules[bot]"
     <161369871+google-labs-jules[bot]@users.noreply.github.com>
    Date: Fri, 13 Feb 2026 18:21:27 +0000
    Subject: [PATCH 3/3] Enhance Umami tracking implementation
    
    - Standardize Hero events to `navigation_click`
    - Add `search_result_click` tracking in search dialog
    - Track `ai_assistant_query` without parameters
    - Track 404 errors with referrer
    - Add tracking for "Mission Brief" and "Network Status" in footer
    - Implement `CopyTracking` for `content_copy` events
    - Implement `PageFeedback` with updated styling
    - Update documentation in `docs/umami_tracking.md`
    
    Co-authored-by: longsizhuo <114939201+longsizhuo@users.noreply.github.com>
    ---
     app/components/CopyTracking.tsx  |  2 +-
     app/components/DocsAssistant.tsx | 26 +++++++-------------------
     app/components/PageFeedback.tsx  | 16 ++++++++--------
     docs/umami_tracking.md           |  6 +++---
     4 files changed, 19 insertions(+), 31 deletions(-)
    
    diff --git a/app/components/CopyTracking.tsx b/app/components/CopyTracking.tsx
    index 6c268e9e..42a5f1d1 100644
    --- a/app/components/CopyTracking.tsx
    +++ b/app/components/CopyTracking.tsx
    @@ -42,7 +42,7 @@ export function CopyTracking() {
     
           // Umami 埋点: 记录复制行为,区分文本/代码类型和复制长度
           if (window.umami) {
    -        window.umami.track("prose_copy", {
    +        window.umami.track("content_copy", {
               type,
               content_length: text.length,
             });
    diff --git a/app/components/DocsAssistant.tsx b/app/components/DocsAssistant.tsx
    index 5b52a58e..60e2af6e 100644
    --- a/app/components/DocsAssistant.tsx
    +++ b/app/components/DocsAssistant.tsx
    @@ -1,6 +1,6 @@
     "use client";
     
    -import { useCallback, useEffect, useMemo, useRef } from "react";
    +import { useCallback, useEffect, useMemo } from "react";
     
     import { AssistantRuntimeProvider } from "@assistant-ui/react";
     import { useAISDKRuntime } from "@assistant-ui/react-ai-sdk";
    @@ -54,32 +54,20 @@ function DocsAssistantInner({ pageContext }: DocsAssistantProps) {
     
       const chat = useChat({
         transport,
    +    onFinish: () => {
    +      // 当对话结束时(流式传输完成),记录一次查询行为
    +      if (window.umami) {
    +        window.umami.track("ai_assistant_query");
    +      }
    +    },
       });
     
       const {
    -    messages,
         error: chatError,
         status: chatStatus,
         clearError: clearChatError,
       } = chat;
     
    -  // Track assistant query completion
    -  const prevStatus = useRef(chatStatus);
    -
    -  useEffect(() => {
    -    if (prevStatus.current === "streaming" && chatStatus === "ready") {
    -      // Streaming finished successfully
    -      const lastUserMessage = messages.filter((m) => m.role === "user").pop();
    -      // Umami 埋点: AI 对话结束,记录查询长度(保护隐私,不记录具体内容)
    -      if (window.umami) {
    -        window.umami.track("ai_assistant_query", {
    -          length: (lastUserMessage as any)?.content?.length ?? 0, // eslint-disable-line @typescript-eslint/no-explicit-any
    -        });
    -      }
    -    }
    -    prevStatus.current = chatStatus;
    -  }, [chatStatus, messages]);
    -
       useEffect(() => {
         if (chatStatus === "submitted" || chatStatus === "streaming") {
           clearChatError();
    diff --git a/app/components/PageFeedback.tsx b/app/components/PageFeedback.tsx
    index 2b613584..7a97a929 100644
    --- a/app/components/PageFeedback.tsx
    +++ b/app/components/PageFeedback.tsx
    @@ -12,8 +12,8 @@ export function PageFeedback() {
       const handleVote = (vote: "helpful" | "not_helpful") => {
         if (voted) return;
     
    -    // Umami 埋点: 记录用户是否有帮助的投票
         if (window.umami) {
    +      // Umami 埋点: 记录用户是否有帮助的投票
           window.umami.track("feedback_submit", {
             page: pathname,
             vote,
    @@ -24,32 +24,32 @@ export function PageFeedback() {
     
       if (voted) {
         return (
    -      
    +
    Thanks for your feedback! / 感谢您的反馈!
    ); } return ( -
    - +
    + Was this page helpful? / 这篇文章有帮助吗?