Related to title generation
This commit is contained in:
parent
32f55ab086
commit
d65996539a
|
|
@ -37,8 +37,12 @@ import { Handle, Position } from 'reactflow';
|
||||||
// Простой компонент для узла типа 'user'
|
// Простой компонент для узла типа 'user'
|
||||||
// Это базовый пример. Ты можешь стилизовать его по своему усмотрению.
|
// Это базовый пример. Ты можешь стилизовать его по своему усмотрению.
|
||||||
const UserNode: React.FC<any> = ({ data, isConnectable }) => {
|
const UserNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||||
|
const displayLabel = data.title || data.label || 'Пользователь';
|
||||||
|
const titleGenerated = data.title_generated;
|
||||||
|
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
React.Fragment,
|
React.Fragment,
|
||||||
|
// className: `custom-node user-node ${titleGenerated ? 'title-generated' : 'title-pending'}`,
|
||||||
null, // Используем React.Fragment для возврата нескольких элементов без лишнего div
|
null, // Используем React.Fragment для возврата нескольких элементов без лишнего div
|
||||||
React.createElement(Handle, {
|
React.createElement(Handle, {
|
||||||
type: 'source',
|
type: 'source',
|
||||||
|
|
@ -50,12 +54,15 @@ const UserNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||||
position: Position.Top,
|
position: Position.Top,
|
||||||
isConnectable: isConnectable
|
isConnectable: isConnectable
|
||||||
}),
|
}),
|
||||||
data.label
|
displayLabel
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Простой компонент для узла типа 'llm'
|
// Простой компонент для узла типа 'llm'
|
||||||
const LLMNode: React.FC<any> = ({ data, isConnectable }) => {
|
const LLMNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||||
|
const displayLabel = data.title || data.label || 'LLM';
|
||||||
|
const titleGenerated = data.title_generated;
|
||||||
|
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
React.Fragment,
|
React.Fragment,
|
||||||
null,
|
null,
|
||||||
|
|
@ -69,12 +76,15 @@ const LLMNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||||
position: Position.Top,
|
position: Position.Top,
|
||||||
isConnectable: isConnectable
|
isConnectable: isConnectable
|
||||||
}),
|
}),
|
||||||
data.label
|
displayLabel
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Простой компонент для узла типа 'tool'
|
// Простой компонент для узла типа 'tool'
|
||||||
const ToolNode: React.FC<any> = ({ data, isConnectable }) => {
|
const ToolNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||||
|
const displayLabel = data.title || data.label || 'Инструмент';
|
||||||
|
const titleGenerated = data.title_generated;
|
||||||
|
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
React.Fragment,
|
React.Fragment,
|
||||||
null,
|
null,
|
||||||
|
|
@ -88,12 +98,15 @@ const ToolNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||||
position: Position.Top,
|
position: Position.Top,
|
||||||
isConnectable: isConnectable
|
isConnectable: isConnectable
|
||||||
}),
|
}),
|
||||||
data.label
|
displayLabel
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Простой компонент для узла типа 'error'
|
// Простой компонент для узла типа 'error'
|
||||||
const ErrorNode: React.FC<any> = ({ data, isConnectable }) => {
|
const ErrorNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||||
|
const displayLabel = data.title || data.label || 'Ошибка';
|
||||||
|
const titleGenerated = data.title_generated;
|
||||||
|
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
React.Fragment,
|
React.Fragment,
|
||||||
null,
|
null,
|
||||||
|
|
@ -107,7 +120,7 @@ const ErrorNode: React.FC<any> = ({ data, isConnectable }) => {
|
||||||
position: Position.Top,
|
position: Position.Top,
|
||||||
isConnectable: isConnectable
|
isConnectable: isConnectable
|
||||||
}),
|
}),
|
||||||
data.label
|
displayLabel
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -137,8 +150,8 @@ const dagreGraph = new dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
|
||||||
|
|
||||||
// Определяем размеры узлов для корректного расположения.
|
// Определяем размеры узлов для корректного расположения.
|
||||||
// Может потребоваться корректировка в зависимости от реального размера узла в CSS.
|
// Может потребоваться корректировка в зависимости от реального размера узла в CSS.
|
||||||
const nodeWidth = 120;
|
const nodeWidth = 120;
|
||||||
const nodeHeight = 40;
|
const nodeHeight = 40;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Применяет алгоритм расположения Dagre к узлам и рёбрам графа.
|
* Применяет алгоритм расположения Dagre к узлам и рёбрам графа.
|
||||||
|
|
@ -150,13 +163,13 @@ const nodeHeight = 40;
|
||||||
const getLayoutedElements = (nodes: any[], edges: any[], direction: 'TB' | 'LR' = 'TB') => {
|
const getLayoutedElements = (nodes: any[], edges: any[], direction: 'TB' | 'LR' = 'TB') => {
|
||||||
const isHorizontal = direction === 'LR';
|
const isHorizontal = direction === 'LR';
|
||||||
// Компактные настройки для расположения в 2 ряда
|
// Компактные настройки для расположения в 2 ряда
|
||||||
dagreGraph.setGraph({
|
dagreGraph.setGraph({
|
||||||
rankdir: direction,
|
rankdir: direction,
|
||||||
ranksep: 40, // Уменьшенное расстояние между рангами (рядами)
|
ranksep: 40, // Уменьшенное расстояние между рангами (рядами)
|
||||||
nodesep: 10, // Уменьшенное расстояние между узлами в одном ранге
|
nodesep: 10, // Уменьшенное расстояние между узлами в одном ранге
|
||||||
edgesep: 20, // Расстояние между рёбрами
|
edgesep: 20, // Расстояние между рёбрами
|
||||||
marginx: 20, // Отступы по X
|
marginx: 20, // Отступы по X
|
||||||
marginy: 20 // Отступы по Y
|
marginy: 20 // Отступы по Y
|
||||||
});
|
});
|
||||||
|
|
||||||
// Добавляем узлы в граф Dagre с динамическими размерами
|
// Добавляем узлы в граф Dagre с динамическими размерами
|
||||||
|
|
@ -164,10 +177,10 @@ const getLayoutedElements = (nodes: any[], edges: any[], direction: 'TB' | 'LR'
|
||||||
// Пытаемся найти элемент узла в DOM для получения реальных размеров
|
// Пытаемся найти элемент узла в DOM для получения реальных размеров
|
||||||
const nodeElement = document.querySelector(`[data-id="${node.id}"]`) as HTMLElement;
|
const nodeElement = document.querySelector(`[data-id="${node.id}"]`) as HTMLElement;
|
||||||
const dimensions = getNodeDimensions(nodeElement);
|
const dimensions = getNodeDimensions(nodeElement);
|
||||||
|
|
||||||
dagreGraph.setNode(node.id, {
|
dagreGraph.setNode(node.id, {
|
||||||
width: Math.min(dimensions.width + 10, 140), // Ограничиваем максимальную ширину
|
width: Math.min(dimensions.width + 10, 140), // Ограничиваем максимальную ширину
|
||||||
height: Math.min(dimensions.height + 10, 50) // Ограничиваем максимальную высоту
|
height: Math.min(dimensions.height + 10, 50) // Ограничиваем максимальную высоту
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -184,7 +197,7 @@ const getLayoutedElements = (nodes: any[], edges: any[], direction: 'TB' | 'LR'
|
||||||
const newNodes = nodes.map((node) => {
|
const newNodes = nodes.map((node) => {
|
||||||
const nodeWithPosition: DagreNode = dagreGraph.node(node.id);
|
const nodeWithPosition: DagreNode = dagreGraph.node(node.id);
|
||||||
const nodeDimensions = dagreGraph.node(node.id);
|
const nodeDimensions = dagreGraph.node(node.id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...node,
|
...node,
|
||||||
targetPosition: isHorizontal ? 'left' : 'top',
|
targetPosition: isHorizontal ? 'left' : 'top',
|
||||||
|
|
@ -204,11 +217,11 @@ const getNodeDimensions = (nodeElement: HTMLElement | null): { width: number; he
|
||||||
if (!nodeElement) {
|
if (!nodeElement) {
|
||||||
return { width: 120, height: 40 }; // Fallback размеры
|
return { width: 120, height: 40 }; // Fallback размеры
|
||||||
}
|
}
|
||||||
|
|
||||||
const rect = nodeElement.getBoundingClientRect();
|
const rect = nodeElement.getBoundingClientRect();
|
||||||
return {
|
return {
|
||||||
width: Math.max(Math.min(rect.width, 130), 100), // Ограничиваем ширину: мин 100px, макс 130px
|
width: Math.max(Math.min(rect.width, 130), 100), // Ограничиваем ширину: мин 100px, макс 130px
|
||||||
height: Math.max(Math.min(rect.height, 45), 30) // Ограничиваем высоту: мин 30px, макс 45px
|
height: Math.max(Math.min(rect.height, 45), 30) // Ограничиваем высоту: мин 30px, макс 45px
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -242,10 +255,14 @@ export const GraphPanelComponent: React.FC<GraphPanelProps> = ({ graphData, onNo
|
||||||
const initialNodesForLayout = graphData.nodes.map((node) => ({
|
const initialNodesForLayout = graphData.nodes.map((node) => ({
|
||||||
id: node.id,
|
id: node.id,
|
||||||
type: node.type || 'default',
|
type: node.type || 'default',
|
||||||
data: { label: node.data?.label || `Node ${node.id}` },
|
data: {
|
||||||
|
label: node.data?.label || `Node ${node.id}`,
|
||||||
|
title: node.data?.title,
|
||||||
|
title_generated: node.data?.title_generated || false
|
||||||
|
},
|
||||||
position: { x: 0, y: 0 }
|
position: { x: 0, y: 0 }
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const initialEdgesForLayout = graphData.edges.map((edge) => ({
|
const initialEdgesForLayout = graphData.edges.map((edge) => ({
|
||||||
id: edge.id,
|
id: edge.id,
|
||||||
source: edge.source,
|
source: edge.source,
|
||||||
|
|
@ -259,10 +276,7 @@ export const GraphPanelComponent: React.FC<GraphPanelProps> = ({ graphData, onNo
|
||||||
|
|
||||||
// Применяем layout с задержкой, чтобы DOM успел отрендерить узлы
|
// Применяем layout с задержкой, чтобы DOM успел отрендерить узлы
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(
|
const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(initialNodesForLayout, initialEdgesForLayout);
|
||||||
initialNodesForLayout,
|
|
||||||
initialEdgesForLayout
|
|
||||||
);
|
|
||||||
setNodes(layoutedNodes);
|
setNodes(layoutedNodes);
|
||||||
setEdges(layoutedEdges);
|
setEdges(layoutedEdges);
|
||||||
}, 100); // Задержка 100ms для рендеринга DOM
|
}, 100); // Задержка 100ms для рендеринга DOM
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export class HistoryPanel {
|
||||||
container: HTMLElement,
|
container: HTMLElement,
|
||||||
props: {
|
props: {
|
||||||
graphs: any[];
|
graphs: any[];
|
||||||
eventBus: EventBus;
|
eventBus: EventBus;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
|
|
@ -57,7 +57,7 @@ export class HistoryPanel {
|
||||||
|
|
||||||
setupEventListeners(): void {
|
setupEventListeners(): void {
|
||||||
this.newChatButton.addEventListener('click', () => {
|
this.newChatButton.addEventListener('click', () => {
|
||||||
this.props.eventBus.emit('new-chat');
|
this.props.eventBus.emit('new-chat');
|
||||||
});
|
});
|
||||||
|
|
||||||
this.deleteButton.addEventListener('click', () => {
|
this.deleteButton.addEventListener('click', () => {
|
||||||
|
|
@ -76,13 +76,16 @@ export class HistoryPanel {
|
||||||
|
|
||||||
renderGraphs(): void {
|
renderGraphs(): void {
|
||||||
this.chatList.innerHTML = this.graphs
|
this.chatList.innerHTML = this.graphs
|
||||||
.map(
|
.map((graph) => {
|
||||||
(graph) => `
|
const titleClass = graph.title_generated ? 'graph-title-generated' : 'graph-title-pending';
|
||||||
|
const displayTitle = graph.title || graph.first_message || `Граф ${graph.id}`;
|
||||||
|
|
||||||
|
return `
|
||||||
<div class="chat-list-item" data-graph-id="${graph.id}">
|
<div class="chat-list-item" data-graph-id="${graph.id}">
|
||||||
<div class="chat-item-content">
|
<div class="chat-item-content">
|
||||||
<div class="chat-item-text">
|
<div class="chat-item-text">
|
||||||
<span class="truncate">
|
<span class="truncate ${titleClass}">
|
||||||
${graph.first_message || `Граф ${graph.id}`}
|
${displayTitle}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="chat-item-actions">
|
<div class="chat-item-actions">
|
||||||
|
|
@ -92,8 +95,8 @@ export class HistoryPanel {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`;
|
||||||
)
|
})
|
||||||
.join('');
|
.join('');
|
||||||
|
|
||||||
// Добавляем обработчики событий
|
// Добавляем обработчики событий
|
||||||
|
|
@ -102,7 +105,7 @@ export class HistoryPanel {
|
||||||
if (!(e.target as HTMLElement).classList.contains('chat-item-options-button')) {
|
if (!(e.target as HTMLElement).classList.contains('chat-item-options-button')) {
|
||||||
const graphId = (item as HTMLElement).dataset.graphId;
|
const graphId = (item as HTMLElement).dataset.graphId;
|
||||||
if (graphId) {
|
if (graphId) {
|
||||||
this.props.eventBus.emit('graph-selected', { graphId: graphId, currentNode: null });
|
this.props.eventBus.emit('graph-selected', { graphId: graphId, currentNode: null });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ export class ChatView extends ItemView {
|
||||||
chatContainer: HTMLDivElement;
|
chatContainer: HTMLDivElement;
|
||||||
chatPanel: ChatPanel | null;
|
chatPanel: ChatPanel | null;
|
||||||
availableModels: string[];
|
availableModels: string[];
|
||||||
|
// TODO: сделать аккуратнее - добавлен таймер для обновления заголовков
|
||||||
|
titleUpdateTimer: NodeJS.Timeout | null;
|
||||||
|
|
||||||
constructor(leaf: WorkspaceLeaf, plugin: LLMAgentPlugin) {
|
constructor(leaf: WorkspaceLeaf, plugin: LLMAgentPlugin) {
|
||||||
super(leaf);
|
super(leaf);
|
||||||
|
|
@ -28,6 +30,7 @@ export class ChatView extends ItemView {
|
||||||
this.currentNode = null;
|
this.currentNode = null;
|
||||||
this.chatPanel = null;
|
this.chatPanel = null;
|
||||||
this.availableModels = [];
|
this.availableModels = [];
|
||||||
|
this.titleUpdateTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewType(): string {
|
getViewType(): string {
|
||||||
|
|
@ -174,6 +177,18 @@ export class ChatView extends ItemView {
|
||||||
// Вместо того, чтобы отправлять частичные данные, мы запрашиваем полный граф у бэкенда.
|
// Вместо того, чтобы отправлять частичные данные, мы запрашиваем полный граф у бэкенда.
|
||||||
this.plugin.eventBus.emit('graph-selected', { graphId: this.selectedGraphId, currentNode: this.currentNode });
|
this.plugin.eventBus.emit('graph-selected', { graphId: this.selectedGraphId, currentNode: this.currentNode });
|
||||||
|
|
||||||
|
// TODO: сделать аккуратнее - устанавливаем таймер на 30 секунд для обновления заголовков
|
||||||
|
this.titleUpdateTimer = setTimeout(() => {
|
||||||
|
console.log('Обновление графа для получения сгенерированных заголовков...');
|
||||||
|
if (this.selectedGraphId && this.currentNode) {
|
||||||
|
this.plugin.eventBus.emit('graph-selected', {
|
||||||
|
graphId: this.selectedGraphId,
|
||||||
|
currentNode: this.currentNode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.titleUpdateTimer = null;
|
||||||
|
}, 20000); // 20 секунд, эмпирически
|
||||||
|
|
||||||
// Затем обновим ChatView, загрузив историю до нового current_node_id
|
// Затем обновим ChatView, загрузив историю до нового current_node_id
|
||||||
if (newCurrentNodeId) {
|
if (newCurrentNodeId) {
|
||||||
await this.fetchMessagesFromRootToNode(newCurrentNodeId);
|
await this.fetchMessagesFromRootToNode(newCurrentNodeId);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user