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,
|
||||
'plugTaxSettings' => true,
|
||||
'inquiry' => true,
|
||||
'ai' => true,
|
||||
];
|
||||
} elseif ($perm) {
|
||||
$result = [
|
||||
|
@ -591,6 +592,7 @@ class BusinessController extends AbstractController
|
|||
'plugGhestaManager' => $perm->isPlugGhestaManager(),
|
||||
'plugTaxSettings' => $perm->isPlugTaxSettings(),
|
||||
'inquiry' => $perm->isInquiry(),
|
||||
'ai' => $perm->isAi(),
|
||||
];
|
||||
}
|
||||
return $this->json($result);
|
||||
|
@ -662,6 +664,7 @@ class BusinessController extends AbstractController
|
|||
$perm->setPlugGhestaManager($params['plugGhestaManager']);
|
||||
$perm->setPlugTaxSettings($params['plugTaxSettings']);
|
||||
$perm->setInquiry($params['inquiry']);
|
||||
$perm->setAi($params['ai']);
|
||||
$entityManager->persist($perm);
|
||||
$entityManager->flush();
|
||||
$log->insert('تنظیمات پایه', 'ویرایش دسترسیهای کاربر با پست الکترونیکی ' . $user->getEmail(), $this->getUser(), $business);
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
<?php
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\AIConversation;
|
||||
use App\Entity\AIMessage;
|
||||
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\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\Security\Http\Attribute\CurrentUser;
|
||||
|
||||
class wizardController extends AbstractController
|
||||
{
|
||||
|
@ -18,9 +25,27 @@ class wizardController extends AbstractController
|
|||
}
|
||||
|
||||
#[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 {
|
||||
$acc = $access->hasRole('join');
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
// بررسی دسترسی هوش مصنوعی
|
||||
if (!$acc['ai']) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'شما دسترسی استفاده از هوش مصنوعی را ندارید'
|
||||
]);
|
||||
}
|
||||
|
||||
$params = json_decode($request->getContent(), true) ?? [];
|
||||
|
||||
if (!isset($params['message']) || empty($params['message'])) {
|
||||
|
@ -32,6 +57,7 @@ class wizardController extends AbstractController
|
|||
|
||||
$message = $params['message'];
|
||||
$options = $params['options'] ?? [];
|
||||
$conversationId = $params['conversationId'] ?? null;
|
||||
|
||||
// بررسی فعال بودن هوش مصنوعی
|
||||
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);
|
||||
|
||||
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 = [
|
||||
'success' => true,
|
||||
'response' => $result['response'],
|
||||
'conversationId' => $conversation->getId(),
|
||||
'model' => $result['model'] ?? null,
|
||||
'usage' => $result['usage'] ?? null
|
||||
];
|
||||
|
||||
// محاسبه هزینه در صورت وجود اطلاعات usage
|
||||
if (isset($result['usage'])) {
|
||||
$cost = $this->aiService->calculateCost($result['usage']);
|
||||
$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);
|
||||
} else {
|
||||
// حذف پیام کاربر در صورت خطا
|
||||
$entityManager->remove($userMessage);
|
||||
$entityManager->flush();
|
||||
|
||||
return $this->json([
|
||||
'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)]
|
||||
private Collection $plugHrmDocs;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'business', targetEntity: AIConversation::class, orphanRemoval: true)]
|
||||
private Collection $aiConversations;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->logs = new ArrayCollection();
|
||||
|
@ -343,6 +346,7 @@ class Business
|
|||
$this->accountingPackageOrders = new ArrayCollection();
|
||||
$this->PlugGhestaDocs = new ArrayCollection();
|
||||
$this->plugHrmDocs = new ArrayCollection();
|
||||
$this->aiConversations = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
|
@ -2086,4 +2090,34 @@ class Business
|
|||
}
|
||||
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)]
|
||||
private ?bool $inquiry = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?bool $ai = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
|
@ -619,4 +622,16 @@ class Permission
|
|||
|
||||
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')]
|
||||
private Collection $PlugGhestaDocs;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'user', targetEntity: AIConversation::class, orphanRemoval: true)]
|
||||
private Collection $aiConversations;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->userTokens = new ArrayCollection();
|
||||
|
@ -162,6 +165,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||
$this->accountingPackageOrders = new ArrayCollection();
|
||||
$this->backBuiltModules = new ArrayCollection();
|
||||
$this->PlugGhestaDocs = new ArrayCollection();
|
||||
$this->aiConversations = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
|
@ -997,4 +1001,34 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||
|
||||
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,
|
||||
'error' => 'خطا در ارتباط با سرویس هوش مصنوعی: ' . $e->getMessage()
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'خطای غیرمنتظره در سرویس هوش مصنوعی: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,7 +127,7 @@ class AIService
|
|||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $response['error'] ?? 'خطا در ارتباط با GapGPT'
|
||||
'error' => $response['error'] ?? 'خطا در ارتباط با GapGPT: پاسخ نامعتبر'
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -178,7 +183,7 @@ class AIService
|
|||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $response['error'] ?? 'خطا در ارتباط با AvalAI'
|
||||
'error' => $response['error'] ?? 'خطا در ارتباط با AvalAI: پاسخ نامعتبر'
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -239,7 +244,7 @@ class AIService
|
|||
|
||||
return [
|
||||
'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);
|
||||
$outputTokenPrice = (float) ($this->registryMGR->get('system', 'outputTokenPrice') ?: 0);
|
||||
|
@ -371,7 +376,13 @@ class AIService
|
|||
|
||||
$inputCost = ($inputTokens / 1000) * $inputTokenPrice;
|
||||
$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
|
||||
];
|
||||
|
||||
// بررسی دسترسی هوش مصنوعی
|
||||
$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()){
|
||||
//user is owner
|
||||
//user is owner - همه دسترسیها را دارد
|
||||
$accessArray['ai'] = true;
|
||||
return $accessArray;
|
||||
}
|
||||
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/starter-kit": "^2.11.7",
|
||||
"@tiptap/vue-3": "^2.11.7",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@vuelidate/core": "^2.0.3",
|
||||
"@vuelidate/validators": "^2.0.4",
|
||||
"@vueuse/core": "^13.1.0",
|
||||
|
@ -27,12 +28,14 @@
|
|||
"axios": "^1.8.4",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns-jalali": "^3.2.0-0",
|
||||
"dompurify": "^3.2.6",
|
||||
"downloadjs": "^1.4.7",
|
||||
"file-saver": "^2.0.5",
|
||||
"html5-qrcode": "^2.3.8",
|
||||
"jalali-moment": "^3.3.11",
|
||||
"libphonenumber-js": "^1.12.7",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "^16.1.0",
|
||||
"maska": "^3.1.1",
|
||||
"maz-ui": "^3.50.1",
|
||||
"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/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/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/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 },
|
||||
|
@ -798,6 +799,13 @@ export default {
|
|||
<span v-if="isCtrlShiftPressed" class="shortcut-key">{{ getShortcutKey('/acc/inquiry/panel') }}</span>
|
||||
</v-list-item-title>
|
||||
</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">
|
||||
<template v-slot:activator="{ props }">
|
||||
<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>
|
||||
</v-app-bar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-tooltip text="جادوگر" location="bottom">
|
||||
<v-tooltip text="هوش مصنوعی" location="bottom" v-if="permissions.ai">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn class="" stacked v-bind="props" to="/acc/wizard/home">
|
||||
<v-icon>mdi-robot</v-icon>
|
||||
|
|
|
@ -174,6 +174,18 @@
|
|||
:disabled="loadingSwitches.inquiry"
|
||||
></v-switch>
|
||||
</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-card-text>
|
||||
</v-card>
|
||||
|
@ -719,7 +731,8 @@ export default {
|
|||
plugHrmDocs: false,
|
||||
plugGhestaManager: false,
|
||||
plugTaxSettings: false,
|
||||
inquiry: false
|
||||
inquiry: false,
|
||||
ai: false
|
||||
};
|
||||
|
||||
axios.post('/api/business/get/user/permissions',
|
||||
|
|
|
@ -56,7 +56,6 @@
|
|||
<ul class="mb-0 ps-4">
|
||||
<li class="mb-2">به مبالغ انتخاب شده ۱۰ درصد مالیات بر ارزش افزوده اضافه میگردد.</li>
|
||||
<li class="mb-2">اعتبار خریداری شده بلافاصله به حساب شما اضافه خواهد شد.</li>
|
||||
<li>این اعتبار صرفاً برای استفاده از سرویس پیامک کوتاه قابل استفاده است و برای سایر خدمات قابل استفاده نمیباشد.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</v-alert>
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue