153 lines
4.8 KiB
TypeScript
153 lines
4.8 KiB
TypeScript
/*
|
||
* Управляет пользовательским интерфейсом чата, включая ввод сообщений,
|
||
* отображение истории и подсказки команд для взаимодействия с пользователем.
|
||
*/
|
||
|
||
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 = '';
|
||
}
|
||
}
|