diff --git a/eslint.config.ts b/eslint.config.ts index 4e19eba2e..c64ae14ab 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -67,7 +67,6 @@ export default defineConfig([ ...commonParserOptions, projectService: { allowDefaultProject: [ - 'vite.config.mts', 'vite.electron.config.mts', 'vite.types.config.mts', 'playwright.config.ts', diff --git a/global.d.ts b/global.d.ts index 2134f9b49..5493cbb18 100644 --- a/global.d.ts +++ b/global.d.ts @@ -4,6 +4,7 @@ declare const __SENTRY_DSN__: string declare const __ALGOLIA_APP_ID__: string declare const __ALGOLIA_API_KEY__: string declare const __USE_PROD_CONFIG__: boolean +declare const __MIXPANEL_TOKEN__: string type BuildFeatureFlags = { REQUIRE_SUBSCRIPTION: boolean diff --git a/package.json b/package.json index 9076140c5..c6407e72b 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "jsdom": "catalog:", "knip": "catalog:", "lint-staged": "catalog:", + "mixpanel-browser": "catalog:", "nx": "catalog:", "postcss-html": "catalog:", "prettier": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed5601390..a9ec818d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,9 +183,18 @@ catalogs: lint-staged: specifier: ^15.2.7 version: 15.2.7 + markdown-table: + specifier: ^3.0.4 + version: 3.0.4 + mixpanel-browser: + specifier: ^2.71.0 + version: 2.71.0 nx: specifier: 21.4.1 version: 21.4.1 + picocolors: + specifier: ^1.1.1 + version: 1.1.1 pinia: specifier: ^2.1.7 version: 2.2.2 @@ -193,14 +202,20 @@ catalogs: specifier: ^1.8.0 version: 1.8.0 prettier: - specifier: ^3.3.2 - version: 3.3.2 + specifier: ^3.6.2 + version: 3.6.2 + pretty-bytes: + specifier: ^7.1.0 + version: 7.1.0 primeicons: specifier: ^7.0.0 version: 7.0.0 primevue: specifier: ^4.2.5 version: 4.2.5 + rollup-plugin-visualizer: + specifier: ^6.0.4 + version: 6.0.4 storybook: specifier: ^9.1.6 version: 9.1.6 @@ -251,7 +266,7 @@ catalogs: version: 3.5.13 vue-component-type-helpers: specifier: ^3.0.7 - version: 3.1.0 + version: 3.1.1 vue-eslint-parser: specifier: ^10.2.0 version: 10.2.0 @@ -473,7 +488,7 @@ importers: version: 21.4.1(@babel/traverse@7.28.3)(@playwright/test@1.52.0)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(typescript@5.9.2) '@nx/storybook': specifier: 'catalog:' - version: 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2) + version: 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2) '@nx/vite': specifier: 'catalog:' version: 21.4.1(@babel/traverse@7.28.3)(nx@21.4.1)(typescript@5.9.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vitest@3.2.4) @@ -485,19 +500,19 @@ importers: version: 1.52.0 '@storybook/addon-docs': specifier: 'catalog:' - version: 9.1.1(@types/react@19.1.9)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))) + version: 9.1.1(@types/react@19.1.9)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))) '@storybook/vue3': specifier: 'catalog:' - version: 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2)) + version: 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2)) '@storybook/vue3-vite': specifier: 'catalog:' - version: 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2)) + version: 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2)) '@tailwindcss/vite': specifier: 'catalog:' version: 4.1.12(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) '@trivago/prettier-plugin-sort-imports': specifier: 'catalog:' - version: 5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.3.2) + version: 5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.6.2) '@types/eslint-plugin-tailwindcss': specifier: 'catalog:' version: 3.17.0 @@ -545,10 +560,10 @@ importers: version: 4.16.1(@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.35.0(jiti@2.4.2)) eslint-plugin-prettier: specifier: 'catalog:' - version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.3.2) + version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.6.2) eslint-plugin-storybook: specifier: 'catalog:' - version: 9.1.6(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2) + version: 9.1.6(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2) eslint-plugin-tailwindcss: specifier: 'catalog:' version: 4.0.0-beta.0(tailwindcss@4.1.12) @@ -582,18 +597,33 @@ importers: lint-staged: specifier: 'catalog:' version: 15.2.7 + markdown-table: + specifier: 'catalog:' + version: 3.0.4 + mixpanel-browser: + specifier: 'catalog:' + version: 2.71.0 nx: specifier: 'catalog:' version: 21.4.1 + picocolors: + specifier: 'catalog:' + version: 1.1.1 postcss-html: specifier: 'catalog:' version: 1.8.0 prettier: specifier: 'catalog:' - version: 3.3.2 + version: 3.6.2 + pretty-bytes: + specifier: 'catalog:' + version: 7.1.0 + rollup-plugin-visualizer: + specifier: 'catalog:' + version: 6.0.4(rollup@4.22.4) storybook: specifier: 'catalog:' - version: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + version: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) stylelint: specifier: 'catalog:' version: 16.24.0(typescript@5.9.2) @@ -641,7 +671,7 @@ importers: version: 3.2.4(@types/debug@4.1.12)(@types/node@20.14.10)(@vitest/ui@3.2.4)(happy-dom@15.11.0)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.39.2) vue-component-type-helpers: specifier: 'catalog:' - version: 3.1.0 + version: 3.1.1 vue-eslint-parser: specifier: 'catalog:' version: 10.2.0(eslint@9.35.0(jiti@2.4.2)) @@ -740,11 +770,11 @@ importers: packages/shared-frontend-utils: dependencies: axios: - specifier: ^1.11.0 + specifier: 'catalog:' version: 1.11.0 devDependencies: typescript: - specifier: ^5.9.2 + specifier: 'catalog:' version: 5.9.2 packages/tailwind-utils: @@ -2177,6 +2207,21 @@ packages: '@microsoft/tsdoc@0.15.1': resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} + '@mixpanel/rrdom@2.0.0-alpha.18.2': + resolution: {integrity: sha512-vX/tbnS14ZzzatC7vOyvAm9tOLU8tof0BuppBlphzEx1YHTSw8DQiAmyAc0AmXidchLV0W+cUHV/WsehPLh2hQ==} + + '@mixpanel/rrweb-snapshot@2.0.0-alpha.18.2': + resolution: {integrity: sha512-2kSnjZZ3QZ9zOz/isOt8s54mXUUDgXk/u0eEi/rE0xBWDeuA0NHrBcqiMc+w4F/yWWUpo5F5zcuPeYpc6ufAsw==} + + '@mixpanel/rrweb-types@2.0.0-alpha.18.2': + resolution: {integrity: sha512-ucIYe1mfJ2UksvXW+d3bOySTB2/0yUSqQJlUydvbBz6OO2Bhq3nJHyLXV9ExkgUMZm1ZyDcvvmNUd1+5tAXlpA==} + + '@mixpanel/rrweb-utils@2.0.0-alpha.18.2': + resolution: {integrity: sha512-OomKIB6GTx5xvCLJ7iic2khT/t/tnCJUex13aEqsbSqIT/UzUUsqf+LTrgUK5ex+f6odmkCNjre2y5jvpNqn+g==} + + '@mixpanel/rrweb@2.0.0-alpha.18.2': + resolution: {integrity: sha512-J3dVTEu6Z4p8di7y9KKvUooNuBjX97DdG6XGWoPEPi07A9512h9M8MEtvlY3mK0PGfuC0Mz5Pv/Ws6gjGYfKQg==} + '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -2996,6 +3041,9 @@ packages: '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + '@types/css-font-loading-module@0.0.7': + resolution: {integrity: sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -3494,6 +3542,9 @@ packages: '@webgpu/types@0.1.51': resolution: {integrity: sha512-ktR3u64NPjwIViNCck+z9QeyN0iPkQCUOQ07ZCV1RzlkfP+olLTeEZ95O1QHS+v4w9vJeY9xj/uJuSphsHy5rQ==} + '@xstate/fsm@1.6.5': + resolution: {integrity: sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==} + '@xterm/addon-fit@0.10.0': resolution: {integrity: sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==} peerDependencies: @@ -3776,6 +3827,10 @@ packages: balanced-match@2.0.0: resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} + base64-arraybuffer@1.0.2: + resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} + engines: {node: '>= 0.6.0'} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -4886,9 +4941,6 @@ packages: get-tsconfig@4.10.1: resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} - get-tsconfig@4.7.5: - resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -5966,6 +6018,9 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mixpanel-browser@2.71.0: + resolution: {integrity: sha512-jKmDXe68/oQFgk/9ns9Z36bA0CJ31PH8Y77XTLLGfJvhsUPbvu+7Se9e281NejZF6+OMqx7cE+zFxToozYyNrA==} + mkdirp@3.0.1: resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} engines: {node: '>=10'} @@ -5993,11 +6048,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@3.3.8: - resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - nanoid@5.1.5: resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} engines: {node: ^18 || >=20} @@ -6345,10 +6395,6 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.5.1: - resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -6361,11 +6407,15 @@ packages: resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} - prettier@3.3.2: - resolution: {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==} + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} engines: {node: '>=14'} hasBin: true + pretty-bytes@7.1.0: + resolution: {integrity: sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw==} + engines: {node: '>=20'} + pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -6668,6 +6718,19 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rollup-plugin-visualizer@6.0.4: + resolution: {integrity: sha512-q8Q7J/6YofkmaGW1sH/fPRAz37x/+pd7VBuaUU7lwvOS/YikuiiEU9jeb9PH8XHiq50XFrUsBbOxeAMYQ7KZkg==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + rolldown: 1.x || ^1.0.0-beta + rollup: 2.x || 3.x || 4.x + peerDependenciesMeta: + rolldown: + optional: true + rollup: + optional: true + rollup@4.22.4: resolution: {integrity: sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -6822,6 +6885,10 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + speakingurl@14.0.1: resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} engines: {node: '>=0.10.0'} @@ -7450,9 +7517,6 @@ packages: vue-component-type-helpers@2.2.12: resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==} - vue-component-type-helpers@3.1.0: - resolution: {integrity: sha512-cC1pYNRZkSS1iCvdlaMbbg2sjDwxX098FucEjtz9Yig73zYjWzQsnMe5M9H8dRNv55hAIDGUI29hF2BEUA4FMQ==} - vue-component-type-helpers@3.1.1: resolution: {integrity: sha512-B0kHv7qX6E7+kdc5nsaqjdGZ1KwNKSUQDWGy7XkTYT7wFsOpkEyaJ1Vq79TjwrrtuLRgizrTV7PPuC4rRQo+vw==} @@ -9469,6 +9533,29 @@ snapshots: '@microsoft/tsdoc@0.15.1': {} + '@mixpanel/rrdom@2.0.0-alpha.18.2': + dependencies: + '@mixpanel/rrweb-snapshot': 2.0.0-alpha.18.2 + + '@mixpanel/rrweb-snapshot@2.0.0-alpha.18.2': + dependencies: + postcss: 8.5.6 + + '@mixpanel/rrweb-types@2.0.0-alpha.18.2': {} + + '@mixpanel/rrweb-utils@2.0.0-alpha.18.2': {} + + '@mixpanel/rrweb@2.0.0-alpha.18.2': + dependencies: + '@mixpanel/rrdom': 2.0.0-alpha.18.2 + '@mixpanel/rrweb-snapshot': 2.0.0-alpha.18.2 + '@mixpanel/rrweb-types': 2.0.0-alpha.18.2 + '@mixpanel/rrweb-utils': 2.0.0-alpha.18.2 + '@types/css-font-loading-module': 0.0.7 + '@xstate/fsm': 1.6.5 + base64-arraybuffer: 1.0.2 + mitt: 3.0.1 + '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.4.5 @@ -9645,7 +9732,7 @@ snapshots: - typescript - verdaccio - '@nx/storybook@21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)': + '@nx/storybook@21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)': dependencies: '@nx/cypress': 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(typescript@5.9.2) '@nx/devkit': 21.4.1(nx@21.4.1) @@ -9653,7 +9740,7 @@ snapshots: '@nx/js': 21.4.1(@babel/traverse@7.28.3)(nx@21.4.1) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.9.2) semver: 7.7.2 - storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) tslib: 2.8.1 transitivePeerDependencies: - '@babel/traverse' @@ -10004,29 +10091,29 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} - '@storybook/addon-docs@9.1.1(@types/react@19.1.9)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))': + '@storybook/addon-docs@9.1.1(@types/react@19.1.9)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))': dependencies: '@mdx-js/react': 3.1.0(@types/react@19.1.9)(react@19.1.1) - '@storybook/csf-plugin': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))) + '@storybook/csf-plugin': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))) '@storybook/icons': 1.4.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@storybook/react-dom-shim': 9.1.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))) + '@storybook/react-dom-shim': 9.1.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' - '@storybook/builder-vite@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))': + '@storybook/builder-vite@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))': dependencies: - '@storybook/csf-plugin': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))) - storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + '@storybook/csf-plugin': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))) + storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) ts-dedent: 2.2.0 vite: 5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2) - '@storybook/csf-plugin@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))': + '@storybook/csf-plugin@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))': dependencies: - storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) unplugin: 1.16.1 '@storybook/global@5.0.0': {} @@ -10036,19 +10123,19 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@storybook/react-dom-shim@9.1.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))': + '@storybook/react-dom-shim@9.1.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))': dependencies: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) - '@storybook/vue3-vite@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2))': + '@storybook/vue3-vite@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2))': dependencies: - '@storybook/builder-vite': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) - '@storybook/vue3': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2)) + '@storybook/builder-vite': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + '@storybook/vue3': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2)) find-package-json: 1.2.0 magic-string: 0.30.19 - storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) typescript: 5.9.2 vite: 5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2) vue-component-meta: 2.2.12(typescript@5.9.2) @@ -10056,10 +10143,10 @@ snapshots: transitivePeerDependencies: - vue - '@storybook/vue3@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2))': + '@storybook/vue3@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2))': dependencies: '@storybook/global': 5.0.0 - storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) type-fest: 2.19.0 vue: 3.5.13(typescript@5.9.2) vue-component-type-helpers: 3.1.1 @@ -10324,7 +10411,7 @@ snapshots: '@tiptap/extension-text-style': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) '@tiptap/pm': 2.10.4 - '@trivago/prettier-plugin-sort-imports@5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.3.2)': + '@trivago/prettier-plugin-sort-imports@5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.6.2)': dependencies: '@babel/generator': 7.28.3 '@babel/parser': 7.28.4 @@ -10332,7 +10419,7 @@ snapshots: '@babel/types': 7.28.4 javascript-natural-sort: 0.7.1 lodash: 4.17.21 - prettier: 3.3.2 + prettier: 3.6.2 optionalDependencies: '@vue/compiler-sfc': 3.5.13 transitivePeerDependencies: @@ -10357,6 +10444,8 @@ snapshots: dependencies: '@types/deep-eql': 4.0.2 + '@types/css-font-loading-module@0.0.7': {} + '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 @@ -10950,6 +11039,8 @@ snapshots: '@webgpu/types@0.1.51': {} + '@xstate/fsm@1.6.5': {} + '@xterm/addon-fit@0.10.0(@xterm/xterm@5.5.0)': dependencies: '@xterm/xterm': 5.5.0 @@ -11266,6 +11357,8 @@ snapshots: balanced-match@2.0.0: {} + base64-arraybuffer@1.0.2: {} + base64-js@1.5.1: {} better-opn@3.0.2: @@ -12110,20 +12203,20 @@ snapshots: - supports-color optional: true - eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.3.2): + eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.6.2): dependencies: eslint: 9.35.0(jiti@2.4.2) - prettier: 3.3.2 + prettier: 3.6.2 prettier-linter-helpers: 1.0.0 synckit: 0.11.11 optionalDependencies: eslint-config-prettier: 10.1.8(eslint@9.35.0(jiti@2.4.2)) - eslint-plugin-storybook@9.1.6(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2): + eslint-plugin-storybook@9.1.6(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2): dependencies: '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) eslint: 9.35.0(jiti@2.4.2) - storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) transitivePeerDependencies: - supports-color - typescript @@ -12562,10 +12655,6 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - get-tsconfig@4.7.5: - dependencies: - resolve-pkg-maps: 1.0.0 - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -13838,6 +13927,10 @@ snapshots: mitt@3.0.1: {} + mixpanel-browser@2.71.0: + dependencies: + '@mixpanel/rrweb': 2.0.0-alpha.18.2 + mkdirp@3.0.1: {} mlly@1.8.0: @@ -13860,8 +13953,6 @@ snapshots: nanoid@3.3.11: {} - nanoid@3.3.8: {} - nanoid@5.1.5: {} napi-postinstall@0.3.3: {} @@ -14241,14 +14332,14 @@ snapshots: dependencies: htmlparser2: 8.0.2 js-tokens: 9.0.1 - postcss: 8.5.1 - postcss-safe-parser: 6.0.0(postcss@8.5.1) + postcss: 8.5.6 + postcss-safe-parser: 6.0.0(postcss@8.5.6) postcss-resolve-nested-selector@0.1.6: {} - postcss-safe-parser@6.0.0(postcss@8.5.1): + postcss-safe-parser@6.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.1 + postcss: 8.5.6 postcss-safe-parser@7.0.1(postcss@8.5.6): dependencies: @@ -14266,12 +14357,6 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@8.5.1: - dependencies: - nanoid: 3.3.8 - picocolors: 1.1.1 - source-map-js: 1.2.1 - postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -14284,7 +14369,9 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier@3.3.2: {} + prettier@3.6.2: {} + + pretty-bytes@7.1.0: {} pretty-format@27.5.1: dependencies: @@ -14733,6 +14820,15 @@ snapshots: rfdc@1.4.1: {} + rollup-plugin-visualizer@6.0.4(rollup@4.22.4): + dependencies: + open: 8.4.2 + picomatch: 4.0.3 + source-map: 0.7.6 + yargs: 17.7.2 + optionalDependencies: + rollup: 4.22.4 + rollup@4.22.4: dependencies: '@types/estree': 1.0.5 @@ -14924,6 +15020,8 @@ snapshots: source-map@0.6.1: {} + source-map@0.7.6: {} + speakingurl@14.0.1: {} sprintf-js@1.0.3: {} @@ -14950,7 +15048,7 @@ snapshots: internal-slot: 1.1.0 optional: true - storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)): + storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)): dependencies: '@storybook/global': 5.0.0 '@testing-library/jest-dom': 6.6.4 @@ -14965,7 +15063,7 @@ snapshots: semver: 7.7.2 ws: 8.18.3 optionalDependencies: - prettier: 3.3.2 + prettier: 3.6.2 transitivePeerDependencies: - '@testing-library/dom' - bufferutil @@ -15287,7 +15385,7 @@ snapshots: tsx@4.19.4: dependencies: esbuild: 0.25.5 - get-tsconfig: 4.7.5 + get-tsconfig: 4.10.1 optionalDependencies: fsevents: 2.3.3 @@ -15639,7 +15737,7 @@ snapshots: vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2): dependencies: esbuild: 0.21.5 - postcss: 8.5.1 + postcss: 8.5.6 rollup: 4.22.4 optionalDependencies: '@types/node': 20.14.10 @@ -15704,8 +15802,6 @@ snapshots: vue-component-type-helpers@2.2.12: {} - vue-component-type-helpers@3.1.0: {} - vue-component-type-helpers@3.1.1: {} vue-demi@0.14.10(vue@3.5.13(typescript@5.9.2)): diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f3b1ce376..205c84a51 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -94,6 +94,7 @@ catalog: zod: ^3.23.8 zod-to-json-schema: ^3.24.1 zod-validation-error: ^3.3.0 + mixpanel-browser: ^2.71.0 cleanupUnusedCatalogs: true diff --git a/src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue b/src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue index aeaa18220..eddc9c3f7 100644 --- a/src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue +++ b/src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue @@ -86,6 +86,8 @@ import SplitButton from 'primevue/splitbutton' import { computed } from 'vue' import { useI18n } from 'vue-i18n' +import { isCloud } from '@/platform/distribution/types' +import { useTelemetry } from '@/platform/telemetry' import { useCommandStore } from '@/stores/commandStore' import { useQueuePendingTaskCountStore, @@ -141,10 +143,15 @@ const hasPendingTasks = computed( const commandStore = useCommandStore() const queuePrompt = async (e: Event) => { - const commandId = - 'shiftKey' in e && e.shiftKey - ? 'Comfy.QueuePromptFront' - : 'Comfy.QueuePrompt' + const isShiftPressed = 'shiftKey' in e && e.shiftKey + const commandId = isShiftPressed + ? 'Comfy.QueuePromptFront' + : 'Comfy.QueuePrompt' + + if (isCloud) { + useTelemetry()?.trackRunButton({ subscribe_to_run: false }) + } + await commandStore.execute(commandId) } diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index dbcc96efc..96de83a70 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -17,7 +17,9 @@ import { import type { Point } from '@/lib/litegraph/src/litegraph' import { useAssetBrowserDialog } from '@/platform/assets/composables/useAssetBrowserDialog' import { createModelNodeFromAsset } from '@/platform/assets/utils/createModelNodeFromAsset' +import { isCloud } from '@/platform/distribution/types' import { useSettingStore } from '@/platform/settings/settingStore' +import { useTelemetry } from '@/platform/telemetry' import { useToastStore } from '@/platform/updates/common/toastStore' import { useWorkflowService } from '@/platform/workflow/core/services/workflowService' import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' @@ -447,6 +449,11 @@ export function useCoreCommands(): ComfyCommand[] { category: 'essentials' as const, function: async () => { const batchCount = useQueueSettingsStore().batchCount + + if (isCloud) { + useTelemetry()?.trackWorkflowExecution() + } + await app.queuePrompt(0, batchCount) } }, @@ -458,6 +465,11 @@ export function useCoreCommands(): ComfyCommand[] { category: 'essentials' as const, function: async () => { const batchCount = useQueueSettingsStore().batchCount + + if (isCloud) { + useTelemetry()?.trackWorkflowExecution() + } + await app.queuePrompt(-1, batchCount) } }, diff --git a/src/platform/cloud/subscription/components/SubscribeButton.vue b/src/platform/cloud/subscription/components/SubscribeButton.vue index e9eb65fee..c50120f75 100644 --- a/src/platform/cloud/subscription/components/SubscribeButton.vue +++ b/src/platform/cloud/subscription/components/SubscribeButton.vue @@ -15,6 +15,8 @@ import Button from 'primevue/button' import { onBeforeUnmount, ref } from 'vue' import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription' +import { isCloud } from '@/platform/distribution/types' +import { useTelemetry } from '@/platform/telemetry' withDefaults( defineProps<{ @@ -82,6 +84,10 @@ const stopPolling = () => { } const handleSubscribe = async () => { + if (isCloud) { + useTelemetry()?.trackSubscription('subscribe_clicked') + } + isLoading.value = true try { await subscribe() diff --git a/src/platform/cloud/subscription/components/SubscribeToRun.vue b/src/platform/cloud/subscription/components/SubscribeToRun.vue index 75dec98ca..9048e20a7 100644 --- a/src/platform/cloud/subscription/components/SubscribeToRun.vue +++ b/src/platform/cloud/subscription/components/SubscribeToRun.vue @@ -10,7 +10,7 @@ severity="primary" size="small" data-testid="subscribe-to-run-button" - @click="showSubscriptionDialog" + @click="handleSubscribeToRun" /> @@ -18,6 +18,16 @@ import Button from 'primevue/button' import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription' +import { isCloud } from '@/platform/distribution/types' +import { useTelemetry } from '@/platform/telemetry' const { showSubscriptionDialog } = useSubscription() + +const handleSubscribeToRun = () => { + if (isCloud) { + useTelemetry()?.trackRunButton({ subscribe_to_run: true }) + } + + showSubscriptionDialog() +} diff --git a/src/platform/cloud/subscription/composables/useSubscription.ts b/src/platform/cloud/subscription/composables/useSubscription.ts index 694cbe239..b67c8086b 100644 --- a/src/platform/cloud/subscription/composables/useSubscription.ts +++ b/src/platform/cloud/subscription/composables/useSubscription.ts @@ -7,6 +7,7 @@ import { COMFY_API_BASE_URL } from '@/config/comfyApi' import { MONTHLY_SUBSCRIPTION_PRICE } from '@/config/subscriptionPricesConfig' import { t } from '@/i18n' import { isCloud } from '@/platform/distribution/types' +import { useTelemetry } from '@/platform/telemetry' import { useDialogService } from '@/services/dialogService' import { FirebaseAuthStoreError, @@ -78,6 +79,10 @@ export function useSubscription() { }, reportError) const showSubscriptionDialog = () => { + if (isCloud) { + useTelemetry()?.trackSubscription('modal_opened') + } + dialogService.showSubscriptionRequiredDialog() } diff --git a/src/platform/telemetry/index.ts b/src/platform/telemetry/index.ts new file mode 100644 index 000000000..83d7f2c9f --- /dev/null +++ b/src/platform/telemetry/index.ts @@ -0,0 +1,42 @@ +/** + * Telemetry Provider - OSS Build Safety + * + * CRITICAL: OSS Build Safety + * This module is conditionally compiled based on distribution. When building + * the open source version (DISTRIBUTION unset), this entire module and its dependencies + * are excluded through via tree-shaking. + * + * To verify OSS builds exclude this code: + * 1. `DISTRIBUTION= pnpm build` (OSS build) + * 2. `grep -RinE --include='*.js' 'trackWorkflow|trackEvent|mixpanel' dist/` (should find nothing) + * 3. Check dist/assets/*.js files contain no tracking code + * + * This approach maintains complete separation between cloud and OSS builds + * while ensuring the open source version contains no telemetry dependencies. + */ +import { isCloud } from '@/platform/distribution/types' + +import { MixpanelTelemetryProvider } from './providers/cloud/MixpanelTelemetryProvider' +import type { TelemetryProvider } from './types' + +// Singleton instance +let _telemetryProvider: TelemetryProvider | null = null + +/** + * Telemetry factory - conditionally creates provider based on distribution + * Returns singleton instance. + * + * CRITICAL: This returns undefined in OSS builds. There is no telemetry provider + * for OSS builds and all tracking calls are no-ops. + */ +export function useTelemetry(): TelemetryProvider | null { + if (_telemetryProvider === null) { + // Use distribution check for tree-shaking + if (isCloud) { + _telemetryProvider = new MixpanelTelemetryProvider() + } + // For OSS builds, _telemetryProvider stays null + } + + return _telemetryProvider +} diff --git a/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts b/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts new file mode 100644 index 000000000..05866cfd0 --- /dev/null +++ b/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts @@ -0,0 +1,221 @@ +import type { OverridedMixpanel } from 'mixpanel-browser' + +import { useCurrentUser } from '@/composables/auth/useCurrentUser' +import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' +import { useWorkflowTemplatesStore } from '@/platform/workflow/templates/repositories/workflowTemplatesStore' + +import type { + AuthMetadata, + ExecutionContext, + RunButtonProperties, + SurveyResponses, + TelemetryEventName, + TelemetryEventProperties, + TelemetryProvider, + TemplateMetadata +} from '../../types' +import { TelemetryEvents } from '../../types' + +interface QueuedEvent { + eventName: TelemetryEventName + properties?: TelemetryEventProperties +} + +/** + * Mixpanel Telemetry Provider - Cloud Build Implementation + * + * CRITICAL: OSS Build Safety + * This provider integrates with Mixpanel for cloud telemetry tracking. + * Entire file is tree-shaken away in OSS builds (DISTRIBUTION unset). + * + * To verify OSS builds exclude this code: + * 1. `DISTRIBUTION= pnpm build` (OSS build) + * 2. `grep -RinE --include='*.js' 'trackWorkflow|trackEvent|mixpanel' dist/` (should find nothing) + * 3. Check dist/assets/*.js files contain no tracking code + */ +export class MixpanelTelemetryProvider implements TelemetryProvider { + private isEnabled = true + private mixpanel: OverridedMixpanel | null = null + private eventQueue: QueuedEvent[] = [] + private isInitialized = false + + constructor() { + const token = __MIXPANEL_TOKEN__ + + if (token) { + try { + // Dynamic import to avoid bundling mixpanel in OSS builds + void import('mixpanel-browser') + .then((mixpanelModule) => { + this.mixpanel = mixpanelModule.default + this.mixpanel.init(token, { + debug: import.meta.env.DEV, + track_pageview: true, + api_host: 'https://mp.comfy.org', + cross_subdomain_cookie: true, + persistence: 'cookie', + loaded: () => { + this.isInitialized = true + this.flushEventQueue() // flush events that were queued while initializing + useCurrentUser().onUserResolved((user) => { + if (this.mixpanel && user.id) { + this.mixpanel.identify(user.id) + } + }) + } + }) + }) + .catch((error) => { + console.error('Failed to load Mixpanel:', error) + this.isEnabled = false + }) + } catch (error) { + console.error('Failed to initialize Mixpanel:', error) + this.isEnabled = false + } + } else { + console.warn('Mixpanel token not provided') + this.isEnabled = false + } + } + + private flushEventQueue(): void { + if (!this.isInitialized || !this.mixpanel) { + return + } + + while (this.eventQueue.length > 0) { + const event = this.eventQueue.shift()! + try { + this.mixpanel.track(event.eventName, event.properties || {}) + } catch (error) { + console.error('Failed to track queued event:', error) + } + } + } + + private trackEvent( + eventName: TelemetryEventName, + properties?: TelemetryEventProperties + ): void { + if (!this.isEnabled) { + return + } + + const event: QueuedEvent = { eventName, properties } + + if (this.isInitialized && this.mixpanel) { + // Mixpanel is ready, track immediately + try { + this.mixpanel.track(eventName, properties || {}) + } catch (error) { + console.error('Failed to track event:', error) + } + } else { + // Mixpanel not ready yet, queue the event + this.eventQueue.push(event) + } + } + + trackAuth(metadata: AuthMetadata): void { + this.trackEvent(TelemetryEvents.USER_AUTH_COMPLETED, metadata) + } + + trackSubscription(event: 'modal_opened' | 'subscribe_clicked'): void { + const eventName = + event === 'modal_opened' + ? TelemetryEvents.SUBSCRIPTION_REQUIRED_MODAL_OPENED + : TelemetryEvents.SUBSCRIBE_NOW_BUTTON_CLICKED + + this.trackEvent(eventName) + } + + trackRunButton(options?: { subscribe_to_run?: boolean }): void { + const executionContext = this.getExecutionContext() + + const runButtonProperties: RunButtonProperties = { + subscribe_to_run: options?.subscribe_to_run || false, + workflow_type: executionContext.is_template ? 'template' : 'custom', + workflow_name: executionContext.workflow_name ?? 'untitled' + } + + this.trackEvent(TelemetryEvents.RUN_BUTTON_CLICKED, runButtonProperties) + } + + trackSurvey( + stage: 'opened' | 'submitted', + responses?: SurveyResponses + ): void { + const eventName = + stage === 'opened' + ? TelemetryEvents.USER_SURVEY_OPENED + : TelemetryEvents.USER_SURVEY_SUBMITTED + + this.trackEvent(eventName, responses) + } + + trackEmailVerification(stage: 'opened' | 'requested' | 'completed'): void { + let eventName: TelemetryEventName + + switch (stage) { + case 'opened': + eventName = TelemetryEvents.USER_EMAIL_VERIFY_OPENED + break + case 'requested': + eventName = TelemetryEvents.USER_EMAIL_VERIFY_REQUESTED + break + case 'completed': + eventName = TelemetryEvents.USER_EMAIL_VERIFY_COMPLETED + break + } + + this.trackEvent(eventName) + } + + trackTemplate(metadata: TemplateMetadata): void { + this.trackEvent(TelemetryEvents.TEMPLATE_WORKFLOW_OPENED, metadata) + } + + trackWorkflowExecution(): void { + const context = this.getExecutionContext() + this.trackEvent(TelemetryEvents.WORKFLOW_EXECUTION_STARTED, context) + } + + getExecutionContext(): ExecutionContext { + const workflowStore = useWorkflowStore() + const templatesStore = useWorkflowTemplatesStore() + const activeWorkflow = workflowStore.activeWorkflow + + if (activeWorkflow?.filename) { + const isTemplate = templatesStore.knownTemplateNames.has( + activeWorkflow.filename + ) + + if (isTemplate) { + const template = templatesStore.getTemplateByName( + activeWorkflow.filename + ) + return { + is_template: true, + workflow_name: activeWorkflow.filename, + template_source: template?.sourceModule, + template_category: template?.category, + template_tags: template?.tags, + template_models: template?.models, + template_use_case: template?.useCase, + template_license: template?.license + } + } + + return { + is_template: false, + workflow_name: activeWorkflow.filename + } + } + + return { + is_template: false, + workflow_name: undefined + } + } +} diff --git a/src/platform/telemetry/types.ts b/src/platform/telemetry/types.ts new file mode 100644 index 000000000..c749ca569 --- /dev/null +++ b/src/platform/telemetry/types.ts @@ -0,0 +1,138 @@ +/** + * Telemetry Provider Interface + * + * CRITICAL: OSS Build Safety + * This module is excluded from OSS builds via conditional compilation. + * When DISTRIBUTION is unset (OSS builds), Vite's tree-shaking removes this code entirely, + * ensuring the open source build contains no telemetry dependencies. + * + * To verify OSS builds are clean: + * 1. `DISTRIBUTION= pnpm build` (OSS build) + * 2. `grep -RinE --include='*.js' 'trackWorkflow|trackEvent|mixpanel' dist/` (should find nothing) + * 3. Check dist/assets/*.js files contain no tracking code + */ + +/** + * Authentication metadata for sign-up tracking + */ +export interface AuthMetadata { + method?: 'email' | 'google' | 'github' + is_new_user?: boolean + referrer_url?: string + utm_source?: string + utm_medium?: string + utm_campaign?: string +} + +/** + * Survey response data for user profiling + */ +export interface SurveyResponses { + industry?: string + team_size?: string + use_case?: string + familiarity?: string + intended_use?: 'personal' | 'client' | 'inhouse' +} + +/** + * Run button tracking properties + */ +export interface RunButtonProperties { + subscribe_to_run: boolean + workflow_type: 'template' | 'custom' + workflow_name: string +} + +/** + * Execution context for workflow tracking + */ +export interface ExecutionContext { + is_template: boolean + workflow_name?: string + // Template metadata (only present when is_template = true) + template_source?: string + template_category?: string + template_tags?: string[] + template_models?: string[] + template_use_case?: string + template_license?: string +} + +/** + * Template metadata for workflow tracking + */ +export interface TemplateMetadata { + workflow_name: string + template_source?: string + template_category?: string + template_tags?: string[] + template_models?: string[] + template_use_case?: string + template_license?: string +} + +/** + * Core telemetry provider interface + */ +export interface TelemetryProvider { + // Authentication flow events + trackAuth(metadata: AuthMetadata): void + + // Subscription flow events + trackSubscription(event: 'modal_opened' | 'subscribe_clicked'): void + trackRunButton(options?: { subscribe_to_run?: boolean }): void + + // Survey flow events + trackSurvey(stage: 'opened' | 'submitted', responses?: SurveyResponses): void + + // Email verification events + trackEmailVerification(stage: 'opened' | 'requested' | 'completed'): void + + // Template workflow events + trackTemplate(metadata: TemplateMetadata): void + + // Workflow execution events + trackWorkflowExecution(): void +} + +/** + * Telemetry event constants + */ +export const TelemetryEvents = { + // Authentication Flow + USER_AUTH_COMPLETED: 'user_auth_completed', + + // Subscription Flow + RUN_BUTTON_CLICKED: 'run_button_clicked', + SUBSCRIPTION_REQUIRED_MODAL_OPENED: 'subscription_required_modal_opened', + SUBSCRIBE_NOW_BUTTON_CLICKED: 'subscribe_now_button_clicked', + + // Onboarding Survey + USER_SURVEY_OPENED: 'user_survey_opened', + USER_SURVEY_SUBMITTED: 'user_survey_submitted', + + // Email Verification + USER_EMAIL_VERIFY_OPENED: 'user_email_verify_opened', + USER_EMAIL_VERIFY_REQUESTED: 'user_email_verify_requested', + USER_EMAIL_VERIFY_COMPLETED: 'user_email_verify_completed', + + // Template Tracking + TEMPLATE_WORKFLOW_OPENED: 'template_workflow_opened', + + // Workflow Execution Tracking + WORKFLOW_EXECUTION_STARTED: 'workflow_execution_started' +} as const + +export type TelemetryEventName = + (typeof TelemetryEvents)[keyof typeof TelemetryEvents] + +/** + * Union type for all possible telemetry event properties + */ +export type TelemetryEventProperties = + | AuthMetadata + | SurveyResponses + | TemplateMetadata + | ExecutionContext + | RunButtonProperties diff --git a/src/platform/workflow/templates/composables/useTemplateWorkflows.ts b/src/platform/workflow/templates/composables/useTemplateWorkflows.ts index 44295d46b..450ab9a47 100644 --- a/src/platform/workflow/templates/composables/useTemplateWorkflows.ts +++ b/src/platform/workflow/templates/composables/useTemplateWorkflows.ts @@ -1,6 +1,8 @@ import { computed, ref } from 'vue' import { useI18n } from 'vue-i18n' +import { isCloud } from '@/platform/distribution/types' +import { useTelemetry } from '@/platform/telemetry' import { useWorkflowTemplatesStore } from '@/platform/workflow/templates/repositories/workflowTemplatesStore' import type { TemplateGroup, @@ -78,7 +80,7 @@ export function useTemplateWorkflows() { const fallback = template.title ?? template.name ?? `${sourceModule} Template` return sourceModule === 'default' - ? template.localizedTitle ?? fallback + ? (template.localizedTitle ?? fallback) : fallback } @@ -128,6 +130,13 @@ export function useTemplateWorkflows() { ? t(`templateWorkflows.template.${id}`, id) : id + if (isCloud) { + useTelemetry()?.trackTemplate({ + workflow_name: workflowName, + template_source: actualSourceModule + }) + } + dialogStore.closeDialog() await app.loadGraphData(json, true, true, workflowName) @@ -142,6 +151,13 @@ export function useTemplateWorkflows() { ? t(`templateWorkflows.template.${id}`, id) : id + if (isCloud) { + useTelemetry()?.trackTemplate({ + workflow_name: workflowName, + template_source: sourceModule + }) + } + dialogStore.closeDialog() await app.loadGraphData(json, true, true, workflowName) diff --git a/src/platform/workflow/templates/repositories/workflowTemplatesStore.ts b/src/platform/workflow/templates/repositories/workflowTemplatesStore.ts index 87f9378c7..8d73911f1 100644 --- a/src/platform/workflow/templates/repositories/workflowTemplatesStore.ts +++ b/src/platform/workflow/templates/repositories/workflowTemplatesStore.ts @@ -30,6 +30,11 @@ export const useWorkflowTemplatesStore = defineStore( const customTemplates = shallowRef<{ [moduleName: string]: string[] }>({}) const coreTemplates = shallowRef([]) const isLoaded = ref(false) + const knownTemplateNames = ref(new Set()) + + const getTemplateByName = (name: string): EnhancedTemplate | undefined => { + return enhancedTemplates.value.find((template) => template.name === name) + } // Store filter mappings for dynamic categories type FilterData = { @@ -432,6 +437,13 @@ export const useWorkflowTemplatesStore = defineStore( customTemplates.value = await api.getWorkflowTemplates() const locale = i18n.global.locale.value coreTemplates.value = await api.getCoreWorkflowTemplates(locale) + + const coreNames = coreTemplates.value.flatMap((category) => + category.templates.map((template) => template.name) + ) + const customNames = Object.values(customTemplates.value).flat() + knownTemplateNames.value = new Set([...coreNames, ...customNames]) + isLoaded.value = true } } catch (error) { @@ -446,7 +458,9 @@ export const useWorkflowTemplatesStore = defineStore( templateFuse, filterTemplatesByCategory, isLoaded, - loadWorkflowTemplates + loadWorkflowTemplates, + knownTemplateNames, + getTemplateByName } } ) diff --git a/src/stores/firebaseAuthStore.ts b/src/stores/firebaseAuthStore.ts index 526a86d00..4abbd9f7a 100644 --- a/src/stores/firebaseAuthStore.ts +++ b/src/stores/firebaseAuthStore.ts @@ -6,6 +6,7 @@ import { browserLocalPersistence, createUserWithEmailAndPassword, deleteUser, + getAdditionalUserInfo, onAuthStateChanged, sendEmailVerification, sendPasswordResetEmail, @@ -22,6 +23,8 @@ import { useFirebaseAuth } from 'vuefire' import { COMFY_API_BASE_URL } from '@/config/comfyApi' import { t } from '@/i18n' +import { isCloud } from '@/platform/distribution/types' +import { useTelemetry } from '@/platform/telemetry' import { useDialogService } from '@/services/dialogService' import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore' import type { AuthHeader } from '@/types/authTypes' @@ -246,36 +249,79 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => { const login = async ( email: string, password: string - ): Promise => - executeAuthAction( + ): Promise => { + const result = await executeAuthAction( (authInstance) => signInWithEmailAndPassword(authInstance, email, password), { createCustomer: true } ) + if (isCloud) { + useTelemetry()?.trackAuth({ + method: 'email', + is_new_user: false + }) + } + + return result + } + const register = async ( email: string, password: string ): Promise => { - return executeAuthAction( + const result = await executeAuthAction( (authInstance) => createUserWithEmailAndPassword(authInstance, email, password), { createCustomer: true } ) + + if (isCloud) { + useTelemetry()?.trackAuth({ + method: 'email', + is_new_user: true + }) + } + + return result } - const loginWithGoogle = async (): Promise => - executeAuthAction( + const loginWithGoogle = async (): Promise => { + const result = await executeAuthAction( (authInstance) => signInWithPopup(authInstance, googleProvider), { createCustomer: true } ) - const loginWithGithub = async (): Promise => - executeAuthAction( + if (isCloud) { + const additionalUserInfo = getAdditionalUserInfo(result) + const isNewUser = additionalUserInfo?.isNewUser ?? false + useTelemetry()?.trackAuth({ + method: 'google', + is_new_user: isNewUser + }) + } + + return result + } + + const loginWithGithub = async (): Promise => { + const result = await executeAuthAction( (authInstance) => signInWithPopup(authInstance, githubProvider), { createCustomer: true } ) + if (isCloud) { + const additionalUserInfo = getAdditionalUserInfo(result) + const isNewUser = additionalUserInfo?.isNewUser ?? false + useTelemetry()?.trackAuth({ + method: 'github', + is_new_user: isNewUser + }) + } + + return result + } + const logout = async (): Promise => executeAuthAction((authInstance) => signOut(authInstance)) diff --git a/tests-ui/tests/composables/useTemplateWorkflows.test.ts b/tests-ui/tests/composables/useTemplateWorkflows.test.ts index 4b98fb4f1..8a78b5a4d 100644 --- a/tests-ui/tests/composables/useTemplateWorkflows.test.ts +++ b/tests-ui/tests/composables/useTemplateWorkflows.test.ts @@ -31,6 +31,11 @@ vi.mock('@/scripts/app', () => ({ vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: vi.fn((key, fallback) => fallback || key) + }), + createI18n: () => ({ + global: { + t: (key: string) => key + } }) })) diff --git a/tests-ui/tests/platform/cloud/subscription/useSubscription.test.ts b/tests-ui/tests/platform/cloud/subscription/useSubscription.test.ts index a36471bf5..ec356cfc3 100644 --- a/tests-ui/tests/platform/cloud/subscription/useSubscription.test.ts +++ b/tests-ui/tests/platform/cloud/subscription/useSubscription.test.ts @@ -19,6 +19,10 @@ vi.mock('@/composables/auth/useCurrentUser', () => ({ })) })) +vi.mock('@/platform/telemetry', () => ({ + useTelemetry: vi.fn(() => null) +})) + vi.mock('@/composables/auth/useFirebaseAuthActions', () => ({ useFirebaseAuthActions: vi.fn(() => ({ reportError: mockReportError, diff --git a/tests-ui/tests/platform/telemetry/useTelemetry.test.ts b/tests-ui/tests/platform/telemetry/useTelemetry.test.ts new file mode 100644 index 000000000..fa3e584f2 --- /dev/null +++ b/tests-ui/tests/platform/telemetry/useTelemetry.test.ts @@ -0,0 +1,30 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +vi.mock('@/platform/distribution/types', () => ({ + isCloud: false +})) + +describe('useTelemetry', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should return null when not in cloud distribution', async () => { + const { useTelemetry } = await import('@/platform/telemetry') + const provider = useTelemetry() + + // Should return null for OSS builds + expect(provider).toBeNull() + }) + + it('should return null consistently for OSS builds', async () => { + const { useTelemetry } = await import('@/platform/telemetry') + + const provider1 = useTelemetry() + const provider2 = useTelemetry() + + // Both should be null for OSS builds + expect(provider1).toBeNull() + expect(provider2).toBeNull() + }) +}) diff --git a/vite.config.mts b/vite.config.mts index e1e432374..72d34a42b 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -268,7 +268,8 @@ export default defineConfig({ __ALGOLIA_API_KEY__: JSON.stringify(process.env.ALGOLIA_API_KEY || ''), __USE_PROD_CONFIG__: process.env.USE_PROD_CONFIG === 'true', __DISTRIBUTION__: JSON.stringify(DISTRIBUTION), - __BUILD_FLAGS__: JSON.stringify(BUILD_FLAGS) + __BUILD_FLAGS__: JSON.stringify(BUILD_FLAGS), + __MIXPANEL_TOKEN__: JSON.stringify(process.env.MIXPANEL_TOKEN || '') }, resolve: {