Files
crm.clientright.ru/ai_drawer_backup_working/ai-drawer-simple.js.working

610 lines
25 KiB
Plaintext
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.

class AIDrawer {
constructor() {
this.isOpen = false;
this.fontSize = 'normal';
this.avatarType = 'default';
this.sessionId = null;
this.init();
// Загружаем историю сразу при инициализации (при загрузке страницы)
// чтобы когда пользователь откроет drawer - история уже была готова
setTimeout(() => {
this.preloadChatHistory();
}, 2000);
}
init() {
console.log('AI Drawer: Простая инициализация начата');
// Создаем простой HTML без inline стилей
const drawerHTML =
'<button class="ai-drawer-toggle">AI</button>' +
'<div class="ai-drawer font-normal">' +
'<div class="ai-drawer-header">' +
'<span>AI Ассистент</span>' +
'<button class="ai-drawer-close">&times;</button>' +
'</div>' +
'<div class="ai-font-controls">' +
'<label>Размер шрифта:</label>' +
'<button class="font-btn" data-size="small">Мелкий</button>' +
'<button class="font-btn active" data-size="normal">Обычный</button>' +
'<button class="font-btn" data-size="large">Крупный</button>' +
'<button class="font-btn" data-size="extra-large">Очень крупный</button>' +
'</div>' +
'<div class="ai-avatar-controls">' +
'<label>Аватарка ассистента:</label>' +
'<button class="avatar-btn active" data-type="default">🤖</button>' +
'<button class="avatar-btn" data-type="friendly">😊</button>' +
'<button class="avatar-btn" data-type="helpful">💡</button>' +
'<button class="avatar-btn" data-type="smart">🧠</button>' +
'</div>' +
'<div class="ai-drawer-content">' +
'<div class="ai-chat-messages">' +
'<div class="ai-message assistant">' +
'<div class="ai-avatar assistant"></div>' +
'<div class="ai-message-content">' +
'<p>Привет! Я ваш AI ассистент. Чем могу помочь?</p>' +
'<div class="ai-message-time">' + new Date().toLocaleTimeString() + '</div>' +
'</div>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="ai-chat-input-container">' +
'<input type="text" id="ai-chat-input" placeholder="Введите сообщение..." class="ai-chat-input">' +
'<button id="ai-send-button" class="ai-send-button">Отправить</button>' +
'</div>' +
'</div>' +
'<div class="ai-loading-overlay">' +
'<div class="ai-loading-spinner"></div>' +
'<div>Обрабатываю запрос...</div>' +
'</div>';
// Добавляем в DOM
document.body.insertAdjacentHTML('beforeend', drawerHTML);
console.log('AI Drawer: HTML добавлен в DOM');
// Находим элементы
this.drawer = document.querySelector('.ai-drawer');
this.toggleBtn = document.querySelector('.ai-drawer-toggle');
this.closeBtn = document.querySelector('.ai-drawer-close');
this.loadingOverlay = document.querySelector('.ai-loading-overlay');
this.fontButtons = document.querySelectorAll('.font-btn');
this.avatarButtons = document.querySelectorAll('.avatar-btn');
this.chatInput = document.querySelector('#ai-chat-input');
this.sendButton = document.querySelector('#ai-send-button');
// Обработчики событий
if (this.toggleBtn) {
this.toggleBtn.onclick = () => this.toggle();
}
if (this.closeBtn) {
this.closeBtn.onclick = () => this.close();
}
// Обработчики для кнопок управления шрифтом
this.fontButtons.forEach(button => {
button.onclick = () => {
const size = button.dataset.size;
this.setFontSize(size);
this.fontButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
};
});
// Обработчики для кнопок управления аватаркой
this.avatarButtons.forEach(button => {
button.onclick = () => {
const type = button.dataset.type;
this.setAvatarType(type);
this.avatarButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
};
});
// Обработчики для поля ввода
if (this.sendButton && this.chatInput) {
this.sendButton.onclick = () => this.sendMessage();
this.chatInput.onkeypress = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.sendMessage();
}
};
}
// Восстанавливаем настройки
this.restoreSettings();
console.log('AI Drawer: Простая инициализация завершена');
}
toggle() {
if (this.isOpen) {
this.close();
} else {
this.open();
}
}
open() {
console.log('AI Drawer: Opening drawer');
if (this.drawer) {
this.drawer.classList.add('open');
}
document.body.classList.add('ai-drawer-open');
this.isOpen = true;
// История уже загружена при инициализации страницы
// Не нужно дополнительных запросов при открытии
}
close() {
console.log('AI Drawer: Closing drawer');
if (this.drawer) {
this.drawer.classList.remove('open');
}
document.body.classList.remove('ai-drawer-open');
this.isOpen = false;
}
setFontSize(size) {
if (this.drawer) {
this.drawer.classList.remove('font-small', 'font-normal', 'font-large', 'font-extra-large');
this.drawer.classList.add('font-' + size);
}
this.fontSize = size;
localStorage.setItem('ai-drawer-font-size', size);
}
setAvatarType(type) {
this.avatarType = type;
// Обновляем существующие аватарки ассистента
const existingAvatars = this.drawer.querySelectorAll('.ai-avatar.assistant');
existingAvatars.forEach(avatar => {
avatar.classList.remove('friendly', 'helpful', 'smart');
if (type !== 'default') {
avatar.classList.add(type);
}
});
localStorage.setItem('ai-drawer-avatar-type', type);
}
showLoading(message = 'Обрабатываю запрос...') {
if (this.loadingOverlay) {
const textElement = this.loadingOverlay.querySelector('div:last-child');
if (textElement) {
textElement.textContent = message;
}
this.loadingOverlay.classList.add('show');
}
}
hideLoading() {
if (this.loadingOverlay) {
this.loadingOverlay.classList.remove('show');
}
}
addMessage(text, isUser = false, customTime = null) {
console.log('AI Drawer: addMessage called with:', {text: text.substring(0, 50), isUser, customTime});
// Ищем контейнер сообщений - может быть .ai-chat-messages или .ai-drawer-content
const chatMessages = this.drawer.querySelector('.ai-chat-messages');
const drawerContent = this.drawer.querySelector('.ai-drawer-content');
const content = chatMessages || drawerContent;
console.log('AI Drawer: Container search results:', {
chatMessages: !!chatMessages,
drawerContent: !!drawerContent,
selectedContent: !!content,
drawerExists: !!this.drawer
});
if (content) {
const messageDiv = document.createElement('div');
messageDiv.className = `ai-message ${isUser ? 'user' : 'assistant'}`;
// Создаем аватарку
const avatarDiv = document.createElement('div');
let avatarClass = `ai-avatar ${isUser ? 'user' : 'assistant'}`;
if (!isUser && this.avatarType !== 'default') {
avatarClass += ` ${this.avatarType}`;
}
avatarDiv.className = avatarClass;
if (isUser) {
avatarDiv.textContent = '👤';
}
// Создаем контейнер для контента
const contentDiv = document.createElement('div');
contentDiv.className = 'ai-message-content';
const textDiv = document.createElement('p');
textDiv.textContent = text;
contentDiv.appendChild(textDiv);
const timeDiv = document.createElement('div');
timeDiv.className = 'ai-message-time';
if (customTime) {
// Если передано время из истории, используем его
const historyTime = new Date(customTime);
timeDiv.textContent = historyTime.toLocaleTimeString();
} else {
timeDiv.textContent = new Date().toLocaleTimeString();
}
contentDiv.appendChild(timeDiv);
messageDiv.appendChild(avatarDiv);
messageDiv.appendChild(contentDiv);
content.appendChild(messageDiv);
content.scrollTop = content.scrollHeight;
console.log('AI Drawer: Message successfully added to DOM');
// Сохранение происходит автоматически в n8n, поэтому не дублируем
} else {
console.error('AI Drawer: Content container not found! Cannot add message.');
}
}
addStreamingMessage(text, isUser = false, speed = 30) {
// Ищем контейнер сообщений - может быть .ai-chat-messages или .ai-drawer-content
const content = this.drawer.querySelector('.ai-chat-messages') || this.drawer.querySelector('.ai-drawer-content');
if (content) {
const messageDiv = document.createElement('div');
messageDiv.className = `ai-message ${isUser ? 'user' : 'assistant'}`;
// Создаем аватарку
const avatarDiv = document.createElement('div');
let avatarClass = `ai-avatar ${isUser ? 'user' : 'assistant'}`;
if (!isUser && this.avatarType !== 'default') {
avatarClass += ` ${this.avatarType}`;
}
avatarDiv.className = avatarClass;
if (isUser) {
avatarDiv.textContent = '👤';
}
// Создаем контейнер для контента
const contentDiv = document.createElement('div');
contentDiv.className = 'ai-message-content';
const textDiv = document.createElement('p');
textDiv.textContent = '';
contentDiv.appendChild(textDiv);
const timeDiv = document.createElement('div');
timeDiv.className = 'ai-message-time';
timeDiv.textContent = new Date().toLocaleTimeString();
contentDiv.appendChild(timeDiv);
messageDiv.appendChild(avatarDiv);
messageDiv.appendChild(contentDiv);
content.appendChild(messageDiv);
content.scrollTop = content.scrollHeight;
// Запускаем стриминг
this.streamText(textDiv, text, speed);
}
}
streamText(element, text, speed = 30) {
let index = 0;
const interval = setInterval(() => {
if (index < text.length) {
element.textContent += text[index];
index++;
const content = this.drawer.querySelector('.ai-drawer-content');
if (content) {
content.scrollTop = content.scrollHeight;
}
} else {
clearInterval(interval);
}
}, speed);
}
showTypingIndicator() {
// Ищем контейнер сообщений - может быть .ai-chat-messages или .ai-drawer-content
const content = this.drawer.querySelector('.ai-chat-messages') || this.drawer.querySelector('.ai-drawer-content');
if (content) {
const existingIndicator = content.querySelector('.ai-typing-indicator');
if (existingIndicator) {
existingIndicator.remove();
}
const messageDiv = document.createElement('div');
messageDiv.className = 'ai-message assistant';
const avatarDiv = document.createElement('div');
let avatarClass = `ai-avatar assistant`;
if (this.avatarType !== 'default') {
avatarClass += ` ${this.avatarType}`;
}
avatarDiv.className = avatarClass;
const contentDiv = document.createElement('div');
contentDiv.className = 'ai-message-content';
const typingDiv = document.createElement('div');
typingDiv.className = 'ai-typing-indicator';
typingDiv.innerHTML = `
<div class="ai-typing-dots">
<div class="ai-typing-dot"></div>
<div class="ai-typing-dot"></div>
<div class="ai-typing-dot"></div>
</div>
<span class="ai-typing-text">печатает...</span>
`;
contentDiv.appendChild(typingDiv);
messageDiv.appendChild(avatarDiv);
messageDiv.appendChild(contentDiv);
content.appendChild(messageDiv);
content.scrollTop = content.scrollHeight;
return messageDiv;
}
}
hideTypingIndicator() {
// Ищем контейнер сообщений - может быть .ai-chat-messages или .ai-drawer-content
const content = this.drawer.querySelector('.ai-chat-messages') || this.drawer.querySelector('.ai-drawer-content');
if (content) {
const typingIndicator = content.querySelector('.ai-typing-indicator');
if (typingIndicator) {
const messageDiv = typingIndicator.closest('.ai-message');
if (messageDiv) {
messageDiv.remove();
}
}
}
}
sendMessage() {
if (!this.chatInput || !this.sendButton) return;
const message = this.chatInput.value.trim();
if (!message) return;
console.log('AI Drawer: Sending message:', message);
this.addMessage(message, true);
this.chatInput.value = '';
this.sendToN8N(message);
}
async sendToN8N(message) {
try {
console.log('AI Drawer: Sending to n8n:', message);
this.showTypingIndicator();
const context = this.getCurrentContext();
const response = await fetch('aiassist/n8n_proxy.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: message,
context: context,
sessionId: 'ai-drawer-session-' + Date.now()
})
});
if (!response.ok) {
throw new Error(`N8N Proxy error: ${response.status}`);
}
const data = await response.json();
console.log('AI Drawer: n8n response:', data);
this.hideLoading();
this.hideTypingIndicator();
if (data.response) {
this.addStreamingMessage(data.response, false, 25);
} else {
this.addStreamingMessage('Получен ответ от n8n, но сообщение пустое', false, 25);
}
} catch (error) {
console.error('AI Drawer: n8n error:', error);
this.hideLoading();
this.hideTypingIndicator();
this.addStreamingMessage('Извините, произошла ошибка при обработке запроса. Попробуйте еще раз.', false, 25);
}
}
getCurrentContext() {
const urlParams = new URLSearchParams(window.location.search);
const projectId = urlParams.get('record') || '';
const currentModule = urlParams.get('module') || '';
const currentView = urlParams.get('view') || '';
let userId = '';
let userName = '';
let userEmail = '';
if (typeof _USERMETA !== 'undefined') {
userId = _USERMETA.id || '';
userName = _USERMETA.name || '';
userEmail = _USERMETA.email || '';
}
let projectName = '';
try {
const recordLabel = document.querySelector('.recordLabel, .record-name, h1');
if (recordLabel) {
projectName = recordLabel.textContent.trim();
}
} catch (e) {
console.log('AI Drawer: Could not get project name:', e);
}
return {
projectId: projectId,
currentModule: currentModule,
currentView: currentView,
userId: userId,
userName: userName,
userEmail: userEmail,
projectName: projectName,
pageTitle: document.title || '',
currentDate: new Date().toLocaleDateString('ru-RU'),
url: window.location.href,
timestamp: Date.now()
};
}
async initializeChat() {
try {
console.log('AI Drawer: Initializing chat with context');
const context = this.getCurrentContext();
let contextMessage = 'Привет! Я готов к работе. ';
if (context.projectName) {
contextMessage += `Сейчас я работаю с записью "${context.projectName}" в модуле ${context.currentModule}. `;
} else if (context.currentModule) {
contextMessage += `Сейчас я работаю в модуле ${context.currentModule}. `;
}
contextMessage += 'Чем могу помочь?';
this.showLoading('🤖 Инициализирую ассистента...');
await this.sendToN8N(contextMessage);
} catch (error) {
console.error('AI Drawer: Chat initialization error:', error);
this.hideLoading();
this.addStreamingMessage('Привет! Я готов к работе. Чем могу помочь?', false, 25);
}
}
restoreSettings() {
const savedFontSize = localStorage.getItem('ai-drawer-font-size');
if (savedFontSize && savedFontSize !== this.fontSize) {
this.setFontSize(savedFontSize);
this.fontButtons.forEach(btn => {
btn.classList.remove('active');
if (btn.dataset.size === savedFontSize) {
btn.classList.add('active');
}
});
}
const savedAvatarType = localStorage.getItem('ai-drawer-avatar-type');
if (savedAvatarType && savedAvatarType !== this.avatarType) {
this.setAvatarType(savedAvatarType);
this.avatarButtons.forEach(btn => {
btn.classList.remove('active');
if (btn.dataset.type === savedAvatarType) {
btn.classList.add('active');
}
});
}
}
// Метод для предзагрузки истории чата через n8n вебхук
async preloadChatHistory() {
try {
console.log('AI Drawer: Preloading chat history from n8n webhook');
const context = this.getCurrentContext();
console.log('AI Drawer: Context for history:', context);
// Отправляем запрос на получение истории в n8n вебхук
const response = await fetch('https://n8n.clientright.pro/webhook/5f50933f-f761-455a-9a7d-9fe0909e3f26', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
action: 'get_history',
context: context,
limit: 10 // Последние 10 сообщений
})
});
if (!response.ok) {
console.log('AI Drawer: Chat history not available from n8n:', response.status);
return;
}
const data = await response.json();
console.log('AI Drawer: Chat history loaded from n8n:', data);
// n8n возвращает массив напрямую, а не объект с success
if (Array.isArray(data) && data.length > 0) {
console.log('AI Drawer: Found', data.length, 'history messages from n8n');
// Очищаем текущие сообщения
this.clearMessages();
// Добавляем историю - n8n возвращает массив с полем data
const messages = data[0]?.data || data;
if (Array.isArray(messages)) {
messages.forEach((msg, index) => {
try {
console.log(`AI Drawer: Adding history message ${index + 1}:`, msg.sender_type, msg.content.substring(0, 30) + '...');
this.addMessage(msg.content, msg.sender_type === 'user', msg.created_at);
console.log(`AI Drawer: Successfully added message ${index + 1}`);
} catch (error) {
console.error('AI Drawer: Error adding history message:', error, msg);
}
});
console.log('AI Drawer: Chat history restored from n8n -', messages.length, 'messages');
} else {
console.log('AI Drawer: Messages data is not an array:', messages);
}
} else {
console.log('AI Drawer: No chat history found in n8n response. Data:', data);
}
} catch (error) {
console.error('AI Drawer: Error loading chat history from n8n:', error);
}
}
// Метод для очистки сообщений
clearMessages() {
// Ищем контейнер сообщений - может быть .ai-chat-messages или .ai-drawer-content
const messagesContainer = this.drawer.querySelector('.ai-chat-messages') || this.drawer.querySelector('.ai-drawer-content');
if (messagesContainer) {
// Удаляем все сообщения
const messages = messagesContainer.querySelectorAll('.ai-message');
messages.forEach(msg => msg.remove());
console.log('AI Drawer: Messages cleared from', messagesContainer.className);
} else {
console.log('AI Drawer: Messages container not found');
}
}
// Метод для обновления истории (при смене страницы)
async refreshPreloadedHistory() {
console.log('AI Drawer: Refreshing preloaded history');
await this.preloadChatHistory();
}
// Сохранение сообщений происходит автоматически в n8n
// Поэтому метод saveMessageToHistory не нужен
}