From c76f27456218616efc95c7d6a17abb852ea59a96 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Mon, 10 Feb 2025 17:37:14 +0800 Subject: [PATCH] feat: google analytics --- package.json | 2 + src/renderer/src/hooks/useAppInit.ts | 7 +- src/renderer/src/i18n/locales/en-us.json | 4 + src/renderer/src/i18n/locales/ja-jp.json | 4 + src/renderer/src/i18n/locales/ru-ru.json | 4 + src/renderer/src/i18n/locales/zh-cn.json | 4 + src/renderer/src/i18n/locales/zh-tw.json | 4 + src/renderer/src/init.ts | 2 + .../src/pages/settings/GeneralSettings.tsx | 13 ++- src/renderer/src/store/migrate.ts | 8 ++ src/renderer/src/store/settings.ts | 12 +- src/renderer/src/utils/analytics.ts | 54 +++++++++ yarn.lock | 105 ++++++++++++++++++ 13 files changed, 217 insertions(+), 6 deletions(-) create mode 100644 src/renderer/src/utils/analytics.ts diff --git a/package.json b/package.json index e0f9353c..d338da4e 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "@agentic/exa": "^7.3.3", "@agentic/searxng": "^7.3.3", "@agentic/tavily": "^7.3.3", + "@analytics/google-analytics": "^1.1.0", "@ant-design/v5-patch-for-react-19": "^1.0.3", "@anthropic-ai/sdk": "^0.38.0", "@electron-toolkit/eslint-config-prettier": "^3.0.0", @@ -126,6 +127,7 @@ "@types/react-infinite-scroll-component": "^5.0.0", "@types/tinycolor2": "^1", "@vitejs/plugin-react": "^4.2.1", + "analytics": "^0.8.16", "antd": "^5.22.5", "applescript": "^1.0.0", "axios": "^1.7.3", diff --git a/src/renderer/src/hooks/useAppInit.ts b/src/renderer/src/hooks/useAppInit.ts index 90b778da..d8bcbe0a 100644 --- a/src/renderer/src/hooks/useAppInit.ts +++ b/src/renderer/src/hooks/useAppInit.ts @@ -6,6 +6,7 @@ import i18n from '@renderer/i18n' import { useAppDispatch } from '@renderer/store' import { setAvatar, setFilesPath, setResourcesPath, setUpdateState } from '@renderer/store/runtime' import { delay, runAsyncFunction } from '@renderer/utils' +import { disableAnalytics, initAnalytics } from '@renderer/utils/analytics' import { defaultLanguage } from '@shared/config/constant' import { useLiveQuery } from 'dexie-react-hooks' import { useEffect } from 'react' @@ -18,7 +19,7 @@ import useUpdateHandler from './useUpdateHandler' export function useAppInit() { const dispatch = useAppDispatch() - const { proxyUrl, language, windowStyle, autoCheckUpdate, proxyMode, customCss } = useSettings() + const { proxyUrl, language, windowStyle, autoCheckUpdate, proxyMode, customCss, enableDataCollection } = useSettings() const { minappShow } = useRuntime() const { setDefaultModel, setTopicNamingModel, setTranslateModel } = useDefaultModel() const avatar = useLiveQuery(() => db.settings.get('image://avatar')) @@ -103,4 +104,8 @@ export function useAppInit() { document.head.appendChild(style) } }, [customCss]) + + useEffect(() => { + enableDataCollection ? initAnalytics() : disableAnalytics() + }, [enableDataCollection]) } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 71a4313c..683da908 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1285,6 +1285,10 @@ "back": "Back", "forward": "Forward", "multiple": "Multiple Select" + }, + "privacy": { + "title": "Privacy Settings", + "enable_privacy_mode": "Anonymous reporting of errors and statistics" } }, "translate": { diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index cd2a48a6..f841fed6 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1285,6 +1285,10 @@ "back": "戻る", "forward": "進む", "multiple": "複数選択" + }, + "privacy": { + "title": "プライバシー設定", + "enable_privacy_mode": "匿名エラーレポートとデータ統計の送信" } }, "translate": { diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index d23d4dc2..dbb15d00 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1285,6 +1285,10 @@ "back": "Назад", "forward": "Вперед", "multiple": "Множественный выбор" + }, + "privacy": { + "title": "Настройки приватности", + "enable_privacy_mode": "Анонимная отправка отчетов об ошибках и статистики" } }, "translate": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 89a02e10..49abf42f 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1285,6 +1285,10 @@ "back": "后退", "forward": "前进", "multiple": "多选" + }, + "privacy": { + "title": "隐私设置", + "enable_privacy_mode": "匿名发送错误报告和数据统计" } }, "translate": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index e6311633..9c0a4dad 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1285,6 +1285,10 @@ "back": "後退", "forward": "前進", "multiple": "多選" + }, + "privacy": { + "title": "隱私設定", + "enable_privacy_mode": "匿名發送錯誤報告和資料統計" } }, "translate": { diff --git a/src/renderer/src/init.ts b/src/renderer/src/init.ts index 8962da5a..91c78680 100644 --- a/src/renderer/src/init.ts +++ b/src/renderer/src/init.ts @@ -1,3 +1,5 @@ +import './utils/analytics' + import KeyvStorage from '@kangfenmao/keyv-storage' import { startAutoSync } from './services/BackupService' diff --git a/src/renderer/src/pages/settings/GeneralSettings.tsx b/src/renderer/src/pages/settings/GeneralSettings.tsx index a041c924..cf6a062b 100644 --- a/src/renderer/src/pages/settings/GeneralSettings.tsx +++ b/src/renderer/src/pages/settings/GeneralSettings.tsx @@ -2,7 +2,7 @@ import { useTheme } from '@renderer/context/ThemeProvider' import { useSettings } from '@renderer/hooks/useSettings' import i18n from '@renderer/i18n' import { useAppDispatch } from '@renderer/store' -import { setLanguage } from '@renderer/store/settings' +import { setEnableDataCollection, setLanguage } from '@renderer/store/settings' import { setProxyMode, setProxyUrl as _setProxyUrl } from '@renderer/store/settings' import { LanguageVarious } from '@renderer/types' import { isValidProxyUrl } from '@renderer/utils' @@ -24,7 +24,8 @@ const GeneralSettings: FC = () => { launchToTray, trayOnClose, tray, - proxyMode: storeProxyMode + proxyMode: storeProxyMode, + enableDataCollection } = useSettings() const [proxyUrl, setProxyUrl] = useState(storeProxyUrl) const { theme: themeMode } = useTheme() @@ -179,6 +180,14 @@ const GeneralSettings: FC = () => { updateTrayOnClose(checked)} /> + + {t('settings.privacy.title')} + + + {t('settings.privacy.enable_privacy_mode')} + dispatch(setEnableDataCollection(v))} /> + + ) } diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index 282bb6b6..7a534378 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -1178,6 +1178,14 @@ const migrateConfig = { } catch (error) { return state } + }, + '90': (state: RootState) => { + try { + state.settings.enableDataCollection = true + return state + } catch (error) { + return state + } } } diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 97bd9289..23c21dc8 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -103,6 +103,8 @@ export interface SettingsState { siyuanRootPath: string | null maxKeepAliveMinapps: number showOpenedMinappsInSidebar: boolean + // 隐私设置 + enableDataCollection: boolean } export type MultiModelMessageStyle = 'horizontal' | 'vertical' | 'fold' | 'grid' @@ -179,13 +181,13 @@ const initialState: SettingsState = { joplinToken: '', joplinUrl: '', defaultObsidianVault: null, - // 思源笔记配置初始值 siyuanApiUrl: null, siyuanToken: null, siyuanBoxId: null, siyuanRootPath: null, maxKeepAliveMinapps: 3, - showOpenedMinappsInSidebar: true + showOpenedMinappsInSidebar: true, + enableDataCollection: false } const settingsSlice = createSlice({ @@ -425,6 +427,9 @@ const settingsSlice = createSlice({ }, setShowOpenedMinappsInSidebar: (state, action: PayloadAction) => { state.showOpenedMinappsInSidebar = action.payload + }, + setEnableDataCollection: (state, action: PayloadAction) => { + state.enableDataCollection = action.payload } } }) @@ -505,7 +510,8 @@ export const { setSiyuanBoxId, setSiyuanRootPath, setMaxKeepAliveMinapps, - setShowOpenedMinappsInSidebar + setShowOpenedMinappsInSidebar, + setEnableDataCollection } = settingsSlice.actions export default settingsSlice.reducer diff --git a/src/renderer/src/utils/analytics.ts b/src/renderer/src/utils/analytics.ts new file mode 100644 index 00000000..948201fb --- /dev/null +++ b/src/renderer/src/utils/analytics.ts @@ -0,0 +1,54 @@ +import googleAnalytics from '@analytics/google-analytics' +import Analytics from 'analytics' + +import { version } from '../../../../package.json' + +let analytics: ReturnType | null = null + +export function initAnalytics() { + if (analytics) { + return analytics + } + + analytics = Analytics({ + app: 'cherry-studio', + version, + plugins: [ + googleAnalytics({ + measurementIds: ['G-YST80FC1ZC'] + }) + ] + }) + + return analytics +} + +export function disableAnalytics() { + analytics = null +} + +export function track(eventName: string) { + try { + const instance = initAnalytics() + instance?.track(eventName) + } catch (error) { + console.warn('[Analytics] Failed to track event:', error) + } +} + +export function page(pageName: string) { + try { + const instance = initAnalytics() + instance?.page({ + title: pageName, + path: pageName + }) + } catch (error) { + console.warn('[Analytics] Failed to track page view:', error) + } +} + +export default { + track, + page +} diff --git a/yarn.lock b/yarn.lock index 04db202b..2b0bb627 100644 --- a/yarn.lock +++ b/yarn.lock @@ -77,6 +77,80 @@ __metadata: languageName: node linkType: hard +"@analytics/cookie-utils@npm:^0.2.12": + version: 0.2.12 + resolution: "@analytics/cookie-utils@npm:0.2.12" + dependencies: + "@analytics/global-storage-utils": "npm:^0.1.7" + checksum: 10c0/b631b5d482b6865984fd72ed24c66c0e3ba90d9aff21f7ec6edee597c60e68d4c34195058efab108167e9e34b2d4a65b452bafd6996e7a942a9e8e165099d767 + languageName: node + linkType: hard + +"@analytics/core@npm:^0.12.17": + version: 0.12.17 + resolution: "@analytics/core@npm:0.12.17" + dependencies: + "@analytics/global-storage-utils": "npm:^0.1.7" + "@analytics/type-utils": "npm:^0.6.2" + analytics-utils: "npm:^1.0.14" + checksum: 10c0/752ccaca1187297fe1665ffcfc23923b28f2a097c2e0c76024ef253e9a848f3f16e430a5155a4bc665931ba84da813ca88ee1634ef8c9491e28975d95ce5a8e1 + languageName: node + linkType: hard + +"@analytics/global-storage-utils@npm:^0.1.7": + version: 0.1.7 + resolution: "@analytics/global-storage-utils@npm:0.1.7" + dependencies: + "@analytics/type-utils": "npm:^0.6.2" + checksum: 10c0/c9f4e412106edc590cf3df73743ae9da5579cac3f452ff80e67a9c205ba6afa58f21804f3ffc4433628faf8d9e1306249cdba951a3947da3e93e3382e772a28b + languageName: node + linkType: hard + +"@analytics/google-analytics@npm:^1.1.0": + version: 1.1.0 + resolution: "@analytics/google-analytics@npm:1.1.0" + checksum: 10c0/b5adb9741d20c97c22bee10eb01a60658d7564dc6607b93729e908485533597adab3b9816dfdc83cf3d9c9fe5fd5196640a1122fb578c6123dd16926736433d4 + languageName: node + linkType: hard + +"@analytics/localstorage-utils@npm:^0.1.10": + version: 0.1.10 + resolution: "@analytics/localstorage-utils@npm:0.1.10" + dependencies: + "@analytics/global-storage-utils": "npm:^0.1.7" + checksum: 10c0/a540eb8b02f209858f339e7bba328e6076f5e9680b07b58a9e0278e990b3030c94067ba9f8f5501361fbb0b12841b2e3c99f777d18c7606a51a0e3c08208f3a6 + languageName: node + linkType: hard + +"@analytics/session-storage-utils@npm:^0.0.7": + version: 0.0.7 + resolution: "@analytics/session-storage-utils@npm:0.0.7" + dependencies: + "@analytics/global-storage-utils": "npm:^0.1.7" + checksum: 10c0/742aec35eea9cf8f79037408bd3bc256ac3ba009bb11c424bfec910126b7cb3ab8edb8be810ab3bcaead152da6cdb756592b0abfbfbde7e82c420b2e94e03897 + languageName: node + linkType: hard + +"@analytics/storage-utils@npm:^0.4.2": + version: 0.4.2 + resolution: "@analytics/storage-utils@npm:0.4.2" + dependencies: + "@analytics/cookie-utils": "npm:^0.2.12" + "@analytics/global-storage-utils": "npm:^0.1.7" + "@analytics/localstorage-utils": "npm:^0.1.10" + "@analytics/session-storage-utils": "npm:^0.0.7" + "@analytics/type-utils": "npm:^0.6.2" + checksum: 10c0/68e884e2a5356c8c40a76508574603c3923cf58e87309190f71951c6a12123ab8eb2fb125d6cf45efaf662aa02df1ddddecf0e49be261e5cac2c0893e037cc5a + languageName: node + linkType: hard + +"@analytics/type-utils@npm:^0.6.2": + version: 0.6.2 + resolution: "@analytics/type-utils@npm:0.6.2" + checksum: 10c0/63332155ed4f361a30a240a43dc73d0809f66aa2f484d870c900634ef32d3bf363dccf4961a9592bb0de627da401f88fada6afa3045aa695acfb0acdaea14988 + languageName: node + linkType: hard + "@ant-design/colors@npm:^7.0.0, @ant-design/colors@npm:^7.2.0": version: 7.2.0 resolution: "@ant-design/colors@npm:7.2.0" @@ -3831,6 +3905,7 @@ __metadata: "@agentic/exa": "npm:^7.3.3" "@agentic/searxng": "npm:^7.3.3" "@agentic/tavily": "npm:^7.3.3" + "@analytics/google-analytics": "npm:^1.1.0" "@ant-design/v5-patch-for-react-19": "npm:^1.0.3" "@anthropic-ai/sdk": "npm:^0.38.0" "@cherrystudio/embedjs": "npm:^0.1.28" @@ -3878,6 +3953,7 @@ __metadata: "@vitejs/plugin-react": "npm:^4.2.1" "@xyflow/react": "npm:^12.4.4" adm-zip: "npm:^0.5.16" + analytics: "npm:^0.8.16" antd: "npm:^5.22.5" applescript: "npm:^1.0.0" axios: "npm:^1.7.3" @@ -4108,6 +4184,28 @@ __metadata: languageName: node linkType: hard +"analytics-utils@npm:^1.0.14": + version: 1.0.14 + resolution: "analytics-utils@npm:1.0.14" + dependencies: + "@analytics/type-utils": "npm:^0.6.2" + dlv: "npm:^1.1.3" + peerDependencies: + "@types/dlv": ^1.0.0 + checksum: 10c0/8a55d5592f4588d7d0e555b3baeed9dca63d1a6e44f5c128ac1bc32616c4cc23361d69611c0490339bedbca11407bf9e62b3f2fddaf52c66fd82e749ebdfaf48 + languageName: node + linkType: hard + +"analytics@npm:^0.8.16": + version: 0.8.16 + resolution: "analytics@npm:0.8.16" + dependencies: + "@analytics/core": "npm:^0.12.17" + "@analytics/storage-utils": "npm:^0.4.2" + checksum: 10c0/818cdb0b4fa308d501f6db435aed7653b0137387b3c9f5002ebf9c8b7400afdda05538dba76f43c93ce18606f19792e456604e353e196aee0e11a6e40d188155 + languageName: node + linkType: hard + "ansi-escapes@npm:^7.0.0": version: 7.0.0 resolution: "ansi-escapes@npm:7.0.0" @@ -6079,6 +6177,13 @@ __metadata: languageName: node linkType: hard +"dlv@npm:^1.1.3": + version: 1.1.3 + resolution: "dlv@npm:1.1.3" + checksum: 10c0/03eb4e769f19a027fd5b43b59e8a05e3fd2100ac239ebb0bf9a745de35d449e2f25cfaf3aa3934664551d72856f4ae8b7822016ce5c42c2d27c18ae79429ec42 + languageName: node + linkType: hard + "dmg-builder@npm:24.13.3": version: 24.13.3 resolution: "dmg-builder@npm:24.13.3"