forked from morrning/hesabixCore
almost finish wizard
This commit is contained in:
parent
186229c848
commit
fc2aa36b0e
245
hesabixCore/src/Controller/AIConversationController.php
Normal file
245
hesabixCore/src/Controller/AIConversationController.php
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\AIConversation;
|
||||||
|
use App\Entity\AIMessage;
|
||||||
|
use App\Service\Access;
|
||||||
|
use App\Service\Extractor;
|
||||||
|
use App\Service\Jdate;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Security\Http\Attribute\CurrentUser;
|
||||||
|
|
||||||
|
class AIConversationController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route('/api/ai/conversations/list', name: 'api_ai_conversations_list', methods: ['POST'])]
|
||||||
|
public function listConversations(
|
||||||
|
Request $request,
|
||||||
|
Access $access,
|
||||||
|
EntityManagerInterface $entityManager,
|
||||||
|
Jdate $jdate
|
||||||
|
): JsonResponse {
|
||||||
|
$acc = $access->hasRole('join');
|
||||||
|
if (!$acc) {
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = $request->getPayload()->all();
|
||||||
|
$search = $params['search'] ?? '';
|
||||||
|
$category = $params['category'] ?? '';
|
||||||
|
|
||||||
|
$conversationRepo = $entityManager->getRepository(AIConversation::class);
|
||||||
|
|
||||||
|
if (!empty($search)) {
|
||||||
|
$conversations = $conversationRepo->searchByTitle($acc['user'], $acc['bid'], $search);
|
||||||
|
} elseif (!empty($category)) {
|
||||||
|
$conversations = $conversationRepo->findByCategory($acc['user'], $acc['bid'], $category);
|
||||||
|
} else {
|
||||||
|
$conversations = $conversationRepo->findActiveConversations($acc['user'], $acc['bid']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($conversations as $conversation) {
|
||||||
|
$messageRepo = $entityManager->getRepository(AIMessage::class);
|
||||||
|
$lastMessage = $messageRepo->findLastMessageByConversation($conversation);
|
||||||
|
$stats = $messageRepo->getConversationStats($conversation);
|
||||||
|
|
||||||
|
$result[] = [
|
||||||
|
'id' => $conversation->getId(),
|
||||||
|
'title' => $conversation->getTitle(),
|
||||||
|
'category' => $conversation->getCategory(),
|
||||||
|
'createdAt' => $jdate->jdate('Y/m/d H:i', $conversation->getCreatedAt()),
|
||||||
|
'updatedAt' => $jdate->jdate('Y/m/d H:i', $conversation->getUpdatedAt()),
|
||||||
|
'messageCount' => count($conversation->getMessages()),
|
||||||
|
'lastMessage' => $lastMessage ? $lastMessage->getContent() : '',
|
||||||
|
'stats' => $stats
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/ai/conversations/create', name: 'api_ai_conversations_create', methods: ['POST'])]
|
||||||
|
public function createConversation(
|
||||||
|
Request $request,
|
||||||
|
Access $access,
|
||||||
|
EntityManagerInterface $entityManager
|
||||||
|
): JsonResponse {
|
||||||
|
$acc = $access->hasRole('join');
|
||||||
|
if (!$acc) {
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = $request->getPayload()->all();
|
||||||
|
$title = $params['title'] ?? 'گفتگوی جدید';
|
||||||
|
$category = $params['category'] ?? 'عمومی';
|
||||||
|
|
||||||
|
$conversation = new AIConversation();
|
||||||
|
$conversation->setUser($acc['user']);
|
||||||
|
$conversation->setBusiness($acc['bid']);
|
||||||
|
$conversation->setTitle($title);
|
||||||
|
$conversation->setCategory($category);
|
||||||
|
|
||||||
|
$entityManager->persist($conversation);
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'id' => $conversation->getId(),
|
||||||
|
'title' => $conversation->getTitle(),
|
||||||
|
'category' => $conversation->getCategory(),
|
||||||
|
'createdAt' => $conversation->getCreatedAt()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/ai/conversations/{id}/messages', name: 'api_ai_conversations_messages', methods: ['POST'])]
|
||||||
|
public function getConversationMessages(
|
||||||
|
int $id,
|
||||||
|
Access $access,
|
||||||
|
EntityManagerInterface $entityManager,
|
||||||
|
Jdate $jdate
|
||||||
|
): JsonResponse {
|
||||||
|
$acc = $access->hasRole('join');
|
||||||
|
if (!$acc) {
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$conversation = $entityManager->getRepository(AIConversation::class)->find($id);
|
||||||
|
if (!$conversation) {
|
||||||
|
throw $this->createNotFoundException('گفتگو یافت نشد');
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی دسترسی کاربر به این گفتگو
|
||||||
|
if ($conversation->getUser()->getId() !== $acc['user']->getId() ||
|
||||||
|
$conversation->getBusiness()->getId() !== $acc['bid']->getId()) {
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$messageRepo = $entityManager->getRepository(AIMessage::class);
|
||||||
|
$messages = $messageRepo->findByConversation($conversation);
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($messages as $message) {
|
||||||
|
$result[] = [
|
||||||
|
'id' => $message->getId(),
|
||||||
|
'role' => $message->getRole(),
|
||||||
|
'content' => $message->getContent(),
|
||||||
|
'createdAt' => $jdate->jdate('Y/m/d H:i', $message->getCreatedAt()),
|
||||||
|
'inputTokens' => $message->getInputTokens(),
|
||||||
|
'outputTokens' => $message->getOutputTokens(),
|
||||||
|
'inputCost' => $message->getInputCost(),
|
||||||
|
'outputCost' => $message->getOutputCost(),
|
||||||
|
'totalCost' => $message->getTotalCost(),
|
||||||
|
'model' => $message->getModel(),
|
||||||
|
'agentSource' => $message->getAgentSource()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/ai/conversations/{id}/update', name: 'api_ai_conversations_update', methods: ['POST'])]
|
||||||
|
public function updateConversation(
|
||||||
|
int $id,
|
||||||
|
Request $request,
|
||||||
|
Access $access,
|
||||||
|
EntityManagerInterface $entityManager
|
||||||
|
): JsonResponse {
|
||||||
|
$acc = $access->hasRole('join');
|
||||||
|
if (!$acc) {
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$conversation = $entityManager->getRepository(AIConversation::class)->find($id);
|
||||||
|
if (!$conversation) {
|
||||||
|
throw $this->createNotFoundException('گفتگو یافت نشد');
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی دسترسی کاربر به این گفتگو
|
||||||
|
if ($conversation->getUser()->getId() !== $acc['user']->getId() ||
|
||||||
|
$conversation->getBusiness()->getId() !== $acc['bid']->getId()) {
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = $request->getPayload()->all();
|
||||||
|
|
||||||
|
if (isset($params['title'])) {
|
||||||
|
$conversation->setTitle($params['title']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($params['category'])) {
|
||||||
|
$conversation->setCategory($params['category']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$conversation->setUpdatedAt(time());
|
||||||
|
$entityManager->persist($conversation);
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
|
return $this->json(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/ai/conversations/{id}/delete', name: 'api_ai_conversations_delete', methods: ['POST'])]
|
||||||
|
public function deleteConversation(
|
||||||
|
int $id,
|
||||||
|
Access $access,
|
||||||
|
EntityManagerInterface $entityManager
|
||||||
|
): JsonResponse {
|
||||||
|
$acc = $access->hasRole('join');
|
||||||
|
if (!$acc) {
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$conversation = $entityManager->getRepository(AIConversation::class)->find($id);
|
||||||
|
if (!$conversation) {
|
||||||
|
throw $this->createNotFoundException('گفتگو یافت نشد');
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی دسترسی کاربر به این گفتگو
|
||||||
|
if ($conversation->getUser()->getId() !== $acc['user']->getId() ||
|
||||||
|
$conversation->getBusiness()->getId() !== $acc['bid']->getId()) {
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی اینکه آیا گفتگو قبلاً حذف نشده است
|
||||||
|
if ($conversation->isDeleted()) {
|
||||||
|
return $this->json([
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'این گفتگو قبلاً حذف شده است'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// حذف نرم گفتگو (تنها علامتگذاری به عنوان حذف شده)
|
||||||
|
$conversationRepo = $entityManager->getRepository(AIConversation::class);
|
||||||
|
$conversationRepo->softDelete($conversation, true);
|
||||||
|
|
||||||
|
return $this->json(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/ai/conversations/categories', name: 'api_ai_conversations_categories', methods: ['POST'])]
|
||||||
|
public function getCategories(
|
||||||
|
Access $access,
|
||||||
|
EntityManagerInterface $entityManager
|
||||||
|
): JsonResponse {
|
||||||
|
$acc = $access->hasRole('join');
|
||||||
|
if (!$acc) {
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$conversations = $entityManager->getRepository(AIConversation::class)
|
||||||
|
->findActiveConversations($acc['user'], $acc['bid']);
|
||||||
|
|
||||||
|
$categories = [];
|
||||||
|
foreach ($conversations as $conversation) {
|
||||||
|
$category = $conversation->getCategory();
|
||||||
|
if ($category && !in_array($category, $categories)) {
|
||||||
|
$categories[] = $category;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json($categories);
|
||||||
|
}
|
||||||
|
}
|
|
@ -546,6 +546,7 @@ class BusinessController extends AbstractController
|
||||||
'plugGhestaManager' => true,
|
'plugGhestaManager' => true,
|
||||||
'plugTaxSettings' => true,
|
'plugTaxSettings' => true,
|
||||||
'inquiry' => true,
|
'inquiry' => true,
|
||||||
|
'ai' => true,
|
||||||
];
|
];
|
||||||
} elseif ($perm) {
|
} elseif ($perm) {
|
||||||
$result = [
|
$result = [
|
||||||
|
@ -591,6 +592,7 @@ class BusinessController extends AbstractController
|
||||||
'plugGhestaManager' => $perm->isPlugGhestaManager(),
|
'plugGhestaManager' => $perm->isPlugGhestaManager(),
|
||||||
'plugTaxSettings' => $perm->isPlugTaxSettings(),
|
'plugTaxSettings' => $perm->isPlugTaxSettings(),
|
||||||
'inquiry' => $perm->isInquiry(),
|
'inquiry' => $perm->isInquiry(),
|
||||||
|
'ai' => $perm->isAi(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return $this->json($result);
|
return $this->json($result);
|
||||||
|
@ -662,6 +664,7 @@ class BusinessController extends AbstractController
|
||||||
$perm->setPlugGhestaManager($params['plugGhestaManager']);
|
$perm->setPlugGhestaManager($params['plugGhestaManager']);
|
||||||
$perm->setPlugTaxSettings($params['plugTaxSettings']);
|
$perm->setPlugTaxSettings($params['plugTaxSettings']);
|
||||||
$perm->setInquiry($params['inquiry']);
|
$perm->setInquiry($params['inquiry']);
|
||||||
|
$perm->setAi($params['ai']);
|
||||||
$entityManager->persist($perm);
|
$entityManager->persist($perm);
|
||||||
$entityManager->flush();
|
$entityManager->flush();
|
||||||
$log->insert('تنظیمات پایه', 'ویرایش دسترسیهای کاربر با پست الکترونیکی ' . $user->getEmail(), $this->getUser(), $business);
|
$log->insert('تنظیمات پایه', 'ویرایش دسترسیهای کاربر با پست الکترونیکی ' . $user->getEmail(), $this->getUser(), $business);
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
<?php
|
<?php
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\AIConversation;
|
||||||
|
use App\Entity\AIMessage;
|
||||||
use App\Service\AIService;
|
use App\Service\AIService;
|
||||||
|
use App\Service\Access;
|
||||||
|
use App\Service\Extractor;
|
||||||
|
use App\Service\Log;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\Security\Http\Attribute\CurrentUser;
|
||||||
|
|
||||||
class wizardController extends AbstractController
|
class wizardController extends AbstractController
|
||||||
{
|
{
|
||||||
|
@ -18,9 +25,27 @@ class wizardController extends AbstractController
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/api/wizard/talk', name: 'wizard_talk', methods: ['POST'])]
|
#[Route('/api/wizard/talk', name: 'wizard_talk', methods: ['POST'])]
|
||||||
public function wizard_talk(Request $request): JsonResponse
|
public function wizard_talk(
|
||||||
|
Request $request,
|
||||||
|
Access $access,
|
||||||
|
EntityManagerInterface $entityManager,
|
||||||
|
Log $log
|
||||||
|
): JsonResponse
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$acc = $access->hasRole('join');
|
||||||
|
if (!$acc) {
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی دسترسی هوش مصنوعی
|
||||||
|
if (!$acc['ai']) {
|
||||||
|
return $this->json([
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'شما دسترسی استفاده از هوش مصنوعی را ندارید'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$params = json_decode($request->getContent(), true) ?? [];
|
$params = json_decode($request->getContent(), true) ?? [];
|
||||||
|
|
||||||
if (!isset($params['message']) || empty($params['message'])) {
|
if (!isset($params['message']) || empty($params['message'])) {
|
||||||
|
@ -32,6 +57,7 @@ class wizardController extends AbstractController
|
||||||
|
|
||||||
$message = $params['message'];
|
$message = $params['message'];
|
||||||
$options = $params['options'] ?? [];
|
$options = $params['options'] ?? [];
|
||||||
|
$conversationId = $params['conversationId'] ?? null;
|
||||||
|
|
||||||
// بررسی فعال بودن هوش مصنوعی
|
// بررسی فعال بودن هوش مصنوعی
|
||||||
if (!$this->aiService->isAIEnabled()) {
|
if (!$this->aiService->isAIEnabled()) {
|
||||||
|
@ -41,28 +67,128 @@ class wizardController extends AbstractController
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// بررسی اعتبار کسب و کار
|
||||||
|
$business = $acc['bid'];
|
||||||
|
$currentBalance = (float) ($business->getSmsCharge() ?? 0);
|
||||||
|
|
||||||
|
// محاسبه هزینه تخمینی (حداقل 100 ریال)
|
||||||
|
$estimatedCost = 100;
|
||||||
|
|
||||||
|
if ($currentBalance < $estimatedCost) {
|
||||||
|
return $this->json([
|
||||||
|
'success' => false,
|
||||||
|
'error' => "اعتبار شما کافی نیست (اعتبار فعلی: {$currentBalance} ریال). برای شارژ حساب خود به بخش شارژ مراجعه کنید.",
|
||||||
|
'balance' => $currentBalance,
|
||||||
|
'required' => $estimatedCost,
|
||||||
|
'showChargeButton' => true
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// دریافت یا ایجاد گفتگو
|
||||||
|
$conversation = null;
|
||||||
|
if ($conversationId) {
|
||||||
|
$conversation = $entityManager->getRepository(AIConversation::class)->find($conversationId);
|
||||||
|
if (!$conversation ||
|
||||||
|
$conversation->getUser()->getId() !== $acc['user']->getId() ||
|
||||||
|
$conversation->getBusiness()->getId() !== $acc['bid']->getId()) {
|
||||||
|
return $this->json([
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'گفتگو یافت نشد'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ایجاد گفتگوی جدید
|
||||||
|
$conversation = new AIConversation();
|
||||||
|
$conversation->setUser($acc['user']);
|
||||||
|
$conversation->setBusiness($acc['bid']);
|
||||||
|
$conversation->setTitle(substr($message, 0, 50) . '...');
|
||||||
|
$conversation->setCategory('عمومی');
|
||||||
|
$entityManager->persist($conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ذخیره پیام کاربر
|
||||||
|
$userMessage = new AIMessage();
|
||||||
|
$userMessage->setConversation($conversation);
|
||||||
|
$userMessage->setRole('user');
|
||||||
|
$userMessage->setContent($message);
|
||||||
|
$userMessage->setModel($this->aiService->getAIModel());
|
||||||
|
$userMessage->setAgentSource($this->aiService->getAIAgentSource());
|
||||||
|
$entityManager->persist($userMessage);
|
||||||
|
|
||||||
// ارسال درخواست به سرویس هوش مصنوعی
|
// ارسال درخواست به سرویس هوش مصنوعی
|
||||||
$result = $this->aiService->sendRequest($message, $options);
|
$result = $this->aiService->sendRequest($message, $options);
|
||||||
|
|
||||||
if ($result['success']) {
|
if ($result['success']) {
|
||||||
|
// ذخیره پاسخ هوش مصنوعی
|
||||||
|
$aiMessage = new AIMessage();
|
||||||
|
$aiMessage->setConversation($conversation);
|
||||||
|
$aiMessage->setRole('assistant');
|
||||||
|
$aiMessage->setContent($result['response']);
|
||||||
|
$aiMessage->setModel($result['model'] ?? $this->aiService->getAIModel());
|
||||||
|
$aiMessage->setAgentSource($this->aiService->getAIAgentSource());
|
||||||
|
|
||||||
|
// ذخیره اطلاعات usage
|
||||||
|
if (isset($result['usage'])) {
|
||||||
|
$aiMessage->setInputTokens($result['usage']['prompt_tokens'] ?? null);
|
||||||
|
$aiMessage->setOutputTokens($result['usage']['completion_tokens'] ?? null);
|
||||||
|
|
||||||
|
// محاسبه هزینه
|
||||||
|
$cost = $this->aiService->calculateCost($result['usage']);
|
||||||
|
$aiMessage->setInputCost($cost['input_cost'] ?? null);
|
||||||
|
$aiMessage->setOutputCost($cost['output_cost'] ?? null);
|
||||||
|
$aiMessage->setTotalCost($cost['total_cost'] ?? null);
|
||||||
|
}
|
||||||
|
|
||||||
|
$entityManager->persist($aiMessage);
|
||||||
|
|
||||||
|
// بهروزرسانی زمان آخرین تغییر گفتگو
|
||||||
|
$conversation->setUpdatedAt(time());
|
||||||
|
$entityManager->persist($conversation);
|
||||||
|
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
$response = [
|
$response = [
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'response' => $result['response'],
|
'response' => $result['response'],
|
||||||
|
'conversationId' => $conversation->getId(),
|
||||||
'model' => $result['model'] ?? null,
|
'model' => $result['model'] ?? null,
|
||||||
'usage' => $result['usage'] ?? null
|
'usage' => $result['usage'] ?? null
|
||||||
];
|
];
|
||||||
|
|
||||||
// محاسبه هزینه در صورت وجود اطلاعات usage
|
// محاسبه هزینه در صورت وجود اطلاعات usage
|
||||||
if (isset($result['usage'])) {
|
if (isset($result['usage'])) {
|
||||||
$cost = $this->aiService->calculateCost($result['usage']);
|
|
||||||
$response['cost'] = $cost;
|
$response['cost'] = $cost;
|
||||||
|
|
||||||
|
// کسر اعتبار از کسب و کار
|
||||||
|
$totalCost = $cost['total_cost'] ?? 0;
|
||||||
|
if ($totalCost > 0) {
|
||||||
|
// گرد کردن هزینه به عدد صحیح
|
||||||
|
$totalCost = (int) round($totalCost);
|
||||||
|
$newBalance = (int) ($currentBalance - $totalCost);
|
||||||
|
$business->setSmsCharge((string) $newBalance);
|
||||||
|
$entityManager->persist($business);
|
||||||
|
|
||||||
|
// ثبت لاگ کسر اعتبار
|
||||||
|
$log->insert(
|
||||||
|
'هوش مصنوعی',
|
||||||
|
"کسر اعتبار برای استفاده از هوش مصنوعی: {$totalCost} ریال (اعتبار قبلی: {$currentBalance} ریال، اعتبار جدید: {$newBalance} ریال)",
|
||||||
|
$acc['user'],
|
||||||
|
$business
|
||||||
|
);
|
||||||
|
|
||||||
|
$entityManager->flush();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->json($response);
|
return $this->json($response);
|
||||||
} else {
|
} else {
|
||||||
|
// حذف پیام کاربر در صورت خطا
|
||||||
|
$entityManager->remove($userMessage);
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
return $this->json([
|
return $this->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'error' => $result['error']
|
'error' => $result['error'] ?? 'خطای نامشخص در سرویس هوش مصنوعی'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,4 +315,37 @@ class wizardController extends AbstractController
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/api/wizard/balance', name: 'wizard_balance', methods: ['GET'])]
|
||||||
|
public function wizard_balance(Access $access): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$acc = $access->hasRole('join');
|
||||||
|
if (!$acc) {
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی دسترسی هوش مصنوعی
|
||||||
|
if (!$acc['ai']) {
|
||||||
|
return $this->json([
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'شما دسترسی استفاده از هوش مصنوعی را ندارید'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$business = $acc['bid'];
|
||||||
|
$balance = (float) ($business->getSmsCharge() ?? 0);
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'success' => true,
|
||||||
|
'balance' => $balance,
|
||||||
|
'formatted_balance' => number_format($balance, 0, '.', ',') . ' ریال'
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return $this->json([
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'خطا در دریافت اعتبار: ' . $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
174
hesabixCore/src/Entity/AIConversation.php
Normal file
174
hesabixCore/src/Entity/AIConversation.php
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\AIConversationRepository;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: AIConversationRepository::class)]
|
||||||
|
class AIConversation
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'aiConversations')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
private ?User $user = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'aiConversations')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
private ?Business $business = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255)]
|
||||||
|
private ?string $title = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $category = null;
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $createdAt = null;
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $updatedAt = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?bool $isActive = true;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?bool $deleted = false;
|
||||||
|
|
||||||
|
#[ORM\OneToMany(mappedBy: 'conversation', targetEntity: AIMessage::class, orphanRemoval: true)]
|
||||||
|
private Collection $messages;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->messages = new ArrayCollection();
|
||||||
|
$this->createdAt = time();
|
||||||
|
$this->updatedAt = time();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): ?User
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUser(?User $user): static
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBusiness(): ?Business
|
||||||
|
{
|
||||||
|
return $this->business;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBusiness(?Business $business): static
|
||||||
|
{
|
||||||
|
$this->business = $business;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): ?string
|
||||||
|
{
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTitle(string $title): static
|
||||||
|
{
|
||||||
|
$this->title = $title;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCategory(): ?string
|
||||||
|
{
|
||||||
|
return $this->category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCategory(?string $category): static
|
||||||
|
{
|
||||||
|
$this->category = $category;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt(): ?int
|
||||||
|
{
|
||||||
|
return $this->createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreatedAt(int $createdAt): static
|
||||||
|
{
|
||||||
|
$this->createdAt = $createdAt;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUpdatedAt(): ?int
|
||||||
|
{
|
||||||
|
return $this->updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUpdatedAt(int $updatedAt): static
|
||||||
|
{
|
||||||
|
$this->updatedAt = $updatedAt;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isActive(): ?bool
|
||||||
|
{
|
||||||
|
return $this->isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setIsActive(?bool $isActive): static
|
||||||
|
{
|
||||||
|
$this->isActive = $isActive;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isDeleted(): ?bool
|
||||||
|
{
|
||||||
|
return $this->deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDeleted(?bool $deleted): static
|
||||||
|
{
|
||||||
|
$this->deleted = $deleted;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, AIMessage>
|
||||||
|
*/
|
||||||
|
public function getMessages(): Collection
|
||||||
|
{
|
||||||
|
return $this->messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addMessage(AIMessage $message): static
|
||||||
|
{
|
||||||
|
if (!$this->messages->contains($message)) {
|
||||||
|
$this->messages->add($message);
|
||||||
|
$message->setConversation($this);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeMessage(AIMessage $message): static
|
||||||
|
{
|
||||||
|
if ($this->messages->removeElement($message)) {
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($message->getConversation() === $this) {
|
||||||
|
$message->setConversation(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
181
hesabixCore/src/Entity/AIMessage.php
Normal file
181
hesabixCore/src/Entity/AIMessage.php
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\AIMessageRepository;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: AIMessageRepository::class)]
|
||||||
|
class AIMessage
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'messages')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
private ?AIConversation $conversation = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 20)]
|
||||||
|
private ?string $role = null; // 'user' یا 'assistant'
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::TEXT)]
|
||||||
|
private ?string $content = null;
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $createdAt = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?int $inputTokens = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?int $outputTokens = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?float $inputCost = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?float $outputCost = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?float $totalCost = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $model = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $agentSource = null;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->createdAt = time();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConversation(): ?AIConversation
|
||||||
|
{
|
||||||
|
return $this->conversation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setConversation(?AIConversation $conversation): static
|
||||||
|
{
|
||||||
|
$this->conversation = $conversation;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRole(): ?string
|
||||||
|
{
|
||||||
|
return $this->role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRole(string $role): static
|
||||||
|
{
|
||||||
|
$this->role = $role;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContent(): ?string
|
||||||
|
{
|
||||||
|
return $this->content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setContent(string $content): static
|
||||||
|
{
|
||||||
|
$this->content = $content;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt(): ?int
|
||||||
|
{
|
||||||
|
return $this->createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreatedAt(int $createdAt): static
|
||||||
|
{
|
||||||
|
$this->createdAt = $createdAt;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInputTokens(): ?int
|
||||||
|
{
|
||||||
|
return $this->inputTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setInputTokens(?int $inputTokens): static
|
||||||
|
{
|
||||||
|
$this->inputTokens = $inputTokens;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOutputTokens(): ?int
|
||||||
|
{
|
||||||
|
return $this->outputTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setOutputTokens(?int $outputTokens): static
|
||||||
|
{
|
||||||
|
$this->outputTokens = $outputTokens;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInputCost(): ?float
|
||||||
|
{
|
||||||
|
return $this->inputCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setInputCost(?float $inputCost): static
|
||||||
|
{
|
||||||
|
$this->inputCost = $inputCost;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOutputCost(): ?float
|
||||||
|
{
|
||||||
|
return $this->outputCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setOutputCost(?float $outputCost): static
|
||||||
|
{
|
||||||
|
$this->outputCost = $outputCost;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalCost(): ?float
|
||||||
|
{
|
||||||
|
return $this->totalCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTotalCost(?float $totalCost): static
|
||||||
|
{
|
||||||
|
$this->totalCost = $totalCost;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getModel(): ?string
|
||||||
|
{
|
||||||
|
return $this->model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setModel(?string $model): static
|
||||||
|
{
|
||||||
|
$this->model = $model;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAgentSource(): ?string
|
||||||
|
{
|
||||||
|
return $this->agentSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAgentSource(?string $agentSource): static
|
||||||
|
{
|
||||||
|
$this->agentSource = $agentSource;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -300,6 +300,9 @@ class Business
|
||||||
#[ORM\OneToMany(mappedBy: 'business', targetEntity: PlugHrmDoc::class)]
|
#[ORM\OneToMany(mappedBy: 'business', targetEntity: PlugHrmDoc::class)]
|
||||||
private Collection $plugHrmDocs;
|
private Collection $plugHrmDocs;
|
||||||
|
|
||||||
|
#[ORM\OneToMany(mappedBy: 'business', targetEntity: AIConversation::class, orphanRemoval: true)]
|
||||||
|
private Collection $aiConversations;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->logs = new ArrayCollection();
|
$this->logs = new ArrayCollection();
|
||||||
|
@ -343,6 +346,7 @@ class Business
|
||||||
$this->accountingPackageOrders = new ArrayCollection();
|
$this->accountingPackageOrders = new ArrayCollection();
|
||||||
$this->PlugGhestaDocs = new ArrayCollection();
|
$this->PlugGhestaDocs = new ArrayCollection();
|
||||||
$this->plugHrmDocs = new ArrayCollection();
|
$this->plugHrmDocs = new ArrayCollection();
|
||||||
|
$this->aiConversations = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
|
@ -2086,4 +2090,34 @@ class Business
|
||||||
}
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, AIConversation>
|
||||||
|
*/
|
||||||
|
public function getAiConversations(): Collection
|
||||||
|
{
|
||||||
|
return $this->aiConversations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addAiConversation(AIConversation $aiConversation): static
|
||||||
|
{
|
||||||
|
if (!$this->aiConversations->contains($aiConversation)) {
|
||||||
|
$this->aiConversations->add($aiConversation);
|
||||||
|
$aiConversation->setBusiness($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeAiConversation(AIConversation $aiConversation): static
|
||||||
|
{
|
||||||
|
if ($this->aiConversations->removeElement($aiConversation)) {
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($aiConversation->getBusiness() === $this) {
|
||||||
|
$aiConversation->setBusiness(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,9 @@ class Permission
|
||||||
#[ORM\Column(nullable: true)]
|
#[ORM\Column(nullable: true)]
|
||||||
private ?bool $inquiry = null;
|
private ?bool $inquiry = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?bool $ai = null;
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
|
@ -619,4 +622,16 @@ class Permission
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isAi(): ?bool
|
||||||
|
{
|
||||||
|
return $this->ai;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAi(?bool $ai): static
|
||||||
|
{
|
||||||
|
$this->ai = $ai;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,6 +137,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||||
#[ORM\OneToMany(targetEntity: PlugGhestaDoc::class, mappedBy: 'submitter')]
|
#[ORM\OneToMany(targetEntity: PlugGhestaDoc::class, mappedBy: 'submitter')]
|
||||||
private Collection $PlugGhestaDocs;
|
private Collection $PlugGhestaDocs;
|
||||||
|
|
||||||
|
#[ORM\OneToMany(mappedBy: 'user', targetEntity: AIConversation::class, orphanRemoval: true)]
|
||||||
|
private Collection $aiConversations;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->userTokens = new ArrayCollection();
|
$this->userTokens = new ArrayCollection();
|
||||||
|
@ -162,6 +165,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||||
$this->accountingPackageOrders = new ArrayCollection();
|
$this->accountingPackageOrders = new ArrayCollection();
|
||||||
$this->backBuiltModules = new ArrayCollection();
|
$this->backBuiltModules = new ArrayCollection();
|
||||||
$this->PlugGhestaDocs = new ArrayCollection();
|
$this->PlugGhestaDocs = new ArrayCollection();
|
||||||
|
$this->aiConversations = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
|
@ -997,4 +1001,34 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, AIConversation>
|
||||||
|
*/
|
||||||
|
public function getAiConversations(): Collection
|
||||||
|
{
|
||||||
|
return $this->aiConversations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addAiConversation(AIConversation $aiConversation): static
|
||||||
|
{
|
||||||
|
if (!$this->aiConversations->contains($aiConversation)) {
|
||||||
|
$this->aiConversations->add($aiConversation);
|
||||||
|
$aiConversation->setUser($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeAiConversation(AIConversation $aiConversation): static
|
||||||
|
{
|
||||||
|
if ($this->aiConversations->removeElement($aiConversation)) {
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($aiConversation->getUser() === $this) {
|
||||||
|
$aiConversation->setUser(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
122
hesabixCore/src/Repository/AIConversationRepository.php
Normal file
122
hesabixCore/src/Repository/AIConversationRepository.php
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\AIConversation;
|
||||||
|
use App\Entity\Business;
|
||||||
|
use App\Entity\User;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<AIConversation>
|
||||||
|
*
|
||||||
|
* @method AIConversation|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
|
* @method AIConversation|null findOneBy(array $criteria, array $orderBy = null)
|
||||||
|
* @method AIConversation[] findAll()
|
||||||
|
* @method AIConversation[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
*/
|
||||||
|
class AIConversationRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, AIConversation::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(AIConversation $entity, bool $flush = false): void
|
||||||
|
{
|
||||||
|
$this->getEntityManager()->persist($entity);
|
||||||
|
|
||||||
|
if ($flush) {
|
||||||
|
$this->getEntityManager()->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remove(AIConversation $entity, bool $flush = false): void
|
||||||
|
{
|
||||||
|
$this->getEntityManager()->remove($entity);
|
||||||
|
|
||||||
|
if ($flush) {
|
||||||
|
$this->getEntityManager()->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* حذف نرم گفتگو (تنها علامتگذاری به عنوان حذف شده)
|
||||||
|
*/
|
||||||
|
public function softDelete(AIConversation $entity, bool $flush = false): void
|
||||||
|
{
|
||||||
|
$entity->setDeleted(true);
|
||||||
|
$this->getEntityManager()->persist($entity);
|
||||||
|
|
||||||
|
if ($flush) {
|
||||||
|
$this->getEntityManager()->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* دریافت گفتگوهای کاربر در یک کسب و کار خاص
|
||||||
|
*/
|
||||||
|
public function findByUserAndBusiness(User $user, Business $business, bool $activeOnly = true): array
|
||||||
|
{
|
||||||
|
$criteria = [
|
||||||
|
'user' => $user,
|
||||||
|
'business' => $business,
|
||||||
|
'deleted' => false
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($activeOnly) {
|
||||||
|
$criteria['isActive'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->findBy($criteria, ['updatedAt' => 'DESC']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* دریافت گفتگوهای فعال کاربر در کسب و کار فعلی
|
||||||
|
*/
|
||||||
|
public function findActiveConversations(User $user, Business $business): array
|
||||||
|
{
|
||||||
|
return $this->findBy([
|
||||||
|
'user' => $user,
|
||||||
|
'business' => $business,
|
||||||
|
'isActive' => true,
|
||||||
|
'deleted' => false
|
||||||
|
], ['updatedAt' => 'DESC']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* جستجو در گفتگوها بر اساس عنوان
|
||||||
|
*/
|
||||||
|
public function searchByTitle(User $user, Business $business, string $searchTerm): array
|
||||||
|
{
|
||||||
|
$qb = $this->createQueryBuilder('c')
|
||||||
|
->where('c.user = :user')
|
||||||
|
->andWhere('c.business = :business')
|
||||||
|
->andWhere('c.isActive = :active')
|
||||||
|
->andWhere('c.deleted = :deleted')
|
||||||
|
->andWhere('c.title LIKE :search')
|
||||||
|
->setParameter('user', $user)
|
||||||
|
->setParameter('business', $business)
|
||||||
|
->setParameter('active', true)
|
||||||
|
->setParameter('deleted', false)
|
||||||
|
->setParameter('search', '%' . $searchTerm . '%')
|
||||||
|
->orderBy('c.updatedAt', 'DESC');
|
||||||
|
|
||||||
|
return $qb->getQuery()->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* دریافت گفتگوها بر اساس دستهبندی
|
||||||
|
*/
|
||||||
|
public function findByCategory(User $user, Business $business, string $category): array
|
||||||
|
{
|
||||||
|
return $this->findBy([
|
||||||
|
'user' => $user,
|
||||||
|
'business' => $business,
|
||||||
|
'category' => $category,
|
||||||
|
'isActive' => true,
|
||||||
|
'deleted' => false
|
||||||
|
], ['updatedAt' => 'DESC']);
|
||||||
|
}
|
||||||
|
}
|
88
hesabixCore/src/Repository/AIMessageRepository.php
Normal file
88
hesabixCore/src/Repository/AIMessageRepository.php
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\AIMessage;
|
||||||
|
use App\Entity\AIConversation;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<AIMessage>
|
||||||
|
*
|
||||||
|
* @method AIMessage|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
|
* @method AIMessage|null findOneBy(array $criteria, array $orderBy = null)
|
||||||
|
* @method AIMessage[] findAll()
|
||||||
|
* @method AIMessage[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
*/
|
||||||
|
class AIMessageRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, AIMessage::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(AIMessage $entity, bool $flush = false): void
|
||||||
|
{
|
||||||
|
$this->getEntityManager()->persist($entity);
|
||||||
|
|
||||||
|
if ($flush) {
|
||||||
|
$this->getEntityManager()->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remove(AIMessage $entity, bool $flush = false): void
|
||||||
|
{
|
||||||
|
$this->getEntityManager()->remove($entity);
|
||||||
|
|
||||||
|
if ($flush) {
|
||||||
|
$this->getEntityManager()->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* دریافت پیامهای یک گفتگو
|
||||||
|
*/
|
||||||
|
public function findByConversation(AIConversation $conversation): array
|
||||||
|
{
|
||||||
|
return $this->findBy(['conversation' => $conversation], ['createdAt' => 'ASC']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* دریافت آخرین پیام یک گفتگو
|
||||||
|
*/
|
||||||
|
public function findLastMessageByConversation(AIConversation $conversation): ?AIMessage
|
||||||
|
{
|
||||||
|
return $this->findOneBy(['conversation' => $conversation], ['createdAt' => 'DESC']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* محاسبه آمار استفاده برای یک گفتگو
|
||||||
|
*/
|
||||||
|
public function getConversationStats(AIConversation $conversation): array
|
||||||
|
{
|
||||||
|
$qb = $this->createQueryBuilder('m')
|
||||||
|
->select('SUM(m.inputTokens) as totalInputTokens')
|
||||||
|
->addSelect('SUM(m.outputTokens) as totalOutputTokens')
|
||||||
|
->addSelect('SUM(m.inputCost) as totalInputCost')
|
||||||
|
->addSelect('SUM(m.outputCost) as totalOutputCost')
|
||||||
|
->addSelect('SUM(m.totalCost) as totalCost')
|
||||||
|
->where('m.conversation = :conversation')
|
||||||
|
->setParameter('conversation', $conversation);
|
||||||
|
|
||||||
|
$result = $qb->getQuery()->getSingleResult();
|
||||||
|
|
||||||
|
$totalInputTokens = $result['totalInputTokens'] ?? 0;
|
||||||
|
$totalOutputTokens = $result['totalOutputTokens'] ?? 0;
|
||||||
|
$totalTokens = $totalInputTokens + $totalOutputTokens;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'totalInputTokens' => $totalInputTokens,
|
||||||
|
'totalOutputTokens' => $totalOutputTokens,
|
||||||
|
'totalTokens' => $totalTokens,
|
||||||
|
'totalInputCost' => $result['totalInputCost'] ?? 0,
|
||||||
|
'totalOutputCost' => $result['totalOutputCost'] ?? 0,
|
||||||
|
'totalCost' => $result['totalCost'] ?? 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,6 +52,11 @@ class AIService
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'error' => 'خطا در ارتباط با سرویس هوش مصنوعی: ' . $e->getMessage()
|
'error' => 'خطا در ارتباط با سرویس هوش مصنوعی: ' . $e->getMessage()
|
||||||
];
|
];
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'خطای غیرمنتظره در سرویس هوش مصنوعی: ' . $e->getMessage()
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +127,7 @@ class AIService
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'error' => $response['error'] ?? 'خطا در ارتباط با GapGPT'
|
'error' => $response['error'] ?? 'خطا در ارتباط با GapGPT: پاسخ نامعتبر'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +183,7 @@ class AIService
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'error' => $response['error'] ?? 'خطا در ارتباط با AvalAI'
|
'error' => $response['error'] ?? 'خطا در ارتباط با AvalAI: پاسخ نامعتبر'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,7 +244,7 @@ class AIService
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'error' => $response['error'] ?? 'خطا در ارتباط با مدل لوکال'
|
'error' => $response['error'] ?? 'خطا در ارتباط با مدل لوکال: پاسخ نامعتبر'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,7 +366,7 @@ class AIService
|
||||||
/**
|
/**
|
||||||
* محاسبه هزینه بر اساس تعداد توکنها
|
* محاسبه هزینه بر اساس تعداد توکنها
|
||||||
*/
|
*/
|
||||||
public function calculateCost(array $usage): float
|
public function calculateCost(array $usage): array
|
||||||
{
|
{
|
||||||
$inputTokenPrice = (float) ($this->registryMGR->get('system', 'inputTokenPrice') ?: 0);
|
$inputTokenPrice = (float) ($this->registryMGR->get('system', 'inputTokenPrice') ?: 0);
|
||||||
$outputTokenPrice = (float) ($this->registryMGR->get('system', 'outputTokenPrice') ?: 0);
|
$outputTokenPrice = (float) ($this->registryMGR->get('system', 'outputTokenPrice') ?: 0);
|
||||||
|
@ -371,7 +376,13 @@ class AIService
|
||||||
|
|
||||||
$inputCost = ($inputTokens / 1000) * $inputTokenPrice;
|
$inputCost = ($inputTokens / 1000) * $inputTokenPrice;
|
||||||
$outputCost = ($outputTokens / 1000) * $outputTokenPrice;
|
$outputCost = ($outputTokens / 1000) * $outputTokenPrice;
|
||||||
|
$totalCost = $inputCost + $outputCost;
|
||||||
|
|
||||||
return $inputCost + $outputCost;
|
// گرد کردن هزینهها به اعداد صحیح
|
||||||
|
return [
|
||||||
|
'input_cost' => (int) round($inputCost),
|
||||||
|
'output_cost' => (int) round($outputCost),
|
||||||
|
'total_cost' => (int) round($totalCost)
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -96,8 +96,19 @@ class Access
|
||||||
'money'=>$money
|
'money'=>$money
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// بررسی دسترسی هوش مصنوعی
|
||||||
|
$aiPermission = $this->em->getRepository(Permission::class)->findOneBy([
|
||||||
|
'bid'=>$bid,
|
||||||
|
'user'=>$this->user
|
||||||
|
]);
|
||||||
|
$accessArray['ai'] = false;
|
||||||
|
if($aiPermission && $aiPermission->isAi()){
|
||||||
|
$accessArray['ai'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
if($bid->getOwner()->getEmail() === $this->user->getUserIdentifier()){
|
if($bid->getOwner()->getEmail() === $this->user->getUserIdentifier()){
|
||||||
//user is owner
|
//user is owner - همه دسترسیها را دارد
|
||||||
|
$accessArray['ai'] = true;
|
||||||
return $accessArray;
|
return $accessArray;
|
||||||
}
|
}
|
||||||
elseif ($this->user && $roll == 'join' && count($this->em->getRepository(Permission::class)->findBy(['user'=>$this->user,'bid'=>$bid]))){
|
elseif ($this->user && $roll == 'join' && count($this->em->getRepository(Permission::class)->findBy(['user'=>$this->user,'bid'=>$bid]))){
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
"@tiptap/extension-text-align": "^2.11.7",
|
"@tiptap/extension-text-align": "^2.11.7",
|
||||||
"@tiptap/starter-kit": "^2.11.7",
|
"@tiptap/starter-kit": "^2.11.7",
|
||||||
"@tiptap/vue-3": "^2.11.7",
|
"@tiptap/vue-3": "^2.11.7",
|
||||||
|
"@types/dompurify": "^3.0.5",
|
||||||
"@vuelidate/core": "^2.0.3",
|
"@vuelidate/core": "^2.0.3",
|
||||||
"@vuelidate/validators": "^2.0.4",
|
"@vuelidate/validators": "^2.0.4",
|
||||||
"@vueuse/core": "^13.1.0",
|
"@vueuse/core": "^13.1.0",
|
||||||
|
@ -27,12 +28,14 @@
|
||||||
"axios": "^1.8.4",
|
"axios": "^1.8.4",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"date-fns-jalali": "^3.2.0-0",
|
"date-fns-jalali": "^3.2.0-0",
|
||||||
|
"dompurify": "^3.2.6",
|
||||||
"downloadjs": "^1.4.7",
|
"downloadjs": "^1.4.7",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"html5-qrcode": "^2.3.8",
|
"html5-qrcode": "^2.3.8",
|
||||||
"jalali-moment": "^3.3.11",
|
"jalali-moment": "^3.3.11",
|
||||||
"libphonenumber-js": "^1.12.7",
|
"libphonenumber-js": "^1.12.7",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"marked": "^16.1.0",
|
||||||
"maska": "^3.1.1",
|
"maska": "^3.1.1",
|
||||||
"maz-ui": "^3.50.1",
|
"maz-ui": "^3.50.1",
|
||||||
"pinia": "^3.0.2",
|
"pinia": "^3.0.2",
|
||||||
|
|
|
@ -170,6 +170,7 @@ export default {
|
||||||
{ path: '/acc/business/logs', key: '=', label: this.$t('drawer.history'), ctrl: true, shift: true, permission: () => this.permissions.log },
|
{ path: '/acc/business/logs', key: '=', label: this.$t('drawer.history'), ctrl: true, shift: true, permission: () => this.permissions.log },
|
||||||
{ path: '/acc/plugin/repservice/order/list', key: '[', label: this.$t('drawer.repservice_reqs'), ctrl: true, shift: true, permission: () => this.permissions.plugRepservice && this.isPluginActive('repservice') },
|
{ path: '/acc/plugin/repservice/order/list', key: '[', label: this.$t('drawer.repservice_reqs'), ctrl: true, shift: true, permission: () => this.permissions.plugRepservice && this.isPluginActive('repservice') },
|
||||||
{ path: '/acc/inquiry/panel', key: ']', label: this.$t('drawer.inquiry'), ctrl: true, shift: true, permission: () => true },
|
{ path: '/acc/inquiry/panel', key: ']', label: this.$t('drawer.inquiry'), ctrl: true, shift: true, permission: () => true },
|
||||||
|
{ path: '/acc/wizard/home', key: 'A', label: 'هوش مصنوعی', ctrl: true, shift: true, permission: () => this.permissions.ai },
|
||||||
{ path: '/acc/printers/list', key: ';', label: this.$t('drawer.cloud_printers'), ctrl: true, shift: true, permission: () => this.permissions.owner },
|
{ path: '/acc/printers/list', key: ';', label: this.$t('drawer.cloud_printers'), ctrl: true, shift: true, permission: () => this.permissions.owner },
|
||||||
{ path: '/acc/sms/panel', key: '`', label: this.$t('drawer.sms_panel'), ctrl: true, shift: true, permission: () => this.permissions.owner },
|
{ path: '/acc/sms/panel', key: '`', label: this.$t('drawer.sms_panel'), ctrl: true, shift: true, permission: () => this.permissions.owner },
|
||||||
{ path: '/acc/archive/list', key: '\'', label: this.$t('drawer.archive_files'), ctrl: true, shift: true, permission: () => this.permissions.archiveUpload || this.permissions.archiveMod || this.permissions.archiveDelete },
|
{ path: '/acc/archive/list', key: '\'', label: this.$t('drawer.archive_files'), ctrl: true, shift: true, permission: () => this.permissions.archiveUpload || this.permissions.archiveMod || this.permissions.archiveDelete },
|
||||||
|
@ -798,6 +799,13 @@ export default {
|
||||||
<span v-if="isCtrlShiftPressed" class="shortcut-key">{{ getShortcutKey('/acc/inquiry/panel') }}</span>
|
<span v-if="isCtrlShiftPressed" class="shortcut-key">{{ getShortcutKey('/acc/inquiry/panel') }}</span>
|
||||||
</v-list-item-title>
|
</v-list-item-title>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
<v-list-item v-if="permissions.ai" to="/acc/wizard/home">
|
||||||
|
<template v-slot:prepend><v-icon icon="mdi-robot"></v-icon></template>
|
||||||
|
<v-list-item-title>
|
||||||
|
هوش مصنوعی
|
||||||
|
<span v-if="isCtrlShiftPressed" class="shortcut-key">{{ getShortcutKey('/acc/wizard/home') }}</span>
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
<v-list-group v-show="isPluginActive('hrm') && permissions.plugHrmDocs">
|
<v-list-group v-show="isPluginActive('hrm') && permissions.plugHrmDocs">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-list-item class="text-dark" v-bind="props" :title="$t('drawer.hrm')">
|
<v-list-item class="text-dark" v-bind="props" :title="$t('drawer.hrm')">
|
||||||
|
@ -952,7 +960,7 @@ export default {
|
||||||
<span class="d-none d-sm-flex">{{ business.name }}</span>
|
<span class="d-none d-sm-flex">{{ business.name }}</span>
|
||||||
</v-app-bar-title>
|
</v-app-bar-title>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-tooltip text="جادوگر" location="bottom">
|
<v-tooltip text="هوش مصنوعی" location="bottom" v-if="permissions.ai">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn class="" stacked v-bind="props" to="/acc/wizard/home">
|
<v-btn class="" stacked v-bind="props" to="/acc/wizard/home">
|
||||||
<v-icon>mdi-robot</v-icon>
|
<v-icon>mdi-robot</v-icon>
|
||||||
|
|
|
@ -174,6 +174,18 @@
|
||||||
:disabled="loadingSwitches.inquiry"
|
:disabled="loadingSwitches.inquiry"
|
||||||
></v-switch>
|
></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.ai"
|
||||||
|
label="هوش مصنوعی"
|
||||||
|
@change="savePerms('ai')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.ai"
|
||||||
|
:disabled="loadingSwitches.ai"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
@ -719,7 +731,8 @@ export default {
|
||||||
plugHrmDocs: false,
|
plugHrmDocs: false,
|
||||||
plugGhestaManager: false,
|
plugGhestaManager: false,
|
||||||
plugTaxSettings: false,
|
plugTaxSettings: false,
|
||||||
inquiry: false
|
inquiry: false,
|
||||||
|
ai: false
|
||||||
};
|
};
|
||||||
|
|
||||||
axios.post('/api/business/get/user/permissions',
|
axios.post('/api/business/get/user/permissions',
|
||||||
|
|
|
@ -56,7 +56,6 @@
|
||||||
<ul class="mb-0 ps-4">
|
<ul class="mb-0 ps-4">
|
||||||
<li class="mb-2">به مبالغ انتخاب شده ۱۰ درصد مالیات بر ارزش افزوده اضافه میگردد.</li>
|
<li class="mb-2">به مبالغ انتخاب شده ۱۰ درصد مالیات بر ارزش افزوده اضافه میگردد.</li>
|
||||||
<li class="mb-2">اعتبار خریداری شده بلافاصله به حساب شما اضافه خواهد شد.</li>
|
<li class="mb-2">اعتبار خریداری شده بلافاصله به حساب شما اضافه خواهد شد.</li>
|
||||||
<li>این اعتبار صرفاً برای استفاده از سرویس پیامک کوتاه قابل استفاده است و برای سایر خدمات قابل استفاده نمیباشد.</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</v-alert>
|
</v-alert>
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue