@lobehub/chat 1.97.1 → 1.97.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/changelog/v1.json +14 -0
- package/docker-compose/local/docker-compose.yml +1 -1
- package/docker-compose/local/logto/docker-compose.yml +1 -1
- package/docker-compose/local/zitadel/docker-compose.yml +1 -1
- package/docker-compose/production/logto/docker-compose.yml +1 -1
- package/docker-compose/production/zitadel/docker-compose.yml +1 -1
- package/docs/self-hosting/server-database/docker-compose.mdx +1 -1
- package/package.json +2 -1
- package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/index.tsx +2 -0
- package/src/app/[variants]/(main)/chat/(workspace)/_layout/Mobile/index.tsx +2 -0
- package/src/app/[variants]/(main)/chat/@session/features/SessionListContent/List/index.tsx +35 -2
- package/src/components/Analytics/LobeAnalyticsProvider.tsx +68 -0
- package/src/components/Analytics/LobeAnalyticsProviderWrapper.tsx +23 -0
- package/src/components/Analytics/MainInterfaceTracker.tsx +52 -0
- package/src/components/Analytics/index.tsx +0 -8
- package/src/const/analytics.ts +3 -0
- package/src/features/AgentSetting/store/action.ts +26 -1
- package/src/features/ChatInput/useSend.ts +29 -1
- package/src/features/User/UserLoginOrSignup/Community.tsx +14 -1
- package/src/layout/GlobalProvider/index.tsx +4 -1
- package/src/libs/analytics/index.ts +25 -0
- package/src/server/services/user/index.test.ts +8 -0
- package/src/server/services/user/index.ts +18 -0
- package/src/store/session/slices/session/action.ts +28 -2
- package/src/store/user/slices/common/action.ts +9 -2
- package/tests/setup.ts +11 -0
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,48 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
### [Version 1.97.3](https://github.com/lobehub/lobe-chat/compare/v1.97.2...v1.97.3)
|
6
|
+
|
7
|
+
<sup>Released on **2025-07-10**</sup>
|
8
|
+
|
9
|
+
<br/>
|
10
|
+
|
11
|
+
<details>
|
12
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
13
|
+
|
14
|
+
</details>
|
15
|
+
|
16
|
+
<div align="right">
|
17
|
+
|
18
|
+
[](#readme-top)
|
19
|
+
|
20
|
+
</div>
|
21
|
+
|
22
|
+
### [Version 1.97.2](https://github.com/lobehub/lobe-chat/compare/v1.97.1...v1.97.2)
|
23
|
+
|
24
|
+
<sup>Released on **2025-07-10**</sup>
|
25
|
+
|
26
|
+
#### 💄 Styles
|
27
|
+
|
28
|
+
- **misc**: Implement data analytics event tracking framework.
|
29
|
+
|
30
|
+
<br/>
|
31
|
+
|
32
|
+
<details>
|
33
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
34
|
+
|
35
|
+
#### Styles
|
36
|
+
|
37
|
+
- **misc**: Implement data analytics event tracking framework, closes [#8352](https://github.com/lobehub/lobe-chat/issues/8352) ([f433aca](https://github.com/lobehub/lobe-chat/commit/f433aca))
|
38
|
+
|
39
|
+
</details>
|
40
|
+
|
41
|
+
<div align="right">
|
42
|
+
|
43
|
+
[](#readme-top)
|
44
|
+
|
45
|
+
</div>
|
46
|
+
|
5
47
|
### [Version 1.97.1](https://github.com/lobehub/lobe-chat/compare/v1.97.0...v1.97.1)
|
6
48
|
|
7
49
|
<sup>Released on **2025-07-10**</sup>
|
package/changelog/v1.json
CHANGED
@@ -1,4 +1,18 @@
|
|
1
1
|
[
|
2
|
+
{
|
3
|
+
"children": {},
|
4
|
+
"date": "2025-07-10",
|
5
|
+
"version": "1.97.3"
|
6
|
+
},
|
7
|
+
{
|
8
|
+
"children": {
|
9
|
+
"improvements": [
|
10
|
+
"Implement data analytics event tracking framework."
|
11
|
+
]
|
12
|
+
},
|
13
|
+
"date": "2025-07-10",
|
14
|
+
"version": "1.97.2"
|
15
|
+
},
|
2
16
|
{
|
3
17
|
"children": {
|
4
18
|
"fixes": [
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.97.
|
3
|
+
"version": "1.97.3",
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
5
5
|
"keywords": [
|
6
6
|
"framework",
|
@@ -144,6 +144,7 @@
|
|
144
144
|
"@lobechat/electron-server-ipc": "workspace:*",
|
145
145
|
"@lobechat/file-loaders": "workspace:*",
|
146
146
|
"@lobechat/web-crawler": "workspace:*",
|
147
|
+
"@lobehub/analytics": "^1.5.1",
|
147
148
|
"@lobehub/charts": "^2.0.0",
|
148
149
|
"@lobehub/chat-plugin-sdk": "^1.32.4",
|
149
150
|
"@lobehub/chat-plugins-gateway": "^1.9.0",
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { Suspense } from 'react';
|
2
2
|
import { Flexbox } from 'react-layout-kit';
|
3
3
|
|
4
|
+
import MainInterfaceTracker from '@/components/Analytics/MainInterfaceTracker';
|
4
5
|
import BrandTextLoading from '@/components/Loading/BrandTextLoading';
|
5
6
|
|
6
7
|
import { LayoutProps } from '../type';
|
@@ -31,6 +32,7 @@ const Layout = ({ children, topic, conversation, portal }: LayoutProps) => {
|
|
31
32
|
</Portal>
|
32
33
|
<TopicPanel>{topic}</TopicPanel>
|
33
34
|
</Flexbox>
|
35
|
+
<MainInterfaceTracker />
|
34
36
|
</>
|
35
37
|
);
|
36
38
|
};
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import MainInterfaceTracker from '@/components/Analytics/MainInterfaceTracker';
|
1
2
|
import MobileContentLayout from '@/components/server/MobileNavLayout';
|
2
3
|
|
3
4
|
import { LayoutProps } from '../type';
|
@@ -13,6 +14,7 @@ const Layout = ({ children, topic, conversation, portal }: LayoutProps) => {
|
|
13
14
|
</MobileContentLayout>
|
14
15
|
<TopicModal>{topic}</TopicModal>
|
15
16
|
{portal}
|
17
|
+
<MainInterfaceTracker />
|
16
18
|
</>
|
17
19
|
);
|
18
20
|
};
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import { useAnalytics } from '@lobehub/analytics/react';
|
1
2
|
import { Empty } from 'antd';
|
2
3
|
import { createStyles } from 'antd-style';
|
3
4
|
import Link from 'next/link';
|
@@ -9,8 +10,10 @@ import LazyLoad from 'react-lazy-load';
|
|
9
10
|
import { SESSION_CHAT_URL } from '@/const/url';
|
10
11
|
import { useSwitchSession } from '@/hooks/useSwitchSession';
|
11
12
|
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
12
|
-
import { useSessionStore } from '@/store/session';
|
13
|
-
import { sessionSelectors } from '@/store/session/selectors';
|
13
|
+
import { getSessionStoreState, useSessionStore } from '@/store/session';
|
14
|
+
import { sessionGroupSelectors, sessionSelectors } from '@/store/session/selectors';
|
15
|
+
import { getUserStoreState } from '@/store/user';
|
16
|
+
import { userProfileSelectors } from '@/store/user/selectors';
|
14
17
|
import { LobeAgentSession } from '@/types/session';
|
15
18
|
|
16
19
|
import SkeletonList from '../../SkeletonList';
|
@@ -29,6 +32,7 @@ interface SessionListProps {
|
|
29
32
|
}
|
30
33
|
const SessionList = memo<SessionListProps>(({ dataSource, groupId, showAddButton = true }) => {
|
31
34
|
const { t } = useTranslation('chat');
|
35
|
+
const { analytics } = useAnalytics();
|
32
36
|
const { styles } = useStyles();
|
33
37
|
|
34
38
|
const isInit = useSessionStore(sessionSelectors.isSessionListInit);
|
@@ -49,6 +53,35 @@ const SessionList = memo<SessionListProps>(({ dataSource, groupId, showAddButton
|
|
49
53
|
onClick={(e) => {
|
50
54
|
e.preventDefault();
|
51
55
|
switchSession(id);
|
56
|
+
|
57
|
+
// Enhanced analytics tracking
|
58
|
+
if (analytics) {
|
59
|
+
const userStore = getUserStoreState();
|
60
|
+
const sessionStore = getSessionStoreState();
|
61
|
+
|
62
|
+
const userId = userProfileSelectors.userId(userStore);
|
63
|
+
const session = sessionSelectors.getSessionById(id)(sessionStore);
|
64
|
+
|
65
|
+
if (session) {
|
66
|
+
const sessionGroupId = session.group || 'default';
|
67
|
+
const group = sessionGroupSelectors.getGroupById(sessionGroupId)(sessionStore);
|
68
|
+
const groupName =
|
69
|
+
group?.name || (sessionGroupId === 'default' ? 'Default' : 'Unknown');
|
70
|
+
|
71
|
+
analytics?.track({
|
72
|
+
name: 'switch_session',
|
73
|
+
properties: {
|
74
|
+
assistant_name: session.meta?.title || 'Untitled Agent',
|
75
|
+
assistant_tags: session.meta?.tags || [],
|
76
|
+
group_id: sessionGroupId,
|
77
|
+
group_name: groupName,
|
78
|
+
session_id: id,
|
79
|
+
spm: 'homepage.chat.session_list_item.click',
|
80
|
+
user_id: userId || 'anonymous',
|
81
|
+
},
|
82
|
+
});
|
83
|
+
}
|
84
|
+
}
|
52
85
|
}}
|
53
86
|
>
|
54
87
|
<SessionItem id={id} />
|
@@ -0,0 +1,68 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { createSingletonAnalytics } from '@lobehub/analytics';
|
4
|
+
import { AnalyticsProvider } from '@lobehub/analytics/react';
|
5
|
+
import { ReactNode, memo, useMemo } from 'react';
|
6
|
+
|
7
|
+
import { BUSINESS_LINE } from '@/const/analytics';
|
8
|
+
import { isDesktop } from '@/const/version';
|
9
|
+
import { isDev } from '@/utils/env';
|
10
|
+
|
11
|
+
type Props = {
|
12
|
+
children: ReactNode;
|
13
|
+
debugPosthog: boolean;
|
14
|
+
posthogEnabled: boolean;
|
15
|
+
posthogHost: string;
|
16
|
+
posthogToken: string;
|
17
|
+
};
|
18
|
+
|
19
|
+
let analyticsInstance: ReturnType<typeof createSingletonAnalytics> | null = null;
|
20
|
+
|
21
|
+
export const LobeAnalyticsProvider = memo(
|
22
|
+
({ children, posthogHost, posthogToken, posthogEnabled, debugPosthog }: Props) => {
|
23
|
+
const analytics = useMemo(() => {
|
24
|
+
if (analyticsInstance) {
|
25
|
+
return analyticsInstance;
|
26
|
+
}
|
27
|
+
|
28
|
+
analyticsInstance = createSingletonAnalytics({
|
29
|
+
business: BUSINESS_LINE,
|
30
|
+
debug: isDev,
|
31
|
+
providers: {
|
32
|
+
posthog: {
|
33
|
+
debug: debugPosthog,
|
34
|
+
enabled: posthogEnabled,
|
35
|
+
host: posthogHost,
|
36
|
+
key: posthogToken,
|
37
|
+
person_profiles: 'always',
|
38
|
+
},
|
39
|
+
},
|
40
|
+
});
|
41
|
+
|
42
|
+
return analyticsInstance;
|
43
|
+
}, []);
|
44
|
+
|
45
|
+
if (!analytics) return children;
|
46
|
+
|
47
|
+
return (
|
48
|
+
<AnalyticsProvider
|
49
|
+
client={analytics}
|
50
|
+
onInitializeSuccess={() => {
|
51
|
+
analyticsInstance?.setGlobalContext({
|
52
|
+
platform: isDesktop ? 'desktop' : 'web',
|
53
|
+
});
|
54
|
+
|
55
|
+
analyticsInstance
|
56
|
+
?.getProvider('posthog')
|
57
|
+
?.getNativeInstance()
|
58
|
+
?.register({
|
59
|
+
platform: isDesktop ? 'desktop' : 'web',
|
60
|
+
});
|
61
|
+
}}
|
62
|
+
>
|
63
|
+
{children}
|
64
|
+
</AnalyticsProvider>
|
65
|
+
);
|
66
|
+
},
|
67
|
+
() => true,
|
68
|
+
);
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { ReactNode, memo } from 'react';
|
2
|
+
|
3
|
+
import { LobeAnalyticsProvider } from '@/components/Analytics/LobeAnalyticsProvider';
|
4
|
+
import { analyticsEnv } from '@/config/analytics';
|
5
|
+
|
6
|
+
type Props = {
|
7
|
+
children: ReactNode;
|
8
|
+
};
|
9
|
+
|
10
|
+
export const LobeAnalyticsProviderWrapper = memo<Props>(({ children }) => {
|
11
|
+
return (
|
12
|
+
<LobeAnalyticsProvider
|
13
|
+
debugPosthog={analyticsEnv.DEBUG_POSTHOG_ANALYTICS}
|
14
|
+
posthogEnabled={analyticsEnv.ENABLED_POSTHOG_ANALYTICS}
|
15
|
+
posthogHost={analyticsEnv.POSTHOG_HOST}
|
16
|
+
posthogToken={analyticsEnv.POSTHOG_KEY ?? ''}
|
17
|
+
>
|
18
|
+
{children}
|
19
|
+
</LobeAnalyticsProvider>
|
20
|
+
);
|
21
|
+
});
|
22
|
+
|
23
|
+
LobeAnalyticsProviderWrapper.displayName = 'LobeAnalyticsProviderWrapper';
|
@@ -0,0 +1,52 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { useAnalytics } from '@lobehub/analytics/react';
|
4
|
+
import { memo, useCallback, useEffect } from 'react';
|
5
|
+
|
6
|
+
import { getChatStoreState } from '@/store/chat';
|
7
|
+
import { chatSelectors } from '@/store/chat/slices/message/selectors';
|
8
|
+
import { useGlobalStore } from '@/store/global';
|
9
|
+
import { systemStatusSelectors } from '@/store/global/selectors';
|
10
|
+
import { getSessionStoreState } from '@/store/session';
|
11
|
+
import { sessionSelectors } from '@/store/session/selectors';
|
12
|
+
|
13
|
+
const MainInterfaceTracker = memo(() => {
|
14
|
+
const { analytics } = useAnalytics();
|
15
|
+
|
16
|
+
const getMainInterfaceAnalyticsData = useCallback(() => {
|
17
|
+
const currentSession = sessionSelectors.currentSession(getSessionStoreState());
|
18
|
+
const activeSessionId = currentSession?.id;
|
19
|
+
const defaultSessions = sessionSelectors.defaultSessions(getSessionStoreState());
|
20
|
+
const showChatSideBar = systemStatusSelectors.showChatSideBar(useGlobalStore.getState());
|
21
|
+
const messages = chatSelectors.activeBaseChats(getChatStoreState());
|
22
|
+
return {
|
23
|
+
active_assistant: activeSessionId === 'inbox' ? null : currentSession?.meta?.title || null,
|
24
|
+
has_chat_history: messages.length > 0,
|
25
|
+
session_id: activeSessionId ? activeSessionId : 'inbox',
|
26
|
+
sidebar_state: showChatSideBar ? 'expanded' : 'collapsed',
|
27
|
+
visible_assistants_count: defaultSessions.length,
|
28
|
+
};
|
29
|
+
}, []);
|
30
|
+
|
31
|
+
useEffect(() => {
|
32
|
+
if (!analytics) return;
|
33
|
+
|
34
|
+
const timer = setTimeout(() => {
|
35
|
+
analytics.track({
|
36
|
+
name: 'main_page_view',
|
37
|
+
properties: {
|
38
|
+
...getMainInterfaceAnalyticsData(),
|
39
|
+
spm: 'main_page.interface.view',
|
40
|
+
},
|
41
|
+
});
|
42
|
+
}, 1000);
|
43
|
+
|
44
|
+
return () => clearTimeout(timer);
|
45
|
+
}, [analytics, getMainInterfaceAnalyticsData]);
|
46
|
+
|
47
|
+
return null;
|
48
|
+
});
|
49
|
+
|
50
|
+
MainInterfaceTracker.displayName = 'MainInterfaceTracker';
|
51
|
+
|
52
|
+
export default MainInterfaceTracker;
|
@@ -8,7 +8,6 @@ import Google from './Google';
|
|
8
8
|
import Vercel from './Vercel';
|
9
9
|
|
10
10
|
const Plausible = dynamic(() => import('./Plausible'));
|
11
|
-
const Posthog = dynamic(() => import('./Posthog'));
|
12
11
|
const Umami = dynamic(() => import('./Umami'));
|
13
12
|
const Clarity = dynamic(() => import('./Clarity'));
|
14
13
|
const ReactScan = dynamic(() => import('./ReactScan'));
|
@@ -24,13 +23,6 @@ const Analytics = () => {
|
|
24
23
|
scriptBaseUrl={analyticsEnv.PLAUSIBLE_SCRIPT_BASE_URL}
|
25
24
|
/>
|
26
25
|
)}
|
27
|
-
{analyticsEnv.ENABLED_POSTHOG_ANALYTICS && (
|
28
|
-
<Posthog
|
29
|
-
debug={analyticsEnv.DEBUG_POSTHOG_ANALYTICS}
|
30
|
-
host={analyticsEnv.POSTHOG_HOST!}
|
31
|
-
token={analyticsEnv.POSTHOG_KEY}
|
32
|
-
/>
|
33
|
-
)}
|
34
26
|
{analyticsEnv.ENABLED_UMAMI_ANALYTICS && (
|
35
27
|
<Umami
|
36
28
|
scriptUrl={analyticsEnv.UMAMI_SCRIPT_URL}
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import { getSingletonAnalyticsOptional } from '@lobehub/analytics';
|
1
2
|
import { DeepPartial } from 'utility-types';
|
2
3
|
import { StateCreator } from 'zustand/vanilla';
|
3
4
|
|
@@ -260,7 +261,31 @@ export const store: StateCreator<Store, [['zustand/devtools', never]]> = (set, g
|
|
260
261
|
await get().dispatchConfig({ config, type: 'update' });
|
261
262
|
},
|
262
263
|
setAgentMeta: async (meta) => {
|
263
|
-
|
264
|
+
const { dispatchMeta, id, meta: currentMeta } = get();
|
265
|
+
const mergedMeta = merge(currentMeta, meta);
|
266
|
+
|
267
|
+
try {
|
268
|
+
const analytics = getSingletonAnalyticsOptional();
|
269
|
+
if (analytics) {
|
270
|
+
analytics.track({
|
271
|
+
name: 'agent_meta_updated',
|
272
|
+
properties: {
|
273
|
+
assistant_avatar: mergedMeta.avatar,
|
274
|
+
assistant_background_color: mergedMeta.backgroundColor,
|
275
|
+
assistant_description: mergedMeta.description,
|
276
|
+
assistant_name: mergedMeta.title,
|
277
|
+
assistant_tags: mergedMeta.tags,
|
278
|
+
is_inbox: id === 'inbox',
|
279
|
+
session_id: id || 'unknown',
|
280
|
+
timestamp: Date.now(),
|
281
|
+
user_id: useUserStore.getState().user?.id || 'anonymous',
|
282
|
+
},
|
283
|
+
});
|
284
|
+
}
|
285
|
+
} catch (error) {
|
286
|
+
console.warn('Failed to track agent meta update:', error);
|
287
|
+
}
|
288
|
+
await dispatchMeta({ type: 'update', value: meta });
|
264
289
|
},
|
265
290
|
|
266
291
|
setChatConfig: async (config) => {
|
@@ -1,8 +1,12 @@
|
|
1
|
+
import { useAnalytics } from '@lobehub/analytics/react';
|
1
2
|
import { useCallback, useMemo } from 'react';
|
2
3
|
|
4
|
+
import { getAgentStoreState } from '@/store/agent';
|
5
|
+
import { agentSelectors } from '@/store/agent/selectors';
|
3
6
|
import { useChatStore } from '@/store/chat';
|
4
|
-
import { chatSelectors } from '@/store/chat/selectors';
|
7
|
+
import { chatSelectors, topicSelectors } from '@/store/chat/selectors';
|
5
8
|
import { fileChatSelectors, useFileStore } from '@/store/file';
|
9
|
+
import { getUserStoreState } from '@/store/user';
|
6
10
|
import { SendMessageParams } from '@/types/message';
|
7
11
|
|
8
12
|
export type UseSendMessageParams = Pick<
|
@@ -15,6 +19,7 @@ export const useSendMessage = () => {
|
|
15
19
|
s.sendMessage,
|
16
20
|
s.updateInputMessage,
|
17
21
|
]);
|
22
|
+
const { analytics } = useAnalytics();
|
18
23
|
|
19
24
|
const clearChatUploadFileList = useFileStore((s) => s.clearChatUploadFileList);
|
20
25
|
|
@@ -49,6 +54,29 @@ export const useSendMessage = () => {
|
|
49
54
|
updateInputMessage('');
|
50
55
|
clearChatUploadFileList();
|
51
56
|
|
57
|
+
// 获取分析数据
|
58
|
+
const userStore = getUserStoreState();
|
59
|
+
const agentStore = getAgentStoreState();
|
60
|
+
|
61
|
+
// 直接使用现有数据结构判断消息类型
|
62
|
+
const hasImages = fileList.some((file) => file.file?.type?.startsWith('image'));
|
63
|
+
const messageType = fileList.length === 0 ? 'text' : hasImages ? 'image' : 'file';
|
64
|
+
|
65
|
+
analytics?.track({
|
66
|
+
name: 'send_message',
|
67
|
+
properties: {
|
68
|
+
chat_id: store.activeId || 'unknown',
|
69
|
+
current_topic: topicSelectors.currentActiveTopic(store)?.title || null,
|
70
|
+
has_attachments: fileList.length > 0,
|
71
|
+
history_message_count: chatSelectors.activeBaseChats(store).length,
|
72
|
+
message: store.inputMessage,
|
73
|
+
message_length: store.inputMessage.length,
|
74
|
+
message_type: messageType,
|
75
|
+
selected_model: agentSelectors.currentAgentModel(agentStore),
|
76
|
+
session_id: store.activeId || 'inbox', // 当前活跃的会话ID
|
77
|
+
user_id: userStore.user?.id || 'anonymous',
|
78
|
+
},
|
79
|
+
});
|
52
80
|
// const hasSystemRole = agentSelectors.hasSystemRole(useAgentStore.getState());
|
53
81
|
// const agentSetting = useAgentStore.getState().agentSettingInstance;
|
54
82
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import { useAnalytics } from '@lobehub/analytics/react';
|
1
2
|
import { Button } from '@lobehub/ui';
|
2
3
|
import { memo } from 'react';
|
3
4
|
import { useTranslation } from 'react-i18next';
|
@@ -7,12 +8,24 @@ import UserInfo from '../UserInfo';
|
|
7
8
|
|
8
9
|
const UserLoginOrSignup = memo<{ onClick: () => void }>(({ onClick }) => {
|
9
10
|
const { t } = useTranslation('auth');
|
11
|
+
const { analytics } = useAnalytics();
|
12
|
+
|
13
|
+
const handleClick = () => {
|
14
|
+
analytics?.track({
|
15
|
+
name: 'login_or_signup_clicked',
|
16
|
+
properties: {
|
17
|
+
spm: 'homepage.login_or_signup.click',
|
18
|
+
},
|
19
|
+
});
|
20
|
+
|
21
|
+
onClick();
|
22
|
+
};
|
10
23
|
|
11
24
|
return (
|
12
25
|
<>
|
13
26
|
<UserInfo />
|
14
27
|
<Flexbox paddingBlock={12} paddingInline={16} width={'100%'}>
|
15
|
-
<Button block onClick={
|
28
|
+
<Button block onClick={handleClick} type={'primary'}>
|
16
29
|
{t('loginOrSignup')}
|
17
30
|
</Button>
|
18
31
|
</Flexbox>
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import { ReactNode, Suspense } from 'react';
|
2
2
|
|
3
|
+
import { LobeAnalyticsProviderWrapper } from '@/components/Analytics/LobeAnalyticsProviderWrapper';
|
3
4
|
import { getServerFeatureFlagsValue } from '@/config/featureFlags';
|
4
5
|
import { appEnv } from '@/envs/app';
|
5
6
|
import DevPanel from '@/features/DevPanel';
|
@@ -54,7 +55,9 @@ const GlobalLayout = async ({
|
|
54
55
|
isMobile={isMobile}
|
55
56
|
serverConfig={serverConfig}
|
56
57
|
>
|
57
|
-
<QueryProvider>
|
58
|
+
<QueryProvider>
|
59
|
+
<LobeAnalyticsProviderWrapper>{children}</LobeAnalyticsProviderWrapper>
|
60
|
+
</QueryProvider>
|
58
61
|
<StoreInitialization />
|
59
62
|
<Suspense>
|
60
63
|
<ImportSettings />
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import { createServerAnalytics } from '@lobehub/analytics/server';
|
2
|
+
|
3
|
+
import { analyticsEnv } from '@/config/analytics';
|
4
|
+
import { BUSINESS_LINE } from '@/const/analytics';
|
5
|
+
import { isDev } from '@/utils/env';
|
6
|
+
|
7
|
+
export const serverAnalytics = createServerAnalytics({
|
8
|
+
business: BUSINESS_LINE,
|
9
|
+
debug: isDev,
|
10
|
+
providers: {
|
11
|
+
posthogNode: {
|
12
|
+
debug: analyticsEnv.DEBUG_POSTHOG_ANALYTICS,
|
13
|
+
enabled: analyticsEnv.ENABLED_POSTHOG_ANALYTICS,
|
14
|
+
host: analyticsEnv.POSTHOG_HOST,
|
15
|
+
key: analyticsEnv.POSTHOG_KEY ?? '',
|
16
|
+
},
|
17
|
+
},
|
18
|
+
});
|
19
|
+
|
20
|
+
export const initializeServerAnalytics = async () => {
|
21
|
+
await serverAnalytics.initialize();
|
22
|
+
return serverAnalytics;
|
23
|
+
};
|
24
|
+
|
25
|
+
export default serverAnalytics;
|
@@ -8,6 +8,14 @@ import { AgentService } from '@/server/services/agent';
|
|
8
8
|
|
9
9
|
import { UserService } from './index';
|
10
10
|
|
11
|
+
// Mock @/libs/analytics to avoid server-side environment variable access in client test environment
|
12
|
+
vi.mock('@/libs/analytics', () => ({
|
13
|
+
initializeServerAnalytics: vi.fn().mockResolvedValue({
|
14
|
+
identify: vi.fn(),
|
15
|
+
track: vi.fn(),
|
16
|
+
}),
|
17
|
+
}));
|
18
|
+
|
11
19
|
// Mock dependencies
|
12
20
|
vi.mock('@/database/models/user', () => {
|
13
21
|
const MockUserModel = vi.fn();
|
@@ -2,6 +2,7 @@ import { UserJSON } from '@clerk/backend';
|
|
2
2
|
|
3
3
|
import { UserModel } from '@/database/models/user';
|
4
4
|
import { serverDB } from '@/database/server';
|
5
|
+
import { initializeServerAnalytics } from '@/libs/analytics';
|
5
6
|
import { pino } from '@/libs/logger';
|
6
7
|
import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
|
7
8
|
import { S3 } from '@/server/modules/S3';
|
@@ -51,6 +52,23 @@ export class UserService {
|
|
51
52
|
|
52
53
|
/* ↑ cloud slot ↑ */
|
53
54
|
|
55
|
+
//analytics
|
56
|
+
const analytics = await initializeServerAnalytics();
|
57
|
+
analytics?.identify(id, {
|
58
|
+
email: email?.email_address,
|
59
|
+
firstName: params.first_name,
|
60
|
+
lastName: params.last_name,
|
61
|
+
phone: phone?.phone_number,
|
62
|
+
username: params.username,
|
63
|
+
});
|
64
|
+
analytics?.track({
|
65
|
+
name: 'user_register_completed',
|
66
|
+
properties: {
|
67
|
+
spm: 'user_service.create_user.user_created',
|
68
|
+
},
|
69
|
+
userId: id,
|
70
|
+
});
|
71
|
+
|
54
72
|
return { message: 'user created', success: true };
|
55
73
|
};
|
56
74
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import { getSingletonAnalyticsOptional } from '@lobehub/analytics';
|
1
2
|
import isEqual from 'fast-deep-equal';
|
2
3
|
import { t } from 'i18next';
|
3
4
|
import useSWR, { SWRResponse, mutate } from 'swr';
|
@@ -10,8 +11,8 @@ import { DEFAULT_AGENT_LOBE_SESSION, INBOX_SESSION_ID } from '@/const/session';
|
|
10
11
|
import { useClientDataSWR } from '@/libs/swr';
|
11
12
|
import { sessionService } from '@/services/session';
|
12
13
|
import { SessionStore } from '@/store/session';
|
13
|
-
import { useUserStore } from '@/store/user';
|
14
|
-
import { settingsSelectors } from '@/store/user/selectors';
|
14
|
+
import { getUserStoreState, useUserStore } from '@/store/user';
|
15
|
+
import { settingsSelectors, userProfileSelectors } from '@/store/user/selectors';
|
15
16
|
import { MetaData } from '@/types/meta';
|
16
17
|
import {
|
17
18
|
ChatSessionList,
|
@@ -24,6 +25,7 @@ import {
|
|
24
25
|
import { merge } from '@/utils/merge';
|
25
26
|
import { setNamespace } from '@/utils/storeDebug';
|
26
27
|
|
28
|
+
import { sessionGroupSelectors } from '../sessionGroup/selectors';
|
27
29
|
import { SessionDispatch, sessionsReducer } from './reducers';
|
28
30
|
import { sessionSelectors } from './selectors';
|
29
31
|
import { sessionMetaSelectors } from './selectors/meta';
|
@@ -114,6 +116,30 @@ export const createSessionSlice: StateCreator<
|
|
114
116
|
const id = await sessionService.createSession(LobeSessionType.Agent, newSession);
|
115
117
|
await refreshSessions();
|
116
118
|
|
119
|
+
// Track new agent creation analytics
|
120
|
+
const analytics = getSingletonAnalyticsOptional();
|
121
|
+
if (analytics) {
|
122
|
+
const userStore = getUserStoreState();
|
123
|
+
const userId = userProfileSelectors.userId(userStore);
|
124
|
+
|
125
|
+
// Get group information
|
126
|
+
const groupId = newSession.group || 'default';
|
127
|
+
const group = sessionGroupSelectors.getGroupById(groupId)(get());
|
128
|
+
const groupName = group?.name || (groupId === 'default' ? 'Default' : 'Unknown');
|
129
|
+
|
130
|
+
analytics.track({
|
131
|
+
name: 'new_agent_created',
|
132
|
+
properties: {
|
133
|
+
assistant_name: newSession.meta?.title || 'Untitled Agent',
|
134
|
+
assistant_tags: newSession.meta?.tags || [],
|
135
|
+
group_id: groupId,
|
136
|
+
group_name: groupName,
|
137
|
+
session_id: id,
|
138
|
+
user_id: userId || 'anonymous',
|
139
|
+
},
|
140
|
+
});
|
141
|
+
}
|
142
|
+
|
117
143
|
// Whether to goto to the new session after creation, the default is to switch to
|
118
144
|
if (isSwitchSession) switchSession(id);
|
119
145
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import { getSingletonAnalyticsOptional } from '@lobehub/analytics';
|
1
2
|
import useSWR, { SWRResponse, mutate } from 'swr';
|
2
3
|
import { DeepPartial } from 'utility-types';
|
3
4
|
import type { StateCreator } from 'zustand/vanilla';
|
@@ -22,7 +23,6 @@ const GET_USER_STATE_KEY = 'initUserState';
|
|
22
23
|
*/
|
23
24
|
export interface CommonAction {
|
24
25
|
refreshUserState: () => Promise<void>;
|
25
|
-
|
26
26
|
updateAvatar: (avatar: string) => Promise<void>;
|
27
27
|
useCheckTrace: (shouldFetch: boolean) => SWRResponse;
|
28
28
|
useInitUserState: (
|
@@ -118,7 +118,14 @@ export const createCommonSlice: StateCreator<
|
|
118
118
|
false,
|
119
119
|
n('initUserState'),
|
120
120
|
);
|
121
|
-
|
121
|
+
//analytics
|
122
|
+
const analytics = getSingletonAnalyticsOptional();
|
123
|
+
analytics?.identify(data.userId || '', {
|
124
|
+
email: data.email,
|
125
|
+
firstName: data.firstName,
|
126
|
+
lastName: data.lastName,
|
127
|
+
username: data.username,
|
128
|
+
});
|
122
129
|
get().refreshDefaultModelProviderList({ trigger: 'fetchUserState' });
|
123
130
|
}
|
124
131
|
},
|
package/tests/setup.ts
CHANGED
@@ -5,6 +5,17 @@ import { theme } from 'antd';
|
|
5
5
|
// refs: https://github.com/dumbmatter/fakeIndexedDB#dexie-and-other-indexeddb-api-wrappers
|
6
6
|
import 'fake-indexeddb/auto';
|
7
7
|
import React from 'react';
|
8
|
+
import { vi } from 'vitest';
|
9
|
+
|
10
|
+
// Global mock for @lobehub/analytics/react to avoid AnalyticsProvider dependency
|
11
|
+
// This prevents tests from failing when components use useAnalytics hook
|
12
|
+
vi.mock('@lobehub/analytics/react', () => ({
|
13
|
+
useAnalytics: () => ({
|
14
|
+
analytics: {
|
15
|
+
track: vi.fn(),
|
16
|
+
},
|
17
|
+
}),
|
18
|
+
}));
|
8
19
|
|
9
20
|
// only inject in the dom environment
|
10
21
|
if (
|