First solution for voice panel
This commit is contained in:
parent
cb8ed95bcd
commit
aee60c1ac2
152
main.ts
152
main.ts
|
|
@ -17,6 +17,15 @@ interface LLMAgentSettings {
|
||||||
enablePromptReferences: boolean;
|
enablePromptReferences: boolean;
|
||||||
excludedFolders: string[];
|
excludedFolders: string[];
|
||||||
refCacheFolder: string;
|
refCacheFolder: string;
|
||||||
|
|
||||||
|
voiceModel: string;
|
||||||
|
voiceInterval: number;
|
||||||
|
voiceDisplayLimit: number; // 3000
|
||||||
|
voiceContextLimit: number; // N2 символов для LLM
|
||||||
|
promptRegularPath: string;
|
||||||
|
promptCommandsPath: string;
|
||||||
|
markerStart: string; // "старт"
|
||||||
|
markerStop: string; // "стоп"
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: LLMAgentSettings = {
|
const DEFAULT_SETTINGS: LLMAgentSettings = {
|
||||||
|
|
@ -27,7 +36,16 @@ const DEFAULT_SETTINGS: LLMAgentSettings = {
|
||||||
enableFileReferences: true,
|
enableFileReferences: true,
|
||||||
enablePromptReferences: true,
|
enablePromptReferences: true,
|
||||||
excludedFolders: ['.obsidian', 'llm-agent-backend', 'templates', 'ref-cache'], // Папка "templates" по умолчанию исключена
|
excludedFolders: ['.obsidian', 'llm-agent-backend', 'templates', 'ref-cache'], // Папка "templates" по умолчанию исключена
|
||||||
refCacheFolder: 'ref-cache' // По умолчанию относительно vault
|
refCacheFolder: 'ref-cache', // По умолчанию относительно vault
|
||||||
|
|
||||||
|
voiceModel: '',
|
||||||
|
voiceInterval: 1,
|
||||||
|
voiceDisplayLimit: 3000,
|
||||||
|
voiceContextLimit: 3000,
|
||||||
|
promptRegularPath: '',
|
||||||
|
promptCommandsPath: '',
|
||||||
|
markerStart: 'старт',
|
||||||
|
markerStop: 'стоп'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ----------------------------------------------- Main Plugin Class ---------------------------------------------------------------
|
// ----------------------------------------------- Main Plugin Class ---------------------------------------------------------------
|
||||||
|
|
@ -38,6 +56,30 @@ export default class LLMAgentPlugin extends Plugin {
|
||||||
webSocketService: WebSocketService;
|
webSocketService: WebSocketService;
|
||||||
activeStreamControllers: Map<string, AbortController>;
|
activeStreamControllers: Map<string, AbortController>;
|
||||||
|
|
||||||
|
async syncVoiceSettings() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${this.settings.apiBaseUrl}/settings/sync`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
systemPrompt: this.settings.systemPrompt,
|
||||||
|
interval: this.settings.voiceInterval,
|
||||||
|
context_limit: this.settings.voiceContextLimit,
|
||||||
|
marker_start: this.settings.markerStart,
|
||||||
|
marker_stop: this.settings.markerStop,
|
||||||
|
// Передаем также пути к файлам промптов, если нужно
|
||||||
|
prompt_regular: this.settings.promptRegularPath,
|
||||||
|
prompt_commands: this.settings.promptCommandsPath
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
console.log("LLM Agent: Voice settings synced with backend.");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("LLM Agent: Failed to sync settings", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async onload() {
|
async onload() {
|
||||||
await this.loadSettings();
|
await this.loadSettings();
|
||||||
|
|
||||||
|
|
@ -84,6 +126,10 @@ export default class LLMAgentPlugin extends Plugin {
|
||||||
|
|
||||||
// This adds a settings tab so the user can configure various aspects of the plugin
|
// This adds a settings tab so the user can configure various aspects of the plugin
|
||||||
this.addSettingTab(new SampleSettingTab(this.app, this));
|
this.addSettingTab(new SampleSettingTab(this.app, this));
|
||||||
|
|
||||||
|
|
||||||
|
// Вызываем один раз при запуске
|
||||||
|
await this.syncVoiceSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
onunload() {
|
onunload() {
|
||||||
|
|
@ -165,15 +211,16 @@ class SampleSettingTab extends PluginSettingTab {
|
||||||
|
|
||||||
display(): void {
|
display(): void {
|
||||||
const { containerEl } = this;
|
const { containerEl } = this;
|
||||||
|
|
||||||
containerEl.empty();
|
containerEl.empty();
|
||||||
|
|
||||||
|
containerEl.createEl('h2', { text: 'Основные настройки' });
|
||||||
|
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName('API Base URL')
|
.setName('API Base URL')
|
||||||
.setDesc('Базовый URL для API LLM Agent')
|
.setDesc('Базовый URL для API LLM Agent')
|
||||||
.addText((text) =>
|
.addText((text) =>
|
||||||
text
|
text
|
||||||
.setPlaceholder('Введите URL')
|
.setPlaceholder('http://localhost:5000/api')
|
||||||
.setValue(this.plugin.settings.apiBaseUrl)
|
.setValue(this.plugin.settings.apiBaseUrl)
|
||||||
.onChange(async (value) => {
|
.onChange(async (value) => {
|
||||||
this.plugin.settings.apiBaseUrl = value;
|
this.plugin.settings.apiBaseUrl = value;
|
||||||
|
|
@ -181,7 +228,6 @@ class SampleSettingTab extends PluginSettingTab {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Новое поле для системного промпта
|
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName('Системный промпт')
|
.setName('Системный промпт')
|
||||||
.setDesc('Этот промпт будет отправляться LLM перед каждым диалогом.')
|
.setDesc('Этот промпт будет отправляться LLM перед каждым диалогом.')
|
||||||
|
|
@ -195,7 +241,92 @@ class SampleSettingTab extends PluginSettingTab {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Новое поле для папки промптов
|
containerEl.createEl('h2', { text: 'Голосовое управление (Voice Service)' });
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Интервал регулярного анализа (мин)')
|
||||||
|
.setDesc('Как часто LLM анализирует последние фразы (Prompt 1).')
|
||||||
|
.addText((text) =>
|
||||||
|
text
|
||||||
|
.setValue(String(this.plugin.settings.voiceInterval))
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.voiceInterval = Number(value);
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
await this.plugin.syncVoiceSettings();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Объем контекста (символы)')
|
||||||
|
.setDesc('Сколько последних символов транскрибации отправлять в модель.')
|
||||||
|
.addText((text) =>
|
||||||
|
text
|
||||||
|
.setValue(String(this.plugin.settings.voiceContextLimit))
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.voiceContextLimit = Number(value);
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
await this.plugin.syncVoiceSettings();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Маркер начала команды')
|
||||||
|
.setDesc('Слово, после которого текст считается командой (Response 2).')
|
||||||
|
.addText((text) =>
|
||||||
|
text
|
||||||
|
.setPlaceholder('старт')
|
||||||
|
.setValue(this.plugin.settings.markerStart)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.markerStart = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
await this.plugin.syncVoiceSettings();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Маркер конца команды')
|
||||||
|
.setDesc('Слово, завершающее захват голосовой команды.')
|
||||||
|
.addText((text) =>
|
||||||
|
text
|
||||||
|
.setPlaceholder('стоп')
|
||||||
|
.setValue(this.plugin.settings.markerStop)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.markerStop = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
await this.plugin.syncVoiceSettings();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Регулярный промпт')
|
||||||
|
.setDesc('Инструкции для Промпта №1.')
|
||||||
|
.addTextArea((text) =>
|
||||||
|
text
|
||||||
|
// .setPlaceholder('prompts/regular_analysis.md')
|
||||||
|
.setValue(this.plugin.settings.promptRegularPath)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.promptRegularPath = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
await this.plugin.syncVoiceSettings();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Промпт команд')
|
||||||
|
.setDesc('Инструкции для Промпта №2.')
|
||||||
|
.addTextArea((text) =>
|
||||||
|
text
|
||||||
|
// .setPlaceholder('prompts/voice_commands.md')
|
||||||
|
.setValue(this.plugin.settings.promptCommandsPath)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.promptCommandsPath = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
await this.plugin.syncVoiceSettings();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
containerEl.createEl('h2', { text: 'Управление ссылками и файлами' });
|
||||||
|
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName('Папка с промптами')
|
.setName('Папка с промптами')
|
||||||
.setDesc('Путь к папке, содержащей промпты для команд #/##')
|
.setDesc('Путь к папке, содержащей промпты для команд #/##')
|
||||||
|
|
@ -251,11 +382,12 @@ class SampleSettingTab extends PluginSettingTab {
|
||||||
.addToggle((toggle) =>
|
.addToggle((toggle) =>
|
||||||
toggle.setValue(this.plugin.settings.enableFileReferences).onChange(async (value) => {
|
toggle.setValue(this.plugin.settings.enableFileReferences).onChange(async (value) => {
|
||||||
this.plugin.settings.enableFileReferences = value;
|
this.plugin.settings.enableFileReferences = value;
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Динамический список исключенных папок
|
// Динамический список исключенных папок
|
||||||
|
containerEl.createEl('h3', { text: 'Исключенные папки' });
|
||||||
this.plugin.settings.excludedFolders.forEach((folder, index) => {
|
this.plugin.settings.excludedFolders.forEach((folder, index) => {
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setClass('llm-agent-excluded-folder-item') // Добавляем класс для стилизации
|
.setClass('llm-agent-excluded-folder-item') // Добавляем класс для стилизации
|
||||||
|
|
@ -263,11 +395,11 @@ class SampleSettingTab extends PluginSettingTab {
|
||||||
text.setPlaceholder('Путь к папке (например, templates/)' + index)
|
text.setPlaceholder('Путь к папке (например, templates/)' + index)
|
||||||
.setValue(folder)
|
.setValue(folder)
|
||||||
.onChange(async (value) => {
|
.onChange(async (value) => {
|
||||||
this.plugin.settings.excludedFolders[index] = value;
|
this.plugin.settings.excludedFolders[index] = value;
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
// Очищаем кэш автокомплита при изменении настроек
|
// Очищаем кэш автокомплита при изменении настроек
|
||||||
this.plugin.eventBus.emit('settings-changed');
|
this.plugin.eventBus.emit('settings-changed');
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.addExtraButton((button) => {
|
.addExtraButton((button) => {
|
||||||
button
|
button
|
||||||
|
|
|
||||||
71
src/components/VoicePanel.ts
Normal file
71
src/components/VoicePanel.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import LLMAgentPlugin from 'main';
|
||||||
|
|
||||||
|
export class VoicePanel {
|
||||||
|
private activeTab: 'res1' | 'res2' | 'trans' = 'res1';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private container: HTMLElement,
|
||||||
|
private plugin: LLMAgentPlugin
|
||||||
|
) {
|
||||||
|
this.render();
|
||||||
|
this.startPolling();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.container.innerHTML = `
|
||||||
|
<div class="voice-panel-container">
|
||||||
|
<div class="voice-header">
|
||||||
|
<div class="v-controls">
|
||||||
|
<button id="v-new" class="mod-cta">Start New</button>
|
||||||
|
<button id="v-cont">Continue</button>
|
||||||
|
<button id="v-stop" class="mod-warning">Stop</button>
|
||||||
|
</div>
|
||||||
|
<div class="v-tabs">
|
||||||
|
<button class="v-tab active" data-tab="res1">Response 1</button>
|
||||||
|
<button class="v-tab" data-tab="res2">Response 2</button>
|
||||||
|
<button class="v-tab" data-tab="trans">Transcription</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="v-body" class="voice-body"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Навешиваем логику вкладок
|
||||||
|
this.container.querySelectorAll('.v-tab').forEach((tab) => {
|
||||||
|
tab.addEventListener('click', (e) => {
|
||||||
|
this.container.querySelectorAll('.v-tab').forEach((t) => t.classList.remove('active'));
|
||||||
|
(e.target as HTMLElement).classList.add('active');
|
||||||
|
this.activeTab = (e.target as HTMLElement).dataset.tab as any;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Навешиваем управление через API
|
||||||
|
const control = async (action: string) => {
|
||||||
|
await fetch(`${this.plugin.settings.apiBaseUrl}/voice/control`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ action })
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.container.querySelector('#v-new')?.addEventListener('click', () => control('start'));
|
||||||
|
this.container.querySelector('#v-cont')?.addEventListener('click', () => control('continue'));
|
||||||
|
this.container.querySelector('#v-stop')?.addEventListener('click', () => control('stop'));
|
||||||
|
}
|
||||||
|
|
||||||
|
async startPolling() {
|
||||||
|
setInterval(async () => {
|
||||||
|
const res = await fetch(`${this.plugin.settings.apiBaseUrl}/voice/status`);
|
||||||
|
const data = await res.json();
|
||||||
|
this.updateContent(data);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateContent(data: any) {
|
||||||
|
const body = this.container.querySelector('#v-body');
|
||||||
|
if (!body) return;
|
||||||
|
if (this.activeTab === 'trans') body.textContent = data.transcription;
|
||||||
|
if (this.activeTab === 'res1') body.textContent = data.response1;
|
||||||
|
if (this.activeTab === 'res2') body.innerHTML = data.response2.join('<br>---<br>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -740,9 +740,71 @@
|
||||||
|
|
||||||
.llm-agent-graph-main {
|
.llm-agent-graph-main {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
}
|
||||||
/* background-color: var(--background-primary); */
|
|
||||||
|
.upper-content-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Голосовая панель */
|
||||||
|
.voice-panel-fixed-bottom {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-top: 1px solid var(--background-modifier-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.voice-panel-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voice-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
border-bottom: 1px solid var(--background-modifier-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-tab {
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-tab.active {
|
||||||
|
color: var(--text-accent);
|
||||||
|
border-bottom: 2px solid var(--text-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.voice-body {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: var(--font-monospace);
|
||||||
|
padding: 10px;
|
||||||
|
background-color: var(--background-primary);
|
||||||
|
white-space: pre-wrap; /* Чтобы транскрибация не уходила в одну строку */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Кнопки управления голосом */
|
||||||
|
.v-controls button {
|
||||||
|
margin-right: 5px;
|
||||||
|
padding: 4px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.llm-agent-history-sidebar {
|
.llm-agent-history-sidebar {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { HistoryPanel } from '../components/HistoryPanel'; // Предполаг
|
||||||
import { GraphPanelComponent } from '../components/GraphPanel'; // Импортируем React-компонент и ReactDOM
|
import { GraphPanelComponent } from '../components/GraphPanel'; // Импортируем React-компонент и ReactDOM
|
||||||
import { Dialog } from 'src/utils/Dialog';
|
import { Dialog } from 'src/utils/Dialog';
|
||||||
import { GraphSettingsModal } from '../modals/GraphSettingsModal';
|
import { GraphSettingsModal } from '../modals/GraphSettingsModal';
|
||||||
|
import { VoicePanel } from '../components/VoicePanel';
|
||||||
|
|
||||||
export const GRAPH_VIEW_TYPE = 'llm-agent-graph-view';
|
export const GRAPH_VIEW_TYPE = 'llm-agent-graph-view';
|
||||||
|
|
||||||
|
|
@ -90,60 +91,81 @@ export class GraphView extends ItemView {
|
||||||
const container = this.containerEl.children[1] as HTMLElement;
|
const container = this.containerEl.children[1] as HTMLElement;
|
||||||
container.empty();
|
container.empty();
|
||||||
|
|
||||||
// Создаем основной контейнер с flexbox layout
|
// 1. Основной контейнер - теперь вертикальный стек
|
||||||
const mainContainer = container.createDiv('llm-agent-graph-main');
|
const mainContainer = container.createDiv('llm-agent-graph-main');
|
||||||
mainContainer.style.cssText = `
|
mainContainer.style.cssText = `
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column; /* Элементы кладутся друг под друга */
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: var(--background-primary);
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Левая панель для истории
|
// 2. Верхняя часть (История + Граф)
|
||||||
this.historyContainer = mainContainer.createDiv('llm-agent-history-sidebar') as HTMLDivElement;
|
const upperContent = mainContainer.createDiv('upper-content-wrapper');
|
||||||
|
upperContent.style.cssText = `
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row; /* Элементы бок о бок */
|
||||||
|
flex: 1; /* Занимает всё свободное место (около 75-80%) */
|
||||||
|
width: 100%;
|
||||||
|
min-height: 0; /* Важно для корректного скролла внутри */
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 3. Левая панель для истории (внутри верхней части)
|
||||||
|
this.historyContainer = upperContent.createDiv('llm-agent-history-sidebar');
|
||||||
this.historyContainer.classList.add(this.isHistoryCollapsed ? 'collapsed' : 'expanded');
|
this.historyContainer.classList.add(this.isHistoryCollapsed ? 'collapsed' : 'expanded');
|
||||||
|
|
||||||
// Правая панель для графа
|
// 4. Правая панель для графа (внутри верхней части)
|
||||||
this.graphContainer = mainContainer.createDiv('llm-agent-graph-container') as HTMLDivElement;
|
this.graphContainer = upperContent.createDiv('llm-agent-graph-container');
|
||||||
this.graphContainer.style.cssText = `
|
this.graphContainer.style.cssText = `
|
||||||
flex: 1;
|
flex: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: var(--background-primary);
|
background-color: var(--background-primary);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Инициализируем панель истории
|
// 5. Контейнер для голосовой панели (строго ВНИЗУ)
|
||||||
|
const voiceContainer = mainContainer.createDiv('voice-panel-fixed-bottom');
|
||||||
|
voiceContainer.style.cssText = `
|
||||||
|
height: 200px; /* Фиксированная высота или % */
|
||||||
|
width: 100%;
|
||||||
|
border-top: 1px solid var(--divider-color);
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0; /* Не дает панели сжиматься */
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Инициализация компонентов
|
||||||
this.historyPanel = new HistoryPanel(
|
this.historyPanel = new HistoryPanel(
|
||||||
this.historyContainer,
|
this.historyContainer,
|
||||||
{
|
{ graphs: this.graphsList, eventBus: this.plugin.eventBus },
|
||||||
graphs: this.graphsList,
|
|
||||||
eventBus: this.plugin.eventBus
|
|
||||||
},
|
|
||||||
this.plugin
|
this.plugin
|
||||||
);
|
);
|
||||||
|
|
||||||
// Создаем кнопки переключения и глобальные кнопки действий
|
|
||||||
this.addToggleButton();
|
this.addToggleButton();
|
||||||
this.addGlobalActionButtons(); // Добавляем вызов новой функции
|
this.addGlobalActionButtons();
|
||||||
|
|
||||||
// Инициализируем React Flow
|
|
||||||
this.initializeGraphPanel();
|
this.initializeGraphPanel();
|
||||||
|
|
||||||
// Подписываемся на события
|
// Инициализируем VoicePanel
|
||||||
this.plugin.eventBus.on('new-chat', this.handleNewChat.bind(this));
|
new VoicePanel(voiceContainer, this.plugin);
|
||||||
this.plugin.eventBus.on('graph-selected', this.handleGraphSelected.bind(this));
|
|
||||||
this.plugin.eventBus.on('delete-node', this.handleDeleteNodeFromChat.bind(this)); // Слушаем событие из ChatPanel
|
|
||||||
|
|
||||||
// WebSocket Event Handlers
|
// Подписки на события
|
||||||
|
this.setupEventListeners();
|
||||||
|
|
||||||
|
// Загружаем данные
|
||||||
|
await this.fetchGraphsList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вынес для чистоты
|
||||||
|
private setupEventListeners() {
|
||||||
|
this.plugin.eventBus.on('new-chat', this.handleNewChat.bind(this));
|
||||||
|
this.plugin.eventBus.on('graph-selected', this.handleGraphSelected.bind(this));
|
||||||
|
this.plugin.eventBus.on('delete-node', this.handleDeleteNodeFromChat.bind(this));
|
||||||
this.plugin.eventBus.on('ws-graph-title-updated', this.handleGraphTitleUpdated.bind(this));
|
this.plugin.eventBus.on('ws-graph-title-updated', this.handleGraphTitleUpdated.bind(this));
|
||||||
this.plugin.eventBus.on('ws-node-title-updated', this.handleNodeTitleUpdated.bind(this));
|
this.plugin.eventBus.on('ws-node-title-updated', this.handleNodeTitleUpdated.bind(this));
|
||||||
this.plugin.eventBus.on('ws-node-type-changed', this.handleNodeTypeChanged.bind(this));
|
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-started', this.handleStreamStarted.bind(this));
|
||||||
this.plugin.eventBus.on('stream-ended', this.handleStreamEnded.bind(this));
|
this.plugin.eventBus.on('stream-ended', this.handleStreamEnded.bind(this));
|
||||||
|
}
|
||||||
// Загружаем список графов
|
|
||||||
await this.fetchGraphsList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Вызывается при закрытии представления. Очищает ресурсы.
|
* Вызывается при закрытии представления. Очищает ресурсы.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user