Add setting to show or hide voice panel

This commit is contained in:
dimitrievgs 2026-05-16 18:55:53 +03:00
parent 89e57198ad
commit b8382c2d4f
3 changed files with 76 additions and 8 deletions

16
main.ts
View File

@ -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 для обработки голосовых команд.')

View File

@ -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 = '';
}
}

View File

@ -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 ---------------------------------------------------------------
/**