From e1a1920e4ca9011506e96af627b17e8ecb0a832d Mon Sep 17 00:00:00 2001 From: Loong Loong Date: Wed, 11 Feb 2026 00:45:16 +0800 Subject: [PATCH 1/4] feat: connect umami --- app/components/Community.tsx | 18 ++++- app/components/Contribute.tsx | 10 +++ app/components/DocsAssistant.tsx | 10 ++- app/components/EditOnGithub.tsx | 2 + app/components/Footer.tsx | 21 +++++ app/components/Header.tsx | 12 +++ app/components/Hero.tsx | 10 ++- app/components/SignInButton.tsx | 9 ++- app/components/ThemeToggle.tsx | 3 + app/components/UserMenu.tsx | 1 + .../assistant-ui/assistant-modal.tsx | 6 ++ app/types/umami.d.ts | 9 +++ .../PNPM_LOCKFILE_GUIDE.md | 12 +-- docs/README.md | 5 ++ docs/umami_tracking.md | 77 +++++++++++++++++++ 15 files changed, 195 insertions(+), 10 deletions(-) create mode 100644 app/types/umami.d.ts rename PNPM_LOCKFILE_GUIDE.md => docs/PNPM_LOCKFILE_GUIDE.md (99%) create mode 100644 docs/README.md create mode 100644 docs/umami_tracking.md diff --git a/app/components/Community.tsx b/app/components/Community.tsx index 65ffcefb..1ce8f86c 100644 --- a/app/components/Community.tsx +++ b/app/components/Community.tsx @@ -41,7 +41,14 @@ export function Community() { asChild className="bg-transparent border-[var(--foreground)] text-[var(--foreground)] hover:bg-[var(--foreground)] hover:text-[var(--background)] px-12 py-8 h-auto font-sans text-sm uppercase tracking-widest font-bold" > - + Access Articles / 访问文章{" "} @@ -73,6 +80,9 @@ export function Community() { href="https://github.com/involutionhell" target="_blank" rel="noopener noreferrer" + data-umami-event="social_click" + data-umami-event-platform="github" + data-umami-event-location="community_card" > Source Code @@ -98,6 +108,9 @@ export function Community() { href="https://discord.com/invite/6CGP73ZWbD" target="_blank" rel="noopener noreferrer" + data-umami-event="social_click" + data-umami-event-platform="discord" + data-umami-event-location="community_card" > Join Dispatch @@ -123,6 +136,9 @@ export function Community() { href="https://www.zotero.org/groups/6053219/unsw_ai/library" target="_blank" rel="noopener noreferrer" + data-umami-event="resource_click" + data-umami-event-type="zotero" + data-umami-event-location="community_card" > View Library diff --git a/app/components/Contribute.tsx b/app/components/Contribute.tsx index dff5ce03..e068407f 100644 --- a/app/components/Contribute.tsx +++ b/app/components/Contribute.tsx @@ -132,6 +132,14 @@ export function Contribute() { if (filename !== articleFile) { setArticleFile(filename); } + + if (window.umami) { + window.umami.track("contribute_github_redirect", { + dir: finalDirPath, + filename: filename, + }); + } + window.open( buildGithubNewUrl(finalDirPath, filename, title), "_blank", @@ -162,6 +170,8 @@ export function Contribute() { text-2xl font-serif font-black uppercase italic tracking-tighter bg-[var(--foreground)] text-[var(--background)] border border-[var(--foreground)] hover:bg-[var(--background)] hover:text-[var(--foreground)] transition-all duration-300" + data-umami-event="contribute_trigger" + data-umami-event-location="hero" onClick={(event) => { event.preventDefault(); router.push("/editor"); diff --git a/app/components/DocsAssistant.tsx b/app/components/DocsAssistant.tsx index e877ed8e..60e2af6e 100644 --- a/app/components/DocsAssistant.tsx +++ b/app/components/DocsAssistant.tsx @@ -52,7 +52,15 @@ function DocsAssistantInner({ pageContext }: DocsAssistantProps) { [geminiApiKey, openaiApiKey, pageContext, provider], ); - const chat = useChat({ transport }); + const chat = useChat({ + transport, + onFinish: () => { + // 当对话结束时(流式传输完成),记录一次查询行为 + if (window.umami) { + window.umami.track("ai_assistant_query"); + } + }, + }); const { error: chatError, diff --git a/app/components/EditOnGithub.tsx b/app/components/EditOnGithub.tsx index b1a22947..55d1c494 100644 --- a/app/components/EditOnGithub.tsx +++ b/app/components/EditOnGithub.tsx @@ -5,6 +5,8 @@ export function EditOnGithub({ href }: { href: string }) {
00{idx + 1} diff --git a/app/components/SignInButton.tsx b/app/components/SignInButton.tsx index a93cdce8..768ee915 100644 --- a/app/components/SignInButton.tsx +++ b/app/components/SignInButton.tsx @@ -27,7 +27,14 @@ export function SignInButton({ }); }} > - diff --git a/app/components/ThemeToggle.tsx b/app/components/ThemeToggle.tsx index 1afc9d5d..367db426 100644 --- a/app/components/ThemeToggle.tsx +++ b/app/components/ThemeToggle.tsx @@ -13,6 +13,9 @@ export function ThemeToggle() { onClick={() => { const next = theme === "light" ? "dark" : "light"; setTheme(next); + if (window.umami) { + window.umami.track("theme_toggle", { theme: next }); + } }} className="h-10 w-10 rounded-none transition-colors" > diff --git a/app/components/UserMenu.tsx b/app/components/UserMenu.tsx index 7e78040d..3924d165 100644 --- a/app/components/UserMenu.tsx +++ b/app/components/UserMenu.tsx @@ -22,6 +22,7 @@ export function UserMenu({ user, provider }: UserMenuProps) { {user.image ? ( diff --git a/app/components/assistant-ui/assistant-modal.tsx b/app/components/assistant-ui/assistant-modal.tsx index 5550fd50..a484ad2b 100644 --- a/app/components/assistant-ui/assistant-modal.tsx +++ b/app/components/assistant-ui/assistant-modal.tsx @@ -107,6 +107,12 @@ const AssistantModalButton = forwardRef< if (onCloseBubble) { onCloseBubble(); } + + // 如果当前是关闭状态,说明即将打开,记录埋点 + if (state === "closed" && window.umami) { + window.umami.track("ai_assistant_open"); + } + // 继续执行原有的点击事件 if (rest.onClick) { rest.onClick(e); diff --git a/app/types/umami.d.ts b/app/types/umami.d.ts new file mode 100644 index 00000000..6513a1cf --- /dev/null +++ b/app/types/umami.d.ts @@ -0,0 +1,9 @@ +export {}; + +declare global { + interface Window { + umami?: { + track: (eventName: string, data?: Record) => void; + }; + } +} diff --git a/PNPM_LOCKFILE_GUIDE.md b/docs/PNPM_LOCKFILE_GUIDE.md similarity index 99% rename from PNPM_LOCKFILE_GUIDE.md rename to docs/PNPM_LOCKFILE_GUIDE.md index 20e57034..8c4be0be 100644 --- a/PNPM_LOCKFILE_GUIDE.md +++ b/docs/PNPM_LOCKFILE_GUIDE.md @@ -149,24 +149,24 @@ git status ```yaml steps: - uses: actions/checkout@v4 - + # 显式启用 corepack - name: Enable Corepack run: corepack enable - + - uses: pnpm/action-setup@v4 - + - uses: actions/setup-node@v4 with: node-version: 20 cache: pnpm - + # 验证版本 - name: Check pnpm version run: node scripts/check-pnpm-version.mjs - + - run: pnpm install --frozen-lockfile - + # 验证 lockfile 没有被修改 - name: Check lockfile consistency run: | diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..c6477674 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,5 @@ +本文件夹用于存放开发文档, 避免都在主页面造成混乱, +需注意, 可以在GitHub渲染的文档比如 SECURITY.md 不要放入本文件夹中 + +This folder is used to store development documentation to avoid clutter on the main page. +Note that documents that can be rendered by GitHub, such as SECURITY.md, should not be placed in this folder. diff --git a/docs/umami_tracking.md b/docs/umami_tracking.md new file mode 100644 index 00000000..dd62be97 --- /dev/null +++ b/docs/umami_tracking.md @@ -0,0 +1,77 @@ +# Umami 埋点设计文档 + +本文档描述了 Involution Hell 网站 (involutionhell.com) 的用户行为埋点设计。使用 Umami v2 进行数据采集。 + +## 1. 全局组件 (Global Components) + +这些元素出现在几乎所有页面上(如页眉、页脚)。 + +| 区域 | 按钮/元素说明 | 触发行为 | 埋点事件名 (Event Name) | 埋点传参 (Event Data) | +| :--------- | :-------------------------------- | :------- | :---------------------- | :------------------------------------------------------------------- | +| **Header** | 导航栏链接 (特点, 社区, 联系我们) | 点击 | `header_nav_click` | `label`: 具体链接名称 (e.g., "features", "community", "contact") | +| **Header** | GitHub 图标按钮 | 点击 | `social_click` | `platform`: "github", `location`: "header" | +| **Header** | Discord 图标按钮 | 点击 | `social_click` | `platform`: "discord", `location`: "header" | +| **Header** | 主题切换 (Theme Toggle) | 点击 | `theme_toggle` | `theme`: 切换后的主题 ("light" / "dark") | +| **Header** | 登录按钮 (Sign In) | 点击 | `auth_click` | `action`: "signin", `location`: "header" | +| **Header** | 用户菜单 (User Menu) | 点击 | `user_menu_click` | - | +| **Footer** | GitHub 链接 | 点击 | `social_click` | `platform`: "github", `location`: "footer" | +| **Footer** | Discord 链接 | 点击 | `social_click` | `platform`: "discord", `location`: "footer" | +| **Footer** | 归档链接 (Archives) | 点击 | `footer_link_click` | `category`: "archives", `label`: 链接名称 (e.g., "AI & Mathematics") | +| **Footer** | 资源链接 (Resources) | 点击 | `footer_link_click` | `category`: "resources", `label`: 链接名称 (e.g., "Zotero Library") | + +## 2. 首页 (Home Page - /) + +| 区域 | 按钮/元素说明 | 触发行为 | 埋点事件名 (Event Name) | 埋点传参 (Event Data) | +| :-------------------- | :----------------------------- | :------- | :--------------------------- | :----------------------------------------------------------- | +| **Hero** | 核心分类卡片 (AI, CS, etc.) | 点击 | `home_category_click` | `category`: 分类名称 (e.g., "AI", "Computer Science") | +| **Hero** | 投稿按钮 (Submit Contribution) | 点击 | `contribute_trigger` | `location`: "hero" | +| **Hero (Contribute)** | 投稿弹窗 - 跳转 GitHub | 点击 | `contribute_github_redirect` | `dir`: 目录路径, `filename`: 文件名 | +| **Hero (Right)** | 访问文章按钮 (Access Articles) | 点击 | `feature_cta_click` | `action`: "access_articles", `location`: "hero_sidebar" | +| **Community** | 知识库 - 访问文章按钮 | 点击 | `feature_cta_click` | `action`: "access_articles", `location`: "community_section" | +| **Community** | GitHub 仓库卡片按钮 | 点击 | `social_click` | `platform`: "github", `location`: "community_card" | +| **Community** | Discord 社区卡片按钮 | 点击 | `social_click` | `platform`: "discord", `location`: "community_card" | +| **Community** | Zotero 文献库卡片按钮 | 点击 | `resource_click` | `type`: "zotero", `location`: "community_card" | + +## 3. 文档页 (Documentation - /docs/\*) + +| 区域 | 按钮/元素说明 | 触发行为 | 埋点事件名 (Event Name) | 埋点传参 (Event Data) | +| :------------ | :-------------------------------- | :------- | :---------------------- | :----------------------------------------------------------------------------- | +| **Sidebar** | 侧边栏导航链接 | 点击 | `docs_nav_click` | `path`: 目标路径 | +| **Content** | 在 GitHub 上编辑 (Edit on GitHub) | 点击 | `docs_edit_click` | `page`: 当前页面路径 | +| **Assistant** | AI 助手 - 打开聊天 | 点击 | `ai_assistant_open` | - | +| **Assistant** | AI 助手 - 发送消息 | 发送 | `ai_assistant_query` | `length`: 字符数范围 (e.g., "0-50", "50-100") _注意:不记录具体内容以保护隐私_ | +| **Content** | 复制生词/代码块 | 点击 | `content_copy` | `type`: "code" 或 "text" | + +## 4. 登录页 (Login - /login) + +| 区域 | 按钮/元素说明 | 触发行为 | 埋点事件名 (Event Name) | 埋点传参 (Event Data) | +| :------- | :------------ | :------- | :---------------------- | :------------------------------------------- | +| **Main** | 登录按钮 | 点击 | `auth_click` | `action`: "signin", `location`: "login_page" | + +## 实施指南 + +在 Next.js / React 中使用 Umami 进行埋点可以通过添加 `data-umami-event` 属性来实现,或者使用 `window.umami.track()` 函数。 + +### 示例 1: HTML 属性方式 (推荐用于静态内容) + +```jsx + +``` + +### 示例 2: JS 函数方式 (推荐用于动态交互) + +```javascript +// 在组件中调用 +const handleThemeToggle = (newTheme) => { + setTheme(newTheme); + if (window.umami) { + window.umami.track("theme_toggle", { theme: newTheme }); + } +}; +``` From ec58dc710683c13a4983026e778d78079749966a Mon Sep 17 00:00:00 2001 From: Loong Loong Date: Wed, 11 Feb 2026 00:55:47 +0800 Subject: [PATCH 2/4] feat: 404 --- app/components/UmamiIdentity.tsx | 28 +++++++++++++++++++++++ app/layout.tsx | 14 +++++++++++- app/not-found.tsx | 38 ++++++++++++++++++++++++++++++++ app/types/umami.d.ts | 1 + 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 app/components/UmamiIdentity.tsx create mode 100644 app/not-found.tsx diff --git a/app/components/UmamiIdentity.tsx b/app/components/UmamiIdentity.tsx new file mode 100644 index 00000000..44a87631 --- /dev/null +++ b/app/components/UmamiIdentity.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { useEffect } from "react"; + +interface UmamiIdentityProps { + user?: { + name?: string | null; + email?: string | null; + image?: string | null; + id?: string; + } | null; +} + +export function UmamiIdentity({ user }: UmamiIdentityProps) { + useEffect(() => { + if (user && window.umami && window.umami.identify) { + // 识别用户,发送用户基础信息 + // Umami v2 支持通过 session data 关联用户信息 + window.umami.identify({ + email: user.email, + name: user.name, + id: user.id, // 如果有 ID + }); + } + }, [user]); + + return null; +} diff --git a/app/layout.tsx b/app/layout.tsx index bfa4f0cc..d025a071 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -7,6 +7,8 @@ import "./globals.css"; import "katex/dist/katex.min.css"; import { ThemeProvider } from "@/app/components/ThemeProvider"; import { SpeedInsights } from "@vercel/speed-insights/next"; +import { auth } from "@/auth"; +import { UmamiIdentity } from "@/app/components/UmamiIdentity"; const geistSans = localFont({ src: "./fonts/GeistVF.woff", @@ -119,9 +121,10 @@ export const metadata: Metadata = { }, }; -export default function RootLayout({ +export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode }>) { + const session = await auth(); return ( @@ -224,6 +227,15 @@ export default function RootLayout({ gtag('config', 'G-ED4GVN8YVW'); `} + {/* Umami Analytics */} +