fix: guard voice input UI promises

This commit is contained in:
mssoftjp 2025-11-10 00:54:36 +09:00
parent 7ddd851713
commit 5ff1787f6d
4 changed files with 48 additions and 19 deletions

View file

@ -118,7 +118,9 @@ export default class VoiceInputPlugin extends Plugin {
// Add ribbon icon
this.addRibbonIcon('microphone', i18n.t('ui.titles.main'), () => {
this.logger.debug('Ribbon icon clicked');
void this.viewManager.activateVoiceInputView();
void this.viewManager.activateVoiceInputView().catch((error) => {
this.logger.error('Failed to activate Voice Input view from ribbon', error);
});
});
this.logger.debug('Ribbon icon added');
@ -128,7 +130,9 @@ export default class VoiceInputPlugin extends Plugin {
name: i18n.t('ui.commands.openView'),
callback: () => {
this.logger.debug('Command executed: open-view');
void this.viewManager.activateVoiceInputView();
void this.viewManager.activateVoiceInputView().catch((error) => {
this.logger.error('Failed to activate Voice Input view from command', error);
});
}
});
this.logger.debug('Commands registered');
@ -153,7 +157,13 @@ export default class VoiceInputPlugin extends Plugin {
}
onunload(): void {
void this.teardownPlugin();
void this.teardownPlugin().catch((error) => {
if (this.logger?.error) {
this.logger.error('Voice Input Plugin unload failed', error);
} else if (typeof console !== 'undefined' && console.error) {
console.error('Voice Input Plugin unload failed', error);
}
});
}
private async teardownPlugin(): Promise<void> {

View file

@ -42,7 +42,9 @@ export class VoiceInputView extends ItemView {
}
onOpen(): void {
void this.handleOpen();
void this.handleOpen().catch((error) => {
console.error('Failed to open Voice Input view', error);
});
}
private async handleOpen(): Promise<void> {
@ -83,7 +85,9 @@ export class VoiceInputView extends ItemView {
}
onClose(): void {
void this.handleClose();
void this.handleClose().catch((error) => {
console.error('Failed to close Voice Input view', error);
});
}
private async handleClose(): Promise<void> {

View file

@ -213,7 +213,7 @@ export class VoiceInputViewActions {
: 'max-duration';
this.pendingVadAutoStop = false;
this.updateUIAfterStop();
void this.processRecordedAudio(audioBlob, { type: reasonType });
this.processRecordedAudio(audioBlob, { type: reasonType });
};
const handleMicrophoneStatusChange = (status: 'initializing' | 'ready' | 'error') => {
@ -303,7 +303,7 @@ export class VoiceInputViewActions {
// Process audio for manual stop
if (audioBlob && audioBlob.size > 0) {
await this.processRecordedAudio(audioBlob, stopReason);
this.processRecordedAudio(audioBlob, stopReason);
}
} catch (error) {
@ -378,7 +378,7 @@ export class VoiceInputViewActions {
/**
* Process recorded audio with proper notifications
*/
private async processRecordedAudio(audioBlob: Blob, stopReason: StopReason): Promise<void> {
private processRecordedAudio(audioBlob: Blob, stopReason: StopReason): void {
// Show appropriate status based on stop reason
if (this.view.ui.statusEl) {
switch (stopReason.type) {
@ -406,7 +406,9 @@ export class VoiceInputViewActions {
// Process queue if not already processing
if (!this.isProcessingAudio) {
void this.processQueue();
void this.processQueue().catch((error) => {
this.logger.error('Failed to process audio queue', error);
});
}
}
@ -452,7 +454,9 @@ export class VoiceInputViewActions {
if (this.recordingState.processingQueue.length > 0) {
// Use setTimeout to avoid potential stack overflow
setTimeout(() => {
void this.processQueue();
void this.processQueue().catch((error) => {
this.logger.error('Failed to continue processing audio queue', error);
});
}, 0);
}
}

View file

@ -121,7 +121,7 @@ export class VoiceInputViewUI {
});
this.clipboardButton.setAttribute('aria-label', this.i18n.t('ui.tooltips.copy'));
this.clipboardButton.addEventListener('click', () => {
void this.view.actions.copyToClipboard();
this.runAction(() => this.view.actions.copyToClipboard(), 'Failed to copy text to clipboard');
});
// Clear button
@ -150,7 +150,7 @@ export class VoiceInputViewUI {
});
this.insertAtCursorButton.setAttribute('aria-label', this.i18n.t('ui.tooltips.insertAtCursor'));
this.insertAtCursorButton.addEventListener('click', () => {
void this.view.actions.insertToNote();
this.runAction(() => this.view.actions.insertToNote(), 'Failed to insert text at cursor');
});
// Append button
@ -163,7 +163,7 @@ export class VoiceInputViewUI {
});
this.appendButton.setAttribute('aria-label', this.i18n.t('ui.tooltips.append'));
this.appendButton.addEventListener('click', () => {
void this.view.actions.appendToNote();
this.runAction(() => this.view.actions.appendToNote(), 'Failed to append text to note');
});
// Keep the old insertButton for backward compatibility (set to hidden)
@ -178,7 +178,7 @@ export class VoiceInputViewUI {
cls: 'voice-input-cancel-button-full voice-input-hidden'
});
this.cancelButton.addEventListener('click', () => {
void this.view.actions.cancelRecording();
this.runAction(() => this.view.actions.cancelRecording(), 'Failed to cancel recording');
});
// Second row - Record button container
@ -267,7 +267,7 @@ export class VoiceInputViewUI {
return;
}
void this.view.actions.toggleRecording();
this.runAction(() => this.view.actions.toggleRecording(), 'Failed to toggle recording');
};
// Push-to-talk functionality with delayed start
@ -386,13 +386,15 @@ export class VoiceInputViewUI {
*/
updateSettingsUI() {
// Update correction toggle
if (this.correctionToggle) {
this.correctionToggle.setValue(this.plugin.settings.enableTranscriptionCorrection);
const correctionToggle = this.correctionToggle;
if (correctionToggle) {
correctionToggle.setValue(this.plugin.settings.enableTranscriptionCorrection);
}
// Update transcription model dropdown
if (this.transcriptionModelDropdown) {
this.transcriptionModelDropdown.setValue(this.plugin.settings.transcriptionModel);
const transcriptionModelDropdown = this.transcriptionModelDropdown;
if (transcriptionModelDropdown) {
transcriptionModelDropdown.setValue(this.plugin.settings.transcriptionModel);
}
// (no additional post-processing UI elements to update)
@ -416,6 +418,15 @@ export class VoiceInputViewUI {
this.textArea.addEventListener('cut', this.textChangeHandler);
}
/**
* Helper to safely run async view actions without unhandled rejections
*/
private runAction(task: () => Promise<void>, context: string): void {
void task().catch((error) => {
console.error(context, error);
});
}
/**
* Clean up resources
*/