mirror of https://github.com/langgenius/dify.git
Add posthog
This commit is contained in:
parent
897458fbe1
commit
2b8be2c869
|
|
@ -3,6 +3,7 @@
|
|||
import type { FC } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import * as amplitude from '@amplitude/analytics-browser'
|
||||
import { sessionReplayPlugin } from '@amplitude/plugin-session-replay-browser'
|
||||
|
||||
export type IAmplitudeProps = {
|
||||
apiKey?: string
|
||||
|
|
@ -18,7 +19,12 @@ const AmplitudeProvider: FC<IAmplitudeProps> = ({
|
|||
// return
|
||||
// }
|
||||
|
||||
// Initialize Amplitude with proxy configuration to bypass CSP
|
||||
// Create Session Replay plugin instance
|
||||
const sessionReplay = sessionReplayPlugin({
|
||||
sampleRate: 1,
|
||||
})
|
||||
|
||||
// Initialize Amplitude with proxy configuration to bypass CSP and Session Replay
|
||||
amplitude.init(apiKey, {
|
||||
defaultTracking: {
|
||||
sessions: true,
|
||||
|
|
@ -31,10 +37,10 @@ const AmplitudeProvider: FC<IAmplitudeProps> = ({
|
|||
// Use Next.js proxy to bypass CSP restrictions
|
||||
serverUrl: '/api/amplitude/2/httpapi',
|
||||
})
|
||||
|
||||
amplitude.add(sessionReplay)
|
||||
// Log initialization success in development
|
||||
if (process.env.NODE_ENV === 'development')
|
||||
console.log('[Amplitude] Initialized successfully, API Key:', apiKey)
|
||||
console.log('[Amplitude] Initialized successfully with Session Replay, API Key:', apiKey)
|
||||
}, [apiKey])
|
||||
|
||||
// This is a client component that renders nothing
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
'use client'
|
||||
|
||||
import type { FC } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import { usePathname, useSearchParams } from 'next/navigation'
|
||||
import posthog from 'posthog-js'
|
||||
|
||||
const PostHogProvider: FC = () => {
|
||||
const pathname = usePathname()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
useEffect(() => {
|
||||
posthog.init('phc_Nqsm7RqSX7ZUbH9C47ZRfiUsVBCiLZIrapbWjHAYTBV', {
|
||||
api_host: 'https://us.i.posthog.com',
|
||||
person_profiles: 'identified_only', // 为已识别用户创建 profile 并存储 UTM
|
||||
autocapture: true, // 自动捕获用户交互
|
||||
capture_exceptions: true, // 捕获异常
|
||||
})
|
||||
|
||||
// 初始化后,确保捕获 UTM 参数
|
||||
// PostHog 会自动将首次访问的 UTM 保存为 Initial UTM
|
||||
if (typeof window !== 'undefined' && window.location.search) {
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const utmParams: Record<string, string> = {}
|
||||
|
||||
// 提取所有 UTM 参数
|
||||
const utmKeys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']
|
||||
utmKeys.forEach((key) => {
|
||||
const value = urlParams.get(key)
|
||||
if (value)
|
||||
utmParams[key] = value
|
||||
})
|
||||
|
||||
// 如果有 UTM 参数,注册为持久属性
|
||||
if (Object.keys(utmParams).length > 0)
|
||||
posthog.register(utmParams)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 追踪页面浏览
|
||||
useEffect(() => {
|
||||
if (pathname && posthog.__loaded) {
|
||||
let url = window.origin + pathname
|
||||
if (searchParams.toString())
|
||||
url = `${url}?${searchParams.toString()}`
|
||||
|
||||
posthog.capture('$pageview', {
|
||||
$current_url: url,
|
||||
})
|
||||
}
|
||||
}, [pathname, searchParams])
|
||||
|
||||
// This is a client component that renders nothing
|
||||
return null
|
||||
}
|
||||
|
||||
export default React.memo(PostHogProvider)
|
||||
|
|
@ -3,6 +3,7 @@ import type { Viewport } from 'next'
|
|||
import I18nServer from './components/i18n-server'
|
||||
import BrowserInitializer from './components/browser-initializer'
|
||||
import SentryInitializer from './components/sentry-initializer'
|
||||
import PostHogProvider from './components/posthog-provider'
|
||||
import { getLocaleOnServer } from '@/i18n-config/server'
|
||||
import { TanstackQueryInitializer } from '@/context/query-client'
|
||||
import { ThemeProvider } from 'next-themes'
|
||||
|
|
@ -93,15 +94,18 @@ const LocaleLayout = async ({
|
|||
enableColorScheme={false}
|
||||
>
|
||||
<BrowserInitializer>
|
||||
<SentryInitializer>
|
||||
<TanstackQueryInitializer>
|
||||
<I18nServer>
|
||||
<GlobalPublicStoreProvider>
|
||||
{children}
|
||||
</GlobalPublicStoreProvider>
|
||||
</I18nServer>
|
||||
</TanstackQueryInitializer>
|
||||
</SentryInitializer>
|
||||
<>
|
||||
<PostHogProvider />
|
||||
<SentryInitializer>
|
||||
<TanstackQueryInitializer>
|
||||
<I18nServer>
|
||||
<GlobalPublicStoreProvider>
|
||||
{children}
|
||||
</GlobalPublicStoreProvider>
|
||||
</I18nServer>
|
||||
</TanstackQueryInitializer>
|
||||
</SentryInitializer>
|
||||
</>
|
||||
</BrowserInitializer>
|
||||
</ThemeProvider>
|
||||
<RoutePrefixHandle />
|
||||
|
|
|
|||
|
|
@ -131,6 +131,8 @@ const nextConfig = {
|
|||
'@heroicons/react'
|
||||
],
|
||||
},
|
||||
// This is required to support PostHog trailing slash API requests
|
||||
skipTrailingSlashRedirect: true,
|
||||
// fix all before production. Now it slow the develop speed.
|
||||
eslint: {
|
||||
// Warning: This allows production builds to successfully complete even if
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
"knip": "knip"
|
||||
},
|
||||
"dependencies": {
|
||||
"@amplitude/plugin-session-replay-browser": "^1.23.2",
|
||||
"@amplitude/unified": "1.0.0-beta.9",
|
||||
"@emoji-mart/data": "^1.2.1",
|
||||
"@floating-ui/react": "^0.26.28",
|
||||
|
|
@ -106,6 +107,8 @@
|
|||
"next-pwa": "^5.6.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"pinyin-pro": "^3.27.0",
|
||||
"posthog-js": "^1.288.0",
|
||||
"posthog-node": "^5.11.1",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"qs": "^6.14.0",
|
||||
"react": "19.1.1",
|
||||
|
|
@ -131,9 +134,9 @@
|
|||
"remark-gfm": "^4.0.1",
|
||||
"remark-math": "^6.0.0",
|
||||
"scheduler": "^0.26.0",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"semver": "^7.7.3",
|
||||
"sharp": "^0.33.5",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"sortablejs": "^1.15.6",
|
||||
"swr": "^2.3.6",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@ importers:
|
|||
|
||||
.:
|
||||
dependencies:
|
||||
'@amplitude/plugin-session-replay-browser':
|
||||
specifier: ^1.23.2
|
||||
version: 1.23.2(@amplitude/rrweb@2.0.0-alpha.33)(rollup@2.79.2)
|
||||
'@amplitude/unified':
|
||||
specifier: 1.0.0-beta.9
|
||||
version: 1.0.0-beta.9(@amplitude/rrweb@2.0.0-alpha.33)(rollup@2.79.2)
|
||||
|
|
@ -246,6 +249,12 @@ importers:
|
|||
pinyin-pro:
|
||||
specifier: ^3.27.0
|
||||
version: 3.27.0
|
||||
posthog-js:
|
||||
specifier: ^1.288.0
|
||||
version: 1.288.0
|
||||
posthog-node:
|
||||
specifier: ^5.11.1
|
||||
version: 5.11.1
|
||||
qrcode.react:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0(react@19.1.1)
|
||||
|
|
@ -2619,6 +2628,9 @@ packages:
|
|||
'@polka/url@1.0.0-next.29':
|
||||
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
||||
|
||||
'@posthog/core@1.5.1':
|
||||
resolution: {integrity: sha512-8fdEzfvdStr45iIncTD+gnqp45UBTUpRK/bwB4shP5usCKytnPIeilU8rIpNBOVjJPwfW+2N8yWhQ0l14x191Q==}
|
||||
|
||||
'@preact/signals-core@1.12.1':
|
||||
resolution: {integrity: sha512-BwbTXpj+9QutoZLQvbttRg5x3l5468qaV2kufh+51yha1c53ep5dY4kTuZR35+3pAZxpfQerGJiQqg34ZNZ6uA==}
|
||||
|
||||
|
|
@ -4304,6 +4316,9 @@ packages:
|
|||
core-js-pure@3.46.0:
|
||||
resolution: {integrity: sha512-NMCW30bHNofuhwLhYPt66OLOKTMbOhgTTatKVbaQC3KRHpTCiRIBYvtshr+NBYSnBxwAFhjW/RfJ0XbIjS16rw==}
|
||||
|
||||
core-js@3.46.0:
|
||||
resolution: {integrity: sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==}
|
||||
|
||||
core-util-is@1.0.3:
|
||||
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||
|
||||
|
|
@ -7064,6 +7079,16 @@ packages:
|
|||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
posthog-js@1.288.0:
|
||||
resolution: {integrity: sha512-KOeF8PK/zxBuFB4b3FVkj5JxSWAfSOrfDVvWj5VrJNBGYqr8igDbAl10huFv9NB4/K9XeIWQ7AzPPGV4D3lbEA==}
|
||||
|
||||
posthog-node@5.11.1:
|
||||
resolution: {integrity: sha512-P6rtzdCVvS718r011x0W0cwmJo7gfP5YWXiWh0/S3OL+pnHtcqbWHDjrtRxN/IrMkjZWzMU4xDze5vRK/cZ23w==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
preact@10.27.2:
|
||||
resolution: {integrity: sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==}
|
||||
|
||||
prebuild-install@7.1.3:
|
||||
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -8387,6 +8412,9 @@ packages:
|
|||
web-namespaces@2.0.1:
|
||||
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
|
||||
|
||||
web-vitals@4.2.4:
|
||||
resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==}
|
||||
|
||||
web-vitals@5.1.0:
|
||||
resolution: {integrity: sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg==}
|
||||
|
||||
|
|
@ -8752,7 +8780,7 @@ snapshots:
|
|||
|
||||
'@amplitude/rrweb-packer@2.0.0-alpha.32':
|
||||
dependencies:
|
||||
'@amplitude/rrweb-types': 2.0.0-alpha.32
|
||||
'@amplitude/rrweb-types': 2.0.0-alpha.33
|
||||
fflate: 0.4.8
|
||||
|
||||
'@amplitude/rrweb-plugin-console-record@2.0.0-alpha.32(@amplitude/rrweb@2.0.0-alpha.33)':
|
||||
|
|
@ -8762,7 +8790,7 @@ snapshots:
|
|||
'@amplitude/rrweb-record@2.0.0-alpha.32':
|
||||
dependencies:
|
||||
'@amplitude/rrweb': 2.0.0-alpha.33
|
||||
'@amplitude/rrweb-types': 2.0.0-alpha.32
|
||||
'@amplitude/rrweb-types': 2.0.0-alpha.33
|
||||
|
||||
'@amplitude/rrweb-snapshot@2.0.0-alpha.33':
|
||||
dependencies:
|
||||
|
|
@ -11127,6 +11155,10 @@ snapshots:
|
|||
|
||||
'@polka/url@1.0.0-next.29': {}
|
||||
|
||||
'@posthog/core@1.5.1':
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
|
||||
'@preact/signals-core@1.12.1': {}
|
||||
|
||||
'@radix-ui/primitive@1.1.3': {}
|
||||
|
|
@ -13059,6 +13091,8 @@ snapshots:
|
|||
|
||||
core-js-pure@3.46.0: {}
|
||||
|
||||
core-js@3.46.0: {}
|
||||
|
||||
core-util-is@1.0.3: {}
|
||||
|
||||
cose-base@1.0.3:
|
||||
|
|
@ -16627,6 +16661,20 @@ snapshots:
|
|||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
posthog-js@1.288.0:
|
||||
dependencies:
|
||||
'@posthog/core': 1.5.1
|
||||
core-js: 3.46.0
|
||||
fflate: 0.4.8
|
||||
preact: 10.27.2
|
||||
web-vitals: 4.2.4
|
||||
|
||||
posthog-node@5.11.1:
|
||||
dependencies:
|
||||
'@posthog/core': 1.5.1
|
||||
|
||||
preact@10.27.2: {}
|
||||
|
||||
prebuild-install@7.1.3:
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
|
|
@ -18108,6 +18156,8 @@ snapshots:
|
|||
|
||||
web-namespaces@2.0.1: {}
|
||||
|
||||
web-vitals@4.2.4: {}
|
||||
|
||||
web-vitals@5.1.0: {}
|
||||
|
||||
webidl-conversions@4.0.2: {}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
import posthog from 'posthog-js'
|
||||
import type { UserProfileResponse } from '@/models/common'
|
||||
|
||||
/**
|
||||
* 从 URL 中提取 UTM 参数
|
||||
*/
|
||||
function getUTMParams(): Record<string, string> {
|
||||
const utmParams: Record<string, string> = {}
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
|
||||
const utmKeys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']
|
||||
|
||||
utmKeys.forEach((key) => {
|
||||
const value = urlParams.get(key)
|
||||
if (value)
|
||||
utmParams[key] = value
|
||||
})
|
||||
|
||||
return utmParams
|
||||
}
|
||||
|
||||
/**
|
||||
* 识别用户身份并上报到 PostHog
|
||||
* @param userProfile - 完整的用户资料对象
|
||||
*/
|
||||
export function identifyUser(userProfile: UserProfileResponse) {
|
||||
// 检查 PostHog 是否已加载
|
||||
if (!posthog.__loaded)
|
||||
return
|
||||
|
||||
// 检查是否有有效的用户 ID
|
||||
if (!userProfile?.id)
|
||||
return
|
||||
|
||||
try {
|
||||
const properties: Record<string, any> = {}
|
||||
|
||||
// 用户基本信息
|
||||
if (userProfile.email)
|
||||
properties.email = userProfile.email
|
||||
if (userProfile.name)
|
||||
properties.name = userProfile.name
|
||||
if (userProfile.created_at)
|
||||
properties.created_at = userProfile.created_at
|
||||
if (userProfile.interface_language)
|
||||
properties.interface_language = userProfile.interface_language
|
||||
if (userProfile.interface_theme)
|
||||
properties.interface_theme = userProfile.interface_theme
|
||||
if (userProfile.timezone)
|
||||
properties.timezone = userProfile.timezone
|
||||
|
||||
// 添加当前 URL 的 UTM 参数(如果有)
|
||||
const utmParams = getUTMParams()
|
||||
if (Object.keys(utmParams).length > 0) {
|
||||
Object.entries(utmParams).forEach(([key, value]) => {
|
||||
properties[key] = value
|
||||
})
|
||||
}
|
||||
|
||||
posthog.identify(userProfile.id, properties)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('[PostHog] identify 失败:', error)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue