handle race condition while making simultaneous changes
This commit is contained in:
parent
2613776c4c
commit
2bf5763eb1
|
|
@ -9,15 +9,15 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom/client'; // <-- ИСПРАВЛЕНО: для createRoot нужен 'react-dom/client'
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
Background,
|
||||
Controls,
|
||||
ReactFlowProvider, // <-- ИСПРАВЛЕНО: импортируем прямо ReactFlowProvider без переименования
|
||||
Connection,
|
||||
Edge,
|
||||
BackgroundVariant // <-- ИСПРАВЛЕНО: используем BackgroundVariant для типизации
|
||||
addEdge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
Background,
|
||||
Controls,
|
||||
ReactFlowProvider, // <-- ИСПРАВЛЕНО: импортируем прямо ReactFlowProvider без переименования
|
||||
Connection,
|
||||
Edge,
|
||||
BackgroundVariant // <-- ИСПРАВЛЕНО: используем BackgroundVariant для типизации
|
||||
} from 'reactflow';
|
||||
|
||||
// <-- ИСПРАВЛЕНО: Это ключевой момент. Убедитесь, что esbuild настроен на обработку CSS импортов.
|
||||
|
|
@ -37,80 +37,88 @@ import { Handle, Position } from 'reactflow';
|
|||
// Простой компонент для узла типа 'user'
|
||||
// Это базовый пример. Ты можешь стилизовать его по своему усмотрению.
|
||||
const UserNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||
return React.createElement(React.Fragment, null, // Используем React.Fragment для возврата нескольких элементов без лишнего div
|
||||
React.createElement(Handle, {
|
||||
type: "source",
|
||||
position: Position.Bottom,
|
||||
isConnectable: isConnectable,
|
||||
}),
|
||||
React.createElement(Handle, {
|
||||
type: "target",
|
||||
position: Position.Top,
|
||||
isConnectable: isConnectable,
|
||||
}),
|
||||
data.label
|
||||
);
|
||||
return React.createElement(
|
||||
React.Fragment,
|
||||
null, // Используем React.Fragment для возврата нескольких элементов без лишнего div
|
||||
React.createElement(Handle, {
|
||||
type: 'source',
|
||||
position: Position.Bottom,
|
||||
isConnectable: isConnectable
|
||||
}),
|
||||
React.createElement(Handle, {
|
||||
type: 'target',
|
||||
position: Position.Top,
|
||||
isConnectable: isConnectable
|
||||
}),
|
||||
data.label
|
||||
);
|
||||
};
|
||||
|
||||
// Простой компонент для узла типа 'llm'
|
||||
const LLMNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||
return React.createElement(React.Fragment, null,
|
||||
React.createElement(Handle, {
|
||||
type: "source",
|
||||
position: Position.Bottom,
|
||||
isConnectable: isConnectable,
|
||||
}),
|
||||
React.createElement(Handle, {
|
||||
type: "target",
|
||||
position: Position.Top,
|
||||
isConnectable: isConnectable,
|
||||
}),
|
||||
data.label
|
||||
);
|
||||
return React.createElement(
|
||||
React.Fragment,
|
||||
null,
|
||||
React.createElement(Handle, {
|
||||
type: 'source',
|
||||
position: Position.Bottom,
|
||||
isConnectable: isConnectable
|
||||
}),
|
||||
React.createElement(Handle, {
|
||||
type: 'target',
|
||||
position: Position.Top,
|
||||
isConnectable: isConnectable
|
||||
}),
|
||||
data.label
|
||||
);
|
||||
};
|
||||
|
||||
// Простой компонент для узла типа 'tool'
|
||||
const ToolNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||
return React.createElement(React.Fragment, null,
|
||||
React.createElement(Handle, {
|
||||
type: "source",
|
||||
position: Position.Bottom,
|
||||
isConnectable: isConnectable,
|
||||
}),
|
||||
React.createElement(Handle, {
|
||||
type: "target",
|
||||
position: Position.Top,
|
||||
isConnectable: isConnectable,
|
||||
}),
|
||||
data.label
|
||||
);
|
||||
return React.createElement(
|
||||
React.Fragment,
|
||||
null,
|
||||
React.createElement(Handle, {
|
||||
type: 'source',
|
||||
position: Position.Bottom,
|
||||
isConnectable: isConnectable
|
||||
}),
|
||||
React.createElement(Handle, {
|
||||
type: 'target',
|
||||
position: Position.Top,
|
||||
isConnectable: isConnectable
|
||||
}),
|
||||
data.label
|
||||
);
|
||||
};
|
||||
|
||||
// Простой компонент для узла типа 'error'
|
||||
const ErrorNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||
return React.createElement(React.Fragment, null,
|
||||
React.createElement(Handle, {
|
||||
type: "source",
|
||||
position: Position.Bottom,
|
||||
isConnectable: isConnectable,
|
||||
}),
|
||||
React.createElement(Handle, {
|
||||
type: "target",
|
||||
position: Position.Top,
|
||||
isConnectable: isConnectable,
|
||||
}),
|
||||
data.label
|
||||
);
|
||||
return React.createElement(
|
||||
React.Fragment,
|
||||
null,
|
||||
React.createElement(Handle, {
|
||||
type: 'source',
|
||||
position: Position.Bottom,
|
||||
isConnectable: isConnectable
|
||||
}),
|
||||
React.createElement(Handle, {
|
||||
type: 'target',
|
||||
position: Position.Top,
|
||||
isConnectable: isConnectable
|
||||
}),
|
||||
data.label
|
||||
);
|
||||
};
|
||||
|
||||
// Регистрируем кастомные типы узлов
|
||||
const nodeTypes = {
|
||||
user: UserNode,
|
||||
llm: LLMNode,
|
||||
tool: ToolNode,
|
||||
error: ErrorNode,
|
||||
// Если у тебя есть node.type === 'default', то можешь его тоже определить,
|
||||
// либо использовать встроенный 'default' React Flow.
|
||||
user: UserNode,
|
||||
llm: LLMNode,
|
||||
tool: ToolNode,
|
||||
error: ErrorNode
|
||||
// Если у тебя есть node.type === 'default', то можешь его тоже определить,
|
||||
// либо использовать встроенный 'default' React Flow.
|
||||
};
|
||||
|
||||
// ----------------------------------------------- Dagre Layout -----------------------------------------------------------------
|
||||
|
|
@ -118,10 +126,10 @@ import dagre from 'dagre'; // Импортируем библиотеку dagre
|
|||
|
||||
// Типы для Dagre (были уже корректны)
|
||||
interface DagreNode {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
// Инициализируем объект Dagre Graph
|
||||
|
|
@ -140,43 +148,43 @@ const nodeHeight = 36;
|
|||
* @returns {{nodes: any[], edges: any[]}} Объект с обновлёнными узлами и рёбрами, содержащими позиции.
|
||||
*/
|
||||
const getLayoutedElements = (nodes: any[], edges: any[], direction: 'TB' | 'LR' = 'TB') => {
|
||||
const isHorizontal = direction === 'LR'; // Проверяем, горизонтальное ли расположение
|
||||
// ДОБАВЛЕНО: ranksep и nodesep для лучшего отступа между узлами
|
||||
dagreGraph.setGraph({ rankdir: direction, ranksep: 100, nodesep: 50 });
|
||||
const isHorizontal = direction === 'LR'; // Проверяем, горизонтальное ли расположение
|
||||
// ДОБАВЛЕНО: ranksep и nodesep для лучшего отступа между узлами
|
||||
dagreGraph.setGraph({ rankdir: direction, ranksep: 100, nodesep: 50 });
|
||||
|
||||
// Добавляем узлы в граф Dagre с их размерами
|
||||
nodes.forEach((node) => {
|
||||
// Убедимся, что узел имеет уникальный id и необходимые размеры для Dagre
|
||||
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
|
||||
});
|
||||
// Добавляем узлы в граф Dagre с их размерами
|
||||
nodes.forEach((node) => {
|
||||
// Убедимся, что узел имеет уникальный id и необходимые размеры для Dagre
|
||||
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
|
||||
});
|
||||
|
||||
// Добавляем рёбра в граф Dagre
|
||||
edges.forEach((edge) => {
|
||||
// Dagre ожидает id, source, target
|
||||
dagreGraph.setEdge(edge.source, edge.target);
|
||||
});
|
||||
// Добавляем рёбра в граф Dagre
|
||||
edges.forEach((edge) => {
|
||||
// Dagre ожидает id, source, target
|
||||
dagreGraph.setEdge(edge.source, edge.target);
|
||||
});
|
||||
|
||||
// Выполняем расчёт расположения графа
|
||||
dagre.layout(dagreGraph);
|
||||
// Выполняем расчёт расположения графа
|
||||
dagre.layout(dagreGraph);
|
||||
|
||||
// Обновляем позиции узлов React Flow на основе расчётов Dagre
|
||||
const newNodes = nodes.map((node) => {
|
||||
const nodeWithPosition: DagreNode = dagreGraph.node(node.id); // Получаем данные узла из Dagre
|
||||
// Смещаем позицию узла Dagre (якорь в центре) к верхнему левому углу,
|
||||
// чтобы она соответствовала точке привязки узла React Flow (верхний левый угол).
|
||||
return {
|
||||
...node,
|
||||
// Обновляем targetPosition и sourcePosition в зависимости от направления для корректного отображения ручек
|
||||
targetPosition: isHorizontal ? 'left' : 'top',
|
||||
sourcePosition: isHorizontal ? 'right' : 'bottom',
|
||||
position: {
|
||||
x: nodeWithPosition.x - nodeWidth / 2, // Корректировка X
|
||||
y: nodeWithPosition.y - nodeHeight / 2, // Корректировка Y
|
||||
},
|
||||
};
|
||||
});
|
||||
// Обновляем позиции узлов React Flow на основе расчётов Dagre
|
||||
const newNodes = nodes.map((node) => {
|
||||
const nodeWithPosition: DagreNode = dagreGraph.node(node.id); // Получаем данные узла из Dagre
|
||||
// Смещаем позицию узла Dagre (якорь в центре) к верхнему левому углу,
|
||||
// чтобы она соответствовала точке привязки узла React Flow (верхний левый угол).
|
||||
return {
|
||||
...node,
|
||||
// Обновляем targetPosition и sourcePosition в зависимости от направления для корректного отображения ручек
|
||||
targetPosition: isHorizontal ? 'left' : 'top',
|
||||
sourcePosition: isHorizontal ? 'right' : 'bottom',
|
||||
position: {
|
||||
x: nodeWithPosition.x - nodeWidth / 2, // Корректировка X
|
||||
y: nodeWithPosition.y - nodeHeight / 2 // Корректировка Y
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return { nodes: newNodes, edges }; // Возвращаем обновлённые узлы и рёбра
|
||||
return { nodes: newNodes, edges }; // Возвращаем обновлённые узлы и рёбра
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -185,8 +193,8 @@ const getLayoutedElements = (nodes: any[], edges: any[], direction: 'TB' | 'LR'
|
|||
* @property {(nodeId: string) => void} onNodeClick - Обработчик клика по узлу.
|
||||
*/
|
||||
interface GraphPanelProps {
|
||||
graphData: { nodes: any[]; edges: any[]; };
|
||||
onNodeClick: (nodeId: string) => void;
|
||||
graphData: { nodes: any[]; edges: any[] };
|
||||
onNodeClick: (nodeId: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -195,88 +203,96 @@ interface GraphPanelProps {
|
|||
* @returns {React.ReactElement} Элемент React.
|
||||
*/
|
||||
export const GraphPanelComponent: React.FC<GraphPanelProps> = ({ graphData, onNodeClick }) => {
|
||||
// ----------------------------------------------- React Hooks ---------------------------------------------------------------
|
||||
// Инициализируем состояния для узлов и рёбер React Flow
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
// ----------------------------------------------- React Hooks ---------------------------------------------------------------
|
||||
// Инициализируем состояния для узлов и рёбер React Flow
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
|
||||
// Используем useEffect для применения Dagre layout при изменении данных графа
|
||||
React.useEffect(() => {
|
||||
// Проверяем, есть ли данные для построения графа
|
||||
if (graphData.nodes.length > 0 || graphData.edges.length > 0) {
|
||||
console.log("Applying Dagre layout with data:", graphData);
|
||||
// Используем useEffect для применения Dagre layout при изменении данных графа
|
||||
React.useEffect(() => {
|
||||
// Проверяем, есть ли данные для построения графа
|
||||
if (graphData.nodes.length > 0 || graphData.edges.length > 0) {
|
||||
console.log('Applying Dagre layout with data:', graphData);
|
||||
|
||||
// Преобразуем входящие данные в формат, необходимый для Dagre и React Flow
|
||||
// Убеждаемся, что каждый узел имеет данные.label для отображения
|
||||
const initialNodesForLayout = graphData.nodes.map(node => ({
|
||||
id: node.id,
|
||||
// ДОБАВЛЕНО: используем node.type, если он есть, иначе 'default'
|
||||
type: node.type || 'default',
|
||||
data: { label: node.data?.label || `Node ${node.id}` },
|
||||
position: { x: 0, y: 0 } // Временная позиция, будет перезаписана Dagre
|
||||
}));
|
||||
const initialEdgesForLayout = graphData.edges.map(edge => ({
|
||||
id: edge.id,
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
// Для рёбер можно добавить тип 'default' или ваш кастомный
|
||||
type: edge.type || 'default',
|
||||
}));
|
||||
// Преобразуем входящие данные в формат, необходимый для Dagre и React Flow
|
||||
// Убеждаемся, что каждый узел имеет данные.label для отображения
|
||||
const initialNodesForLayout = graphData.nodes.map((node) => ({
|
||||
id: node.id,
|
||||
// ДОБАВЛЕНО: используем node.type, если он есть, иначе 'default'
|
||||
type: node.type || 'default',
|
||||
data: { label: node.data?.label || `Node ${node.id}` },
|
||||
position: { x: 0, y: 0 } // Временная позиция, будет перезаписана Dagre
|
||||
}));
|
||||
const initialEdgesForLayout = graphData.edges.map((edge) => ({
|
||||
id: edge.id,
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
// Для рёбер можно добавить тип 'default' или ваш кастомный
|
||||
type: edge.type || 'default'
|
||||
}));
|
||||
|
||||
// Применяем Dagre layout
|
||||
const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(
|
||||
initialNodesForLayout,
|
||||
initialEdgesForLayout
|
||||
);
|
||||
// Применяем Dagre layout
|
||||
const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(initialNodesForLayout, initialEdgesForLayout);
|
||||
|
||||
// Обновляем состояния узлов и рёбер React Flow
|
||||
setNodes(layoutedNodes);
|
||||
setEdges(layoutedEdges);
|
||||
} else {
|
||||
// Если данных нет, очищаем граф
|
||||
setNodes([]);
|
||||
setEdges([]);
|
||||
}
|
||||
}, [graphData.nodes, graphData.edges]); // Зависимости: изменения в узлах и рёбрах графа
|
||||
// Обновляем состояния узлов и рёбер React Flow
|
||||
setNodes(layoutedNodes);
|
||||
setEdges(layoutedEdges);
|
||||
} else {
|
||||
// Если данных нет, очищаем граф
|
||||
setNodes([]);
|
||||
setEdges([]);
|
||||
}
|
||||
}, [graphData.nodes, graphData.edges]); // Зависимости: изменения в узлах и рёбрах графа
|
||||
|
||||
// ----------------------------------------------- Event Handlers ------------------------------------------------------------
|
||||
/**
|
||||
* Обработчик добавления нового соединения между узлами (если разрешено).
|
||||
* @param {Connection | Edge} params - Параметры нового соединения.
|
||||
*/
|
||||
const onConnect = React.useCallback(
|
||||
(params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)),
|
||||
[setEdges]
|
||||
);
|
||||
// ----------------------------------------------- Event Handlers ------------------------------------------------------------
|
||||
/**
|
||||
* Обработчик добавления нового соединения между узлами (если разрешено).
|
||||
* @param {Connection | Edge} params - Параметры нового соединения.
|
||||
*/
|
||||
const onConnect = React.useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
|
||||
|
||||
/**
|
||||
* Обработчик клика по узлу.
|
||||
* @param {React.MouseEvent} event - Объект события клика.
|
||||
* @param {any} node - Данные узла, по которому был клик.
|
||||
*/
|
||||
const nodeClickHandler = React.useCallback((event: React.MouseEvent, node: any) => {
|
||||
onNodeClick(node.id);
|
||||
}, [onNodeClick]);
|
||||
/**
|
||||
* Обработчик клика по узлу.
|
||||
* @param {React.MouseEvent} event - Объект события клика.
|
||||
* @param {any} node - Данные узла, по которому был клик.
|
||||
*/
|
||||
const nodeClickHandler = React.useCallback(
|
||||
(event: React.MouseEvent, node: any) => {
|
||||
onNodeClick(node.id);
|
||||
},
|
||||
[onNodeClick]
|
||||
);
|
||||
|
||||
// ----------------------------------------------- Render --------------------------------------------------------------------
|
||||
return React.createElement('div', { className: 'graph-panel', style: { width: '100%', height: '100%' } },
|
||||
// Оборачиваем ReactFlowProvider вокруг ReactFlow для доступа к контексту React Flow
|
||||
React.createElement(ReactFlowProvider, null,
|
||||
React.createElement(ReactFlow, {
|
||||
nodes,
|
||||
edges,
|
||||
onNodesChange, // Обработчик изменения узлов
|
||||
onEdgesChange, // Обработчик изменения рёбер
|
||||
onConnect, // Обработчик подключения
|
||||
onNodeClick: nodeClickHandler, // Обработчик клика по узлу
|
||||
fitView: true, // Автоматически центрирует и масштабирует граф при загрузке
|
||||
attributionPosition: 'bottom-right', // Позиция атрибуции React Flow
|
||||
nodeTypes, // <-- ИСПРАВЛЕНО: Передаем зарегистрированные типы узлов
|
||||
},
|
||||
// Компоненты Controls и Background являются дочерними для ReactFlow
|
||||
React.createElement(Controls, null),
|
||||
React.createElement(Background, { variant: BackgroundVariant.Dots, gap: 12, size: 1 })
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
// ----------------------------------------------- Render --------------------------------------------------------------------
|
||||
return React.createElement(
|
||||
'div',
|
||||
{ className: 'graph-panel', style: { width: '100%', height: '100%' } },
|
||||
// Оборачиваем ReactFlowProvider вокруг ReactFlow для доступа к контексту React Flow
|
||||
React.createElement(
|
||||
ReactFlowProvider,
|
||||
null,
|
||||
React.createElement(
|
||||
ReactFlow,
|
||||
{
|
||||
nodes,
|
||||
edges,
|
||||
onNodesChange, // Обработчик изменения узлов
|
||||
onEdgesChange, // Обработчик изменения рёбер
|
||||
onConnect, // Обработчик подключения
|
||||
onNodeClick: nodeClickHandler, // Обработчик клика по узлу
|
||||
fitView: true, // Автоматически центрирует и масштабирует граф при загрузке
|
||||
attributionPosition: 'bottom-right', // Позиция атрибуции React Flow
|
||||
nodeTypes,
|
||||
// Дополнительные параметры для лучшего отображения
|
||||
zoomOnScroll: true,
|
||||
panOnDrag: true,
|
||||
minZoom: 0.1,
|
||||
maxZoom: 2
|
||||
},
|
||||
// Компоненты Controls и Background являются дочерними для ReactFlow
|
||||
React.createElement(Controls, null),
|
||||
React.createElement(Background, { variant: BackgroundVariant.Dots, gap: 12, size: 1 })
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export class HistoryPanel {
|
|||
container: HTMLElement;
|
||||
props: {
|
||||
graphs: any[];
|
||||
onGraphSelect: (graphId: string) => void;
|
||||
onGraphSelect: (data: { graphId: string; currentNode: string | null }) => void;
|
||||
eventBus: EventBus;
|
||||
};
|
||||
graphs: any[];
|
||||
|
|
@ -23,7 +23,7 @@ export class HistoryPanel {
|
|||
container: HTMLElement,
|
||||
props: {
|
||||
graphs: any[];
|
||||
onGraphSelect: (graphId: string) => void;
|
||||
onGraphSelect: (data: { graphId: string; currentNode: string | null }) => void;
|
||||
eventBus: EventBus;
|
||||
}
|
||||
) {
|
||||
|
|
@ -104,7 +104,7 @@ export class HistoryPanel {
|
|||
if (!(e.target as HTMLElement).classList.contains('chat-item-options-button')) {
|
||||
const graphId = (item as HTMLElement).dataset.graphId;
|
||||
if (graphId && this.props.onGraphSelect) {
|
||||
this.props.onGraphSelect(graphId);
|
||||
this.props.onGraphSelect({ graphId: graphId, currentNode: null });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -124,36 +124,32 @@ export class ChatView extends ItemView {
|
|||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ошибка! статус: ${response.status}`);
|
||||
// Пытаемся прочитать ошибку из тела ответа, если это возможно
|
||||
const errorData = await response.json().catch(() => ({ message: response.statusText }));
|
||||
throw new Error(`HTTP ошибка при отправке сообщения! Статус: ${response.status} - ${errorData.message}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// Обновляем graph_id и current_node_id всегда
|
||||
this.selectedGraphId = result.graph_id;
|
||||
const newCurrentNodeId = result.graph_visualization_data.current_node_id;
|
||||
this.currentNode = newCurrentNodeId;
|
||||
|
||||
// Обновляем граф
|
||||
this.plugin.eventBus.emit('graph-updated', {
|
||||
graphData: {
|
||||
nodes: result.graph_visualization_data.nodes || [],
|
||||
edges: result.graph_visualization_data.edges || []
|
||||
},
|
||||
currentNode: newCurrentNodeId
|
||||
});
|
||||
|
||||
// Загружаем сообщения
|
||||
// Сначала обновим GraphView, чтобы он запросил последнюю версию графа
|
||||
// Это предотвратит состояние гонки, когда разные запросы возвращают частичные графы.
|
||||
// Вместо того, чтобы отправлять частичные данные, мы запрашиваем полный граф у бэкенда.
|
||||
this.plugin.eventBus.emit('graph-selected', { graphId: this.selectedGraphId, currentNode: this.currentNode });
|
||||
|
||||
// Затем обновим ChatView, загрузив историю до нового current_node_id
|
||||
if (newCurrentNodeId) {
|
||||
await this.fetchMessagesFromRootToNode(newCurrentNodeId);
|
||||
} else {
|
||||
// ИСПРАВЛЕНО: Убедиться, что type передается, если это возможно
|
||||
this.chatHistory = result.messages.map((msg: any) => ({
|
||||
...msg,
|
||||
type: msg.type || msg.role // Используем type, если есть, иначе role как fallback
|
||||
})) || [];
|
||||
if (this.chatPanel) {
|
||||
this.chatPanel.updateHistory(this.chatHistory);
|
||||
}
|
||||
} else if (this.selectedGraphId) {
|
||||
// Если newCurrentNodeId по какой-то причине не вернулся, но graph_id есть,
|
||||
// пытаемся загрузить историю по последнему известному узлу или просто весь граф (чтобы отобразить пустую историю, если граф новый).
|
||||
// В этом случае, если current_node_id отсутствует, historyPanel должна очиститься.
|
||||
this.currentNode = null; // Сбрасываем, если no current_node_id
|
||||
this.fetchMessagesFromRootToNode(''); // Передаем пустую строку или null, чтобы очистить историю
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ export class GraphView extends ItemView {
|
|||
// Инициализируем панель истории
|
||||
this.historyPanel = new HistoryPanel(this.historyContainer, {
|
||||
graphs: this.graphsList,
|
||||
onGraphSelect: this.handleGraphSelect.bind(this),
|
||||
onGraphSelect: this.handleGraphSelected.bind(this),
|
||||
eventBus: this.plugin.eventBus
|
||||
});
|
||||
|
||||
|
|
@ -107,8 +107,8 @@ export class GraphView extends ItemView {
|
|||
this.initializeGraphPanel();
|
||||
|
||||
// Подписываемся на события
|
||||
this.plugin.eventBus.on('graph-updated', this.handleGraphUpdate.bind(this));
|
||||
this.plugin.eventBus.on('new-chat', this.handleNewChat.bind(this));
|
||||
this.plugin.eventBus.on('graph-selected', this.handleGraphSelected.bind(this));
|
||||
|
||||
// Загружаем список графов
|
||||
await this.fetchGraphsList();
|
||||
|
|
@ -130,8 +130,8 @@ export class GraphView extends ItemView {
|
|||
this.reactRoot = null;
|
||||
}
|
||||
|
||||
this.plugin.eventBus.off('graph-updated', this.handleGraphUpdate);
|
||||
this.plugin.eventBus.off('new-chat', this.handleNewChat);
|
||||
this.plugin.eventBus.off('graph-selected', this.handleGraphSelected); // Отписываемся и отсюда
|
||||
}
|
||||
|
||||
// ----------------------------------------------- Graph Panel Integration --------------------------------------------------
|
||||
|
|
@ -212,6 +212,14 @@ export class GraphView extends ItemView {
|
|||
*/
|
||||
async fetchGraphData(graphId: string): Promise<void> {
|
||||
try {
|
||||
if (!graphId) {
|
||||
// Если graphId пуст, очищаем граф
|
||||
this.graphData = { nodes: [], edges: [] };
|
||||
this.currentNode = null;
|
||||
this.renderGraphPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs/${graphId}`);
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
|
|
@ -246,17 +254,6 @@ export class GraphView extends ItemView {
|
|||
this.plugin.eventBus.emit('node-selected', { nodeId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработчик обновления данных графа (например, после нового сообщения).
|
||||
* Обновляет `graphData` и перерендеривает граф.
|
||||
* @param {{ graphData: { nodes: any[], edges: any[] }, currentNode: string | null }} data - Обновленные данные графа.
|
||||
*/
|
||||
handleGraphUpdate(data: { graphData: { nodes: any[]; edges: any[] }; currentNode: string | null }): void {
|
||||
this.graphData = data.graphData;
|
||||
this.currentNode = data.currentNode;
|
||||
this.renderGraphPanel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработчик начала нового чата.
|
||||
* Очищает текущие данные графа и перерендеривает его.
|
||||
|
|
@ -272,12 +269,14 @@ export class GraphView extends ItemView {
|
|||
/**
|
||||
* Обработчик выбора графа из панели истории.
|
||||
* Устанавливает выбранный граф и загружает его данные.
|
||||
* @param {string} graphId - ID выбранного графа.
|
||||
* @param {{ graphId: string; currentNode: string | null }} data - Объект, содержащий ID выбранного графа и текущий узел.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async handleGraphSelect(graphId: string): Promise<void> {
|
||||
this.selectedGraphId = graphId;
|
||||
await this.fetchGraphData(graphId);
|
||||
// Также сообщаем ChatView, что выбран новый граф и он должен обновить свою историю
|
||||
this.plugin.eventBus.emit('graph-selected', { graphId, currentNode: this.currentNode });
|
||||
async handleGraphSelected(data: { graphId: string; currentNode: string | null }): Promise<void> {
|
||||
this.selectedGraphId = data.graphId;
|
||||
// currentNode здесь может быть неактуальным, fetchGraphData загрузит актуальный current_node_id
|
||||
await this.fetchGraphData(data.graphId);
|
||||
// Note: Больше не нужно эмитить 'graph-selected' обратно в ChatView, т.к. ChatView уже инициировал это.
|
||||
// ChatView должен сам обновить свою историю после вызова этого метода или после своего собственного fetch.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user