chore(electron): add telemetry events for enabling meetings (#11327)

fix AF-2436
This commit is contained in:
pengx17
2025-04-01 09:01:31 +00:00
parent 275098abe2
commit 9cb80205f8
12 changed files with 143 additions and 7 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
"exports": { "exports": {
".": "./src/index.ts" ".": "./src/index.ts"
}, },
"sideEffects": "false", "sideEffects": false,
"devDependencies": { "devDependencies": {
"@graphql-codegen/add": "^5.0.3", "@graphql-codegen/add": "^5.0.3",
"@graphql-codegen/cli": "5.0.5", "@graphql-codegen/cli": "5.0.5",
@@ -14,6 +14,7 @@
"@affine/electron-api": "workspace:*", "@affine/electron-api": "workspace:*",
"@affine/i18n": "workspace:*", "@affine/i18n": "workspace:*",
"@affine/nbstore": "workspace:*", "@affine/nbstore": "workspace:*",
"@affine/track": "workspace:*",
"@blocksuite/affine": "workspace:*", "@blocksuite/affine": "workspace:*",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@sentry/react": "^9.2.0", "@sentry/react": "^9.2.0",
@@ -7,6 +7,7 @@ import { WorkbenchService } from '@affine/core/modules/workbench';
import { DebugLogger } from '@affine/debug'; import { DebugLogger } from '@affine/debug';
import { apis, events } from '@affine/electron-api'; import { apis, events } from '@affine/electron-api';
import { i18nTime } from '@affine/i18n'; import { i18nTime } from '@affine/i18n';
import track from '@affine/track';
import type { AttachmentBlockModel } from '@blocksuite/affine/model'; import type { AttachmentBlockModel } from '@blocksuite/affine/model';
import { Text } from '@blocksuite/affine/store'; import { Text } from '@blocksuite/affine/store';
import type { BlobEngine } from '@blocksuite/affine/sync'; import type { BlobEngine } from '@blocksuite/affine/sync';
@@ -114,9 +115,18 @@ export function setupRecordingEvents(frameworkProvider: FrameworkProvider) {
using audioAttachment = workspace.scope using audioAttachment = workspace.scope
.get(AudioAttachmentService) .get(AudioAttachmentService)
.get(model); .get(model);
audioAttachment?.obj.transcribe().catch(err => { audioAttachment?.obj
logger.error('Failed to transcribe recording', err); .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 { } else {
throw new Error('No attachment model found'); throw new Error('No attachment model found');
} }
@@ -4,6 +4,7 @@ import { appIconMap } from '@affine/core/utils';
import { encodeRawBufferToOpus } from '@affine/core/utils/webm-encoding'; import { encodeRawBufferToOpus } from '@affine/core/utils/webm-encoding';
import { apis, events } from '@affine/electron-api'; import { apis, events } from '@affine/electron-api';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import track from '@affine/track';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import * as styles from './styles.css'; import * as styles from './styles.css';
@@ -81,12 +82,20 @@ export function Recording() {
const handleDismiss = useAsyncCallback(async () => { const handleDismiss = useAsyncCallback(async () => {
await apis?.popup?.dismissCurrentRecording(); await apis?.popup?.dismissCurrentRecording();
}, []); track.popup.$.recordingBar.dismissRecording({
type: 'Meeting record',
appName: status?.appName || 'System Audio',
});
}, [status]);
const handleStopRecording = useAsyncCallback(async () => { const handleStopRecording = useAsyncCallback(async () => {
if (!status) { if (!status) {
return; return;
} }
track.popup.$.recordingBar.finishRecording({
type: 'Meeting record',
appName: status.appName || 'System Audio',
});
await apis?.recording?.stopRecording(status.id); await apis?.recording?.stopRecording(status.id);
}, [status]); }, [status]);
@@ -130,6 +139,12 @@ export function Recording() {
useEffect(() => { useEffect(() => {
// allow processing stopped event in tray menu as well: // allow processing stopped event in tray menu as well:
return events?.recording.onRecordingStatusChanged(status => { 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') { if (status?.status === 'stopped') {
handleProcessStoppedRecording(); handleProcessStoppedRecording();
} }
@@ -140,6 +155,10 @@ export function Recording() {
if (!status) { if (!status) {
return; return;
} }
track.popup.$.recordingBar.startRecording({
type: 'Meeting record',
appName: status.appName || 'System Audio',
});
await apis?.recording?.startRecording(status.appGroupId); await apis?.recording?.startRecording(status.appGroupId);
}, [status]); }, [status]);
@@ -14,6 +14,7 @@
{ "path": "../../electron-api" }, { "path": "../../electron-api" },
{ "path": "../../i18n" }, { "path": "../../i18n" },
{ "path": "../../../common/nbstore" }, { "path": "../../../common/nbstore" },
{ "path": "../../track" },
{ "path": "../../../../blocksuite/affine/all" }, { "path": "../../../../blocksuite/affine/all" },
{ "path": "../../../common/infra" }, { "path": "../../../common/infra" },
{ "path": "../../../../tools/utils" } { "path": "../../../../tools/utils" }
@@ -14,6 +14,7 @@ import { GlobalDialogService } from '@affine/core/modules/dialogs';
import type { AudioAttachmentBlock } from '@affine/core/modules/media/entities/audio-attachment-block'; import type { AudioAttachmentBlock } from '@affine/core/modules/media/entities/audio-attachment-block';
import { AudioAttachmentService } from '@affine/core/modules/media/services/audio-attachment'; import { AudioAttachmentService } from '@affine/core/modules/media/services/audio-attachment';
import { Trans, useI18n } from '@affine/i18n'; import { Trans, useI18n } from '@affine/i18n';
import track from '@affine/track';
import type { AttachmentBlockModel } from '@blocksuite/affine/model'; import type { AttachmentBlockModel } from '@blocksuite/affine/model';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
@@ -71,6 +72,11 @@ const AttachmentAudioPlayer = ({ block }: { block: AudioAttachmentBlock }) => {
if (transcribed) { if (transcribed) {
block.expanded$.setValue(!expanded); block.expanded$.setValue(!expanded);
track.doc.editor.audioBlock.openTranscribeNotes({
type: 'Meeting record',
method: 'success',
option: expanded ? 'off' : 'on',
});
return; return;
} }
@@ -87,6 +93,10 @@ const AttachmentAudioPlayer = ({ block }: { block: AudioAttachmentBlock }) => {
globalDialogService.open('sign-in', {}); globalDialogService.open('sign-in', {});
}, },
}); });
track.doc.editor.audioBlock.openTranscribeNotes({
type: 'Meeting record',
method: 'not signed in',
});
return; return;
} }
@@ -108,8 +118,17 @@ const AttachmentAudioPlayer = ({ block }: { block: AudioAttachmentBlock }) => {
variant: 'primary', variant: 'primary',
}, },
}); });
track.doc.editor.audioBlock.openTranscribeNotes({
type: 'Meeting record',
method: 'not owner',
});
} else { } else {
await block.transcribe(); await block.transcribe();
track.doc.editor.audioBlock.transcribeRecording({
type: 'Meeting record',
method: 'success',
option: 'handle transcribing',
});
} }
}, [ }, [
enableAi, enableAi,
@@ -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 { MeetingSettingsService } from '@affine/core/modules/media/services/meeting-settings';
import type { MeetingSettingsSchema } from '@affine/electron/main/shared-state-schema'; import type { MeetingSettingsSchema } from '@affine/electron/main/shared-state-schema';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import track from '@affine/track';
import { import {
ArrowRightSmallIcon, ArrowRightSmallIcon,
DoneIcon, DoneIcon,
@@ -106,6 +107,10 @@ export const MeetingsSettings = () => {
const handleEnabledChange = useAsyncCallback( const handleEnabledChange = useAsyncCallback(
async (checked: boolean) => { async (checked: boolean) => {
try { try {
track.$.settingsPanel.meetings.toggleMeetingFeatureFlag({
option: checked ? 'on' : 'off',
type: 'Meeting record',
});
await meetingSettingsService.setEnabled(checked); await meetingSettingsService.setEnabled(checked);
} catch { } catch {
confirmModal.openConfirmModal({ confirmModal.openConfirmModal({
@@ -1,6 +1,7 @@
import { encodeAudioBlobToOpus } from '@affine/core/utils/webm-encoding'; import { encodeAudioBlobToOpus } from '@affine/core/utils/webm-encoding';
import { DebugLogger } from '@affine/debug'; import { DebugLogger } from '@affine/debug';
import { AiJobStatus } from '@affine/graphql'; import { AiJobStatus } from '@affine/graphql';
import track from '@affine/track';
import { import {
type AttachmentBlockModel, type AttachmentBlockModel,
TranscriptionBlockFlavour, TranscriptionBlockFlavour,
@@ -142,6 +143,10 @@ export class AudioAttachmentBlock extends Entity<AttachmentBlockModel> {
this.fillTranscriptionResult(status.result); this.fillTranscriptionResult(status.result);
} }
} catch (error) { } catch (error) {
track.doc.editor.audioBlock.transcribeRecording({
type: 'Meeting record',
method: 'fail',
});
logger.error('Error transcribing audio:', error); logger.error('Error transcribing audio:', error);
throw error; throw error;
} }
-1
View File
@@ -6,7 +6,6 @@
"exports": { "exports": {
".": "./src/index.ts" ".": "./src/index.ts"
}, },
"sideEffects": "false",
"dependencies": { "dependencies": {
"@affine/debug": "workspace:*", "@affine/debug": "workspace:*",
"@sentry/react": "^9.2.0", "@sentry/react": "^9.2.0",
+76 -1
View File
@@ -165,6 +165,18 @@ type IntegrationEvents =
| 'completeIntegrationImport'; | 'completeIntegrationImport';
// END SECTION // END SECTION
// SECTION: journal
type MeetingEvents =
| 'toggleRecordingBar'
| 'startRecording'
| 'dismissRecording'
| 'finishRecording'
| 'transcribeRecording'
| 'openTranscribeNotes'
| 'toggleMeetingFeatureFlag'
| 'activeMenubarAppItem';
// END SECTION
type UserEvents = type UserEvents =
| GeneralEvents | GeneralEvents
| AppEvents | AppEvents
@@ -185,7 +197,8 @@ type UserEvents =
| AttachmentEvents | AttachmentEvents
| TemplateEvents | TemplateEvents
| NotificationEvents | NotificationEvents
| IntegrationEvents; | IntegrationEvents
| MeetingEvents;
interface PageDivision { interface PageDivision {
[page: string]: { [page: string]: {
[segment: string]: { [segment: string]: {
@@ -262,6 +275,7 @@ const PageEvents = {
'abortIntegrationImport', 'abortIntegrationImport',
'completeIntegrationImport', 'completeIntegrationImport',
], ],
meetings: ['toggleMeetingFeatureFlag'],
}, },
cmdk: { cmdk: {
recent: ['recentDocs'], recent: ['recentDocs'],
@@ -403,6 +417,7 @@ const PageEvents = {
], ],
aiActions: ['requestSignIn'], aiActions: ['requestSignIn'],
starterBar: ['quickStart', 'openTemplateListMenu'], starterBar: ['quickStart', 'openTemplateListMenu'],
audioBlock: ['transcribeRecording', 'openTranscribeNotes'],
}, },
inlineDocInfo: { inlineDocInfo: {
$: ['toggle'], $: ['toggle'],
@@ -459,6 +474,21 @@ const PageEvents = {
$: ['checkout'], $: ['checkout'],
}, },
}, },
menubarApp: {
menubarActionsMenu: {
menubarActionsList: ['activeMenubarAppItem', 'startRecording'],
},
},
popup: {
$: {
recordingBar: [
'toggleRecordingBar',
'startRecording',
'dismissRecording',
'finishRecording',
],
},
},
} as const satisfies PageDivision; } as const satisfies PageDivision;
type OrganizeItemType = 'doc' | 'folder' | 'collection' | 'tag' | 'favorite'; type OrganizeItemType = 'doc' | 'folder' | 'collection' | 'tag' | 'favorite';
@@ -520,6 +550,12 @@ type IntegrationArgs<T extends Record<string, any>> = {
control: 'Readwise Card' | 'Readwise settings' | 'Readwise import list'; control: 'Readwise Card' | 'Readwise settings' | 'Readwise import list';
} & T; } & T;
type RecordingEventArgs = {
type: 'Meeting record';
method?: string;
option?: 'Auto transcribing' | 'handle transcribing' | 'on' | 'off';
};
export type EventArgs = { export type EventArgs = {
createWorkspace: { flavour: string }; createWorkspace: { flavour: string };
signIn: AuthArgs; signIn: AuthArgs;
@@ -622,6 +658,45 @@ export type EventArgs = {
done: number; done: number;
total: 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 // for type checking
+1
View File
@@ -949,6 +949,7 @@ export const PackageList = [
'packages/frontend/electron-api', 'packages/frontend/electron-api',
'packages/frontend/i18n', 'packages/frontend/i18n',
'packages/common/nbstore', 'packages/common/nbstore',
'packages/frontend/track',
'blocksuite/affine/all', 'blocksuite/affine/all',
'packages/common/infra', 'packages/common/infra',
'tools/utils', 'tools/utils',
+1
View File
@@ -509,6 +509,7 @@ __metadata:
"@affine/electron-api": "workspace:*" "@affine/electron-api": "workspace:*"
"@affine/i18n": "workspace:*" "@affine/i18n": "workspace:*"
"@affine/nbstore": "workspace:*" "@affine/nbstore": "workspace:*"
"@affine/track": "workspace:*"
"@blocksuite/affine": "workspace:*" "@blocksuite/affine": "workspace:*"
"@emotion/react": "npm:^11.14.0" "@emotion/react": "npm:^11.14.0"
"@sentry/react": "npm:^9.2.0" "@sentry/react": "npm:^9.2.0"