feat(core): some enhancements to recording (#11287)
- Added a check to verify if AI is enabled before attempting to transcribe meeting recordings - Improved error handling for empty recordings - Fixed the recording timeout logic to ensure it only stops the correct recording session
This commit is contained in:
@@ -12,7 +12,7 @@ import { Text } from '@blocksuite/affine/store';
|
|||||||
import type { BlobEngine } from '@blocksuite/affine/sync';
|
import type { BlobEngine } from '@blocksuite/affine/sync';
|
||||||
import type { FrameworkProvider } from '@toeverything/infra';
|
import type { FrameworkProvider } from '@toeverything/infra';
|
||||||
|
|
||||||
import { getCurrentWorkspace } from './utils';
|
import { getCurrentWorkspace, isAiEnabled } from './utils';
|
||||||
|
|
||||||
const logger = new DebugLogger('electron-renderer:recording');
|
const logger = new DebugLogger('electron-renderer:recording');
|
||||||
|
|
||||||
@@ -44,6 +44,7 @@ export function setupRecordingEvents(frameworkProvider: FrameworkProvider) {
|
|||||||
frameworkProvider.get(EditorSettingService);
|
frameworkProvider.get(EditorSettingService);
|
||||||
const docsService = workspace.scope.get(DocsService);
|
const docsService = workspace.scope.get(DocsService);
|
||||||
const editorSetting = editorSettingService.editorSetting;
|
const editorSetting = editorSettingService.editorSetting;
|
||||||
|
const aiEnabled = isAiEnabled(frameworkProvider);
|
||||||
|
|
||||||
const timestamp = i18nTime(status.startTime, {
|
const timestamp = i18nTime(status.startTime, {
|
||||||
absolute: {
|
absolute: {
|
||||||
@@ -96,7 +97,10 @@ export function setupRecordingEvents(frameworkProvider: FrameworkProvider) {
|
|||||||
MeetingSettingsService
|
MeetingSettingsService
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!meetingSettingsService.settings.autoTranscription) {
|
if (
|
||||||
|
!meetingSettingsService.settings.autoTranscription ||
|
||||||
|
!aiEnabled
|
||||||
|
) {
|
||||||
// auto transcription is disabled,
|
// auto transcription is disabled,
|
||||||
// so we don't need to transcribe the recording by default
|
// so we don't need to transcribe the recording by default
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ServersService } from '@affine/core/modules/cloud';
|
||||||
|
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||||
import { WorkspacesService } from '@affine/core/modules/workspace';
|
import { WorkspacesService } from '@affine/core/modules/workspace';
|
||||||
import type { FrameworkProvider } from '@toeverything/infra';
|
import type { FrameworkProvider } from '@toeverything/infra';
|
||||||
@@ -21,3 +23,23 @@ export function getCurrentWorkspace(frameworkProvider: FrameworkProvider) {
|
|||||||
[Symbol.dispose]: dispose,
|
[Symbol.dispose]: dispose,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCurrentServerService(frameworkProvider: FrameworkProvider) {
|
||||||
|
const currentServerId = frameworkProvider
|
||||||
|
.get(GlobalContextService)
|
||||||
|
.globalContext.serverId.get();
|
||||||
|
const serversService = frameworkProvider.get(ServersService);
|
||||||
|
const serverRef = currentServerId
|
||||||
|
? serversService.servers$.value.find(
|
||||||
|
server => server.id === currentServerId
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
return serverRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isAiEnabled(frameworkProvider: FrameworkProvider) {
|
||||||
|
const featureFlagService = frameworkProvider.get(FeatureFlagService);
|
||||||
|
const serverService = getCurrentServerService(frameworkProvider);
|
||||||
|
const aiConfig = serverService?.features$.value.copilot;
|
||||||
|
return featureFlagService.flags.enable_ai.$ && aiConfig;
|
||||||
|
}
|
||||||
|
|||||||
@@ -517,14 +517,17 @@ export function startRecording(
|
|||||||
appGroup: normalizeAppGroupInfo(appGroup),
|
appGroup: normalizeAppGroupInfo(appGroup),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (state?.status === 'recording') {
|
// set a timeout to stop the recording after MAX_DURATION_FOR_TRANSCRIPTION
|
||||||
// set a timeout to stop the recording after MAX_DURATION_FOR_TRANSCRIPTION
|
setTimeout(() => {
|
||||||
setTimeout(() => {
|
if (
|
||||||
|
state?.status === 'recording' &&
|
||||||
|
state.id === recordingStatus$.value?.id
|
||||||
|
) {
|
||||||
stopRecording(state.id).catch(err => {
|
stopRecording(state.id).catch(err => {
|
||||||
logger.error('failed to stop recording', err);
|
logger.error('failed to stop recording', err);
|
||||||
});
|
});
|
||||||
}, MAX_DURATION_FOR_TRANSCRIPTION);
|
}
|
||||||
}
|
}, MAX_DURATION_FOR_TRANSCRIPTION);
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@@ -549,30 +552,48 @@ export async function stopRecording(id: number) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const recordingStatus = recordingStateMachine.dispatch({
|
|
||||||
type: 'STOP_RECORDING',
|
|
||||||
id,
|
|
||||||
filepath: String(recording.file.path),
|
|
||||||
sampleRate: recording.stream.sampleRate,
|
|
||||||
numberOfChannels: recording.stream.channels,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!recordingStatus) {
|
|
||||||
logger.error('No recording status to stop');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { file } = recording;
|
const { file } = recording;
|
||||||
file.end();
|
file.end();
|
||||||
|
|
||||||
// Wait for file to finish writing
|
// Wait for file to finish writing
|
||||||
await new Promise<void>(resolve => {
|
try {
|
||||||
file.on('finish', () => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
resolve();
|
file.on('finish', () => {
|
||||||
|
// check if the file is empty
|
||||||
|
const stats = fs.statSync(file.path);
|
||||||
|
if (stats.size === 0) {
|
||||||
|
logger.error(`Recording ${id} is empty`);
|
||||||
|
reject(new Error('Recording is empty'));
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const recordingStatus = recordingStateMachine.dispatch({
|
||||||
|
type: 'STOP_RECORDING',
|
||||||
|
id,
|
||||||
|
filepath: String(recording.file.path),
|
||||||
|
sampleRate: recording.stream.sampleRate,
|
||||||
|
numberOfChannels: recording.stream.channels,
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
return serializeRecordingStatus(recordingStatus);
|
if (!recordingStatus) {
|
||||||
|
logger.error('No recording status to stop');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return serializeRecordingStatus(recordingStatus);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
logger.error('Failed to stop recording', error);
|
||||||
|
const recordingStatus = recordingStateMachine.dispatch({
|
||||||
|
type: 'CREATE_BLOCK_FAILED',
|
||||||
|
id,
|
||||||
|
error: error instanceof Error ? error : undefined,
|
||||||
|
});
|
||||||
|
if (!recordingStatus) {
|
||||||
|
logger.error('No recording status to stop');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return serializeRecordingStatus(recordingStatus);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function readyRecording(id: number, buffer: Buffer) {
|
export async function readyRecording(id: number, buffer: Buffer) {
|
||||||
|
|||||||
Reference in New Issue
Block a user