From a7019c8a7f177bb77f11a0d8a1043d5cad7b174b Mon Sep 17 00:00:00 2001 From: dimitrievgs Date: Sun, 10 May 2026 18:14:56 +0300 Subject: [PATCH] Add branch button for messages in chat panel --- src/components/ChatPanel.ts | 21 +++++++++++++++++++++ src/views/GraphView.ts | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/components/ChatPanel.ts b/src/components/ChatPanel.ts index e918811..d1e7f32 100644 --- a/src/components/ChatPanel.ts +++ b/src/components/ChatPanel.ts @@ -1018,6 +1018,27 @@ export class ChatPanel { this.props.plugin.eventBus.emit('regenerate-message', { graphId, nodeId, model: this.selectedModel }); }); + // Кнопка "Начать новую ветку отсюда" + const branchButton = toolsetContainer.createEl('button', { + cls: 'text-token-text-secondary hover:bg-token-bg-secondary rounded-lg ml-1', + attr: { + 'aria-label': 'Перейти к этому сообщению (начать новую ветку отсюда)', + 'data-testid': 'branch-turn-action-button', + 'data-state': 'closed' + } + }); + const branchIconContainer = branchButton.createSpan({ cls: 'flex items-center justify-center touch:w-10 h-8 w-8' }); + setIcon(branchIconContainer, 'git-branch'); // Иконка разветвления диалога + branchButton.addEventListener('click', () => { + // Отправляем событие выбора узла. ChatView перехватит его, + // обрежет сообщения ниже этого узла и сделает его текущим. + // Следующее отправленное пользователем сообщение станет новой веткой! + this.props.plugin.eventBus.emit('node-selected', { nodeId: nodeId, graphId: graphId }); + + // Необязательно, но приятно: уведомляем пользователя + new Notice('История обрезана до этого сообщения. Следующий ответ создаст новую ветку.', 3000); + }); + // Кнопка "Наверх сообщения" const scrollToTopButton = toolsetContainer.createEl('button', { cls: 'text-token-text-secondary hover:bg-token-bg-secondary rounded-lg ml-1', diff --git a/src/views/GraphView.ts b/src/views/GraphView.ts index 13e5e4a..3942e68 100644 --- a/src/views/GraphView.ts +++ b/src/views/GraphView.ts @@ -194,6 +194,7 @@ export class GraphView extends ItemView { 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('node-selected', this.handleNodeSelectedFromBus); // слушаем выбор узла извне 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-node-title-updated', this.handleNodeTitleUpdated.bind(this)); @@ -220,6 +221,7 @@ export class GraphView extends ItemView { this.plugin.eventBus.off('new-chat', this.handleNewChat); this.plugin.eventBus.off('graph-selected', this.handleGraphSelected); + this.plugin.eventBus.off('node-selected', this.handleNodeSelectedFromBus); this.plugin.eventBus.off('delete-node', this.handleDeleteNodeFromChat); // WebSocket Event Cleanup @@ -641,6 +643,25 @@ export class GraphView extends ItemView { await this.fetchGraphData(data.graphId); } + /** + * Обработчик события выбора узла из других компонентов (например, ChatPanel). + * Синхронизирует визуальное состояние графа (подсветку текущего узла). + */ + private handleNodeSelectedFromBus = (data: { nodeId: string; graphId: string }) => { + // Реагируем только если это для нашего текущего графа + if (this.selectedGraphId === data.graphId) { + this.currentNode = data.nodeId; + + // Если какие-то узлы подсвечены руками (через Shift), сбрасываем это + if (this.selectedNodeIds.size > 0) { + this.selectedNodeIds.clear(); + } + + // Перерисовываем канвас (это передаст новый activeNodeId в ReactFlow и сдвинет фокус) + this.renderGraphPanel(); + } + }; + /** * Обработчик события удаления узла из ChatPanel. */