Change to show graph title in graph panel

This commit is contained in:
dimitrievgs 2025-10-05 19:11:41 +03:00
parent 47cff8a121
commit b0d802241b
2 changed files with 259 additions and 215 deletions

35
main.js

File diff suppressed because one or more lines are too long

View File

@ -24,6 +24,7 @@ export class GraphView extends ItemView {
graphsList: any[]; graphsList: any[];
selectedGraphId: string | null; selectedGraphId: string | null;
selectedNodeIds: Set<string>; // Добавляем Set для хранения ID выделенных узлов selectedNodeIds: Set<string>; // Добавляем Set для хранения ID выделенных узлов
selectedGraphTitle: string | null;
// UI Elements // UI Elements
historyPanel: HistoryPanel | null; historyPanel: HistoryPanel | null;
@ -33,9 +34,9 @@ export class GraphView extends ItemView {
toggleButton: HTMLButtonElement; toggleButton: HTMLButtonElement;
isHistoryCollapsed: boolean; isHistoryCollapsed: boolean;
// Глобальные кнопки действий // Глобальные кнопки действий
copySelectedButton: HTMLButtonElement | null = null; copySelectedButton: HTMLButtonElement | null = null;
deleteSelectedButton: HTMLButtonElement | null = null; deleteSelectedButton: HTMLButtonElement | null = null;
constructor(leaf: WorkspaceLeaf, plugin: LLMAgentPlugin) { constructor(leaf: WorkspaceLeaf, plugin: LLMAgentPlugin) {
super(leaf); super(leaf);
@ -47,7 +48,8 @@ export class GraphView extends ItemView {
this.historyPanel = null; this.historyPanel = null;
this.reactRoot = null; this.reactRoot = null;
this.isHistoryCollapsed = true; this.isHistoryCollapsed = true;
this.selectedNodeIds = new Set<string>(); // Инициализируем Set this.selectedNodeIds = new Set<string>(); // Инициализируем Set
this.selectedGraphTitle = null;
} }
/** /**
@ -61,7 +63,7 @@ export class GraphView extends ItemView {
* @returns {string} Отображаемое имя представления. * @returns {string} Отображаемое имя представления.
*/ */
getDisplayText(): string { getDisplayText(): string {
return 'LLM Agent Graph'; return this.selectedGraphTitle || 'LLM Agent Graph';
} }
/** /**
@ -106,9 +108,9 @@ export class GraphView extends ItemView {
eventBus: this.plugin.eventBus eventBus: this.plugin.eventBus
}); });
// Создаем кнопки переключения и глобальные кнопки действий // Создаем кнопки переключения и глобальные кнопки действий
this.addToggleButton(); this.addToggleButton();
this.addGlobalActionButtons(); // Добавляем вызов новой функции this.addGlobalActionButtons(); // Добавляем вызов новой функции
// Инициализируем React Flow // Инициализируем React Flow
this.initializeGraphPanel(); this.initializeGraphPanel();
@ -116,7 +118,7 @@ export class GraphView extends ItemView {
// Подписываемся на события // Подписываемся на события
this.plugin.eventBus.on('new-chat', this.handleNewChat.bind(this)); this.plugin.eventBus.on('new-chat', this.handleNewChat.bind(this));
this.plugin.eventBus.on('graph-selected', this.handleGraphSelected.bind(this)); this.plugin.eventBus.on('graph-selected', this.handleGraphSelected.bind(this));
this.plugin.eventBus.on('delete-node', this.handleDeleteNodeFromChat.bind(this)); // Слушаем событие из ChatPanel this.plugin.eventBus.on('delete-node', this.handleDeleteNodeFromChat.bind(this)); // Слушаем событие из ChatPanel
// Загружаем список графов // Загружаем список графов
await this.fetchGraphsList(); await this.fetchGraphsList();
@ -140,9 +142,23 @@ export class GraphView extends ItemView {
this.plugin.eventBus.off('new-chat', this.handleNewChat); this.plugin.eventBus.off('new-chat', this.handleNewChat);
this.plugin.eventBus.off('graph-selected', this.handleGraphSelected); this.plugin.eventBus.off('graph-selected', this.handleGraphSelected);
this.plugin.eventBus.off('delete-node', this.handleDeleteNodeFromChat); this.plugin.eventBus.off('delete-node', this.handleDeleteNodeFromChat);
} }
// ----------------------------------------------- Custom Title Update ---------------------------------------------------
/**
* Обновляет текстовое содержимое заголовка представления.
* Этот метод должен быть вызван для динамического изменения заголовка
* после первоначальной отрисовки представления.
*/
updateTitle(): void {
const titleElement = this.containerEl.querySelector('.view-header-title');
if (titleElement) {
titleElement.textContent = this.selectedGraphTitle || 'LLM Agent Graph';
}
}
// ----------------------------------------------- History Panel Toggle -------------------------------------------------- // ----------------------------------------------- History Panel Toggle --------------------------------------------------
/** /**
@ -164,37 +180,37 @@ export class GraphView extends ItemView {
// ----------------------------------------------- Graph Panel Integration -------------------------------------------------- // ----------------------------------------------- Graph Panel Integration --------------------------------------------------
addToggleButton(): void { addToggleButton(): void {
const buttonWrapper = this.graphContainer.createDiv({ cls: 'graph-view-toolbar' }); // Создаем контейнер для кнопок const buttonWrapper = this.graphContainer.createDiv({ cls: 'graph-view-toolbar' }); // Создаем контейнер для кнопок
this.toggleButton = buttonWrapper.createEl('button', { cls: 'global-action-button' }); // history-toggle-button this.toggleButton = buttonWrapper.createEl('button', { cls: 'global-action-button' }); // history-toggle-button
this.toggleButton.textContent = this.isHistoryCollapsed ? '📋' : '❌'; this.toggleButton.textContent = this.isHistoryCollapsed ? '📋' : '❌';
this.toggleButton.addEventListener('click', this.toggleHistoryPanel.bind(this)); this.toggleButton.addEventListener('click', this.toggleHistoryPanel.bind(this));
console.log('Toggle button created and appended:', this.toggleButton); console.log('Toggle button created and appended:', this.toggleButton);
} }
/** /**
* Добавляет глобальные кнопки действий для выделенных узлов. * Добавляет глобальные кнопки действий для выделенных узлов.
*/ */
addGlobalActionButtons(): void { addGlobalActionButtons(): void {
const toolbar = this.graphContainer.querySelector('.graph-view-toolbar'); const toolbar = this.graphContainer.querySelector('.graph-view-toolbar');
if (!toolbar) return; if (!toolbar) return;
// Кнопка "Копировать выделенные" // Кнопка "Копировать выделенные"
this.copySelectedButton = toolbar.createEl('button', { this.copySelectedButton = toolbar.createEl('button', {
cls: 'global-action-button', cls: 'global-action-button',
attr: { 'aria-label': 'Копировать выделенные узлы', 'data-tooltip': 'Копировать выделенные узлы' }, attr: { 'aria-label': 'Копировать выделенные узлы', 'data-tooltip': 'Копировать выделенные узлы' },
text: '📄' text: '📄'
}); });
this.copySelectedButton.addEventListener('click', this.handleCopySelectedNodes.bind(this)); this.copySelectedButton.addEventListener('click', this.handleCopySelectedNodes.bind(this));
// Кнопка "Удалить выделенные" // Кнопка "Удалить выделенные"
this.deleteSelectedButton = toolbar.createEl('button', { this.deleteSelectedButton = toolbar.createEl('button', {
cls: 'global-action-button', cls: 'global-action-button',
attr: { 'aria-label': 'Удалить выделенные узлы', 'data-tooltip': 'Удалить выделенные узлы' }, attr: { 'aria-label': 'Удалить выделенные узлы', 'data-tooltip': 'Удалить выделенные узлы' },
text: '🗑️' text: '🗑️'
}); });
this.deleteSelectedButton.addEventListener('click', this.handleDeleteSelectedNodes.bind(this)); this.deleteSelectedButton.addEventListener('click', this.handleDeleteSelectedNodes.bind(this));
} }
// ----------------------------------------------- Graph Panel Integration -------------------------------------------------- // ----------------------------------------------- Graph Panel Integration --------------------------------------------------
@ -204,9 +220,9 @@ export class GraphView extends ItemView {
*/ */
initializeGraphPanel(): void { initializeGraphPanel(): void {
this.graphContainer.empty(); this.graphContainer.empty();
// Перемещаем кнопки обратно после очистки, если они были частью graphContainer // Перемещаем кнопки обратно после очистки, если они были частью graphContainer
this.addToggleButton(); this.addToggleButton();
this.addGlobalActionButtons(); this.addGlobalActionButtons();
const wrapper = this.graphContainer.createDiv(); const wrapper = this.graphContainer.createDiv();
wrapper.style.cssText = 'width: 100%; height: 100%;'; wrapper.style.cssText = 'width: 100%; height: 100%;';
@ -236,10 +252,10 @@ export class GraphView extends ItemView {
edges: this.graphData.edges edges: this.graphData.edges
}, },
onNodeClick: this.handleNodeClick.bind(this), onNodeClick: this.handleNodeClick.bind(this),
onNodeSelectionChange: this.handleNodeSelectionChange.bind(this), // Передаем обработчик изменения выделения onNodeSelectionChange: this.handleNodeSelectionChange.bind(this), // Передаем обработчик изменения выделения
onNodeDelete: this.handleDeleteNode.bind(this), // Передаем колбэк для удаления одиночного узла onNodeDelete: this.handleDeleteNode.bind(this), // Передаем колбэк для удаления одиночного узла
selectedNodeIds: this.selectedNodeIds, // Передаем Set выделенных узлов selectedNodeIds: this.selectedNodeIds, // Передаем Set выделенных узлов
app: this.plugin.app app: this.plugin.app
}) })
); );
} else { } else {
@ -272,87 +288,92 @@ export class GraphView extends ItemView {
} }
} }
/** /**
* Загружает данные конкретного графа по его ID. * Загружает данные конкретного графа по его ID.
* @param {string} graphId - ID графа для загрузки. * @param {string} graphId - ID графа для загрузки.
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async fetchGraphData(graphId: string): Promise<void> { async fetchGraphData(graphId: string): Promise<void> {
try { try {
if (!graphId) { if (!graphId) {
this.graphData = { nodes: [], edges: [] }; this.graphData = { nodes: [], edges: [] };
this.currentNode = null; this.currentNode = null;
this.selectedNodeIds.clear(); // Очищаем выделения this.selectedNodeIds.clear(); // Очищаем выделения
this.renderGraphPanel(); this.selectedGraphTitle = null; // Сброс заголовка
this.plugin.eventBus.emit('chat-history-updated', { graphId: null, messages: [], current_node_id: null }); this.updateTitle(); // Обновляем заголовок
return; this.renderGraphPanel();
} this.plugin.eventBus.emit('chat-history-updated', { graphId: null, messages: [], current_node_id: null });
return;
}
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs/${graphId}`); const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs/${graphId}`);
if (!response.ok) { if (!response.ok) {
const errorText = await response.text(); const errorText = await response.text();
throw new Error(`Ошибка загрузки данных графа: ${response.status} - ${errorText}`); throw new Error(`Ошибка загрузки данных графа: ${response.status} - ${errorText}`);
} }
const data = await response.json(); const data = await response.json();
this.graphData = { this.graphData = {
nodes: data.graph_nodes || [], nodes: data.graph_nodes || [],
edges: data.graph_edges || [] edges: data.graph_edges || []
}; };
this.currentNode = data.current_node_id; this.currentNode = data.current_node_id;
// Перерендериваем граф // Устанавливаем заголовок графа
this.renderGraphPanel(); this.selectedGraphTitle = data.title || data.first_message || `Граф ${graphId.substring(0, 8)}...`;
this.updateTitle(); // Обновляем заголовок
// Также обновляем ChatView с полученными сообщениями и текущим узлом // Перерендериваем граф
this.plugin.eventBus.emit('chat-history-updated', { this.renderGraphPanel();
graphId: graphId,
messages: data.messages || [],
current_node_id: data.current_node_id // Передаем актуальный current_node_id
});
} catch (error) { // Также обновляем ChatView с полученными сообщениями и текущим узлом
console.error('Ошибка загрузки данных графа:', error); this.plugin.eventBus.emit('chat-history-updated', {
// Optionally: show a notice to the user graphId: graphId,
// new Notice(`Ошибка загрузки данных графа: ${error.message}`); messages: data.messages || [],
} current_node_id: data.current_node_id // Передаем актуальный current_node_id
} });
} catch (error) {
console.error('Ошибка загрузки данных графа:', error);
this.selectedGraphTitle = null;
this.updateTitle(); // Обновляем заголовок
// Optionally: show a notice to the user
// new Notice(`Ошибка загрузки данных графа: ${error.message}`);
}
}
/** /**
* Удаляет узел графа по его ID. * Удаляет узел графа по его ID.
* @param {string} graphId - ID графа. * @param {string} graphId - ID графа.
* @param {string} nodeId - ID узла для удаления. * @param {string} nodeId - ID узла для удаления.
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async handleDeleteNode(nodeId: string): Promise<void> { async handleDeleteNode(nodeId: string): Promise<void> {
if (!this.selectedGraphId) { if (!this.selectedGraphId) {
new Notice('Не выбран граф для удаления узла.', 3000); new Notice('Не выбран граф для удаления узла.', 3000);
return; return;
} }
try { try {
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs/${this.selectedGraphId}/nodes/${nodeId}`, { const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs/${this.selectedGraphId}/nodes/${nodeId}`, {
method: 'DELETE' method: 'DELETE'
}); });
if (!response.ok) { if (!response.ok) {
const errorText = await response.text(); const errorText = await response.text();
throw new Error(`Ошибка удаления узла: ${response.status} - ${errorText}`); throw new Error(`Ошибка удаления узла: ${response.status} - ${errorText}`);
} }
new Notice('Узел успешно удален.', 2000); new Notice('Узел успешно удален.', 2000);
// После удаления узла, нужно обновить данные графа и перерисовать его. // После удаления узла, нужно обновить данные графа и перерисовать его.
// Это также обновит ChatPanel, если удаленное сообщение было частью текущей ветки. // Это также обновит ChatPanel, если удаленное сообщение было частью текущей ветки.
await this.fetchGraphData(this.selectedGraphId); await this.fetchGraphData(this.selectedGraphId);
// Сбрасываем выделение, если удаленный узел был выделен // Сбрасываем выделение, если удаленный узел был выделен
this.selectedNodeIds.delete(nodeId); this.selectedNodeIds.delete(nodeId);
} catch (error) {
} catch (error) { console.error(`Ошибка при удалении узла ${nodeId}:`, error);
console.error(`Ошибка при удалении узла ${nodeId}:`, error); new Notice(`Ошибка при удалении узла: ${error.message}`, 5000);
new Notice(`Ошибка при удалении узла: ${error.message}`, 5000); }
} }
}
// ----------------------------------------------- Event Handlers ------------------------------------------------------------ // ----------------------------------------------- Event Handlers ------------------------------------------------------------
@ -363,26 +384,26 @@ export class GraphView extends ItemView {
*/ */
handleNodeClick(nodeId: string): void { handleNodeClick(nodeId: string): void {
this.currentNode = nodeId; this.currentNode = nodeId;
// Очищаем предыдущие выделения при обычном клике // Очищаем предыдущие выделения при обычном клике
if (this.selectedNodeIds.size > 0) { if (this.selectedNodeIds.size > 0) {
this.selectedNodeIds.clear(); this.selectedNodeIds.clear();
} }
this.plugin.eventBus.emit('node-selected', { nodeId, graphId: this.selectedGraphId }); this.plugin.eventBus.emit('node-selected', { nodeId, graphId: this.selectedGraphId });
} }
/** /**
* Обработчик изменения состояния выделения узла. * Обработчик изменения состояния выделения узла.
* @param {string} nodeId - ID узла. * @param {string} nodeId - ID узла.
* @param {boolean} isSelected - Новое состояние выделения. * @param {boolean} isSelected - Новое состояние выделения.
*/ */
handleNodeSelectionChange(nodeId: string, isSelected: boolean): void { handleNodeSelectionChange(nodeId: string, isSelected: boolean): void {
if (isSelected) { if (isSelected) {
this.selectedNodeIds.add(nodeId); this.selectedNodeIds.add(nodeId);
} else { } else {
this.selectedNodeIds.delete(nodeId); this.selectedNodeIds.delete(nodeId);
} }
this.renderGraphPanel(); // Перерендериваем для обновления визуального состояния выделения this.renderGraphPanel(); // Перерендериваем для обновления визуального состояния выделения
} }
/** /**
* Обработчик начала нового чата. * Обработчик начала нового чата.
@ -393,7 +414,9 @@ export class GraphView extends ItemView {
this.currentNode = null; this.currentNode = null;
this.selectedGraphId = null; this.selectedGraphId = null;
this.selectedNodeIds.clear(); // Очищаем выделения this.selectedNodeIds.clear(); // Очищаем выделения
this.renderGraphPanel(); this.selectedGraphTitle = null; // Сброс заголовка при новом чате
this.updateTitle(); // Обновляем заголовок
this.renderGraphPanel();
this.fetchGraphsList(); // Обновить список графов, чтобы показать новый пустой чат this.fetchGraphsList(); // Обновить список графов, чтобы показать новый пустой чат
this.plugin.eventBus.emit('chat-history-updated', { graphId: null, messages: [], current_node_id: null }); this.plugin.eventBus.emit('chat-history-updated', { graphId: null, messages: [], current_node_id: null });
} }
@ -406,100 +429,100 @@ export class GraphView extends ItemView {
*/ */
async handleGraphSelected(data: { graphId: string; currentNode: string | null }): Promise<void> { async handleGraphSelected(data: { graphId: string; currentNode: string | null }): Promise<void> {
this.selectedGraphId = data.graphId; this.selectedGraphId = data.graphId;
this.selectedNodeIds.clear(); // Очищаем выделения при смене графа this.selectedNodeIds.clear(); // Очищаем выделения при смене графа
await this.fetchGraphData(data.graphId); await this.fetchGraphData(data.graphId);
} }
/** /**
* Обработчик события удаления узла из ChatPanel. * Обработчик события удаления узла из ChatPanel.
* @param {{ graphId: string, nodeId: string }} data - Данные об удаляемом узле. * @param {{ graphId: string, nodeId: string }} data - Данные об удаляемом узле.
*/ */
async handleDeleteNodeFromChat(data: { graphId: string, nodeId: string }): Promise<void> { async handleDeleteNodeFromChat(data: { graphId: string, nodeId: string }): Promise<void> {
if (data.graphId === this.selectedGraphId) { if (data.graphId === this.selectedGraphId) {
await this.handleDeleteNode(data.nodeId); await this.handleDeleteNode(data.nodeId);
} else { } else {
new Notice('Сообщение относится к другому графу. Удаление невозможно из текущего контекста.', 3000); new Notice('Сообщение относится к другому графу. Удаление невозможно из текущего контекста.', 3000);
} }
} }
/** /**
* Обработчик для кнопки "Копировать выделенные узлы". * Обработчик для кнопки "Копировать выделенные узлы".
*/ */
async handleCopySelectedNodes(): Promise<void> { async handleCopySelectedNodes(): Promise<void> {
if (this.selectedNodeIds.size === 0) { if (this.selectedNodeIds.size === 0) {
new Notice('Выберите узлы для копирования.', 2000); new Notice('Выберите узлы для копирования.', 2000);
return; return;
} }
const messagesToCopy: string[] = []; const messagesToCopy: string[] = [];
// Собираем сообщения в порядке выделения // Собираем сообщения в порядке выделения
// Необходимо пройтись по nodes в graphData, чтобы получить актуальные данные сообщений // Необходимо пройтись по nodes в graphData, чтобы получить актуальные данные сообщений
const orderedNodeIds = Array.from(this.selectedNodeIds); // Сохраняем порядок const orderedNodeIds = Array.from(this.selectedNodeIds); // Сохраняем порядок
for (const nodeId of orderedNodeIds) { for (const nodeId of orderedNodeIds) {
const node = this.graphData.nodes.find(n => n.id === nodeId); const node = this.graphData.nodes.find(n => n.id === nodeId);
if (node && node.data && node.data.message && node.data.message.content) { if (node && node.data && node.data.message && node.data.message.content) {
messagesToCopy.push(node.data.message.content); messagesToCopy.push(node.data.message.content);
} }
} }
if (messagesToCopy.length > 0) { if (messagesToCopy.length > 0) {
try { try {
await navigator.clipboard.writeText(messagesToCopy.join('\n\n--- Разделитель --- \n\n')); await navigator.clipboard.writeText(messagesToCopy.join('\n\n--- Разделитель --- \n\n'));
new Notice(`Скопировано ${messagesToCopy.length} сообщений.`, 2000); new Notice(`Скопировано ${messagesToCopy.length} сообщений.`, 2000);
} catch (err) { } catch (err) {
console.error('Не удалось скопировать сообщения:', err); console.error('Не удалось скопировать сообщения:', err);
new Notice('Ошибка при копировании сообщений.', 3000); new Notice('Ошибка при копировании сообщений.', 3000);
} }
} else { } else {
new Notice('Нет содержимого для копирования в выделенных узлах.', 2000); new Notice('Нет содержимого для копирования в выделенных узлах.', 2000);
} }
} }
/** /**
* Обработчик для кнопки "Удалить выделенные узлы". * Обработчик для кнопки "Удалить выделенные узлы".
*/ */
async handleDeleteSelectedNodes(): Promise<void> { async handleDeleteSelectedNodes(): Promise<void> {
if (this.selectedNodeIds.size === 0) { if (this.selectedNodeIds.size === 0) {
new Notice('Выберите узлы для удаления.', 2000); new Notice('Выберите узлы для удаления.', 2000);
return; return;
} }
if (!this.selectedGraphId) { if (!this.selectedGraphId) {
new Notice('Не выбран граф для удаления узлов.', 3000); new Notice('Не выбран граф для удаления узлов.', 3000);
return; return;
} }
const confirmed = await Dialog.confirm( const confirmed = await Dialog.confirm(
this.plugin.app, this.plugin.app,
`Вы уверены, что хотите удалить ${this.selectedNodeIds.size} выделенных узлов?` `Вы уверены, что хотите удалить ${this.selectedNodeIds.size} выделенных узлов?`
); );
if (!confirmed) if (!confirmed)
return; return;
new Notice(`Начинаю удаление ${this.selectedNodeIds.size} узлов...`, 2000); new Notice(`Начинаю удаление ${this.selectedNodeIds.size} узлов...`, 2000);
const nodesToDelete = Array.from(this.selectedNodeIds); // Сохраняем порядок выделения const nodesToDelete = Array.from(this.selectedNodeIds); // Сохраняем порядок выделения
for (const nodeId of nodesToDelete) { for (const nodeId of nodesToDelete) {
try { try {
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs/${this.selectedGraphId}/nodes/${nodeId}`, { const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs/${this.selectedGraphId}/nodes/${nodeId}`, {
method: 'DELETE' method: 'DELETE'
}); });
if (!response.ok) { if (!response.ok) {
const errorText = await response.text(); const errorText = await response.text();
throw new Error(`Ошибка удаления узла ${nodeId}: ${response.status} - ${errorText}`); throw new Error(`Ошибка удаления узла ${nodeId}: ${response.status} - ${errorText}`);
} }
new Notice(`Узел ${nodeId.substring(0, 8)}... успешно удален.`, 1000); new Notice(`Узел ${nodeId.substring(0, 8)}... успешно удален.`, 1000);
} catch (error) { } catch (error) {
console.error(`Ошибка при удалении узла ${nodeId}:`, error); console.error(`Ошибка при удалении узла ${nodeId}:`, error);
new Notice(`Ошибка при удалении узла ${nodeId.substring(0, 8)}...: ${error.message}`, 5000); new Notice(`Ошибка при удалении узла ${nodeId.substring(0, 8)}...: ${error.message}`, 5000);
// Продолжаем попытки удаления остальных узлов, но фиксируем ошибку // Продолжаем попытки удаления остальных узлов, но фиксируем ошибку
} }
} }
// После всех удалений очищаем выделение и обновляем граф полностью // После всех удалений очищаем выделение и обновляем граф полностью
this.selectedNodeIds.clear(); this.selectedNodeIds.clear();
await this.fetchGraphData(this.selectedGraphId); // Полностью обновляем граф и чат-панель await this.fetchGraphData(this.selectedGraphId); // Полностью обновляем граф и чат-панель
new Notice('Операция удаления завершена.', 2000); new Notice('Операция удаления завершена.', 2000);
} }
} }