fix: align types with obsidian 1.8

This commit is contained in:
mssoftjp 2025-11-15 18:49:55 +09:00
parent 029508c45c
commit 72879880d7
7 changed files with 77 additions and 38 deletions

View file

@ -390,10 +390,11 @@ export class AudioRecorder extends Disposable {
return;
}
this.processingAnalyserNode.getFloatTimeDomainData(this.analyserDataBuffer);
const analyserBuffer = this.analyserDataBuffer as Float32Array<ArrayBuffer>;
this.processingAnalyserNode.getFloatTimeDomainData(analyserBuffer);
// Copy the buffer to avoid mutation before processing completes
const audioData = new Float32Array(this.analyserDataBuffer);
const audioData = new Float32Array(analyserBuffer);
try {
this.processAudioData(audioData);
} catch (error) {

View file

@ -68,15 +68,16 @@ export class AudioVisualizer extends Disposable {
this.animationId = requestAnimationFrame(() => this.draw());
// Get time domain data (waveform)
this.analyser.getByteTimeDomainData(this.dataArray);
const waveformArray = this.dataArray as Uint8Array<ArrayBuffer>;
this.analyser.getByteTimeDomainData(waveformArray);
// Calculate RMS (Root Mean Square) for current audio level
let sum = 0;
for (let i = 0; i < this.dataArray.length; i++) {
const normalized = (this.dataArray[i] - 128) / 128;
for (let i = 0; i < waveformArray.length; i++) {
const normalized = (waveformArray[i] - 128) / 128;
sum += normalized * normalized;
}
const rms = Math.sqrt(sum / this.dataArray.length);
const rms = Math.sqrt(sum / waveformArray.length);
this.currentLevel = rms;
// Apply scaling factor to make waveform more visible
@ -285,13 +286,14 @@ export class SimpleAudioLevelIndicator extends Disposable {
this.animationId = requestAnimationFrame(() => this.update());
this.analyser.getByteFrequencyData(this.dataArray);
const frequencyArray = this.dataArray as Uint8Array<ArrayBuffer>;
this.analyser.getByteFrequencyData(frequencyArray);
// Calculate average level with emphasis on lower frequencies (voice range)
let sum = 0;
const relevantBins = Math.min(this.dataArray.length / 4, 50); // Focus on voice frequencies
const relevantBins = Math.min(frequencyArray.length / 4, 50); // Focus on voice frequencies
for (let i = 0; i < relevantBins; i++) {
sum += this.dataArray[i];
sum += frequencyArray[i];
}
const average = sum / relevantBins;
// Apply scaling for better visibility

View file

@ -1,7 +1,7 @@
import {
Plugin
} from 'obsidian';
import { VoiceInputSettings, DEFAULT_SETTINGS } from '../interfaces';
import { VoiceInputSettings, DEFAULT_SETTINGS, Locale, SUPPORTED_LOCALES } from '../interfaces';
import { VoiceInputView, VIEW_TYPE_VOICE_INPUT } from '../views';
import { VoiceInputSettingTab } from '../settings';
import { ViewManager, DraftManager } from '../managers';
@ -11,7 +11,12 @@ import { serviceLocator, ServiceKeys, getI18nService } from '../services';
import { getObsidianLocale } from '../types';
import { SafeStorageService } from '../security';
type LegacyVoiceInputSettings = Partial<VoiceInputSettings> & {
type LegacyAdvancedSettings = Partial<Omit<VoiceInputSettings['advanced'], 'transcriptionLanguage'>> & {
transcriptionLanguage?: string;
};
type LegacyVoiceInputSettings = Partial<Omit<VoiceInputSettings, 'advanced'>> & {
advanced?: LegacyAdvancedSettings;
language?: string;
interfaceLanguage?: string;
recordingMode?: string;
@ -19,6 +24,10 @@ type LegacyVoiceInputSettings = Partial<VoiceInputSettings> & {
minSpeechDuration?: number;
};
const isSupportedLocale = (value: unknown): value is Locale => {
return typeof value === 'string' && (SUPPORTED_LOCALES as readonly string[]).includes(value as Locale);
};
export default class VoiceInputPlugin extends Plugin {
settings: VoiceInputSettings;
private viewManager: ViewManager;
@ -244,7 +253,10 @@ export default class VoiceInputPlugin extends Plugin {
// interfaceLanguageからpluginLanguageへの移行
if ('interfaceLanguage' in data && !('pluginLanguage' in data)) {
migratedData.pluginLanguage = data.interfaceLanguage;
const interfaceLanguage = data.interfaceLanguage;
migratedData.pluginLanguage = isSupportedLocale(interfaceLanguage)
? interfaceLanguage
: this.detectPluginLanguage();
delete migratedData.interfaceLanguage;
needsSave = true;
this.logger?.info('Migrating interfaceLanguage to pluginLanguage');
@ -254,7 +266,7 @@ export default class VoiceInputPlugin extends Plugin {
if ('language' in data && !('transcriptionLanguage' in data)) {
// 既存のlanguageフィールドをtranscriptionLanguageに移行
const langValue = data.language;
if (langValue === 'ja' || langValue === 'en' || langValue === 'zh' || langValue === 'ko') {
if (isSupportedLocale(langValue)) {
migratedData.transcriptionLanguage = langValue;
} else if (langValue === 'auto') {
// auto は廃止: 起動環境のロケールへ固定
@ -270,8 +282,8 @@ export default class VoiceInputPlugin extends Plugin {
// languageからpluginLanguageへの移行古いバージョンとの互換性のため
if ('language' in data && !('pluginLanguage' in data)) {
// 言語コードを正規化ja → ja、en → en、zh → zh、ko → ko、その他 → en
const langCode = data.language as string;
if (langCode === 'ja' || langCode === 'en' || langCode === 'zh' || langCode === 'ko') {
const langCode = data.language;
if (isSupportedLocale(langCode)) {
migratedData.pluginLanguage = langCode;
} else {
migratedData.pluginLanguage = 'en';
@ -306,7 +318,7 @@ export default class VoiceInputPlugin extends Plugin {
}
// 保存されたデータをデフォルト設定に上書きundefinedの値は除外
mergeSettings(this.settings, migratedData);
mergeSettings(this.settings, migratedData as Partial<VoiceInputSettings>);
// APIキーの復号化
if (this.settings.openaiApiKey) {
@ -363,14 +375,13 @@ export default class VoiceInputPlugin extends Plugin {
};
needsSave = true;
this.logger?.info('Initialized advanced settings with language linking enabled for backward compatibility');
} else if (data.advanced && !hasSettingsKey(data.advanced, 'languageLinkingEnabled')) {
// advancedオブジェクトは存在するが、languageLinkingEnabledが無い場合
this.settings.advanced.languageLinkingEnabled = true;
needsSave = true;
this.logger?.info('Added languageLinkingEnabled to existing advanced settings');
} else if (data.advanced) {
// auto からの置換
const adv = data.advanced as { transcriptionLanguage?: string };
const adv = data.advanced as LegacyAdvancedSettings;
if (typeof adv.languageLinkingEnabled === 'undefined') {
this.settings.advanced.languageLinkingEnabled = true;
needsSave = true;
this.logger?.info('Added languageLinkingEnabled to existing advanced settings');
}
if (adv.transcriptionLanguage === 'auto') {
this.settings.advanced.transcriptionLanguage = this.detectPluginLanguage();
needsSave = true;

View file

@ -35,11 +35,19 @@ const isElectronWindow = (win: Window): win is ElectronWindow => {
};
const resolveElectronRenderer = (): ElectronRenderer | null => {
if (isElectronWindow(window) && typeof (window as ElectronWindow).require === 'function') {
return (window as ElectronWindow).require('electron') ?? null;
if (isElectronWindow(window)) {
const electronWindow = window as ElectronWindow;
const requireFn = electronWindow.require;
if (typeof requireFn === 'function') {
return requireFn('electron') ?? null;
}
if (electronWindow.electron) {
return electronWindow.electron ?? null;
}
}
if ((window as ElectronWindow).electron) {
return (window as ElectronWindow).electron ?? null;
const windowElectron = (window as ElectronWindow).electron;
if (windowElectron) {
return windowElectron ?? null;
}
return (globalThis as ElectronGlobal).electron ?? null;
};

View file

@ -28,6 +28,17 @@ const isTranscriptionModel = (value: string): value is TranscriptionModelOption
const isVadMode = (value: string): value is VadModeOption =>
value === 'server' || value === 'local' || value === 'disabled';
const toArrayBuffer = (view: Uint8Array): ArrayBuffer => {
const { buffer, byteOffset, byteLength } = view;
if (buffer instanceof ArrayBuffer) {
return byteOffset === 0 && byteLength === buffer.byteLength
? buffer
: buffer.slice(byteOffset, byteOffset + byteLength);
}
const clone = Uint8Array.from(view);
return clone.buffer instanceof ArrayBuffer ? clone.buffer : new ArrayBuffer(0);
};
interface DictionaryExportData {
version: string;
definiteCorrections: CorrectionEntry[];
@ -335,7 +346,7 @@ export class VoiceInputSettingTab extends PluginSettingTab {
}
const wasmTarget = getLocalVadAssetPath(this.app, wasmFileName);
await adapter.writeBinary(wasmTarget, wasmBytes);
await adapter.writeBinary(wasmTarget, toArrayBuffer(wasmBytes));
let loaderPresent = await adapter.exists(getLocalVadAssetPath(this.app, loaderFileName));
if (jsFile) {

View file

@ -1,4 +1,5 @@
import {
Events,
ItemView,
WorkspaceLeaf
} from 'obsidian';
@ -41,10 +42,12 @@ export class VoiceInputView extends ItemView {
return 'microphone';
}
onOpen(): void {
void this.handleOpen().catch((error) => {
async onOpen(): Promise<void> {
try {
await this.handleOpen();
} catch (error) {
console.error('Failed to open Voice Input view', error);
});
}
}
private async handleOpen(): Promise<void> {
@ -75,7 +78,8 @@ export class VoiceInputView extends ItemView {
this.setupPeriodicSave();
// Subscribe to settings change events to keep UI toggles in sync
const eventRef = this.app.workspace.on('voice-input:settings-changed', () => {
const workspaceEvents = this.app.workspace as Events;
const eventRef = workspaceEvents.on('voice-input:settings-changed', () => {
this.ui?.updateSettingsUI();
this.actions?.updateTranscriptionService();
});
@ -84,10 +88,12 @@ export class VoiceInputView extends ItemView {
}
}
onClose(): void {
void this.handleClose().catch((error) => {
async onClose(): Promise<void> {
try {
await this.handleClose();
} catch (error) {
console.error('Failed to close Voice Input view', error);
});
}
}
private async handleClose(): Promise<void> {

View file

@ -4,14 +4,14 @@
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"target": "ES2018",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"lib": ["DOM", "ES5", "ES6", "ES7", "ES2015.Iterable", "DOM.Iterable"]
"lib": ["DOM", "ES2018", "DOM.Iterable"]
},
"include": ["**/*.ts", "src/types/**/*.d.ts"],
"exclude": [
@ -20,4 +20,4 @@
"build/**",
"tests/**"
]
}
}