From b8382c2d4f31023bcca726fa787ab07ac414413a Mon Sep 17 00:00:00 2001 From: dimitrievgs Date: Sat, 16 May 2026 18:55:53 +0300 Subject: [PATCH] Add setting to show or hide voice panel --- main.ts | 16 +++++++++++ src/components/VoicePanel.ts | 17 +++++++++++- src/views/GraphView.ts | 51 +++++++++++++++++++++++++++++++----- 3 files changed, 76 insertions(+), 8 deletions(-) diff --git a/main.ts b/main.ts index ad05304..9118693 100644 --- a/main.ts +++ b/main.ts @@ -43,6 +43,8 @@ interface LLMAgentSettings { markerStart: string; markerStop: string; + showVoicePanel: boolean; + lastActiveGraphId: string | null; lastActiveNodeId: string | null; } @@ -76,6 +78,8 @@ const DEFAULT_SETTINGS: LLMAgentSettings = { markerStart: 'старт', markerStop: 'стоп', + showVoicePanel: false, + lastActiveGraphId: null, lastActiveNodeId: null, }; @@ -365,6 +369,18 @@ class SampleSettingTab extends PluginSettingTab { containerEl.createEl('h2', { text: 'Голосовое управление (Voice Service)' }); + new Setting(containerEl) + .setName('Отображать голосовую панель') + .setDesc('Показывать нижнюю панель Voice Service в графе диалогов.') + .addToggle((toggle) => + toggle.setValue(this.plugin.settings.showVoicePanel).onChange(async (value) => { + this.plugin.settings.showVoicePanel = value; + await this.plugin.saveSettings(); + // Событие 'settings-changed' уже вызывается внутри saveSettings, + // поэтому панель будет скрываться/появляться мгновенно + }) + ); + new Setting(containerEl) .setName('Модель команд') .setDesc('LLM для обработки голосовых команд.') diff --git a/src/components/VoicePanel.ts b/src/components/VoicePanel.ts index e06754c..1725d75 100644 --- a/src/components/VoicePanel.ts +++ b/src/components/VoicePanel.ts @@ -3,6 +3,7 @@ import { MarkdownRenderer, setIcon, Notice } from 'obsidian'; export class VoicePanel { private activeTab: 'res1' | 'res2' | 'trans' | 'metrics' = 'res1'; + private pollInterval: any = null; constructor( private container: HTMLElement, @@ -58,7 +59,9 @@ export class VoicePanel { } async startPolling() { - setInterval(async () => { + if (this.pollInterval) return; // <-- ЗАЩИТА ОТ ДУБЛИКАТОВ ТАЙМЕРА + + this.pollInterval = setInterval(async () => { // <-- ПРИСВАИВАЕМ pollInterval try { const res = await fetch(`${this.plugin.settings.apiBaseUrl}/voice/status`); const data = await res.json(); @@ -199,4 +202,16 @@ export class VoicePanel { }); }); } + + public stopPolling() { + if (this.pollInterval) { + clearInterval(this.pollInterval); + this.pollInterval = null; + } + } + + public destroy() { + this.stopPolling(); + this.container.innerHTML = ''; + } } diff --git a/src/views/GraphView.ts b/src/views/GraphView.ts index 2bb0090..0bbdfd1 100644 --- a/src/views/GraphView.ts +++ b/src/views/GraphView.ts @@ -38,6 +38,10 @@ export class GraphView extends ItemView { toggleButton: HTMLButtonElement; isHistoryCollapsed: boolean; + resizer: HTMLDivElement; + voiceContainer: HTMLDivElement; + voicePanelInstance: VoicePanel | null = null; + // Глобальные кнопки действий copySelectedButton: HTMLButtonElement | null = null; deleteSelectedButton: HTMLButtonElement | null = null; @@ -117,8 +121,8 @@ export class GraphView extends ItemView { this.graphContainer.style.cssText = `flex: 1; position: relative; background-color: var(--background-primary);`; // 2. РАЗДЕЛИТЕЛЬ (Resizer) - const resizer = mainContainer.createDiv('voice-panel-resizer'); - resizer.style.cssText = ` + this.resizer = mainContainer.createDiv('voice-panel-resizer'); + this.resizer.style.cssText = ` height: 4px; width: 100%; cursor: ns-resize; @@ -128,9 +132,9 @@ export class GraphView extends ItemView { `; // 3. Нижняя панель - const voiceContainer = mainContainer.createDiv('voice-panel-fixed-bottom'); + this.voiceContainer = mainContainer.createDiv('voice-panel-fixed-bottom'); let initialHeight = 150; // Высота по умолчанию - voiceContainer.style.cssText = ` + this.voiceContainer.style.cssText = ` height: ${initialHeight}px; width: 100%; background-color: var(--background-secondary); @@ -141,7 +145,7 @@ export class GraphView extends ItemView { // ЛОГИКА ИЗМЕНЕНИЯ ВЫСОТЫ let isResizing = false; - resizer.addEventListener('mousedown', (e) => { + this.resizer.addEventListener('mousedown', (e) => { isResizing = true; document.body.style.cursor = 'ns-resize'; // Чтобы iframe/график не перехватывали события мыши @@ -157,7 +161,7 @@ export class GraphView extends ItemView { // Ограничения (мин 50px, макс 70% высоты) if (newHeight > 50 && newHeight < containerRect.height * 0.7) { - voiceContainer.style.height = `${newHeight}px`; + this.voiceContainer.style.height = `${newHeight}px`; } }); @@ -181,7 +185,12 @@ export class GraphView extends ItemView { this.initializeGraphPanel(); // Инициализируем VoicePanel - new VoicePanel(voiceContainer, this.plugin); + if (this.plugin.settings.showVoicePanel) { + this.voicePanelInstance = new VoicePanel(this.voiceContainer, this.plugin); + } else { + this.resizer.style.display = 'none'; + this.voiceContainer.style.display = 'none'; + } // Подписки на события this.setupEventListeners(); @@ -204,6 +213,7 @@ export class GraphView extends ItemView { this.plugin.eventBus.on('ws-node-type-changed', this.handleNodeTypeChanged.bind(this)); this.plugin.eventBus.on('stream-started', this.handleStreamStarted.bind(this)); this.plugin.eventBus.on('stream-ended', this.handleStreamEnded.bind(this)); + this.plugin.eventBus.on('settings-changed', this.handleSettingsChanged); } /** @@ -234,6 +244,13 @@ export class GraphView extends ItemView { this.plugin.eventBus.off('stream-started', this.handleStreamStarted); this.plugin.eventBus.off('stream-ended', this.handleStreamEnded); + + this.plugin.eventBus.off('settings-changed', this.handleSettingsChanged); + + if (this.voicePanelInstance) { + this.voicePanelInstance.destroy(); + this.voicePanelInstance = null; + } } // ----------------------------------------------- Save graph id --------------------------------------------------- @@ -815,6 +832,26 @@ export class GraphView extends ItemView { this.activeStreamControllers.delete(data.nodeId); } + /** + * Обрабатывает изменение настроек и мгновенно перерисовывает VoicePanel + */ + private handleSettingsChanged = () => { + if (this.plugin.settings.showVoicePanel) { + this.resizer.style.display = ''; + this.voiceContainer.style.display = 'flex'; + if (!this.voicePanelInstance) { + this.voicePanelInstance = new VoicePanel(this.voiceContainer, this.plugin); + } + } else { + this.resizer.style.display = 'none'; + this.voiceContainer.style.display = 'none'; + if (this.voicePanelInstance) { + this.voicePanelInstance.destroy(); + this.voicePanelInstance = null; + } + } + }; + // ----------------------------------------------- WebSocket Event Handlers --------------------------------------------------------------- /**