Compare commits

...

19 Commits

Author SHA1 Message Date
chaim 1eb856b178 feat: rename assistant to שירה (Shira) 2026-03-31 23:12:31 +00:00
chaim c9d08aa1b9 feat: rename assistant to שירה (Shira) 2026-03-31 23:12:29 +00:00
chaim 233f56eccf feat: add status endpoint, hide FAB/dashlet when integration disabled 2026-03-31 23:10:59 +00:00
chaim 0157b31249 feat: add status endpoint, hide FAB/dashlet when integration disabled 2026-03-31 23:10:33 +00:00
chaim 49c42e3d20 feat: add status endpoint, hide FAB/dashlet when integration disabled 2026-03-31 23:10:29 +00:00
chaim 4dad65f578 feat: add status endpoint, hide FAB/dashlet when integration disabled 2026-03-31 23:10:22 +00:00
chaim f116611c73 feat: add status endpoint, hide FAB/dashlet when integration disabled 2026-03-31 23:09:16 +00:00
chaim ff2388e7e1 feat: add status endpoint, hide FAB/dashlet when integration disabled 2026-03-31 23:09:10 +00:00
chaim 92c3cd2cdc release: v1.0.7 2026-03-31 23:01:33 +00:00
chaim 3102bcff77 feat: clickable dashlet cards with detail drill-down 2026-03-31 23:01:19 +00:00
chaim f5c60f4272 release: v1.0.6 2026-03-31 22:56:28 +00:00
chaim 21c3e33635 fix: add getColor to dashlet for EspoCRM v9 compatibility 2026-03-31 22:56:08 +00:00
chaim ffbb648dd5 release: v1.0.5 2026-03-31 22:53:23 +00:00
chaim f915faafdc fix: add getActionItemDataList to dashlet for EspoCRM v9 compatibility 2026-03-31 22:53:10 +00:00
chaim b9abe5f921 release: v1.0.4 — add AssistantConversation entity 2026-03-31 20:33:21 +00:00
chaim a3801deb55 feat: add AssistantConversation i18n (fa_IR/Hebrew) 2026-03-31 20:33:05 +00:00
chaim 300cc58a97 feat: add AssistantConversation i18n (en_US) 2026-03-31 20:33:01 +00:00
chaim c1ea2b7868 feat: add AssistantConversation scope definition 2026-03-31 20:32:53 +00:00
chaim 184cd19285 feat: add AssistantConversation entity definition for chat persistence 2026-03-31 20:32:49 +00:00
11 changed files with 132 additions and 25 deletions
@@ -11,32 +11,60 @@ define('modules/smart-assistant/views/dashlets/smart-assistant', ['view'], funct
templateContent: '' + templateContent: '' +
'<div style="direction: rtl; text-align: right; padding: 8px;">' + '<div style="direction: rtl; text-align: right; padding: 8px;">' +
'<div class="sa-dashlet-cards" style="display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px;"></div>' + '<div class="sa-dashlet-cards" style="display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px;"></div>' +
'<div class="sa-dashlet-alerts" style="max-height: 200px; overflow-y: auto;"></div>' + '<div class="sa-dashlet-detail" style="max-height: 250px; overflow-y: auto;"></div>' +
'</div>', '</div>',
events: {
'click .sa-card': function (e) {
var type = $(e.currentTarget).data('type');
this.showDetail(type);
},
},
afterRender: function () { afterRender: function () {
this.loadSummary(); this._alerts = [];
this._summary = {};
this._activeType = null;
var self = this;
Espo.Ajax.getRequest('SmartAssistant/action/status').then(function (result) {
if (!result.enabled) {
self.$el.find('.sa-dashlet-cards').html(
'<div style="text-align: center; color: #999; padding: 20px; font-size: 13px;">שירה לא מופעלת. הפעל ב-Admin &gt; Integrations &gt; Smart Assistant</div>'
);
return;
}
self.loadSummary();
}).catch(function () {
self.loadSummary();
});
}, },
loadSummary: function () { loadSummary: function () {
var self = this; var self = this;
Espo.Ajax.getRequest('SmartAssistant/action/summary').then(function (response) { Espo.Ajax.getRequest('SmartAssistant/action/summary').then(function (response) {
self.renderCards(response.summary || {}); self._summary = response.summary || {};
self.renderAlerts(response.alerts || []); self._alerts = response.alerts || [];
self.renderCards(self._summary);
self.showDetail(self._activeType);
}).catch(function () {}); }).catch(function () {});
}, },
renderCards: function (summary) { renderCards: function (summary) {
var cards = [ var cards = [
{label: 'תיקים פתוחים', value: summary.totalOpenCases || 0, color: '#1565c0', bg: '#e3f2fd', icon: 'fa-folder-open'}, {type: 'open', label: 'תיקים פתוחים', value: summary.totalOpenCases || 0, color: '#1565c0', bg: '#e3f2fd', icon: 'fa-folder-open'},
{label: 'משימות באיחור', value: summary.totalOverdueTasks || 0, color: '#c62828', bg: '#ffcdd2', icon: 'fa-exclamation-circle'}, {type: 'overdue', label: 'משימות באיחור', value: summary.totalOverdueTasks || 0, color: '#c62828', bg: '#ffcdd2', icon: 'fa-exclamation-circle'},
{label: 'דיונים השבוע', value: summary.upcomingHearings7d || 0, color: '#e65100', bg: '#fff3e0', icon: 'fa-gavel'}, {type: 'hearings', label: 'דיונים השבוע', value: summary.upcomingHearings7d || 0, color: '#e65100', bg: '#fff3e0', icon: 'fa-gavel'},
{label: 'דורשים תשומת לב', value: summary.casesNeedingAttention || 0, color: '#f9a825', bg: '#fff9c4', icon: 'fa-bell'}, {type: 'attention', label: 'דורשים תשומת לב', value: summary.casesNeedingAttention || 0, color: '#f9a825', bg: '#fff9c4', icon: 'fa-bell'},
]; ];
var self = this;
var html = ''; var html = '';
cards.forEach(function (c) { cards.forEach(function (c) {
html += '<div style="flex: 1; min-width: 80px; background: ' + c.bg + '; padding: 10px; border-radius: 8px; text-align: center;">' + var active = self._activeType === c.type;
var border = active ? '2px solid ' + c.color : '2px solid transparent';
var cursor = c.value > 0 ? 'pointer' : 'default';
html += '<div class="sa-card" data-type="' + c.type + '" style="flex: 1; min-width: 80px; background: ' + c.bg + '; padding: 10px; border-radius: 8px; text-align: center; cursor: ' + cursor + '; border: ' + border + '; transition: border 0.2s;">' +
'<div style="font-size: 22px; font-weight: bold; color: ' + c.color + ';">' + c.value + '</div>' + '<div style="font-size: 22px; font-weight: bold; color: ' + c.color + ';">' + c.value + '</div>' +
'<div style="font-size: 11px; color: ' + c.color + ';"><span class="fas ' + c.icon + '"></span> ' + c.label + '</div>' + '<div style="font-size: 11px; color: ' + c.color + ';"><span class="fas ' + c.icon + '"></span> ' + c.label + '</div>' +
'</div>'; '</div>';
@@ -45,10 +73,35 @@ define('modules/smart-assistant/views/dashlets/smart-assistant', ['view'], funct
this.$el.find('.sa-dashlet-cards').html(html); this.$el.find('.sa-dashlet-cards').html(html);
}, },
renderAlerts: function (alerts) { showDetail: function (type) {
var $alerts = this.$el.find('.sa-dashlet-alerts'); var $detail = this.$el.find('.sa-dashlet-detail');
if (alerts.length === 0) {
$alerts.html('<div style="text-align: center; color: #4caf50; padding: 12px;">אין התראות</div>'); if (!type || type === this._activeType && $detail.html()) {
// Toggle off
this._activeType = null;
$detail.html('');
this.renderCards(this._summary);
return;
}
this._activeType = type;
this.renderCards(this._summary);
var alerts = this._alerts;
var items = [];
if (type === 'overdue') {
items = alerts.filter(function (a) { return a.type === 'overdue_task'; });
} else if (type === 'hearings') {
items = alerts.filter(function (a) { return a.type === 'hearing_no_prep'; });
} else if (type === 'attention') {
items = alerts.filter(function (a) { return a.severity === 'critical' || a.severity === 'warning'; });
} else if (type === 'open') {
items = alerts.filter(function (a) { return a.type === 'inactive_case' || a.type === 'unassigned_case'; });
}
if (items.length === 0) {
$detail.html('<div style="text-align: center; color: #999; padding: 12px; font-size: 12px;">אין פריטים להצגה</div>');
return; return;
} }
@@ -56,21 +109,36 @@ define('modules/smart-assistant/views/dashlets/smart-assistant', ['view'], funct
var severityIcons = {critical: 'fa-exclamation-circle', warning: 'fa-exclamation-triangle', info: 'fa-info-circle'}; var severityIcons = {critical: 'fa-exclamation-circle', warning: 'fa-exclamation-triangle', info: 'fa-info-circle'};
var html = ''; var html = '';
alerts.slice(0, 10).forEach(function (a) { items.forEach(function (a) {
var color = severityColors[a.severity] || '#333'; var color = severityColors[a.severity] || '#333';
var icon = severityIcons[a.severity] || 'fa-circle'; var icon = severityIcons[a.severity] || 'fa-circle';
var link = a.caseId ? '<a href="#Case/view/' + a.caseId + '" style="color: ' + color + '; text-decoration: none;">' : '<span>';
var linkEnd = a.caseId ? '</a>' : '</span>';
html += '<div style="padding: 6px 0; border-bottom: 1px solid #f5f5f5; font-size: 12px; color: ' + color + ';">' + html += '<div style="padding: 6px 8px; border-bottom: 1px solid #f0f0f0; font-size: 12px; color: ' + color + ';">' +
'<span class="fas ' + icon + '" style="margin-left: 6px;"></span>' + '<span class="fas ' + icon + '" style="margin-left: 6px;"></span>' +
'<span>' + Espo.Utils.escapeString(a.message) + '</span>' + link + Espo.Utils.escapeString(a.message) + linkEnd +
'</div>'; '</div>';
}); });
$alerts.html(html); $detail.html(html);
}, },
actionRefresh: function () { actionRefresh: function () {
this.loadSummary(); this.loadSummary();
}, },
getActionItemDataList: function () {
return [
{
name: 'refresh',
iconHtml: '<span class="fas fa-sync-alt"></span>',
},
];
},
getColor: function () {
return '#5c6bc0';
},
}); });
}); });
@@ -158,10 +158,22 @@ define('modules/smart-assistant/views/floating-chat', ['view'], function (View)
}, },
afterRender: function () { afterRender: function () {
this.$el.find('.sa-chat-messages').attr('data-empty-text', var self = this;
this.translate('Chat with the assistant', 'labels', 'SmartAssistant') || 'שוחח/י עם העוזר החכם');
this.loadAlertCount(); Espo.Ajax.getRequest('SmartAssistant/action/status').then(function (result) {
this._setupRouteListener(); if (!result.enabled) {
self.$el.find('.sa-fab').hide();
return;
}
self.$el.find('.sa-fab').show();
self.$el.find('.sa-chat-messages').attr('data-empty-text',
self.translate('Chat with the assistant', 'labels', 'SmartAssistant') || 'שוחח/י עם העוזר החכם');
self.loadAlertCount();
self._setupRouteListener();
}).catch(function () {
self.$el.find('.sa-fab').hide();
});
}, },
_setupRouteListener: function () { _setupRouteListener: function () {
@@ -34,6 +34,11 @@ class SmartAssistant
} }
} }
public function getActionStatus(Request $request, Response $response): array
{
return $this->getService()->getStatus();
}
public function postActionChat(Request $request, Response $response): array public function postActionChat(Request $request, Response $response): array
{ {
$this->checkAccess(); $this->checkAccess();
@@ -0,0 +1 @@
{"labels":{"Create AssistantConversation":"New Conversation"},"fields":{"name":"Title","conversationKey":"Conversation Key","conversationType":"Type","status":"Status","assignedUser":"User","case":"Case","caseName":"Case Name","messageCount":"Messages","messagesData":"Messages Data","lastMessageAt":"Last Message","lastMessagePreview":"Preview","createdAt":"Created At","modifiedAt":"Modified At","createdBy":"Created By","modifiedBy":"Modified By"},"links":{"assignedUser":"User","case":"Case"},"options":{"conversationType":{"office":"Office","case":"Case"}}}
@@ -0,0 +1 @@
{"labels":{"Create AssistantConversation":"שיחה חדשה"},"fields":{"name":"כותרת","conversationKey":"מזהה שיחה","conversationType":"סוג","status":"סטטוס","assignedUser":"משתמש","case":"תיק","caseName":"שם התיק","messageCount":"הודעות","messagesData":"נתוני הודעות","lastMessageAt":"הודעה אחרונה","lastMessagePreview":"תצוגה מקדימה","createdAt":"נוצר ב","modifiedAt":"עודכן ב","createdBy":"נוצר על ידי","modifiedBy":"עודכן על ידי"},"links":{"assignedUser":"משתמש","case":"תיק"},"options":{"conversationType":{"office":"משרד","case":"תיק"}}}
@@ -1,6 +1,6 @@
{ {
"labels": { "labels": {
"Smart Assistant": "עוזר חכם", "Smart Assistant": "שירה",
"Office Mode": "מצב משרד", "Office Mode": "מצב משרד",
"Case Mode": "מצב תיק", "Case Mode": "מצב תיק",
"Add Memory": "הוסף זיכרון", "Add Memory": "הוסף זיכרון",
@@ -9,11 +9,11 @@
"Conversation History": "היסטוריית שיחות", "Conversation History": "היסטוריית שיחות",
"Previous Conversations": "שיחות קודמות", "Previous Conversations": "שיחות קודמות",
"No previous conversations": "אין שיחות קודמות", "No previous conversations": "אין שיחות קודמות",
"Ask the assistant...": "שאל/י את העוזר...", "Ask the assistant...": "שאל/י את שירה...",
"Thinking": "חושב...", "Thinking": "חושב...",
"Error": "שגיאה בתקשורת", "Error": "שגיאה בתקשורת",
"Back": "חזרה", "Back": "חזרה",
"Chat with the assistant": "שוחח/י עם העוזר החכם", "Chat with the assistant": "שוחח/י עם שירה",
"Case Memory": "זיכרון תיק", "Case Memory": "זיכרון תיק",
"Pin": "הצמד", "Pin": "הצמד",
"Unpin": "הסר הצמדה", "Unpin": "הסר הצמדה",
@@ -0,0 +1 @@
{"fields":{"name":{"type":"varchar","maxLength":255,"required":true},"conversationKey":{"type":"varchar","maxLength":255,"required":true},"conversationType":{"type":"enum","options":["office","case"],"default":"office","required":true},"status":{"type":"varchar","maxLength":50,"default":"Active"},"assignedUser":{"type":"link"},"case":{"type":"link"},"caseName":{"type":"varchar","maxLength":255},"messageCount":{"type":"int","default":0},"messagesData":{"type":"jsonArray"},"lastMessageAt":{"type":"datetime"},"lastMessagePreview":{"type":"text","maxLength":200},"createdAt":{"type":"datetime","readOnly":true},"modifiedAt":{"type":"datetime","readOnly":true},"createdBy":{"type":"link","readOnly":true},"modifiedBy":{"type":"link","readOnly":true}},"links":{"assignedUser":{"type":"belongsTo","entity":"User"},"case":{"type":"belongsTo","entity":"Case"},"createdBy":{"type":"belongsTo","entity":"User"},"modifiedBy":{"type":"belongsTo","entity":"User"}},"collection":{"orderBy":"lastMessageAt","order":"desc","textFilterFields":["name","lastMessagePreview"]},"indexes":{"conversationKey":{"columns":["conversationKey","deleted"],"type":"unique"},"assignedUser":{"columns":["assignedUserId","deleted"]},"caseId":{"columns":["caseId","deleted"]},"conversationType":{"columns":["conversationType","deleted"]}}}
@@ -0,0 +1 @@
{"entity":true,"object":true,"module":"SmartAssistant","acl":true,"aclActionList":["create","read","edit","delete"],"aclLevelList":["all","team","own","no"],"tab":false,"stream":false,"disabled":false,"importable":false,"customizable":false}
@@ -1,4 +1,12 @@
[ [
{
"route": "/SmartAssistant/action/status",
"method": "get",
"params": {
"controller": "SmartAssistant",
"action": "status"
}
},
{ {
"route": "/SmartAssistant/action/chat", "route": "/SmartAssistant/action/chat",
"method": "post", "method": "post",
@@ -236,6 +236,16 @@ class SmartAssistantService
} }
} }
public function getStatus(): array
{
$webhookUrl = $this->getWebhookUrl();
return [
'enabled' => $webhookUrl !== null,
'webhookConfigured' => !empty($webhookUrl),
];
}
public function getSummary(): array public function getSummary(): array
{ {
$alertCalc = $this->injectableFactory->create(AlertCalculator::class); $alertCalc = $this->injectableFactory->create(AlertCalculator::class);
+1 -1
View File
@@ -3,7 +3,7 @@
"module": "SmartAssistant", "module": "SmartAssistant",
"description": "Unified AI Assistant for Legal CRM — floating chat with case memory, office alerts, and n8n integration", "description": "Unified AI Assistant for Legal CRM — floating chat with case memory, office alerts, and n8n integration",
"author": "klear", "author": "klear",
"version": "1.0.3", "version": "1.0.9",
"acceptableVersions": [ "acceptableVersions": [
">=8.0.0" ">=8.0.0"
], ],