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 += '' + label + ' (' + count + ') ';
});
$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.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;
},
});
});