Compare commits

...

23 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
chaim 2c34d56c64 release: v1.0.3 2026-03-29 21:34:38 +00:00
chaim d5c8a60193 fix: add clientModule to module.json for client-side template resolution
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 21:34:18 +00:00
chaim 96e7c8e9e0 release: v1.0.2 2026-03-29 20:58:35 +00:00
chaim f3805424d3 fix: correct CSS and script paths in app/client.json metadata
Move CSS to module root (not inside src/), fix script paths to include
src/ prefix for handler/view JS files loaded via scriptList.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:58:22 +00:00
15 changed files with 135 additions and 27 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# SmartAssistant - עוזר חכם # SmartAssistant - עוזר חכם
**גרסה:** 1.0.1 | **מחבר:** klear | **EspoCRM:** >= 8.0.0 **גרסה:** 1.0.3 | **מחבר:** klear | **EspoCRM:** >= 8.0.0
## תיאור ## תיאור
עוזר AI מאוחד למשרד עורכי דין. מספק ממשק צ'אט צף עם שני מצבי עבודה: **מצב משרד** (סקירה כללית, התראות, סטטיסטיקות) ו**מצב תיק** (סיוע מעמיק בתיק ספציפי). כולל מערכת זיכרון תיק מובנית, ביצוע פעולות באישור המשתמש, ואינטגרציה עם Stream של EspoCRM. עוזר AI מאוחד למשרד עורכי דין. מספק ממשק צ'אט צף עם שני מצבי עבודה: **מצב משרד** (סקירה כללית, התראות, סטטיסטיקות) ו**מצב תיק** (סיוע מעמיק בתיק ספציפי). כולל מערכת זיכרון תיק מובנית, ביצוע פעולות באישור המשתמש, ואינטגרציה עם Stream של EspoCRM.
Binary file not shown.
Binary file not shown.
@@ -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}
@@ -5,5 +5,6 @@
"espocrm": ">=8.0.0" "espocrm": ">=8.0.0"
}, },
"author": "klear", "author": "klear",
"description": "Unified AI Assistant for Legal CRM" "description": "Unified AI Assistant for Legal CRM",
"clientModule": "smart-assistant"
} }
@@ -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.1", "version": "1.0.9",
"acceptableVersions": [ "acceptableVersions": [
">=8.0.0" ">=8.0.0"
], ],