diff --git a/docs/Analytics.zh-CN.md b/docs/Analytics.zh-CN.md
new file mode 100644
index 000000000000..3537546eff22
--- /dev/null
+++ b/docs/Analytics.zh-CN.md
@@ -0,0 +1,19 @@
+# 数据统计
+
+为更好地帮助分析 LobeChat 的用户使用情况,我们在 LobeChat 中集成了若干免费 / 开源的数据统计服务,用于收集用户的使用情况,你可以按需开启。
+
+## Vercel Analytics
+
+[Vercel Analytics](https://vercel.com/analytics) 是 Vercel 推出的一款数据分析服务,它可以帮助你收集网站的访问情况,包括访问量、访问来源、访问设备等等。
+
+由于我们推荐使用 Vercel 一键部署 LobeChat,因此我们在代码中默认集成了 Vercel Analytics,你可以通过自行打开部署项目中 \[Insights] tab,查看你的应用访问情况。
+
+Vercel Analytics 提供了 2500 次 / 月的免费 Web Analytics Events (可以理解为 PV),对于个人部署自用的产品来说基本够用。
+
+如果你需要了解 Vercel Analytics 的详细使用教程,请查阅[Vercel Web Analytics 快速开始](https://vercel.com/docs/analytics/quickstart)
+
+如果你不需要 Vercel Analytics,你可以通过设置环境变量 `NEXT_PUBLIC_ANALYTICS_VERCEL=0` 来关闭它。
+
+## 🚧 Posthog
+
+## 🚧 Mixpanel
diff --git a/docs/Environment-Variable.zh-CN.md b/docs/Environment-Variable.zh-CN.md
index 256c244b416a..c7c289d9491a 100644
--- a/docs/Environment-Variable.zh-CN.md
+++ b/docs/Environment-Variable.zh-CN.md
@@ -17,6 +17,13 @@ LobeChat 在部署时提供了一些额外的配置项,使用环境变量进
- [`PLUGINS_INDEX_URL`](#plugins_index_url)
- [角色服务](#角色服务)
- [`AGENTS_INDEX_URL`](#agents_index_url)
+- [数据统计](#数据统计)
+ - [Vercel Analytics](#vercel-analytics)
+ - [Mixpanel Analytics](#mixpanel-analytics)
+ - [`NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN`](#next_public_mixpanel_project_token)
+ - [`NEXT_PUBLIC_MIXPANEL_DEBUG`](#next_public_mixpanel_debug)
+ - [Posthog Analytics](#posthog-analytics)
+ - [`NEXT_PUBLIC_POSTHOG_DEBUG`](#next_public_posthog_debug)
- [开发环境](#开发环境)
- [`DEV_API_END_PORT_URL`](#dev_api_end_port_url)
@@ -94,7 +101,76 @@ LobeChat 在部署时提供了一些额外的配置项,使用环境变量进
- 描述:LobeChat 角色市场的索引地址,如果你自行部署了角色市场的服务,可以使用该变量来覆盖默认的插件市场地址
- 默认值:`https://chat-agents.lobehub.com`
-
+## 数据统计
+
+### Vercel Analytics
+
+#### `NEXT_PUBLIC_ANALYTICS_VERCEL`
+
+- 类型:可选
+- 描述:用于配置 Vercel Analytics 的环境变量,当设为 `0` 则关闭 Vercel Analytics
+- 默认值: -
+- 示例:`0`
+
+#### `NEXT_PUBLIC_VERCEL_DEBUG`
+
+- 类型:可选
+- 描述:用于开启 Vercel Analytics 的调试模式
+- 默认值: -
+- 示例:`1`
+
+### Mixpanel Analytics
+
+#### `NEXT_PUBLIC_ANALYTICS_MIXPANEL`
+
+- 类型:可选
+- 描述:用于开启 [Mixpanel Analytics][mixpanel-analytics-url] 的环境变量,设为 `1` 时开启 Mixpanel Analytics
+- 默认值: -
+- 示例:`1`
+
+### `NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN`
+
+- 类型:可选
+- 描述:设置 Mixpanel 项目的识别 Token,可以在[这里][mixpanel-project-url]找到
+- 默认值: -
+- 示例:`60db2abae7fdd29961f4e8f91b074b3a`
+
+### `NEXT_PUBLIC_MIXPANEL_DEBUG`
+
+- 类型:可选
+- 描述:开启 Mixpanel 的调试模式
+- 默认值: -
+- 示例:`1`
+
+### Posthog Analytics
+
+#### `NEXT_PUBLIC_ANALYTICS_POSTHOG`
+
+- 类型:可选
+- 描述:用于开启 [PostHog Analytics][posthog-analytics-url] 的环境变量,设为 `1` 时开启 PostHog Analytics
+- 默认值: -
+- 示例:`1`
+
+#### `NEXT_PUBLIC_POSTHOG_KEY`
+
+- 类型:可选
+- 描述:设置 PostHog 项目 Key
+- 默认值: -
+- 示例:`phc_xxxxxxxx`
+
+#### `NEXT_PUBLIC_POSTHOG_HOST`
+
+- 类型:可选
+- 描述:设置 PostHog 服务的部署地址,默认为官方的 SAAS 地址
+- 默认值:`https://app.posthog.com`
+- 示例:`https://example.com`
+
+### `NEXT_PUBLIC_POSTHOG_DEBUG`
+
+- 类型:可选
+- 描述:开启 PostHog 的调试模式
+- 默认值: -
+- 示例:`1`
## 开发环境
@@ -106,4 +182,7 @@ LobeChat 在部署时提供了一些额外的配置项,使用环境变量进
- 示例:`https://chat-preview.lobehub.com`
[azure-api-verion-url]: https://docs.microsoft.com/zh-cn/azure/developer/javascript/api-reference/es-modules/azure-sdk/ai-translation/translationconfiguration?view=azure-node-latest#api-version
+[mixpanel-analytics-url]: https://mixpanel.com
+[mixpanel-project-url]: https://mixpanel.com/settings/project
[openai-api-page]: https://platform.openai.com/account/api-keys
+[posthog-analytics-url]: https://posthog.com
diff --git a/next.config.mjs b/next.config.mjs
index 7afd14c13825..e12d3c177f46 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -41,6 +41,10 @@ const nextConfig = {
},
];
},
+ env: {
+ AGENTS_INDEX_URL: process.env.AGENTS_INDEX_URL,
+ PLUGINS_INDEX_URL: process.env.PLUGINS_INDEX_URL,
+ },
};
export default isProd ? withPWA(nextConfig) : nextConfig;
diff --git a/package.json b/package.json
index 44944df58ee2..b536ed4463c5 100644
--- a/package.json
+++ b/package.json
@@ -86,10 +86,12 @@
"immer": "^10",
"lodash-es": "^4",
"lucide-react": "latest",
+ "mixpanel-browser": "^2",
"nanoid": "^5",
"next": "^13.5.3",
"openai": "^4",
"polished": "^4",
+ "posthog-js": "^1",
"react": "^18",
"react-dom": "^18",
"react-hotkeys-hook": "^4",
@@ -116,6 +118,7 @@
"@testing-library/react": "^14",
"@types/chroma-js": "^2",
"@types/lodash-es": "^4",
+ "@types/mixpanel-browser": "^2",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
diff --git a/src/components/Analytics/Mixpanel.tsx b/src/components/Analytics/Mixpanel.tsx
new file mode 100644
index 000000000000..cfe9f38e17b2
--- /dev/null
+++ b/src/components/Analytics/Mixpanel.tsx
@@ -0,0 +1,23 @@
+'use client';
+
+import mixpanel from 'mixpanel-browser';
+import { memo, useEffect } from 'react';
+
+import { getClientConfig } from '@/config/client';
+
+const { MIXPANEL_PROJECT_TOKEN, MIXPANEL_DEBUG } = getClientConfig();
+
+const MixpanelAnalytics = memo(() => {
+ useEffect(() => {
+ if (!MIXPANEL_PROJECT_TOKEN) return;
+
+ mixpanel.init(MIXPANEL_PROJECT_TOKEN, {
+ debug: MIXPANEL_DEBUG,
+ persistence: 'localStorage',
+ track_pageview: true,
+ });
+ }, []);
+ return null;
+});
+
+export default MixpanelAnalytics;
diff --git a/src/components/Analytics/Plausible.tsx b/src/components/Analytics/Plausible.tsx
new file mode 100644
index 000000000000..b0b1c82a6ef8
--- /dev/null
+++ b/src/components/Analytics/Plausible.tsx
@@ -0,0 +1,18 @@
+'use client';
+
+import Script from 'next/script';
+import { memo } from 'react';
+
+import { getClientConfig } from '@/config/client';
+
+const { PLAUSIBLE_DOMAIN } = getClientConfig();
+
+const PlausibleAnalytics = memo(() => {
+ return (
+ PLAUSIBLE_DOMAIN && (
+
+ )
+ );
+});
+
+export default PlausibleAnalytics;
diff --git a/src/components/Analytics/Posthog.tsx b/src/components/Analytics/Posthog.tsx
new file mode 100644
index 000000000000..58f57e4a0679
--- /dev/null
+++ b/src/components/Analytics/Posthog.tsx
@@ -0,0 +1,23 @@
+'use client';
+
+import posthog from 'posthog-js';
+import { FC, memo, useEffect } from 'react';
+
+import { getClientConfig } from '@/config/client';
+
+const { POSTHOG_HOST, POSTHOG_KEY, POSTHOG_DEBUG } = getClientConfig();
+
+const PostHog: FC = memo(() => {
+ useEffect(() => {
+ if (!POSTHOG_KEY) return;
+
+ posthog.init(POSTHOG_KEY, {
+ api_host: POSTHOG_HOST ?? 'https://app.posthog.com',
+ debug: POSTHOG_DEBUG,
+ });
+ }, []);
+
+ return null;
+});
+
+export default PostHog;
diff --git a/src/components/Analytics/Vercel.tsx b/src/components/Analytics/Vercel.tsx
new file mode 100644
index 000000000000..225a27a470b1
--- /dev/null
+++ b/src/components/Analytics/Vercel.tsx
@@ -0,0 +1,12 @@
+'use client';
+
+import { Analytics } from '@vercel/analytics/react';
+import { memo } from 'react';
+
+import { getClientConfig } from '@/config/client';
+
+const { VERCEL_DEBUG } = getClientConfig();
+
+const VercelAnalytics = memo(() => );
+
+export default VercelAnalytics;
diff --git a/src/components/Analytics/index.tsx b/src/components/Analytics/index.tsx
index 39f44613ac67..0d0bee7e042e 100644
--- a/src/components/Analytics/index.tsx
+++ b/src/components/Analytics/index.tsx
@@ -1,7 +1,24 @@
-import { Analytics as VercelAnalytics } from '@vercel/analytics/react';
+import dynamic from 'next/dynamic';
+
+import { getClientConfig } from '@/config/client';
+
+const Vercel = dynamic(() => import('./Vercel'), { ssr: false });
+const Mixpanel = dynamic(() => import('./Mixpanel'), { ssr: false });
+const Plausible = dynamic(() => import('./Plausible'), { ssr: false });
+const Posthog = dynamic(() => import('./Posthog'), { ssr: false });
+
+const { ANALYTICS_VERCEL, ANALYTICS_POSTHOG, ANALYTICS_MIXPANEL, ANALYTICS_PLAUSIBLE } =
+ getClientConfig();
const Analytics = () => {
- return ;
+ return (
+ <>
+ {ANALYTICS_VERCEL && }
+ {ANALYTICS_PLAUSIBLE && }
+ {ANALYTICS_MIXPANEL && }
+ {ANALYTICS_POSTHOG && }
+ >
+ );
};
export default Analytics;
diff --git a/src/components/AppTheme/index.tsx b/src/components/AppTheme/index.tsx
index eb0286d07a79..50ac9724c30a 100644
--- a/src/components/AppTheme/index.tsx
+++ b/src/components/AppTheme/index.tsx
@@ -20,9 +20,9 @@ export interface AppThemeProps {
const AppTheme = memo(
({ children, defaultAppearance, defaultPrimaryColor, defaultNeutralColor }) => {
- console.log('server:appearance', defaultAppearance);
- console.log('server:primaryColor', defaultPrimaryColor);
- console.log('server:neutralColor', defaultNeutralColor);
+ // console.log('server:appearance', defaultAppearance);
+ // console.log('server:primaryColor', defaultPrimaryColor);
+ // console.log('server:neutralColor', defaultNeutralColor);
const themeMode = useGlobalStore((s) => s.settings.themeMode);
const [primaryColor, neutralColor] = useGlobalStore((s) => [
@@ -31,7 +31,6 @@ const AppTheme = memo(
]);
useEffect(() => {
- console.log(primaryColor);
setCookie(LOBE_THEME_PRIMARY_COLOR, primaryColor);
}, [primaryColor]);
diff --git a/src/config/client.ts b/src/config/client.ts
new file mode 100644
index 000000000000..d837a9ed96a7
--- /dev/null
+++ b/src/config/client.ts
@@ -0,0 +1,46 @@
+/* eslint-disable sort-keys-fix/sort-keys-fix , typescript-sort-keys/interface */
+
+declare global {
+ // eslint-disable-next-line @typescript-eslint/no-namespace
+ namespace NodeJS {
+ interface ProcessEnv {
+ AGENTS_INDEX_URL?: string;
+ PLUGINS_INDEX_URL?: string;
+
+ NEXT_PUBLIC_ANALYTICS_VERCEL?: string;
+ NEXT_PUBLIC_VERCEL_DEBUG?: string;
+
+ NEXT_PUBLIC_ANALYTICS_MIXPANEL?: string;
+ NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN?: string;
+ NEXT_PUBLIC_MIXPANEL_DEBUG?: string;
+
+ NEXT_PUBLIC_ANALYTICS_PLAUSIBLE?: string;
+ NEXT_PUBLIC_PLAUSIBLE_DOMAIN?: string;
+
+ NEXT_PUBLIC_ANALYTICS_POSTHOG: string;
+ NEXT_PUBLIC_POSTHOG_KEY: string;
+ NEXT_PUBLIC_POSTHOG_HOST: string;
+ NEXT_PUBLIC_POSTHOG_DEBUG: string;
+ }
+ }
+}
+
+export const getClientConfig = () => ({
+ AGENTS_INDEX_URL: process.env.AGENTS_INDEX_URL,
+ PLUGINS_INDEX_URL: process.env.PLUGINS_INDEX_URL,
+
+ ANALYTICS_VERCEL: process.env.NEXT_PUBLIC_ANALYTICS_VERCEL !== '0',
+ VERCEL_DEBUG: process.env.NEXT_PUBLIC_VERCEL_DEBUG === '1',
+
+ ANALYTICS_PLAUSIBLE: process.env.NEXT_PUBLIC_ANALYTICS_PLAUSIBLE === '1',
+ PLAUSIBLE_DOMAIN: process.env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN,
+
+ ANALYTICS_MIXPANEL: process.env.NEXT_PUBLIC_ANALYTICS_MIXPANEL === '1',
+ MIXPANEL_PROJECT_TOKEN: process.env.NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN,
+ MIXPANEL_DEBUG: process.env.NEXT_PUBLIC_MIXPANEL_DEBUG === '1',
+
+ ANALYTICS_POSTHOG: process.env.NEXT_PUBLIC_ANALYTICS_POSTHOG === '1',
+ POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY,
+ POSTHOG_HOST: process.env.NEXT_PUBLIC_POSTHOG_HOST,
+ POSTHOG_DEBUG: process.env.NEXT_PUBLIC_POSTHOG_DEBUG === '1',
+});
diff --git a/src/config/server.ts b/src/config/server.ts
index a2578d365633..a0cd62fb0ab3 100644
--- a/src/config/server.ts
+++ b/src/config/server.ts
@@ -1,12 +1,16 @@
+/* eslint-disable sort-keys-fix/sort-keys-fix , typescript-sort-keys/interface */
+
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace NodeJS {
interface ProcessEnv {
ACCESS_CODE?: string;
- AZURE_API_KEY?: string;
- AZURE_API_VERSION?: string;
+
OPENAI_API_KEY?: string;
OPENAI_PROXY_URL?: string;
+
+ AZURE_API_KEY?: string;
+ AZURE_API_VERSION?: string;
USE_AZURE_OPENAI?: string;
}
}
@@ -19,13 +23,12 @@ export const getServerConfig = () => {
return {
ACCESS_CODE: process.env.ACCESS_CODE,
- /* eslint-disable sort-keys-fix/sort-keys-fix */
- AZURE_API_KEY: process.env.AZURE_API_KEY,
- AZURE_API_VERSION: process.env.AZURE_API_VERSION,
- /* eslint-enabled */
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
OPENAI_PROXY_URL: process.env.OPENAI_PROXY_URL,
+
+ AZURE_API_KEY: process.env.AZURE_API_KEY,
+ AZURE_API_VERSION: process.env.AZURE_API_VERSION,
USE_AZURE_OPENAI: process.env.USE_AZURE_OPENAI === '1',
};
};
diff --git a/src/const/url.ts b/src/const/url.ts
index ce36f5896e68..7e3bafb49e94 100644
--- a/src/const/url.ts
+++ b/src/const/url.ts
@@ -1,5 +1,6 @@
import urlJoin from 'url-join';
+import { getClientConfig } from '@/config/client';
import { localeOptions } from '@/locales/options';
import { Locales } from '@/locales/resources';
@@ -13,7 +14,7 @@ export const FEEDBACK = pkg.bugs.url;
export const DISCORD = 'https://discord.gg/AYFPHvv2jT';
export const PLUGINS_INDEX_URL =
- process.env.PLUGINS_INDEX_URL ?? 'https://chat-plugins.lobehub.com';
+ getClientConfig().PLUGINS_INDEX_URL ?? 'https://chat-plugins.lobehub.com';
export const DEFAULT_LANG = 'en-US';
@@ -26,7 +27,8 @@ export const getPluginIndexJSON = (lang: Locales = DEFAULT_LANG, baseUrl = PLUGI
return urlJoin(baseUrl, `index.${lang}.json`);
};
-export const AGENTS_INDEX_URL = process.env.AGENTS_INDEX_URL ?? 'https://chat-agents.lobehub.com';
+export const AGENTS_INDEX_URL =
+ getClientConfig().AGENTS_INDEX_URL ?? 'https://chat-agents.lobehub.com';
export const getAgentIndexJSON = (lang: Locales = DEFAULT_LANG, baseUrl = AGENTS_INDEX_URL) => {
if (checkLang(lang)) return baseUrl;