cf0e9ad2bc
EspoCRM returns JSON data as stdClass objects, not arrays. Using array access ($data[$i]['role']) on stdClass causes fatal error. Fixed by using json_decode(json_encode(...), true) to ensure deep conversion to associative arrays. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
152 lines
6.9 KiB
PHP
152 lines
6.9 KiB
PHP
<?php
|
|
|
|
namespace Espo\Modules\SmartAssistant\Services;
|
|
|
|
use Espo\ORM\EntityManager;
|
|
use Espo\Entities\User;
|
|
use Espo\Core\Utils\Log;
|
|
|
|
class ConversationRepository
|
|
{
|
|
private EntityManager $entityManager;
|
|
private User $user;
|
|
private Log $log;
|
|
|
|
public function __construct(EntityManager $entityManager, User $user, Log $log)
|
|
{
|
|
$this->entityManager = $entityManager;
|
|
$this->user = $user;
|
|
$this->log = $log;
|
|
}
|
|
|
|
public function startOrResume(string $type, ?string $conversationKey = null, ?string $caseId = null): array
|
|
{
|
|
if ($conversationKey) {
|
|
$entity = $this->entityManager->getRDBRepository('AssistantConversation')
|
|
->where(['conversationKey' => $conversationKey, 'deleted' => false])->findOne();
|
|
if ($entity) return ['id' => $entity->get('id'), 'conversationKey' => $conversationKey];
|
|
}
|
|
|
|
$conversationKey = $conversationKey ?: uniqid($type === 'office' ? 'oconv_' : 'conv_', true);
|
|
$entity = $this->entityManager->getNewEntity('AssistantConversation');
|
|
$entity->set([
|
|
'name' => $type === 'office' ? 'שיחה עם העוזר החכם' : 'שיחה עם העוזר',
|
|
'conversationType' => $type, 'status' => 'Active',
|
|
'conversationKey' => $conversationKey,
|
|
'assignedUserId' => $this->user->getId(),
|
|
'messageCount' => 0, 'messagesData' => [],
|
|
'lastMessageAt' => date('Y-m-d H:i:s'),
|
|
]);
|
|
if ($caseId && $type === 'case') $entity->set('caseId', $caseId);
|
|
$this->entityManager->saveEntity($entity);
|
|
|
|
return ['id' => $entity->get('id'), 'conversationKey' => $conversationKey];
|
|
}
|
|
|
|
public function appendMessage(string $conversationKey, string $role, string $content, array $actions = []): void
|
|
{
|
|
$entity = $this->entityManager->getRDBRepository('AssistantConversation')
|
|
->where(['conversationKey' => $conversationKey, 'deleted' => false])->findOne();
|
|
|
|
if (!$entity) {
|
|
$this->startOrResume('office', $conversationKey);
|
|
$entity = $this->entityManager->getRDBRepository('AssistantConversation')
|
|
->where(['conversationKey' => $conversationKey, 'deleted' => false])->findOne();
|
|
if (!$entity) return;
|
|
}
|
|
|
|
$messages = json_decode(json_encode($entity->get('messagesData') ?? []), true);
|
|
|
|
$newMessage = ['role' => $role, 'content' => $content, 'timestamp' => date('Y-m-d H:i:s')];
|
|
if (!empty($actions)) $newMessage['actions'] = $actions;
|
|
$messages[] = $newMessage;
|
|
|
|
if ($role === 'user' && $entity->get('messageCount') === 0) {
|
|
$title = mb_substr($content, 0, 100);
|
|
if (mb_strlen($content) > 100) $title .= '...';
|
|
$entity->set('name', $title);
|
|
}
|
|
|
|
$entity->set([
|
|
'messagesData' => $messages, 'messageCount' => count($messages),
|
|
'lastMessageAt' => date('Y-m-d H:i:s'),
|
|
'lastMessagePreview' => mb_substr($content, 0, 200),
|
|
]);
|
|
$this->entityManager->saveEntity($entity);
|
|
}
|
|
|
|
public function getMessages(string $conversationKey): array
|
|
{
|
|
$entity = $this->entityManager->getRDBRepository('AssistantConversation')
|
|
->where(['conversationKey' => $conversationKey, 'deleted' => false])->findOne();
|
|
if (!$entity) return [];
|
|
return json_decode(json_encode($entity->get('messagesData') ?? []), true);
|
|
}
|
|
|
|
public function getRecentConversations(?string $type = null, ?string $caseId = null, int $limit = 20): array
|
|
{
|
|
$where = ['assignedUserId' => $this->user->getId(), 'deleted' => false];
|
|
if ($type) $where['conversationType'] = $type;
|
|
if ($caseId) $where['caseId'] = $caseId;
|
|
|
|
$result = [];
|
|
foreach ($this->entityManager->getRDBRepository('AssistantConversation')
|
|
->select(['id', 'name', 'conversationType', 'conversationKey', 'status', 'messageCount', 'lastMessageAt', 'lastMessagePreview', 'caseId', 'caseName'])
|
|
->where($where)->order('lastMessageAt', 'DESC')->limit(0, $limit)->find() as $conv) {
|
|
$result[] = [
|
|
'id' => $conv->get('id'), 'name' => $conv->get('name'),
|
|
'conversationType' => $conv->get('conversationType'),
|
|
'conversationKey' => $conv->get('conversationKey'),
|
|
'status' => $conv->get('status'), 'messageCount' => $conv->get('messageCount'),
|
|
'lastMessageAt' => $conv->get('lastMessageAt'),
|
|
'lastMessagePreview' => $conv->get('lastMessagePreview'),
|
|
'caseId' => $conv->get('caseId'), 'caseName' => $conv->get('caseName'),
|
|
];
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
public function searchConversations(string $query, ?string $type = null, int $limit = 20): array
|
|
{
|
|
$where = ['deleted' => false, 'OR' => [['name*' => "%{$query}%"], ['lastMessagePreview*' => "%{$query}%"]]];
|
|
if ($type) $where['conversationType'] = $type;
|
|
|
|
$result = [];
|
|
foreach ($this->entityManager->getRDBRepository('AssistantConversation')
|
|
->select(['id', 'name', 'conversationType', 'conversationKey', 'messageCount', 'lastMessageAt', 'lastMessagePreview', 'caseId', 'caseName', 'assignedUserName'])
|
|
->where($where)->order('lastMessageAt', 'DESC')->limit(0, $limit)->find() as $conv) {
|
|
$result[] = [
|
|
'id' => $conv->get('id'), 'name' => $conv->get('name'),
|
|
'conversationType' => $conv->get('conversationType'),
|
|
'conversationKey' => $conv->get('conversationKey'),
|
|
'messageCount' => $conv->get('messageCount'),
|
|
'lastMessageAt' => $conv->get('lastMessageAt'),
|
|
'lastMessagePreview' => $conv->get('lastMessagePreview'),
|
|
'caseId' => $conv->get('caseId'), 'caseName' => $conv->get('caseName'),
|
|
'assignedUser' => $conv->get('assignedUserName'),
|
|
];
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
public function storePendingActions(string $conversationKey, string $caseId, string $userId, array $actions): void
|
|
{
|
|
$entity = $this->entityManager->getRDBRepository('AssistantConversation')
|
|
->where(['conversationKey' => $conversationKey, 'deleted' => false])->findOne();
|
|
if (!$entity) return;
|
|
|
|
$data = json_decode(json_encode($entity->get('messagesData') ?? []), true);
|
|
for ($i = count($data) - 1; $i >= 0; $i--) {
|
|
if (($data[$i]['role'] ?? '') === 'assistant') { $data[$i]['pendingActions'] = $actions; break; }
|
|
}
|
|
$entity->set('messagesData', $data);
|
|
$this->entityManager->saveEntity($entity);
|
|
|
|
$dir = 'data/tmp/assistant-conversations';
|
|
if (!is_dir($dir)) mkdir($dir, 0755, true);
|
|
file_put_contents($dir . '/' . $conversationKey . '.json', json_encode([
|
|
'caseId' => $caseId, 'userId' => $userId, 'actions' => $actions, 'createdAt' => date('Y-m-d H:i:s'),
|
|
]));
|
|
}
|
|
}
|