156 lines
5.0 KiB
TypeScript
156 lines
5.0 KiB
TypeScript
/*
|
||
* Управляет и отображает историю графов чата. Предоставляет функционал для выбора
|
||
* прошлых разговоров, начала новых чатов и удаления записей графов.
|
||
*/
|
||
|
||
import { EventBus } from '../utils/EventBus';
|
||
|
||
export class HistoryPanel {
|
||
container: HTMLElement;
|
||
props: {
|
||
graphs: any[];
|
||
onGraphSelect: (data: { graphId: string; currentNode: string | null }) => void;
|
||
eventBus: EventBus;
|
||
};
|
||
graphs: any[];
|
||
newChatButton: HTMLButtonElement;
|
||
chatList: HTMLElement;
|
||
contextMenu: HTMLElement;
|
||
deleteButton: HTMLButtonElement;
|
||
contextMenuGraphId: string | null;
|
||
|
||
constructor(
|
||
container: HTMLElement,
|
||
props: {
|
||
graphs: any[];
|
||
onGraphSelect: (data: { graphId: string; currentNode: string | null }) => void;
|
||
eventBus: EventBus;
|
||
}
|
||
) {
|
||
this.container = container;
|
||
this.props = props;
|
||
this.graphs = props.graphs || [];
|
||
|
||
this.init();
|
||
}
|
||
|
||
init(): void {
|
||
this.container.innerHTML = `
|
||
<div class="llm-agent-history-panel">
|
||
<div class="history-header">
|
||
<h3>История диалогов</h3>
|
||
<button id="new-chat-button" class="new-chat-button">+ Новый чат</button>
|
||
</div>
|
||
<div class="chat-list" id="chat-list"></div>
|
||
<div class="context-menu" id="context-menu" style="display: none;">
|
||
<button id="delete-graph-button">Удалить</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
this.newChatButton = this.container.querySelector('#new-chat-button') as HTMLButtonElement;
|
||
this.chatList = this.container.querySelector('#chat-list') as HTMLElement;
|
||
this.contextMenu = this.container.querySelector('#context-menu') as HTMLElement;
|
||
this.deleteButton = this.container.querySelector('#delete-graph-button') as HTMLButtonElement;
|
||
|
||
this.setupEventListeners();
|
||
this.renderGraphs();
|
||
}
|
||
|
||
setupEventListeners(): void {
|
||
this.newChatButton.addEventListener('click', () => {
|
||
this.props.eventBus.emit('new-chat');
|
||
});
|
||
|
||
this.deleteButton.addEventListener('click', () => {
|
||
if (this.contextMenuGraphId) {
|
||
this.handleDeleteGraph(this.contextMenuGraphId);
|
||
}
|
||
});
|
||
|
||
// Закрытие контекстного меню при клике вне его
|
||
document.addEventListener('click', (e) => {
|
||
if (!this.contextMenu.contains(e.target as Node)) {
|
||
this.contextMenu.style.display = 'none';
|
||
}
|
||
});
|
||
}
|
||
|
||
renderGraphs(): void {
|
||
this.chatList.innerHTML = this.graphs
|
||
.map(
|
||
(graph) => `
|
||
<div class="chat-list-item" data-graph-id="${graph.id}">
|
||
<div class="chat-item-content">
|
||
<div class="chat-item-text">
|
||
<span class="truncate">
|
||
${graph.first_message || `Граф ${graph.id}`}
|
||
</span>
|
||
</div>
|
||
<div class="chat-item-actions">
|
||
<button class="chat-item-options-button" data-graph-id="${graph.id}">
|
||
⋮
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`
|
||
)
|
||
.join('');
|
||
|
||
// Добавляем обработчики событий
|
||
this.chatList.querySelectorAll('.chat-list-item').forEach((item) => {
|
||
item.addEventListener('click', (e) => {
|
||
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: graphId, currentNode: null });
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
this.chatList.querySelectorAll('.chat-item-options-button').forEach((button) => {
|
||
button.addEventListener('click', (e) => {
|
||
e.stopPropagation();
|
||
this.showContextMenu(e, (button as HTMLElement).dataset.graphId || '');
|
||
});
|
||
});
|
||
}
|
||
|
||
showContextMenu(event: Event, graphId: string): void {
|
||
this.contextMenuGraphId = graphId;
|
||
this.contextMenu.style.display = 'block';
|
||
this.contextMenu.style.left = `${(event as MouseEvent).clientX}px`;
|
||
this.contextMenu.style.top = `${(event as MouseEvent).clientY}px`;
|
||
}
|
||
|
||
async handleDeleteGraph(graphId: string): Promise<void> {
|
||
try {
|
||
const response = await fetch(`http://localhost:5000/api/graphs/${graphId}`, {
|
||
method: 'DELETE'
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ошибка! статус: ${response.status}`);
|
||
}
|
||
|
||
// Обновляем список графов
|
||
this.graphs = this.graphs.filter((graph) => graph.id !== graphId);
|
||
this.renderGraphs();
|
||
this.contextMenu.style.display = 'none';
|
||
} catch (error) {
|
||
console.error('Ошибка удаления графа:', error);
|
||
}
|
||
}
|
||
|
||
updateGraphs(newGraphs: any[]): void {
|
||
this.graphs = newGraphs;
|
||
this.renderGraphs();
|
||
}
|
||
|
||
destroy(): void {
|
||
this.container.innerHTML = '';
|
||
}
|
||
}
|