define('modules/smart-assistant/views/floating-chat', ['view'], function (View) { return View.extend({ templateContent: '' + '
' + ' ' + ' ' + '
' + '' + '', conversationId: null, isOpen: false, currentMode: 'office', currentCaseId: null, currentCaseName: null, currentView: 'chat', currentMemoryCategory: null, memoryData: null, events: { 'click .sa-fab': function () { this.togglePanel(); }, 'click .sa-close-btn': function () { this.closePanel(); }, 'click .sa-chat-send': function () { this.sendMessage(); }, 'click .sa-history-btn': function () { this.showHistory(); }, 'click .sa-new-btn': function () { this.startNewConversation(); }, 'click .sa-history-back': function () { this.showChat(); }, 'click .sa-memory-btn': function () { this.showMemory(); }, 'click .sa-memory-back': function () { this.showChat(); }, 'click .sa-add-memory-btn': function () { this.openAddMemoryModal(); }, 'click .sa-history-item': function (e) { var key = $(e.currentTarget).data('key'); if (key) this.loadConversation(key); }, 'click .sa-memory-tab': function (e) { var cat = $(e.currentTarget).data('category'); this.filterMemory(cat); }, 'keypress .sa-chat-input': function (e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.sendMessage(); } }, 'input .sa-chat-input': function (e) { this.$el.find('.sa-chat-send').prop('disabled', !e.target.value.trim()); }, 'click .sa-action-approve': function (e) { var $btn = $(e.currentTarget); this.handleAction($btn.data('conversation-id'), $btn.data('action-id'), true); }, 'click .sa-action-reject': function (e) { var $btn = $(e.currentTarget); this.handleAction($btn.data('conversation-id'), $btn.data('action-id'), false); }, }, afterRender: function () { this.$el.find('.sa-chat-messages').attr('data-empty-text', this.translate('Chat with the assistant', 'labels', 'SmartAssistant') || 'שוחח/י עם העוזר החכם'); this.loadAlertCount(); this._setupRouteListener(); }, _setupRouteListener: function () { var self = this; if (this._routeListenerSet) return; this._routeListenerSet = true; // Check mode on route changes if (Espo.router) { this.listenTo(Espo.router, 'routed', function () { self._detectMode(); }); } this._detectMode(); }, _detectMode: function () { var hash = window.location.hash || ''; var match = hash.match(/#Case\/view\/([a-zA-Z0-9]+)/); if (match) { this.currentMode = 'case'; this.currentCaseId = match[1]; this._loadCaseName(this.currentCaseId); this.$el.find('.sa-memory-btn').show(); } else { this.currentMode = 'office'; this.currentCaseId = null; this.currentCaseName = null; this.$el.find('.sa-memory-btn').hide(); } this._updateModeIndicator(); }, _loadCaseName: function (caseId) { var self = this; Espo.Ajax.getRequest('Case/' + caseId, {select: 'name,number'}).then(function (data) { self.currentCaseName = '#' + (data.number || '') + ' ' + (data.name || ''); self._updateModeIndicator(); }).catch(function () {}); }, _updateModeIndicator: function () { var $badge = this.$el.find('.sa-mode-badge'); if (this.currentMode === 'case' && this.currentCaseName) { $badge.text(this.currentCaseName).show(); } else if (this.currentMode === 'case') { $badge.text(this.translate('Case Mode', 'labels', 'SmartAssistant')).show(); } else { $badge.text(this.translate('Office Mode', 'labels', 'SmartAssistant')).show(); } }, togglePanel: function () { this.isOpen ? this.closePanel() : this.openPanel(); }, openPanel: function () { this.isOpen = true; this._detectMode(); this.$el.find('.sa-chat-panel').css('display', 'flex'); if (!this._summaryLoaded && this.currentMode === 'office') this.loadSummary(); if (!this.conversationId && !this._historyChecked) { this._historyChecked = true; this.loadLatestConversation(); } this.$el.find('.sa-chat-input').focus(); }, closePanel: function () { this.isOpen = false; this.$el.find('.sa-chat-panel').css('display', 'none'); }, showChat: function () { this.currentView = 'chat'; this.$el.find('.sa-history-view, .sa-memory-view').hide(); this.$el.find('.sa-chat-view').show(); this.$el.find('.sa-chat-input').focus(); }, showHistory: function () { this.currentView = 'history'; this.$el.find('.sa-chat-view, .sa-memory-view').hide(); this.$el.find('.sa-history-view').show(); this.loadConversationsList(); }, showMemory: function () { if (this.currentMode !== 'case' || !this.currentCaseId) return; this.currentView = 'memory'; this.$el.find('.sa-chat-view, .sa-history-view').hide(); this.$el.find('.sa-memory-view').show(); this.loadMemory(); }, startNewConversation: function () { this.conversationId = null; this.$el.find('.sa-chat-messages').empty(); this.showChat(); }, loadConversationsList: function () { var $list = this.$el.find('.sa-history-list'); var self = this; $list.html('
'); Espo.Ajax.getRequest('SmartAssistant/action/conversations').then(function (conversations) { self.renderConversationsList(conversations); }).catch(function () { $list.html('
שגיאה בטעינת שיחות
'); }); }, renderConversationsList: function (conversations) { var $list = this.$el.find('.sa-history-list'); var self = this; if (!conversations || conversations.length === 0) { $list.html('
' + self.escapeHtml(self.translate('No previous conversations', 'labels', 'SmartAssistant')) + '
'); return; } var html = ''; conversations.forEach(function (conv) { var timeStr = self.formatConversationTime(conv.lastMessageAt); var typeIcon = conv.conversationType === 'case' ? 'fa-gavel' : 'fa-building'; html += '
' + '
' + '' + self.escapeHtml(conv.name || 'שיחה') + '
' + '
' + '' + self.escapeHtml(timeStr) + '' + '' + (conv.messageCount || 0) + ' הודעות' + '
'; if (conv.lastMessagePreview) { html += '
' + self.escapeHtml(conv.lastMessagePreview.substring(0, 80)) + '
'; } html += '
'; }); $list.html(html); }, loadConversation: function (conversationKey) { var self = this; this.conversationId = conversationKey; var $messages = this.$el.find('.sa-chat-messages'); $messages.html('
טוען שיחה...
'); this.showChat(); Espo.Ajax.getRequest('SmartAssistant/action/conversationMessages', {conversationKey: conversationKey}).then(function (response) { $messages.empty(); (response.messages || []).forEach(function (msg) { var cssClass = msg.role === 'user' ? 'sa-msg-user' : 'sa-msg-assistant'; var content = msg.role === 'user' ? self.escapeHtml(msg.content) : self.formatResponse(msg.content); $messages.append('
' + content + '
'); }); self.scrollToBottom(); }).catch(function () { $messages.html('
שגיאה בטעינת שיחה
'); }); }, loadLatestConversation: function () { var self = this; Espo.Ajax.getRequest('SmartAssistant/action/history').then(function (response) { if (!response || !response.conversationId || !response.messages || response.messages.length === 0) return; var updatedAt = response.updatedAt; if (updatedAt) { var today = new Date().toISOString().substring(0, 10); if (updatedAt.substring(0, 10) !== today) return; } self.conversationId = response.conversationId; var $messages = self.$el.find('.sa-chat-messages'); response.messages.forEach(function (msg) { var cssClass = msg.role === 'user' ? 'sa-msg-user' : 'sa-msg-assistant'; var content = msg.role === 'user' ? self.escapeHtml(msg.content) : self.formatResponse(msg.content); $messages.append('
' + content + '
'); }); self.scrollToBottom(); }).catch(function () {}); }, loadAlertCount: function () { var self = this; Espo.Ajax.getRequest('SmartAssistant/action/summary').then(function (response) { var summary = response.summary || {}; var count = (summary.criticalAlerts || 0) + (summary.warningAlerts || 0); self.updateBadge(count); }).catch(function () {}); }, updateBadge: function (count) { var $badge = this.$el.find('.sa-fab-badge'); count > 0 ? $badge.text(count).show() : $badge.hide(); }, loadSummary: function () { this._summaryLoaded = true; var self = this; var $summary = this.$el.find('.sa-chat-summary'); Espo.Ajax.getRequest('SmartAssistant/action/summary').then(function (response) { var summary = response.summary || {}; var alerts = response.alerts || []; var critical = alerts.filter(function (a) { return a.severity === 'critical'; }); if (critical.length > 0) { var html = '' + self.escapeHtml(critical.length + ' התראות קריטיות') + ''; critical.slice(0, 2).forEach(function (a) { html += '
' + self.escapeHtml('• ' + a.message) + '
'; }); $summary.html(html).show(); } else if (summary.casesNeedingAttention > 0) { $summary.html(self.escapeHtml(summary.casesNeedingAttention + ' תיקים דורשים תשומת לב')).show(); } else { $summary.html('אין התראות קריטיות').show(); } self.updateBadge((summary.criticalAlerts || 0) + (summary.warningAlerts || 0)); }).catch(function () { $summary.hide(); }); }, sendMessage: function () { var $input = this.$el.find('.sa-chat-input'); var message = $input.val().trim(); if (!message) return; var self = this; var $messages = this.$el.find('.sa-chat-messages'); var $btn = this.$el.find('.sa-chat-send'); $messages.append('
' + this.escapeHtml(message) + '
'); var $loading = $('
' + this.escapeHtml(this.translate('Thinking', 'labels', 'SmartAssistant') || 'חושב...') + '
'); $messages.append($loading); this.scrollToBottom(); $btn.prop('disabled', true); $input.prop('disabled', true).val(''); var payload = { message: message, mode: this.currentMode, conversationId: this.conversationId, }; if (this.currentMode === 'case' && this.currentCaseId) { payload.caseId = this.currentCaseId; } Espo.Ajax.postRequest('SmartAssistant/action/chat', payload).then(function (response) { self.conversationId = response.conversationId; $loading.remove(); $messages.append('
' + self.formatResponse(response.text) + '
'); // Render action items if (response.actions && response.actions.length > 0) { response.actions.forEach(function (action) { var actionHtml = '
' + '' + self.escapeHtml(action.displayText || action.tool) + '' + '
' + '' + '' + '
'; $messages.append(actionHtml); }); } self.scrollToBottom(); $input.prop('disabled', false).focus(); }).catch(function () { $loading.remove(); $messages.append('
' + self.escapeHtml(self.translate('Error', 'labels', 'SmartAssistant') || 'שגיאה בתקשורת') + '
'); self.scrollToBottom(); $input.prop('disabled', false); $btn.prop('disabled', false); }); }, handleAction: function (conversationId, actionId, approved) { var self = this; var $messages = this.$el.find('.sa-chat-messages'); Espo.Ajax.postRequest('SmartAssistant/action/execute', { conversationId: conversationId, actionId: actionId, approved: approved, }).then(function (result) { var msg = approved ? (result.message || 'פעולה בוצעה') : 'פעולה נדחתה'; var cls = approved ? (result.success ? 'sa-msg-assistant' : 'sa-msg-error') : 'sa-msg-loading'; $messages.append('
' + self.escapeHtml(msg) + '
'); self.scrollToBottom(); }).catch(function () { $messages.append('
שגיאה בביצוע הפעולה
'); self.scrollToBottom(); }); }, loadMemory: function () { if (!this.currentCaseId) return; var self = this; var $list = this.$el.find('.sa-memory-list'); $list.html('
'); Espo.Ajax.getRequest('SmartAssistant/action/caseMemory', {caseId: this.currentCaseId}).then(function (data) { self.memoryData = data; self.renderMemoryTabs(data); self.renderMemoryEntries(data, null); }).catch(function () { $list.html('
שגיאה בטעינת זיכרון
'); }); }, renderMemoryTabs: function (data) { var $tabs = this.$el.find('.sa-memory-tabs'); var categoryLabels = { 'key_facts': 'עובדות', 'strategy': 'אסטרטגיה', 'decisions': 'החלטות', 'contacts_notes': 'אנשי קשר', 'timeline': 'ציר זמן', 'documents_notes': 'מסמכים', 'billing_notes': 'חיוב' }; var html = ''; var categories = Object.keys(data).filter(function (k) { return k !== '_summary'; }); categories.forEach(function (cat) { var label = categoryLabels[cat] || cat; var count = data[cat] ? data[cat].length : 0; html += ''; }); $tabs.html(html); }, renderMemoryEntries: function (data, category) { var $list = this.$el.find('.sa-memory-list'); var self = this; var entries = []; var importanceLabels = {critical: 'קריטי', high: 'גבוה', normal: 'רגיל', low: 'נמוך'}; var sourceLabels = {manual: 'ידני', assistant: 'AI', auto: 'אוטומטי'}; Object.keys(data).forEach(function (cat) { if (cat === '_summary') return; if (category && cat !== category) return; (data[cat] || []).forEach(function (e) { entries.push(e); }); }); if (entries.length === 0) { $list.html('
' + self.escapeHtml(self.translate('No memories yet', 'labels', 'SmartAssistant')) + '
'); return; } var html = ''; entries.forEach(function (e) { var badgeClass = 'sa-badge-' + (e.importance || 'normal'); html += '
' + '
' + '' + self.escapeHtml(e.name) + '' + '
'; if (e.isPinned) html += ''; html += '' + (importanceLabels[e.importance] || e.importance) + ''; html += '' + (sourceLabels[e.source] || e.source) + ''; html += '
' + '
' + self.escapeHtml(e.content) + '
' + '
' + self.formatConversationTime(e.createdAt) + '
' + '
'; }); $list.html(html); }, filterMemory: function (category) { this.$el.find('.sa-memory-tab').removeClass('active'); this.$el.find('.sa-memory-tab[data-category="' + (category || '') + '"]').addClass('active'); this.currentMemoryCategory = category || null; if (this.memoryData) { this.renderMemoryEntries(this.memoryData, this.currentMemoryCategory); } }, openAddMemoryModal: function () { if (!this.currentCaseId) return; var self = this; this.createView('addMemoryModal', 'modules/smart-assistant/views/case/modals/add-memory', { caseId: this.currentCaseId, }, function (view) { view.render(); self.listenToOnce(view, 'saved', function () { self.loadMemory(); view.close(); }); }); }, scrollToBottom: function () { var el = this.$el.find('.sa-chat-messages').get(0); if (el) el.scrollTop = el.scrollHeight; }, formatConversationTime: function (dateStr) { if (!dateStr) return ''; var date = new Date(dateStr); var now = new Date(); var today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); var yesterday = new Date(today.getTime() - 86400000); var convDay = new Date(date.getFullYear(), date.getMonth(), date.getDate()); var timeStr = ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2); if (convDay.getTime() === today.getTime()) return 'היום ' + timeStr; if (convDay.getTime() === yesterday.getTime()) return 'אתמול ' + timeStr; return ('0' + date.getDate()).slice(-2) + '/' + ('0' + (date.getMonth() + 1)).slice(-2) + ' ' + timeStr; }, formatResponse: function (text) { if (!text) return ''; text = this.escapeHtml(text); text = text.replace(/\*\*(.*?)\*\*/g, '$1'); text = text.replace(/\n/g, '
'); text = text.replace(/^- (.+)/gm, '• $1'); return text; }, escapeHtml: function (text) { if (!text) return ''; var div = document.createElement('div'); div.textContent = text; return div.innerHTML; }, }); });