llm-agent-plugin/src/components/ChatPanel.ts

153 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Управляет пользовательским интерфейсом чата, включая ввод сообщений,
* отображение истории и подсказки команд для взаимодействия с пользователем.
*/
import { ChatHistoryItem } from './models/ChatHistoryItem';
export class ChatPanel {
container: HTMLElement;
props: {
chatHistory: ChatHistoryItem[];
onSendMessage: (message: string) => void;
};
chatHistory: ChatHistoryItem[];
messageInput: HTMLInputElement;
sendButton: HTMLButtonElement;
suggestionsContainer: HTMLDivElement;
constructor(
container: HTMLElement,
props: {
chatHistory: ChatHistoryItem[];
onSendMessage: (message: string) => void;
}
) {
this.container = container;
this.props = props;
this.chatHistory = props.chatHistory || [];
this.init();
}
init(): void {
this.container.innerHTML = `
<div class="llm-agent-chat-panel">
<div class="chat-history" id="chat-history"></div>
<div class="chat-input-form">
<div class="command-input">
<input type="text" id="message-input" placeholder="Введите сообщение или команду (/)..." />
<div class="suggestions" id="suggestions" style="display: none;"></div>
</div>
<button type="button" id="send-button">Отправить</button>
</div>
</div>
`;
this.messageInput = this.container.querySelector('#message-input') as HTMLInputElement;
this.sendButton = this.container.querySelector('#send-button') as HTMLButtonElement;
this.suggestionsContainer = this.container.querySelector('#suggestions') as HTMLDivElement;
this.setupEventListeners();
this.renderHistory();
}
setupEventListeners(): void {
this.sendButton.addEventListener('click', () => this.handleSend());
this.messageInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
if (this.suggestionsContainer.style.display === 'block') {
const firstSuggestion = this.suggestionsContainer.querySelector('.suggestion-item');
if (firstSuggestion) {
this.messageInput.value = firstSuggestion.textContent || '';
this.hideSuggestions();
e.preventDefault();
return;
}
}
this.handleSend();
e.preventDefault();
}
});
this.messageInput.addEventListener('input', (e) => {
this.handleInput((e.target as HTMLInputElement).value);
});
}
handleInput(value: string): void {
const commands = ['/help', '/imagine', '/analyze', '/subtitles_meet', '/subtitles_teams', '/summarize', '/chat'];
if (value.startsWith('/')) {
const filteredCommands = commands.filter((cmd) => cmd.startsWith(value.toLowerCase()));
this.showSuggestions(filteredCommands);
} else {
this.hideSuggestions();
}
}
showSuggestions(suggestions: string[]): void {
if (suggestions.length === 0) {
this.hideSuggestions();
return;
}
this.suggestionsContainer.innerHTML = suggestions.map((suggestion) => `<div class="suggestion-item">${suggestion}</div>`).join('');
this.suggestionsContainer.style.display = 'block';
// Добавляем обработчики кликов на предложения
this.suggestionsContainer.querySelectorAll('.suggestion-item').forEach((item) => {
item.addEventListener('click', () => {
this.messageInput.value = item.textContent || '';
this.hideSuggestions();
this.messageInput.focus();
});
});
}
hideSuggestions(): void {
this.suggestionsContainer.style.display = 'none';
}
handleSend(): void {
const message = this.messageInput.value.trim();
if (message && this.props.onSendMessage) {
this.props.onSendMessage(message);
this.messageInput.value = '';
this.hideSuggestions();
}
}
renderHistory(): void {
const historyContainer = this.container.querySelector('#chat-history') as HTMLElement;
historyContainer.innerHTML = this.chatHistory
.map(
(msg) => `
<div class="message ${msg.role} message-type-${msg.type || 'default'}">
<div class="message-content">${this.processMessageContent(msg.content)}</div>
</div>
`
)
.join('');
// Прокручиваем к концу
historyContainer.scrollTop = historyContainer.scrollHeight;
}
processMessageContent(content: string): string {
// Простейшая обработка Markdown для изображений
return content.replace(/<img([^>]*?)>/g, '<img$1 style="max-width: 100%; height: auto;">').replace(/\n/g, '<br>');
}
updateHistory(newHistory: ChatHistoryItem[]): void {
this.chatHistory = newHistory;
this.renderHistory();
}
destroy(): void {
this.container.innerHTML = '';
}
}