mirror of
https://github.com/mssoftjp/obsidian-voice-input.git
synced 2026-07-22 06:44:48 +00:00
fix: align types with obsidian 1.8
This commit is contained in:
parent
029508c45c
commit
72879880d7
7 changed files with 77 additions and 38 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue