From 543af13c1f4fb1d5f43980fb53060bc4c5d216be Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sat, 10 Oct 2026 19:24:40 +0400 Subject: [PATCH] =?UTF-8?q?fix(obsidian):=20provider=20check=20=E2=80=94?= =?UTF-8?q?=20precise=20error,=20no=20Notice=20after=20unload,=20keep=20un?= =?UTF-8?q?known=20data.json=20keys,=20settings=20button=20(M-3,=20M-4,=20?= =?UTF-8?q?M-5,=20O-6,=20#7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit M-3: only readBinary is in try/catch, so 'Не удалось прочитать' is about reading only; transcribe() never throws. M-4: after each await the check returns silently if the plugin was unloaded. M-5: saveSettings writes {...data loaded from data.json, openai}, other top-level keys survive. O-6: 'Проверить' button in the settings tab runs the same check as the command. Co-Authored-By: Claude Opus 5.5 --- src/main.ts | 25 +++++++++++++++++-------- src/obsidian/settings.ts | 7 +++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/main.ts b/src/main.ts index ebaf5cc..680672d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,6 +12,7 @@ interface VoiceNoteData { // провайдера и команда его проверки. export default class VoiceNotePlugin extends Plugin { override settings: VoiceNoteData = { openai: readSettings(undefined) }; + private data: Record = {}; private readonly controller = new AbortController(); private transcriber = new OpenAiTranscriber(obsidianHttp, () => this.settings.openai, Math.random); @@ -30,16 +31,20 @@ export default class VoiceNotePlugin extends Plugin { } async loadSettings(): Promise { - const data = (await this.loadData()) as { openai?: unknown } | null; - this.settings = { openai: readSettings(data?.openai) }; + const data: unknown = await this.loadData(); + this.data = data !== null && typeof data === 'object' && !Array.isArray(data) ? (data as Record) : {}; + this.settings = { openai: readSettings(this.data.openai) }; } + // Ключи data.json, которых плагин пока не знает, сохраняются как были при загрузке. async saveSettings(): Promise { - await this.saveData(this.settings); + await this.saveData({ ...this.data, ...this.settings }); } // Только уведомление: заметки, frontmatter и очередь не трогаются. - private async checkTranscriber(): Promise { + // После выгрузки плагина (signal.aborted) уведомлений не показывает. + async checkTranscriber(): Promise { + const signal = this.controller.signal; const files = this.app.vault.getFiles(); const picked = pickCheckAudio(files.map((f) => f.path)); const file = picked && files.find((f) => f.path === picked.path); @@ -48,12 +53,16 @@ export default class VoiceNotePlugin extends Plugin { return; } new Notice(`Проверка провайдера: ${picked.path}…`); + let audio: ArrayBuffer; try { - const audio = await this.app.vault.readBinary(file); - const result = await this.transcriber.transcribe(audio, picked.mime, this.controller.signal); - new Notice(checkNotice(result), 10_000); + audio = await this.app.vault.readBinary(file); } catch (e) { - new Notice(`Не удалось прочитать ${picked.path}: ${e instanceof Error ? e.message : String(e)}`, 10_000); + if (!signal.aborted) new Notice(`Не удалось прочитать ${picked.path}: ${e instanceof Error ? e.message : String(e)}`, 10_000); + return; } + if (signal.aborted) return; + // transcribe() не бросает: любая ошибка приходит в result. + const result = await this.transcriber.transcribe(audio, picked.mime, signal); + if (!signal.aborted) new Notice(checkNotice(result), 10_000); } } diff --git a/src/obsidian/settings.ts b/src/obsidian/settings.ts index 71c8760..4507539 100644 --- a/src/obsidian/settings.ts +++ b/src/obsidian/settings.ts @@ -1,4 +1,5 @@ import { type App, PluginSettingTab, Setting } from 'obsidian'; +import { SOURCE_DIR } from '../core/naming'; import type { OpenAiSettings } from '../core/openai'; import type VoiceNotePlugin from '../main'; @@ -18,6 +19,12 @@ export class VoiceNoteSettingTab extends PluginSettingTab { this.field('Модель', 'Например, gpt-4o-transcribe', 'model'); this.field('API-ключ', 'Хранится только в data.json плагина и синхронизируется вместе с vault', 'apiKey', true); this.field('Язык', 'Код языка (ru, en). Пусто — модель определяет язык сама', 'language'); + new Setting(containerEl) + .setName('Проверка провайдера') + .setDesc( + `Отправит последнюю запись из ${SOURCE_DIR}/ и покажет результат в уведомлении. То же — команда «Проверить провайдер распознавания».`, + ) + .addButton((button) => button.setButtonText('Проверить').onClick(() => void this.plugin.checkTranscriber())); } private field(name: string, desc: string, key: keyof OpenAiSettings, secret = false): void {