diff --git a/packages/common/graphql/package.json b/packages/common/graphql/package.json index 274052e62f..fd287ca503 100644 --- a/packages/common/graphql/package.json +++ b/packages/common/graphql/package.json @@ -10,7 +10,7 @@ "exports": { ".": "./src/index.ts" }, - "sideEffects": "false", + "sideEffects": false, "devDependencies": { "@graphql-codegen/add": "^5.0.3", "@graphql-codegen/cli": "5.0.5", diff --git a/packages/frontend/apps/electron-renderer/package.json b/packages/frontend/apps/electron-renderer/package.json index 7778f113d3..1e6dd5f0db 100644 --- a/packages/frontend/apps/electron-renderer/package.json +++ b/packages/frontend/apps/electron-renderer/package.json @@ -14,6 +14,7 @@ "@affine/electron-api": "workspace:*", "@affine/i18n": "workspace:*", "@affine/nbstore": "workspace:*", + "@affine/track": "workspace:*", "@blocksuite/affine": "workspace:*", "@emotion/react": "^11.14.0", "@sentry/react": "^9.2.0", diff --git a/packages/frontend/apps/electron-renderer/src/app/effects/recording.ts b/packages/frontend/apps/electron-renderer/src/app/effects/recording.ts index bdb87c23a4..f5d764690b 100644 --- a/packages/frontend/apps/electron-renderer/src/app/effects/recording.ts +++ b/packages/frontend/apps/electron-renderer/src/app/effects/recording.ts @@ -7,6 +7,7 @@ import { WorkbenchService } from '@affine/core/modules/workbench'; import { DebugLogger } from '@affine/debug'; import { apis, events } from '@affine/electron-api'; import { i18nTime } from '@affine/i18n'; +import track from '@affine/track'; import type { AttachmentBlockModel } from '@blocksuite/affine/model'; import { Text } from '@blocksuite/affine/store'; import type { BlobEngine } from '@blocksuite/affine/sync'; @@ -114,9 +115,18 @@ export function setupRecordingEvents(frameworkProvider: FrameworkProvider) { using audioAttachment = workspace.scope .get(AudioAttachmentService) .get(model); - audioAttachment?.obj.transcribe().catch(err => { - logger.error('Failed to transcribe recording', err); - }); + audioAttachment?.obj + .transcribe() + .then(() => { + track.doc.editor.audioBlock.transcribeRecording({ + type: 'Meeting record', + method: 'success', + option: 'Auto transcribing', + }); + }) + .catch(err => { + logger.error('Failed to transcribe recording', err); + }); } else { throw new Error('No attachment model found'); } diff --git a/packages/frontend/apps/electron-renderer/src/popup/recording/index.tsx b/packages/frontend/apps/electron-renderer/src/popup/recording/index.tsx index d593232676..89ae5d0093 100644 --- a/packages/frontend/apps/electron-renderer/src/popup/recording/index.tsx +++ b/packages/frontend/apps/electron-renderer/src/popup/recording/index.tsx @@ -4,6 +4,7 @@ import { appIconMap } from '@affine/core/utils'; import { encodeRawBufferToOpus } from '@affine/core/utils/webm-encoding'; import { apis, events } from '@affine/electron-api'; import { useI18n } from '@affine/i18n'; +import track from '@affine/track'; import { useEffect, useMemo, useState } from 'react'; import * as styles from './styles.css'; @@ -81,12 +82,20 @@ export function Recording() { const handleDismiss = useAsyncCallback(async () => { await apis?.popup?.dismissCurrentRecording(); - }, []); + track.popup.$.recordingBar.dismissRecording({ + type: 'Meeting record', + appName: status?.appName || 'System Audio', + }); + }, [status]); const handleStopRecording = useAsyncCallback(async () => { if (!status) { return; } + track.popup.$.recordingBar.finishRecording({ + type: 'Meeting record', + appName: status.appName || 'System Audio', + }); await apis?.recording?.stopRecording(status.id); }, [status]); @@ -130,6 +139,12 @@ export function Recording() { useEffect(() => { // allow processing stopped event in tray menu as well: return events?.recording.onRecordingStatusChanged(status => { + if (status?.status === 'new') { + track.popup.$.recordingBar.toggleRecordingBar({ + type: 'Meeting record', + appName: status.appName || 'System Audio', + }); + } if (status?.status === 'stopped') { handleProcessStoppedRecording(); } @@ -140,6 +155,10 @@ export function Recording() { if (!status) { return; } + track.popup.$.recordingBar.startRecording({ + type: 'Meeting record', + appName: status.appName || 'System Audio', + }); await apis?.recording?.startRecording(status.appGroupId); }, [status]); diff --git a/packages/frontend/apps/electron-renderer/tsconfig.json b/packages/frontend/apps/electron-renderer/tsconfig.json index f8fc509d16..c81ab6cca7 100644 --- a/packages/frontend/apps/electron-renderer/tsconfig.json +++ b/packages/frontend/apps/electron-renderer/tsconfig.json @@ -14,6 +14,7 @@ { "path": "../../electron-api" }, { "path": "../../i18n" }, { "path": "../../../common/nbstore" }, + { "path": "../../track" }, { "path": "../../../../blocksuite/affine/all" }, { "path": "../../../common/infra" }, { "path": "../../../../tools/utils" } diff --git a/packages/frontend/core/src/blocksuite/attachment-viewer/audio/audio-block.tsx b/packages/frontend/core/src/blocksuite/attachment-viewer/audio/audio-block.tsx index 17f499b202..a5e409fe29 100644 --- a/packages/frontend/core/src/blocksuite/attachment-viewer/audio/audio-block.tsx +++ b/packages/frontend/core/src/blocksuite/attachment-viewer/audio/audio-block.tsx @@ -14,6 +14,7 @@ import { GlobalDialogService } from '@affine/core/modules/dialogs'; import type { AudioAttachmentBlock } from '@affine/core/modules/media/entities/audio-attachment-block'; import { AudioAttachmentService } from '@affine/core/modules/media/services/audio-attachment'; import { Trans, useI18n } from '@affine/i18n'; +import track from '@affine/track'; import type { AttachmentBlockModel } from '@blocksuite/affine/model'; import { useLiveData, useService } from '@toeverything/infra'; import { useCallback, useEffect, useMemo, useState } from 'react'; @@ -71,6 +72,11 @@ const AttachmentAudioPlayer = ({ block }: { block: AudioAttachmentBlock }) => { if (transcribed) { block.expanded$.setValue(!expanded); + track.doc.editor.audioBlock.openTranscribeNotes({ + type: 'Meeting record', + method: 'success', + option: expanded ? 'off' : 'on', + }); return; } @@ -87,6 +93,10 @@ const AttachmentAudioPlayer = ({ block }: { block: AudioAttachmentBlock }) => { globalDialogService.open('sign-in', {}); }, }); + track.doc.editor.audioBlock.openTranscribeNotes({ + type: 'Meeting record', + method: 'not signed in', + }); return; } @@ -108,8 +118,17 @@ const AttachmentAudioPlayer = ({ block }: { block: AudioAttachmentBlock }) => { variant: 'primary', }, }); + track.doc.editor.audioBlock.openTranscribeNotes({ + type: 'Meeting record', + method: 'not owner', + }); } else { await block.transcribe(); + track.doc.editor.audioBlock.transcribeRecording({ + type: 'Meeting record', + method: 'success', + option: 'handle transcribing', + }); } }, [ enableAi, diff --git a/packages/frontend/core/src/desktop/dialogs/setting/general-setting/meetings/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/meetings/index.tsx index b9b5cfff9e..69d7f617a8 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/general-setting/meetings/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/meetings/index.tsx @@ -15,6 +15,7 @@ import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hoo import { MeetingSettingsService } from '@affine/core/modules/media/services/meeting-settings'; import type { MeetingSettingsSchema } from '@affine/electron/main/shared-state-schema'; import { useI18n } from '@affine/i18n'; +import track from '@affine/track'; import { ArrowRightSmallIcon, DoneIcon, @@ -106,6 +107,10 @@ export const MeetingsSettings = () => { const handleEnabledChange = useAsyncCallback( async (checked: boolean) => { try { + track.$.settingsPanel.meetings.toggleMeetingFeatureFlag({ + option: checked ? 'on' : 'off', + type: 'Meeting record', + }); await meetingSettingsService.setEnabled(checked); } catch { confirmModal.openConfirmModal({ diff --git a/packages/frontend/core/src/modules/media/entities/audio-attachment-block.ts b/packages/frontend/core/src/modules/media/entities/audio-attachment-block.ts index ff4d954daf..0aa6732cc9 100644 --- a/packages/frontend/core/src/modules/media/entities/audio-attachment-block.ts +++ b/packages/frontend/core/src/modules/media/entities/audio-attachment-block.ts @@ -1,6 +1,7 @@ import { encodeAudioBlobToOpus } from '@affine/core/utils/webm-encoding'; import { DebugLogger } from '@affine/debug'; import { AiJobStatus } from '@affine/graphql'; +import track from '@affine/track'; import { type AttachmentBlockModel, TranscriptionBlockFlavour, @@ -142,6 +143,10 @@ export class AudioAttachmentBlock extends Entity { this.fillTranscriptionResult(status.result); } } catch (error) { + track.doc.editor.audioBlock.transcribeRecording({ + type: 'Meeting record', + method: 'fail', + }); logger.error('Error transcribing audio:', error); throw error; } diff --git a/packages/frontend/track/package.json b/packages/frontend/track/package.json index 76d37f0015..fb4e861aec 100644 --- a/packages/frontend/track/package.json +++ b/packages/frontend/track/package.json @@ -6,7 +6,6 @@ "exports": { ".": "./src/index.ts" }, - "sideEffects": "false", "dependencies": { "@affine/debug": "workspace:*", "@sentry/react": "^9.2.0", diff --git a/packages/frontend/track/src/events.ts b/packages/frontend/track/src/events.ts index df0118f2e8..fb217856db 100644 --- a/packages/frontend/track/src/events.ts +++ b/packages/frontend/track/src/events.ts @@ -165,6 +165,18 @@ type IntegrationEvents = | 'completeIntegrationImport'; // END SECTION +// SECTION: journal +type MeetingEvents = + | 'toggleRecordingBar' + | 'startRecording' + | 'dismissRecording' + | 'finishRecording' + | 'transcribeRecording' + | 'openTranscribeNotes' + | 'toggleMeetingFeatureFlag' + | 'activeMenubarAppItem'; +// END SECTION + type UserEvents = | GeneralEvents | AppEvents @@ -185,7 +197,8 @@ type UserEvents = | AttachmentEvents | TemplateEvents | NotificationEvents - | IntegrationEvents; + | IntegrationEvents + | MeetingEvents; interface PageDivision { [page: string]: { [segment: string]: { @@ -262,6 +275,7 @@ const PageEvents = { 'abortIntegrationImport', 'completeIntegrationImport', ], + meetings: ['toggleMeetingFeatureFlag'], }, cmdk: { recent: ['recentDocs'], @@ -403,6 +417,7 @@ const PageEvents = { ], aiActions: ['requestSignIn'], starterBar: ['quickStart', 'openTemplateListMenu'], + audioBlock: ['transcribeRecording', 'openTranscribeNotes'], }, inlineDocInfo: { $: ['toggle'], @@ -459,6 +474,21 @@ const PageEvents = { $: ['checkout'], }, }, + menubarApp: { + menubarActionsMenu: { + menubarActionsList: ['activeMenubarAppItem', 'startRecording'], + }, + }, + popup: { + $: { + recordingBar: [ + 'toggleRecordingBar', + 'startRecording', + 'dismissRecording', + 'finishRecording', + ], + }, + }, } as const satisfies PageDivision; type OrganizeItemType = 'doc' | 'folder' | 'collection' | 'tag' | 'favorite'; @@ -520,6 +550,12 @@ type IntegrationArgs> = { control: 'Readwise Card' | 'Readwise settings' | 'Readwise import list'; } & T; +type RecordingEventArgs = { + type: 'Meeting record'; + method?: string; + option?: 'Auto transcribing' | 'handle transcribing' | 'on' | 'off'; +}; + export type EventArgs = { createWorkspace: { flavour: string }; signIn: AuthArgs; @@ -622,6 +658,45 @@ export type EventArgs = { done: number; total: number; }>; + toggleRecordingBar: RecordingEventArgs & { + method: string; + appName: string; + }; + startRecording: RecordingEventArgs & { + method: string; + appName: string; + }; + dismissRecording: RecordingEventArgs & { + method: string; + appName: string; + }; + finishRecording: RecordingEventArgs & { + method: 'fail' | 'success'; + appName: string; + }; + transcribeRecording: RecordingEventArgs & { + method: 'fail' | 'success'; + option: 'Auto transcribing' | 'handle transcribing'; + }; + openTranscribeNotes: RecordingEventArgs & { + method: 'success' | 'reach limit' | 'not signed in' | 'not owner'; + option: 'on' | 'off'; + }; + toggleMeetingFeatureFlag: RecordingEventArgs & { + option: 'on' | 'off'; + }; + activeMenubarAppItem: RecordingEventArgs & { + control: + | 'Open Journal' + | 'New Page' + | 'New Edgeless' + | 'Start recording meeting' + | 'Stop recording' + | 'Open AFFiNE' + | 'About AFFiNE' + | 'Meeting Settings' + | 'Quit AFFiNE Completely'; + }; }; // for type checking diff --git a/tools/utils/src/workspace.gen.ts b/tools/utils/src/workspace.gen.ts index 443585cd1a..f825c86558 100644 --- a/tools/utils/src/workspace.gen.ts +++ b/tools/utils/src/workspace.gen.ts @@ -949,6 +949,7 @@ export const PackageList = [ 'packages/frontend/electron-api', 'packages/frontend/i18n', 'packages/common/nbstore', + 'packages/frontend/track', 'blocksuite/affine/all', 'packages/common/infra', 'tools/utils', diff --git a/yarn.lock b/yarn.lock index b180baecbc..8253946570 100644 --- a/yarn.lock +++ b/yarn.lock @@ -509,6 +509,7 @@ __metadata: "@affine/electron-api": "workspace:*" "@affine/i18n": "workspace:*" "@affine/nbstore": "workspace:*" + "@affine/track": "workspace:*" "@blocksuite/affine": "workspace:*" "@emotion/react": "npm:^11.14.0" "@sentry/react": "npm:^9.2.0"