More correct working with error nodes + more correct regeneration
This commit is contained in:
parent
547c13b0b5
commit
05fbd5a9f4
|
|
@ -863,7 +863,8 @@ export class ChatPanel {
|
||||||
const regenerateIconContainer = regenerateButton.createSpan({ cls: 'flex items-center justify-center touch:w-10 h-8 w-8' });
|
const regenerateIconContainer = regenerateButton.createSpan({ cls: 'flex items-center justify-center touch:w-10 h-8 w-8' });
|
||||||
setIcon(regenerateIconContainer, 'refresh-cw');
|
setIcon(regenerateIconContainer, 'refresh-cw');
|
||||||
regenerateButton.addEventListener('click', async () => {
|
regenerateButton.addEventListener('click', async () => {
|
||||||
this.props.plugin.eventBus.emit('regenerate-message', { graphId, nodeId });
|
// Передаем выбранную модель при регенерации
|
||||||
|
this.props.plugin.eventBus.emit('regenerate-message', { graphId, nodeId, model: this.selectedModel });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,8 @@ const BaseNodeWrapper: React.FC<BaseNodeWrapperProps> = ({ data, isConnectable,
|
||||||
if (eventBus) {
|
if (eventBus) {
|
||||||
eventBus.emit('regenerate-message', {
|
eventBus.emit('regenerate-message', {
|
||||||
graphId: data.graphId,
|
graphId: data.graphId,
|
||||||
nodeId: data.nodeId
|
nodeId: data.nodeId,
|
||||||
|
model: (data.app as any).plugins?.plugins?.['llm-agent-plugin']?.settings?.defaultModel || 'gemini-2.5-flash' // НОВОЕ: Добавляем модель
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -122,7 +123,10 @@ const BaseNodeWrapper: React.FC<BaseNodeWrapperProps> = ({ data, isConnectable,
|
||||||
isConnectable: isConnectable
|
isConnectable: isConnectable
|
||||||
}),
|
}),
|
||||||
children, // Здесь будет отображаться специфическое содержимое узла
|
children, // Здесь будет отображаться специфическое содержимое узла
|
||||||
data.isSelected && React.createElement('div', {
|
data.isSelected &&
|
||||||
|
React.createElement(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
style: {
|
style: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: '-30px',
|
bottom: '-30px',
|
||||||
|
|
@ -131,18 +135,26 @@ const BaseNodeWrapper: React.FC<BaseNodeWrapperProps> = ({ data, isConnectable,
|
||||||
gap: '5px'
|
gap: '5px'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
React.createElement('button', {
|
React.createElement(
|
||||||
|
'button',
|
||||||
|
{
|
||||||
style: deleteButtonStyle,
|
style: deleteButtonStyle,
|
||||||
onClick: handleNodeDelete,
|
onClick: handleNodeDelete,
|
||||||
'aria-label': 'Удалить узел',
|
'aria-label': 'Удалить узел',
|
||||||
className: 'node-delete-button'
|
className: 'node-delete-button'
|
||||||
}, '🗑️'),
|
},
|
||||||
React.createElement('button', {
|
'🗑️'
|
||||||
|
),
|
||||||
|
React.createElement(
|
||||||
|
'button',
|
||||||
|
{
|
||||||
style: deleteButtonStyle,
|
style: deleteButtonStyle,
|
||||||
onClick: handleRegenerate,
|
onClick: handleRegenerate,
|
||||||
'aria-label': 'Регенерировать',
|
'aria-label': 'Регенерировать',
|
||||||
className: 'node-regenerate-button'
|
className: 'node-regenerate-button'
|
||||||
}, '🔄')
|
},
|
||||||
|
'🔄'
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -163,12 +175,13 @@ const UserNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||||
// Обновите другие типы узлов (LLMNode, ToolNode, ErrorNode) аналогично
|
// Обновите другие типы узлов (LLMNode, ToolNode, ErrorNode) аналогично
|
||||||
const LLMNode: React.FC<any> = ({ data, isConnectable }) => {
|
const LLMNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||||
const displayLabel = data.title || data.label || 'LLM';
|
const displayLabel = data.title || data.label || 'LLM';
|
||||||
// ...
|
// Если узел является заглушкой, добавляем класс для стилизации
|
||||||
|
const nodeClassName = data.is_placeholder ? 'llm-node-placeholder' : '';
|
||||||
|
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
BaseNodeWrapper,
|
BaseNodeWrapper,
|
||||||
{ data: data, isConnectable: isConnectable },
|
{ data: data, isConnectable: isConnectable },
|
||||||
React.createElement('div', null, displayLabel)
|
React.createElement('div', { className: nodeClassName }, displayLabel)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -347,12 +360,13 @@ export const GraphPanelComponent: React.FC<GraphPanelProps> = ({
|
||||||
title_generated: node.data?.title_generated || false,
|
title_generated: node.data?.title_generated || false,
|
||||||
onNodeDelete: onNodeDelete,
|
onNodeDelete: onNodeDelete,
|
||||||
isSelected: selectedNodeIds.has(node.id),
|
isSelected: selectedNodeIds.has(node.id),
|
||||||
graphId: (node.data && node.data.message && node.data.message.graph_id) ? node.data.message.graph_id : 'unknown',
|
graphId: node.data && node.data.message && node.data.message.graph_id ? node.data.message.graph_id : 'unknown',
|
||||||
nodeId: node.id,
|
nodeId: node.id,
|
||||||
app: app
|
app: app,
|
||||||
|
is_placeholder: node.data?.is_placeholder || false // НОВОЕ: Передаем статус placeholder
|
||||||
},
|
},
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
className: selectedNodeIds.has(node.id) ? 'selected-node' : '',
|
className: selectedNodeIds.has(node.id) ? 'selected-node' : ''
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const initialEdgesForLayout = graphData.edges.map((edge) => ({
|
const initialEdgesForLayout = graphData.edges.map((edge) => ({
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@ export class WebSocketService {
|
||||||
private socket: Socket | null = null;
|
private socket: Socket | null = null;
|
||||||
private eventBus: EventBus;
|
private eventBus: EventBus;
|
||||||
private apiBaseUrl: string;
|
private apiBaseUrl: string;
|
||||||
|
//private receivedEvents: Set<string> = new Set(); // Для отслеживания полученных событий
|
||||||
|
//private reconnectAttempts = 0;
|
||||||
|
private maxReconnectAttempts = 10;
|
||||||
|
private pingIntervalId: ReturnType<typeof setInterval> | null = null;
|
||||||
|
|
||||||
constructor(eventBus: EventBus, apiBaseUrl: string) {
|
constructor(eventBus: EventBus, apiBaseUrl: string) {
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
|
|
@ -22,16 +26,25 @@ export class WebSocketService {
|
||||||
|
|
||||||
// Извлекаем базовый URL без /api
|
// Извлекаем базовый URL без /api
|
||||||
const wsUrl = this.apiBaseUrl.replace('/api', '');
|
const wsUrl = this.apiBaseUrl.replace('/api', '');
|
||||||
|
console.log('🔌 Подключение к WebSocket:', wsUrl);
|
||||||
|
|
||||||
this.socket = io(wsUrl, {
|
this.socket = io(wsUrl, {
|
||||||
transports: ['websocket', 'polling'],
|
transports: ['websocket', 'polling'],
|
||||||
reconnection: true,
|
reconnection: true,
|
||||||
reconnectionDelay: 1000,
|
reconnectionDelay: 1000,
|
||||||
reconnectionDelayMax: 5000,
|
reconnectionDelayMax: 5000,
|
||||||
reconnectionAttempts: 5
|
reconnectionAttempts: this.maxReconnectAttempts,
|
||||||
|
timeout: 20000,
|
||||||
|
forceNew: false,
|
||||||
|
upgrade: true,
|
||||||
|
// Добавляем дополнительные опции для стабильности
|
||||||
|
autoConnect: true,
|
||||||
|
rememberUpgrade: true,
|
||||||
|
rejectUnauthorized: false
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setupEventHandlers();
|
this.setupEventHandlers();
|
||||||
|
this.startProactivePing(); // Запускаем проактивный пинг
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -52,17 +65,45 @@ export class WebSocketService {
|
||||||
console.error('Ошибка подключения WebSocket:', error);
|
console.error('Ошибка подключения WebSocket:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ----------------------------------------------- Title Update Events ---------------------------------------------------------------
|
// ----------------------------------------------- Nodes Update Events ---------------------------------------------------------------
|
||||||
|
|
||||||
this.socket.on('graph_title_updated', (data: { graph_id: string; title: string }) => {
|
this.socket.on(
|
||||||
console.log('📊 Получено обновление заголовка графа:', data);
|
'node_type_changed',
|
||||||
|
(data: { graph_id: string; node_id: string; node_type: string }, callback?: (response: { status: string }) => void) => {
|
||||||
|
console.log('📡 WebSocket получено: node_type_changed', data);
|
||||||
|
this.eventBus.emit('ws-node-type-changed', {
|
||||||
|
graphId: data.graph_id,
|
||||||
|
nodeId: data.node_id,
|
||||||
|
nodeType: data.node_type
|
||||||
|
});
|
||||||
|
if (callback) {
|
||||||
|
callback({ status: 'ok' }); // Отправляем подтверждение
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.socket.on(
|
||||||
|
'graph_title_updated',
|
||||||
|
(data: { graph_id: string; title: string }, callback?: (response: { status: string }) => void) => {
|
||||||
|
console.log('📡 WebSocket получено: graph_title_updated', data);
|
||||||
this.eventBus.emit('ws-graph-title-updated', data);
|
this.eventBus.emit('ws-graph-title-updated', data);
|
||||||
});
|
if (callback) {
|
||||||
|
callback({ status: 'ok' }); // Отправляем подтверждение
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.socket.on(
|
||||||
|
'node_title_updated',
|
||||||
|
(data: { graph_id: string; node_id: string; title: string }, callback?: (response: { status: string }) => void) => {
|
||||||
|
console.log('📡 WebSocket получено: node_title_updated', data);
|
||||||
|
|
||||||
this.socket.on('node_title_updated', (data: { graph_id: string; node_id: string; title: string }) => {
|
|
||||||
console.log('📝 Получено обновление заголовка узла:', data);
|
|
||||||
this.eventBus.emit('ws-node-title-updated', data);
|
this.eventBus.emit('ws-node-title-updated', data);
|
||||||
});
|
if (callback) {
|
||||||
|
callback({ status: 'ok' }); // Отправляем подтверждение
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -74,6 +115,7 @@ export class WebSocketService {
|
||||||
this.socket = null;
|
this.socket = null;
|
||||||
console.log('WebSocket отключен');
|
console.log('WebSocket отключен');
|
||||||
}
|
}
|
||||||
|
this.stopProactivePing(); // Останавливаем проактивный пинг
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -82,4 +124,51 @@ export class WebSocketService {
|
||||||
isConnected(): boolean {
|
isConnected(): boolean {
|
||||||
return this.socket?.connected ?? false;
|
return this.socket?.connected ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запускает проактивный пинг для проверки соединения.
|
||||||
|
*/
|
||||||
|
private startProactivePing(): void {
|
||||||
|
this.stopProactivePing(); // Убедимся, что предыдущий интервал остановлен
|
||||||
|
this.pingIntervalId = setInterval(() => {
|
||||||
|
if (this.socket && this.socket.connected) {
|
||||||
|
console.log('Client sending proactive ping...');
|
||||||
|
let ackTimeout = setTimeout(() => {
|
||||||
|
console.warn('Client: No pong response received for proactive ping within timeout. Forcing reconnect.');
|
||||||
|
this.disconnect();
|
||||||
|
this.connect();
|
||||||
|
}, 3000); // Таймаут ожидания ответа на пинг от сервера (3 секунды)
|
||||||
|
|
||||||
|
// Используем callback для получения подтверждения от сервера
|
||||||
|
this.socket.emit('client_proactive_ping', (response: { status: string }) => {
|
||||||
|
clearTimeout(ackTimeout);
|
||||||
|
if (response && response.status === 'pong') {
|
||||||
|
console.log('Client received proactive pong.');
|
||||||
|
} else {
|
||||||
|
console.warn('Client: Invalid pong response or no response for proactive ping. Forcing reconnect.');
|
||||||
|
this.disconnect();
|
||||||
|
this.connect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (this.socket && !this.socket.connected) { // Здесь убираем '&& !this.socket.connecting'
|
||||||
|
// Если сокет не подключен, Socket.IO должен сам управлять переподключением,
|
||||||
|
// так как reconnection: true. Ручной вызов connect() может быть избыточным.
|
||||||
|
console.log('Client proactive ping: Socket not connected. Relying on Socket.IO\'s auto-reconnect logic.');
|
||||||
|
// Если вы все же хотите агрессивно вызвать this.connect() при каждом обнаружении отключения,
|
||||||
|
// оставьте следующую строку:
|
||||||
|
this.connect();
|
||||||
|
}
|
||||||
|
}, 5000); // Проверять каждые 5 секунд
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Останавливает проактивный пинг.
|
||||||
|
*/
|
||||||
|
private stopProactivePing(): void {
|
||||||
|
if (this.pingIntervalId) {
|
||||||
|
clearInterval(this.pingIntervalId);
|
||||||
|
this.pingIntervalId = null;
|
||||||
|
console.log('Proactive ping stopped.');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -67,6 +67,7 @@ export class ChatView 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('regenerate-message', this.handleRegenerateMessage.bind(this)); // Добавлено
|
this.plugin.eventBus.on('regenerate-message', this.handleRegenerateMessage.bind(this)); // Добавлено
|
||||||
|
this.plugin.eventBus.on('ws-node-type-changed', this.handleNodeTypeChanged.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async onClose(): Promise<void> {
|
async onClose(): Promise<void> {
|
||||||
|
|
@ -78,6 +79,7 @@ export class ChatView 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('regenerate-message', this.handleRegenerateMessage);
|
this.plugin.eventBus.off('regenerate-message', this.handleRegenerateMessage);
|
||||||
|
this.plugin.eventBus.off('ws-node-type-changed', this.handleNodeTypeChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------- API Methods ---------------------------------------------------------------
|
// ----------------------------------------------- API Methods ---------------------------------------------------------------
|
||||||
|
|
@ -173,7 +175,8 @@ export class ChatView extends ItemView {
|
||||||
// Шаг 2: Запускаем стриминг ответа
|
// Шаг 2: Запускаем стриминг ответа
|
||||||
if (this.selectedGraphId) {
|
if (this.selectedGraphId) {
|
||||||
// Проверка на null
|
// Проверка на null
|
||||||
await this.streamLLMResponse(this.selectedGraphId, userNodeId, selectedModel);
|
// Передаем null, так как это не регенерация, а новое сообщение
|
||||||
|
await this.streamLLMResponse(this.selectedGraphId, userNodeId, selectedModel, null);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Graph ID не определён после отправки сообщения');
|
throw new Error('Graph ID не определён после отправки сообщения');
|
||||||
}
|
}
|
||||||
|
|
@ -221,23 +224,35 @@ export class ChatView extends ItemView {
|
||||||
await this.fetchMessagesFromRootToNode(data.currentNode);
|
await this.fetchMessagesFromRootToNode(data.currentNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
async streamLLMResponse(graphId: string, userNodeId: string, selectedModel?: string): Promise<void> {
|
/**
|
||||||
const params = new URLSearchParams({
|
* Стримит ответ LLM.
|
||||||
graph_id: graphId,
|
* @param {string} graphId - ID графа.
|
||||||
user_node_id: userNodeId,
|
* @param {string} userNodeId - ID узла пользователя.
|
||||||
system_prompt: this.plugin.settings.systemPrompt || '',
|
* @param {string} selectedModel - Выбранная модель.
|
||||||
model: selectedModel || this.plugin.settings.defaultModel
|
* @param {string | null} existingAssistantNodeId - Существующий ID узла ассистента для повторного использования (для регенерации).
|
||||||
});
|
*/
|
||||||
|
async streamLLMResponse(
|
||||||
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/chat/stream`, {
|
graphId: string,
|
||||||
method: 'POST',
|
userNodeId: string,
|
||||||
headers: { 'Content-Type': 'application/json' },
|
selectedModel?: string,
|
||||||
body: JSON.stringify({
|
existingAssistantNodeId: string | null = null
|
||||||
|
): Promise<void> {
|
||||||
|
const body = {
|
||||||
graph_id: graphId,
|
graph_id: graphId,
|
||||||
user_node_id: userNodeId,
|
user_node_id: userNodeId,
|
||||||
system_prompt: this.plugin.settings.systemPrompt,
|
system_prompt: this.plugin.settings.systemPrompt,
|
||||||
model: selectedModel || this.plugin.settings.defaultModel
|
model: selectedModel || this.plugin.settings.defaultModel
|
||||||
})
|
};
|
||||||
|
|
||||||
|
// Если есть существующий ID узла ассистента, добавляем его в тело запроса
|
||||||
|
if (existingAssistantNodeId) {
|
||||||
|
Object.assign(body, { existing_assistant_node_id: existingAssistantNodeId });
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/chat/stream`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(body) // Используем модифицированное тело запроса
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -267,6 +282,10 @@ export class ChatView extends ItemView {
|
||||||
|
|
||||||
if (data.type === 'node_created') {
|
if (data.type === 'node_created') {
|
||||||
assistantNodeId = data.node_id;
|
assistantNodeId = data.node_id;
|
||||||
|
// Если узел переиспользуется, нужно очистить его содержимое в UI
|
||||||
|
if (data.reused) {
|
||||||
|
this.clearStreamingMessage(assistantNodeId);
|
||||||
|
}
|
||||||
this.plugin.eventBus.emit('graph-selected', {
|
this.plugin.eventBus.emit('graph-selected', {
|
||||||
graphId: graphId,
|
graphId: graphId,
|
||||||
currentNode: assistantNodeId
|
currentNode: assistantNodeId
|
||||||
|
|
@ -275,11 +294,16 @@ export class ChatView extends ItemView {
|
||||||
accumulatedContent += data.content;
|
accumulatedContent += data.content;
|
||||||
this.updateStreamingMessage(assistantNodeId, accumulatedContent);
|
this.updateStreamingMessage(assistantNodeId, accumulatedContent);
|
||||||
} else if (data.type === 'error') {
|
} else if (data.type === 'error') {
|
||||||
console.error('Ошибка стриминга:', data.error);
|
console.error('Ошибка:', data.error);
|
||||||
if (assistantNodeId) {
|
if (assistantNodeId) {
|
||||||
this.markNodeAsError(assistantNodeId, data.error);
|
this.markNodeAsError(assistantNodeId, data.error);
|
||||||
|
// Эмиттируем событие, чтобы GraphView обновил отображение узла с ошибкой
|
||||||
|
this.plugin.eventBus.emit('graph-selected', {
|
||||||
|
graphId: graphId,
|
||||||
|
currentNode: assistantNodeId
|
||||||
|
});
|
||||||
}
|
}
|
||||||
break;
|
break; // Прекращаем обработку при ошибке
|
||||||
} else if (data.type === 'done') {
|
} else if (data.type === 'done') {
|
||||||
this.currentNode = assistantNodeId;
|
this.currentNode = assistantNodeId;
|
||||||
this.plugin.eventBus.emit('graph-selected', {
|
this.plugin.eventBus.emit('graph-selected', {
|
||||||
|
|
@ -296,6 +320,24 @@ export class ChatView extends ItemView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Очищает содержимое сообщения в истории чата, помечая его как заглушку.
|
||||||
|
* Используется для подготовки к регенерации.
|
||||||
|
* @param {string} nodeId - ID узла, содержимое которого нужно очистить.
|
||||||
|
*/
|
||||||
|
clearStreamingMessage(nodeId: string): void {
|
||||||
|
const messageIndex = this.chatHistory.findIndex((msg) => msg.node_id === nodeId);
|
||||||
|
if (messageIndex !== -1) {
|
||||||
|
this.chatHistory[messageIndex].content = 'Получение ответа...';
|
||||||
|
this.chatHistory[messageIndex].type = 'llm'; // Убедимся, что тип узла LLM
|
||||||
|
// Если нужно, сбросьте другие флаги
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.chatPanel) {
|
||||||
|
this.chatPanel.updateHistory(this.chatHistory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateStreamingMessage(nodeId: string, content: string): void {
|
updateStreamingMessage(nodeId: string, content: string): void {
|
||||||
const lastMessage = this.chatHistory[this.chatHistory.length - 1];
|
const lastMessage = this.chatHistory[this.chatHistory.length - 1];
|
||||||
if (lastMessage && lastMessage.node_id === nodeId) {
|
if (lastMessage && lastMessage.node_id === nodeId) {
|
||||||
|
|
@ -319,6 +361,22 @@ export class ChatView extends ItemView {
|
||||||
if (lastMessage && lastMessage.node_id === nodeId) {
|
if (lastMessage && lastMessage.node_id === nodeId) {
|
||||||
lastMessage.type = 'error';
|
lastMessage.type = 'error';
|
||||||
lastMessage.content = errorMessage;
|
lastMessage.content = errorMessage;
|
||||||
|
} else {
|
||||||
|
// Если узел не последний, попробуйте найти его и обновить
|
||||||
|
const messageIndex = this.chatHistory.findIndex((msg) => msg.node_id === nodeId);
|
||||||
|
if (messageIndex !== -1) {
|
||||||
|
this.chatHistory[messageIndex].type = 'error';
|
||||||
|
this.chatHistory[messageIndex].content = errorMessage;
|
||||||
|
} else {
|
||||||
|
// Если узел не найден, добавьте новое сообщение об ошибке
|
||||||
|
this.chatHistory.push({
|
||||||
|
role: 'assistant',
|
||||||
|
content: errorMessage,
|
||||||
|
node_id: nodeId,
|
||||||
|
graph_id: this.selectedGraphId || '',
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.chatPanel) {
|
if (this.chatPanel) {
|
||||||
|
|
@ -326,7 +384,8 @@ export class ChatView extends ItemView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleRegenerateMessage(data: { graphId: string; nodeId: string }): Promise<void> {
|
async handleRegenerateMessage(data: { graphId: string; nodeId: string; model: string }): Promise<void> {
|
||||||
|
// НОВОЕ: Добавляем 'model' в тип данных
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/chat/regenerate`, {
|
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/chat/regenerate`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
@ -334,8 +393,7 @@ export class ChatView extends ItemView {
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
graph_id: data.graphId,
|
graph_id: data.graphId,
|
||||||
node_id: data.nodeId,
|
node_id: data.nodeId,
|
||||||
system_prompt: this.plugin.settings.systemPrompt,
|
model: data.model // НОВОЕ: Передаем выбранную модель для регенерации
|
||||||
model: this.plugin.settings.defaultModel
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -344,16 +402,31 @@ export class ChatView extends ItemView {
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
const newNodeId = result.new_node_id;
|
// НОВОЕ: Получаем ID существующего узла ассистента и ID пользовательского узла
|
||||||
const parentNodeId = result.parent_node_id;
|
const existingAssistantNodeId = result.existing_assistant_node_id;
|
||||||
|
const userNodeId = result.user_node_id;
|
||||||
|
const modelToUse = result.model;
|
||||||
|
|
||||||
// Запускаем стриминг для нового узла
|
// Запускаем стриминг, передавая ID существующего узла для повторного использования
|
||||||
if (data.graphId) {
|
if (data.graphId) {
|
||||||
await this.streamLLMResponse(data.graphId, parentNodeId, this.plugin.settings.defaultModel);
|
await this.streamLLMResponse(data.graphId, userNodeId, modelToUse, existingAssistantNodeId);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Ошибка регенерации:', error);
|
console.error('Ошибка регенерации:', error);
|
||||||
new Notice(`Ошибка регенерации: ${error?.message || error}`, 5000);
|
new Notice(`Ошибка регенерации: ${error?.message || error}`, 5000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------- WebSocket Event Handlers ---------------------------------------------------------------
|
||||||
|
|
||||||
|
handleNodeTypeChanged(data: { graphId: string; nodeId: string; nodeType: string }): void {
|
||||||
|
// Обновляем тип узла в истории чата
|
||||||
|
const messageIndex = this.chatHistory.findIndex((msg) => msg.node_id === data.nodeId);
|
||||||
|
if (messageIndex !== -1) {
|
||||||
|
this.chatHistory[messageIndex].type = data.nodeType;
|
||||||
|
if (this.chatPanel) {
|
||||||
|
this.chatPanel.updateHistory(this.chatHistory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,7 @@ export class GraphView extends ItemView {
|
||||||
// WebSocket Event Handlers
|
// WebSocket Event Handlers
|
||||||
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));
|
||||||
|
|
||||||
// Загружаем список графов
|
// Загружаем список графов
|
||||||
await this.fetchGraphsList();
|
await this.fetchGraphsList();
|
||||||
|
|
@ -151,6 +152,7 @@ export class GraphView extends ItemView {
|
||||||
// WebSocket Event Cleanup
|
// WebSocket Event Cleanup
|
||||||
this.plugin.eventBus.off('ws-graph-title-updated', this.handleGraphTitleUpdated);
|
this.plugin.eventBus.off('ws-graph-title-updated', this.handleGraphTitleUpdated);
|
||||||
this.plugin.eventBus.off('ws-node-title-updated', this.handleNodeTitleUpdated);
|
this.plugin.eventBus.off('ws-node-title-updated', this.handleNodeTitleUpdated);
|
||||||
|
this.plugin.eventBus.off('ws-node-type-changed', this.handleNodeTypeChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------- Custom Title Update ---------------------------------------------------
|
// ----------------------------------------------- Custom Title Update ---------------------------------------------------
|
||||||
|
|
@ -535,6 +537,27 @@ export class GraphView extends ItemView {
|
||||||
|
|
||||||
// ----------------------------------------------- WebSocket Event Handlers ---------------------------------------------------------------
|
// ----------------------------------------------- WebSocket Event Handlers ---------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обработчик изменения типа узла через WebSocket
|
||||||
|
*/
|
||||||
|
handleNodeTypeChanged(data: { graphId: string; nodeId: string; nodeType: string }): void {
|
||||||
|
// Обновляем только если это узел текущего графа
|
||||||
|
if (this.selectedGraphId !== data.graphId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Находим и обновляем тип узла в graphData
|
||||||
|
const nodeIndex = this.graphData.nodes.findIndex((n) => n.id === data.nodeId);
|
||||||
|
if (nodeIndex !== -1) {
|
||||||
|
this.graphData.nodes[nodeIndex].type = data.nodeType;
|
||||||
|
|
||||||
|
// Перерисовываем граф с обновленным типом узла
|
||||||
|
this.renderGraphPanel();
|
||||||
|
|
||||||
|
console.log(`✅ Обновлен тип узла ${data.nodeId}: "${data.nodeType}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Обработчик обновления заголовка графа через WebSocket
|
* Обработчик обновления заголовка графа через WebSocket
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user