forked from morrning/hesabixCore
progress in ai
This commit is contained in:
parent
8a3ebc64cb
commit
9b5e7947cd
|
@ -95,3 +95,8 @@ services:
|
|||
|
||||
App\Twig\NumberFormatExtension:
|
||||
tags: ['twig.extension']
|
||||
|
||||
App\Cog\PersonService:
|
||||
arguments:
|
||||
$entityManager: '@doctrine.orm.entity_manager'
|
||||
$access: '@App\Service\Access'
|
||||
|
|
78
hesabixCore/src/Cog/PersonService.php
Normal file
78
hesabixCore/src/Cog/PersonService.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
namespace App\Cog;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use App\Entity\Person;
|
||||
use App\Entity\PersonType;
|
||||
use App\Entity\HesabdariRow;
|
||||
use App\Service\Explore;
|
||||
use App\Service\Access;
|
||||
|
||||
/**
|
||||
* سرویس مدیریت اشخاص
|
||||
*
|
||||
* این سرویس برای مدیریت عملیات مربوط به اشخاص استفاده میشود
|
||||
*/
|
||||
class PersonService
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
private Access $access;
|
||||
|
||||
/**
|
||||
* سازنده سرویس
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $entityManager, Access $access)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->access = $access;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت اطلاعات شخص
|
||||
*
|
||||
* @param string $code کد شخص
|
||||
* @param array $acc اطلاعات دسترسی
|
||||
* @return array اطلاعات شخص
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function getPersonInfo(string $code, array $acc): array
|
||||
{
|
||||
$person = $this->entityManager->getRepository(Person::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $code
|
||||
]);
|
||||
|
||||
$types = $this->entityManager->getRepository(PersonType::class)->findAll();
|
||||
$response = Explore::ExplorePerson($person, $types);
|
||||
|
||||
$rows = $this->entityManager->getRepository(HesabdariRow::class)->findBy([
|
||||
'person' => $person,
|
||||
'bid' => $acc['bid']
|
||||
]);
|
||||
|
||||
$bs = 0;
|
||||
$bd = 0;
|
||||
|
||||
foreach ($rows as $row) {
|
||||
try {
|
||||
$doc = $row->getDoc();
|
||||
if ($doc && $doc->getMoney() && $doc->getYear() &&
|
||||
$doc->getMoney()->getId() == $acc['money']->getId() &&
|
||||
$doc->getYear()->getId() == $acc['year']->getId()) {
|
||||
$bs += (float) $row->getBs(); // بستانکار
|
||||
$bd += (float) $row->getBd(); // بدهکار
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// در صورت بروز خطا، این ردیف را نادیده میگیریم و به ردیف بعدی میرویم
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$response['bs'] = $bs;
|
||||
$response['bd'] = $bd;
|
||||
$response['balance'] = $bs - $bd;
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ use App\Entity\Storeroom;
|
|||
use App\Entity\StoreroomItem;
|
||||
use App\Entity\StoreroomTicket;
|
||||
use App\Service\Explore;
|
||||
use App\Cog\PersonService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
|
@ -61,40 +62,13 @@ class PersonsController extends AbstractController
|
|||
* @throws \ReflectionException
|
||||
*/
|
||||
#[Route('/api/person/info/{code}', name: 'app_persons_info')]
|
||||
public function app_persons_info($code, Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
|
||||
public function app_persons_info($code, Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager, PersonService $personService): JsonResponse
|
||||
{
|
||||
$acc = $access->hasRole('person');
|
||||
if (!$acc)
|
||||
throw $this->createAccessDeniedException();
|
||||
$person = $entityManager->getRepository(Person::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $code
|
||||
]);
|
||||
$types = $entityManager->getRepository(PersonType::class)->findAll();
|
||||
$response = Explore::ExplorePerson($person, $types);
|
||||
$rows = $entityManager->getRepository(HesabdariRow::class)->findBy([
|
||||
'person' => $person,
|
||||
'bid' => $acc['bid']
|
||||
]);
|
||||
$bs = 0;
|
||||
$bd = 0;
|
||||
foreach ($rows as $row) {
|
||||
try {
|
||||
$doc = $row->getDoc();
|
||||
if ($doc && $doc->getMoney() && $doc->getYear() &&
|
||||
$doc->getMoney()->getId() == $acc['money']->getId() &&
|
||||
$doc->getYear()->getId() == $acc['year']->getId()) {
|
||||
$bs += (float) $row->getBs(); // بستانکار
|
||||
$bd += (float) $row->getBd(); // بدهکار
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// در صورت بروز خطا، این ردیف را نادیده میگیریم و به ردیف بعدی میرویم
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$response['bs'] = $bs;
|
||||
$response['bd'] = $bd;
|
||||
$response['balance'] = $bs - $bd;
|
||||
|
||||
$response = $personService->getPersonInfo($code, $acc);
|
||||
|
||||
return $this->json($response);
|
||||
}
|
||||
|
|
|
@ -35,14 +35,23 @@ class wizardController extends AbstractController
|
|||
try {
|
||||
$acc = $access->hasRole('join');
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'دسترسی غیرمجاز',
|
||||
'debug_info' => [
|
||||
'access' => $acc
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// بررسی دسترسی هوش مصنوعی
|
||||
if (!$acc['ai']) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'شما دسترسی استفاده از هوش مصنوعی را ندارید'
|
||||
'error' => 'شما دسترسی استفاده از هوش مصنوعی را ندارید',
|
||||
'debug_info' => [
|
||||
'ai_access' => $acc['ai']
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -51,7 +60,10 @@ class wizardController extends AbstractController
|
|||
if (!isset($params['message']) || empty($params['message'])) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'پیام الزامی است'
|
||||
'error' => 'پیام الزامی است',
|
||||
'debug_info' => [
|
||||
'params' => $params
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -64,32 +76,35 @@ class wizardController extends AbstractController
|
|||
if (!$aiStatus['isEnabled']) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'سرویس هوش مصنوعی غیرفعال است'
|
||||
'error' => 'سرویس هوش مصنوعی غیرفعال است',
|
||||
'debug_info' => [
|
||||
'ai_status' => $aiStatus
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// بررسی اعتبار کسب و کار
|
||||
$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
|
||||
'showChargeButton' => true,
|
||||
'debug_info' => [
|
||||
'balance' => $currentBalance,
|
||||
'required' => $estimatedCost
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// استفاده از AGIService برای مدیریت گفتگو و ارسال درخواست
|
||||
$result = $this->agiService->sendRequest($message, $business, $acc['user'], $conversationId);
|
||||
$result = $this->agiService->sendRequest($message, $business, $acc['user'], $conversationId, $acc);
|
||||
|
||||
if ($result['success']) {
|
||||
// بررسی وجود کلید response
|
||||
$responseContent = $result['response'] ?? $result['message'] ?? 'عملیات با موفقیت انجام شد';
|
||||
|
||||
$response = [
|
||||
|
@ -132,7 +147,7 @@ class wizardController extends AbstractController
|
|||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => $result['error'] ?? 'خطای نامشخص در سرویس هوش مصنوعی',
|
||||
'debug_info' => $result['debug_info'] ?? null
|
||||
'debug_info' => $result['debug_info'] ?? ['fallback' => 'no debug info from service', 'result' => $result]
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -184,112 +199,5 @@ class wizardController extends AbstractController
|
|||
}
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/models', name: 'wizard_models', methods: ['GET'])]
|
||||
public function wizard_models(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$agentSource = $this->agiService->getAIAgentSource();
|
||||
$currentModel = $this->agiService->getAIModel();
|
||||
|
||||
// لیست مدلهای موجود بر اساس منبع ایجنت
|
||||
$models = [];
|
||||
|
||||
switch ($agentSource) {
|
||||
case 'gapgpt':
|
||||
$models = [
|
||||
'gpt-4o' => 'مدل پیشرفته',
|
||||
'gpt-4-turbo' => 'مدل سریع',
|
||||
'gpt-3.5-turbo' => 'مدل استاندارد',
|
||||
'claude-3-opus' => 'مدل تحلیلی',
|
||||
'claude-3-sonnet' => 'مدل متعادل',
|
||||
'gemini-pro' => 'مدل چندمنظوره'
|
||||
];
|
||||
break;
|
||||
case 'avalai':
|
||||
$models = [
|
||||
'gpt-4' => 'مدل پیشرفته',
|
||||
'gpt-3.5-turbo' => 'مدل استاندارد',
|
||||
'claude-3' => 'مدل تحلیلی',
|
||||
'gemini-pro' => 'مدل چندمنظوره'
|
||||
];
|
||||
break;
|
||||
case 'local':
|
||||
$models = [
|
||||
'local-model' => 'مدل محلی',
|
||||
'custom-model' => 'مدل سفارشی'
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'models' => $models,
|
||||
'current_model' => $currentModel,
|
||||
'service_name' => $this->agiService->getServiceDisplayName($agentSource)
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'خطا در دریافت مدلها: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/settings', name: 'wizard_settings', methods: ['GET'])]
|
||||
public function wizard_settings(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$agentSource = $this->agiService->getAIAgentSource();
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'settings' => [
|
||||
'aiEnabled' => $this->agiService->isAIEnabled(),
|
||||
'serviceName' => $this->agiService->getServiceDisplayName($agentSource),
|
||||
'aiModel' => $this->agiService->getAIModel(),
|
||||
'inputTokenPrice' => $this->agiService->getInputTokenPrice(),
|
||||
'outputTokenPrice' => $this->agiService->getOutputTokenPrice(),
|
||||
'aiPrompt' => $this->agiService->getAIPrompt()
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'خطا در دریافت تنظیمات: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#[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()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Entity;
|
|||
use App\Repository\AIMessageRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Ignore;
|
||||
|
||||
#[ORM\Entity(repositoryClass: AIMessageRepository::class)]
|
||||
class AIMessage
|
||||
|
@ -16,6 +17,7 @@ class AIMessage
|
|||
|
||||
#[ORM\ManyToOne(inversedBy: 'messages')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
#[Ignore]
|
||||
private ?AIConversation $conversation = null;
|
||||
|
||||
#[ORM\Column(length: 20)]
|
||||
|
|
|
@ -6,6 +6,7 @@ use App\Repository\InvoiceTypeRepository;
|
|||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Ignore;
|
||||
|
||||
#[ORM\Entity(repositoryClass: InvoiceTypeRepository::class)]
|
||||
class InvoiceType
|
||||
|
@ -28,12 +29,14 @@ class InvoiceType
|
|||
private ?string $type = null;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'InvoiceLabel', targetEntity: HesabdariDoc::class)]
|
||||
#[Ignore]
|
||||
private Collection $hesabdariDocs;
|
||||
|
||||
/**
|
||||
* @var Collection<int, PreInvoiceDoc>
|
||||
*/
|
||||
#[ORM\OneToMany(mappedBy: 'invoiceLabel', targetEntity: PreInvoiceDoc::class)]
|
||||
#[Ignore]
|
||||
private Collection $preInvoiceDocs;
|
||||
|
||||
public function __construct()
|
||||
|
|
|
@ -7,6 +7,7 @@ use Doctrine\Common\Collections\ArrayCollection;
|
|||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Ignore;
|
||||
|
||||
#[ORM\Entity(repositoryClass: PlugGhestaDocRepository::class)]
|
||||
class PlugGhestaDoc
|
||||
|
@ -18,9 +19,11 @@ class PlugGhestaDoc
|
|||
|
||||
#[ORM\ManyToOne(inversedBy: 'PlugGhestaDocs')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
#[Ignore]
|
||||
private ?Business $bid = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'PlugGhestaDocs')]
|
||||
#[Ignore]
|
||||
private ?User $submitter = null;
|
||||
|
||||
#[ORM\Column(length: 25)]
|
||||
|
@ -43,16 +46,19 @@ class PlugGhestaDoc
|
|||
|
||||
#[ORM\ManyToOne(inversedBy: 'PlugGhestaDocs')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
#[Ignore]
|
||||
private ?Person $person = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, PlugGhestaItem>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: PlugGhestaItem::class, mappedBy: 'doc', orphanRemoval: true)]
|
||||
#[Ignore]
|
||||
private Collection $plugGhestaItems;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'plugGhestaDocs')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
#[Ignore]
|
||||
private ?HesabdariDoc $mainDoc = null;
|
||||
|
||||
public function __construct()
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Entity;
|
|||
|
||||
use App\Repository\PlugGhestaItemRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Ignore;
|
||||
|
||||
#[ORM\Entity(repositoryClass: PlugGhestaItemRepository::class)]
|
||||
class PlugGhestaItem
|
||||
|
@ -15,6 +16,7 @@ class PlugGhestaItem
|
|||
|
||||
#[ORM\ManyToOne(inversedBy: 'plugGhestaItems')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
#[Ignore]
|
||||
private ?PlugGhestaDoc $doc = null;
|
||||
|
||||
#[ORM\Column(length: 25)]
|
||||
|
@ -27,6 +29,7 @@ class PlugGhestaItem
|
|||
private ?int $num = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'plugGhestaItems')]
|
||||
#[Ignore]
|
||||
private ?HesabdariDoc $hesabdariDoc = null;
|
||||
|
||||
public function getId(): ?int
|
||||
|
|
|
@ -6,6 +6,7 @@ use App\Repository\PlugHrmDocRepository;
|
|||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Ignore;
|
||||
|
||||
#[ORM\Entity(repositoryClass: PlugHrmDocRepository::class)]
|
||||
class PlugHrmDoc
|
||||
|
@ -23,19 +24,23 @@ class PlugHrmDoc
|
|||
|
||||
#[ORM\ManyToOne(inversedBy: 'plugHrmDocs')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
#[Ignore]
|
||||
private ?Business $business = null;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
#[Ignore]
|
||||
private ?User $creator = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?int $createDate = null;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'doc', targetEntity: PlugHrmDocItem::class, orphanRemoval: true)]
|
||||
#[Ignore]
|
||||
private Collection $items;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'plugHrmDocs')]
|
||||
#[Ignore]
|
||||
private ?HesabdariDoc $hesabdariDoc = null;
|
||||
|
||||
public function __construct()
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Entity;
|
|||
|
||||
use App\Repository\PlugHrmDocItemRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Ignore;
|
||||
|
||||
#[ORM\Entity(repositoryClass: PlugHrmDocItemRepository::class)]
|
||||
class PlugHrmDocItem
|
||||
|
@ -15,10 +16,12 @@ class PlugHrmDocItem
|
|||
|
||||
#[ORM\ManyToOne(inversedBy: 'items')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
#[Ignore]
|
||||
private ?PlugHrmDoc $doc = null;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
#[Ignore]
|
||||
private ?Person $person = null;
|
||||
|
||||
#[ORM\Column]
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Entity;
|
|||
|
||||
use App\Repository\PreInvoiceItemRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Ignore;
|
||||
|
||||
#[ORM\Entity(repositoryClass: PreInvoiceItemRepository::class)]
|
||||
class PreInvoiceItem
|
||||
|
@ -15,6 +16,7 @@ class PreInvoiceItem
|
|||
|
||||
#[ORM\ManyToOne(inversedBy: 'preInvoiceItems')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
#[Ignore]
|
||||
private ?Commodity $commodity = null;
|
||||
|
||||
#[ORM\Column(length: 100, nullable: true)]
|
||||
|
@ -46,6 +48,7 @@ class PreInvoiceItem
|
|||
|
||||
#[ORM\ManyToOne(inversedBy: 'preInvoiceItems')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
#[Ignore]
|
||||
private ?PreInvoiceDoc $doc = null;
|
||||
|
||||
public function getId(): ?int
|
||||
|
|
|
@ -10,6 +10,9 @@ use App\Service\Log;
|
|||
use App\Service\Provider;
|
||||
use App\Service\AGI\Promps\PromptService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class AGIService
|
||||
{
|
||||
|
@ -18,19 +21,25 @@ class AGIService
|
|||
private $log;
|
||||
private $provider;
|
||||
private $promptService;
|
||||
private $httpClient;
|
||||
private $httpKernel;
|
||||
|
||||
public function __construct(
|
||||
EntityManagerInterface $entityManager,
|
||||
registryMGR $registryMGR,
|
||||
Log $log,
|
||||
Provider $provider,
|
||||
PromptService $promptService
|
||||
PromptService $promptService,
|
||||
HttpClientInterface $httpClient,
|
||||
HttpKernelInterface $httpKernel
|
||||
) {
|
||||
$this->em = $entityManager;
|
||||
$this->registryMGR = $registryMGR;
|
||||
$this->log = $log;
|
||||
$this->provider = $provider;
|
||||
$this->promptService = $promptService;
|
||||
$this->httpClient = $httpClient;
|
||||
$this->httpKernel = $httpKernel;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,9 +48,10 @@ class AGIService
|
|||
* @param Business|null $business کسب و کار
|
||||
* @param mixed $user کاربر
|
||||
* @param int|null $conversationId شناسه گفتگو (اختیاری)
|
||||
* @param array|null $acc اطلاعات دسترسی
|
||||
* @return array
|
||||
*/
|
||||
public function sendRequest(string $message, ?Business $business = null, $user = null, ?int $conversationId = null): array
|
||||
public function sendRequest(string $message, ?Business $business = null, $user = null, ?int $conversationId = null, ?array $acc = null): array
|
||||
{
|
||||
// بررسی فعال بودن هوش مصنوعی
|
||||
$status = $this->checkAIServiceStatus();
|
||||
|
@ -49,7 +59,16 @@ class AGIService
|
|||
return [
|
||||
'success' => false,
|
||||
'error' => 'سرویس هوش مصنوعی غیرفعال است.',
|
||||
'status' => $status
|
||||
'debug_info' => [
|
||||
'context' => 'sendRequest',
|
||||
'service_status' => $status,
|
||||
'inputs' => [
|
||||
'message' => $message,
|
||||
'business' => $business,
|
||||
'user' => $user,
|
||||
'conversationId' => $conversationId
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -69,76 +88,80 @@ class AGIService
|
|||
if (!$apiKey) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'کلید API برای سرویس هوش مصنوعی تنظیم نشده است.'
|
||||
'error' => 'کلید API برای سرویس هوش مصنوعی تنظیم نشده است.',
|
||||
'debug_info' => [
|
||||
'context' => 'sendRequest',
|
||||
'service' => $service,
|
||||
'inputs' => [
|
||||
'message' => $message,
|
||||
'business' => $business,
|
||||
'user' => $user,
|
||||
'conversationId' => $conversationId
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
// ارسال درخواست به سرویس هوش مصنوعی
|
||||
$response = $this->sendToAIService($prompt, $apiKey, $service, $conversationHistory);
|
||||
// ارسال درخواست با function calling
|
||||
$result = $this->sendToAIServiceWithFunctionCalling($prompt, $apiKey, $service, $conversationHistory, $acc);
|
||||
|
||||
if ($response['success']) {
|
||||
// پردازش پاسخ
|
||||
$aiResponse = $this->extractAIResponse($response['data']);
|
||||
$cost = $this->calculateCostFromResponse($response['data']);
|
||||
if (!$result['success']) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// استخراج پاسخ نهایی
|
||||
$aiResponse = $this->extractAIResponse($result['data']);
|
||||
$cost = $this->calculateCostFromResponse($result['data']);
|
||||
|
||||
// ذخیره پاسخ هوش مصنوعی
|
||||
$this->saveAIMessage($conversation, $aiResponse, $response['data'], $cost);
|
||||
$this->saveAIMessage($conversation, $aiResponse, $result['data'], $cost);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'response' => $aiResponse,
|
||||
'usage' => $response['data']['usage'] ?? null,
|
||||
'conversationId' => $conversation->getId(),
|
||||
'model' => $this->getAIModel(),
|
||||
'usage' => $result['data']['usage'] ?? [],
|
||||
'cost' => $cost,
|
||||
'service' => $service,
|
||||
'model' => $this->getAIModel(),
|
||||
'conversation_id' => $conversation->getId()
|
||||
];
|
||||
}
|
||||
|
||||
return $response;
|
||||
'debug_info' => [
|
||||
'context' => 'sendRequest',
|
||||
'function_calls' => $result['function_calls'] ?? [],
|
||||
'tool_results' => $result['tool_results'] ?? []
|
||||
]
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->log->error('خطا در ارسال درخواست به هوش مصنوعی: ' . $e->getMessage(), [
|
||||
'context' => 'AGIService::sendRequest',
|
||||
'message' => $message,
|
||||
'business' => $business,
|
||||
'user' => $user,
|
||||
'conversationId' => $conversationId,
|
||||
'exception' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'خطا در پردازش درخواست: ' . $e->getMessage()
|
||||
'error' => 'خطا در پردازش درخواست: ' . $e->getMessage(),
|
||||
'debug_info' => [
|
||||
'context' => 'sendRequest',
|
||||
'exception' => $e->getMessage(),
|
||||
'inputs' => [
|
||||
'message' => $message,
|
||||
'business' => $business,
|
||||
'user' => $user,
|
||||
'conversationId' => $conversationId
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ساخت پرامپ هوشمند
|
||||
* ارسال درخواست به سرویس هوش مصنوعی با پشتیبانی از function calling
|
||||
*/
|
||||
private function buildSmartPrompt(string $message, ?Business $business, array $conversationHistory = []): string
|
||||
{
|
||||
// دریافت پرامپهای پایه
|
||||
$basePrompts = $this->promptService->getAllPromptsAsString();
|
||||
|
||||
$prompt = $basePrompts;
|
||||
|
||||
// اضافه کردن اطلاعات کسب و کار
|
||||
if ($business) {
|
||||
$prompt .= "\n\nاطلاعات کسب و کار: نام: {$business->getName()}, کد اقتصادی: {$business->getCodeeghtesadi()}.";
|
||||
}
|
||||
|
||||
// اضافه کردن تاریخچه گفتگو
|
||||
if (!empty($conversationHistory)) {
|
||||
$prompt .= "\n\n📜 تاریخچه گفتگو:\n";
|
||||
foreach ($conversationHistory as $historyItem) {
|
||||
$role = $historyItem['role'] === 'user' ? 'کاربر' : 'دستیار';
|
||||
$prompt .= "{$role}: {$historyItem['content']}\n";
|
||||
}
|
||||
$prompt .= "\n💡 نکته: لطفاً context گفتگو را حفظ کنید و به سوالات قبلی مراجعه کنید.";
|
||||
}
|
||||
|
||||
$prompt .= "\n\nسوال کاربر: " . $message;
|
||||
|
||||
return $prompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* ارسال درخواست به سرویس هوش مصنوعی
|
||||
*/
|
||||
private function sendToAIService(string $prompt, string $apiKey, string $service, array $conversationHistory = []): array
|
||||
private function sendToAIServiceWithFunctionCalling(string $prompt, string $apiKey, string $service, array $conversationHistory = [], ?array $acc = null): array
|
||||
{
|
||||
$urls = $this->getServiceUrls($service);
|
||||
$model = $this->getAIModel();
|
||||
|
@ -160,27 +183,222 @@ class AGIService
|
|||
'content' => $prompt
|
||||
];
|
||||
|
||||
// تعریف ابزارهای موجود
|
||||
$tools = $this->buildToolsFromPromptServices();
|
||||
|
||||
$data = [
|
||||
'model' => $model,
|
||||
'messages' => $messages,
|
||||
'tools' => $tools,
|
||||
'tool_choice' => 'auto', // اجازه انتخاب ابزار به مدل
|
||||
'max_tokens' => 12000,
|
||||
'temperature' => 0.1
|
||||
];
|
||||
|
||||
// تلاش برای ارسال به URL های مختلف
|
||||
foreach ($urls as $url) {
|
||||
$result = $this->makeHttpRequest($url, $data, $apiKey);
|
||||
if ($result['success']) {
|
||||
return $result;
|
||||
$maxIterations = 5; // حداکثر تعداد تکرار برای جلوگیری از حلقه بینهایت
|
||||
$iteration = 0;
|
||||
$functionCalls = [];
|
||||
$toolResults = [];
|
||||
|
||||
while ($iteration < $maxIterations) {
|
||||
$iteration++;
|
||||
|
||||
// تلاش برای ارسال به URL های مختلف
|
||||
$result = null;
|
||||
foreach ($urls as $url) {
|
||||
$result = $this->makeHttpRequest($url, $data, $apiKey);
|
||||
if ($result['success']) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$result || !$result['success']) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'خطا در ارتباط با سرور هوش مصنوعی.',
|
||||
'debug_info' => [
|
||||
'context' => 'sendToAIServiceWithFunctionCalling',
|
||||
'url_list' => $urls,
|
||||
'data' => $data,
|
||||
'iteration' => $iteration
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$responseData = $result['data'];
|
||||
$choices = $responseData['choices'] ?? [];
|
||||
|
||||
if (empty($choices)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'پاسخ نامعتبر از سرور هوش مصنوعی.',
|
||||
'debug_info' => [
|
||||
'context' => 'sendToAIServiceWithFunctionCalling',
|
||||
'response_data' => $responseData,
|
||||
'iteration' => $iteration
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$choice = $choices[0];
|
||||
$message = $choice['message'] ?? [];
|
||||
$toolCalls = $message['tool_calls'] ?? [];
|
||||
|
||||
// اگر ابزاری فراخوانی نشده، پاسخ نهایی است
|
||||
if (empty($toolCalls)) {
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => $responseData,
|
||||
'function_calls' => $functionCalls,
|
||||
'tool_results' => $toolResults
|
||||
];
|
||||
}
|
||||
|
||||
// اجرای ابزارهای فراخوانی شده
|
||||
$toolResults = [];
|
||||
foreach ($toolCalls as $toolCall) {
|
||||
$functionCall = $toolCall['function'] ?? [];
|
||||
$functionName = $functionCall['name'] ?? '';
|
||||
$functionArgs = json_decode($functionCall['arguments'] ?? '{}', true);
|
||||
if ($acc && !isset($functionArgs['acc'])) {
|
||||
$functionArgs['acc'] = $acc;
|
||||
}
|
||||
$functionCalls[] = [
|
||||
'name' => $functionName,
|
||||
'arguments' => $functionArgs
|
||||
];
|
||||
|
||||
// اجرای ابزار
|
||||
$toolResult = $this->callTool($functionName, $functionArgs);
|
||||
$toolResults[] = [
|
||||
'tool_call_id' => $toolCall['id'] ?? '',
|
||||
'role' => 'tool',
|
||||
'content' => json_encode($toolResult, JSON_UNESCAPED_UNICODE)
|
||||
];
|
||||
}
|
||||
|
||||
// اضافه کردن نتایج ابزارها به messages برای ارسال مجدد
|
||||
$messages[] = [
|
||||
'role' => 'assistant',
|
||||
'content' => null,
|
||||
'tool_calls' => $toolCalls
|
||||
];
|
||||
|
||||
foreach ($toolResults as $toolResult) {
|
||||
$messages[] = $toolResult;
|
||||
}
|
||||
|
||||
// بهروزرسانی data برای ارسال مجدد
|
||||
$data['messages'] = $messages;
|
||||
}
|
||||
|
||||
|
||||
// اگر به حداکثر تعداد تکرار رسیدیم
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'خطا در ارتباط با سرور هوش مصنوعی.'
|
||||
'error' => 'تعداد تکرار بیش از حد مجاز.',
|
||||
'debug_info' => [
|
||||
'context' => 'sendToAIServiceWithFunctionCalling',
|
||||
'max_iterations' => $maxIterations,
|
||||
'function_calls' => $functionCalls,
|
||||
'tool_results' => $toolResults
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* اجرای ابزار
|
||||
*/
|
||||
private function callTool(string $tool, array $params, array $context = [])
|
||||
{
|
||||
try {
|
||||
switch ($tool) {
|
||||
case 'getPersonInfo':
|
||||
// استفاده مستقیم از سرویس Cog\PersonService
|
||||
return $this->callGetPersonInfoFromCog($params);
|
||||
default:
|
||||
return [
|
||||
'error' => 'ابزار ناشناخته: ' . $tool
|
||||
];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->log->error('خطا در اجرای ابزار: ' . $e->getMessage(), [
|
||||
'context' => 'AGIService::callTool',
|
||||
'tool' => $tool,
|
||||
'params' => $params,
|
||||
'exception' => $e->getMessage()
|
||||
]);
|
||||
|
||||
return [
|
||||
'error' => 'خطا در اجرای ابزار: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* اجرای ابزار getPersonInfo با استفاده از سرویس Cog\PersonService
|
||||
*/
|
||||
private function callGetPersonInfoFromCog(array $params)
|
||||
{
|
||||
$code = $params['code'] ?? null;
|
||||
if (!$code) {
|
||||
return [
|
||||
'error' => 'کد شخص الزامی است'
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
// دریافت اطلاعات دسترسی (acc) از context یا پارامترها
|
||||
$acc = $params['acc'] ?? null;
|
||||
if (!$acc) {
|
||||
return [
|
||||
'error' => 'اطلاعات دسترسی (acc) الزامی است'
|
||||
];
|
||||
}
|
||||
// استفاده از سرویس Cog\PersonService
|
||||
$personService = new \App\Cog\PersonService($this->em, $this->provider->getAccessService());
|
||||
$result = $personService->getPersonInfo($code, $acc);
|
||||
return $result;
|
||||
} catch (\Exception $e) {
|
||||
$this->log->error('خطا در دریافت اطلاعات شخص از Cog: ' . $e->getMessage(), [
|
||||
'context' => 'AGIService::callGetPersonInfoFromCog',
|
||||
'code' => $code,
|
||||
'exception' => $e->getMessage()
|
||||
]);
|
||||
return [
|
||||
'error' => 'خطا در دریافت اطلاعات شخص: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ساخت پرامپ هوشمند
|
||||
*/
|
||||
private function buildSmartPrompt(string $message, ?Business $business, array $conversationHistory = []): string
|
||||
{
|
||||
// دریافت پرامپهای پایه از PromptService
|
||||
$basePrompts = $this->promptService->getAllBasePrompts();
|
||||
$prompt = $basePrompts;
|
||||
|
||||
// قوانین خروجی JSON و مثالها از سرویس مدیریت پرامپتها
|
||||
$prompt .= $this->promptService->getOutputFormatPrompt();
|
||||
|
||||
// اضافه کردن اطلاعات کسب و کار
|
||||
if ($business) {
|
||||
$prompt .= "\n\nاطلاعات کسب و کار: نام: {$business->getName()}, کد اقتصادی: {$business->getCodeeghtesadi()}.";
|
||||
}
|
||||
// اضافه کردن تاریخچه گفتگو
|
||||
if (!empty($conversationHistory)) {
|
||||
$prompt .= "\n\n📜 تاریخچه گفتگو:\n";
|
||||
foreach ($conversationHistory as $historyItem) {
|
||||
$role = $historyItem['role'] === 'user' ? 'کاربر' : 'دستیار';
|
||||
$prompt .= "{$role}: {$historyItem['content']}\n";
|
||||
}
|
||||
$prompt .= "\n💡 نکته: لطفاً context گفتگو را حفظ کنید و به سوالات قبلی مراجعه کنید.";
|
||||
}
|
||||
$prompt .= "\n\nسوال کاربر: " . $message;
|
||||
return $prompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت URL های سرویس
|
||||
*/
|
||||
|
@ -188,10 +406,10 @@ class AGIService
|
|||
{
|
||||
return match ($service) {
|
||||
'gapgpt' => [
|
||||
'https://api.gapgpt.app/v1/chat/completions',
|
||||
'https://api.gapgpt.ir/v1/chat/completions'
|
||||
'https://api.gapgpt.ir/v1/chat/completions',
|
||||
'https://api.gapgpt.app/v1/chat/completions'
|
||||
],
|
||||
'avalai' => ['https://api.avalai.com/v1/chat/completions'],
|
||||
'avalai' => ['https://api.avalapis.ir/v1/chat/completions'],
|
||||
'local' => [$this->registryMGR->get('system', 'localModelAddress') ?? ''],
|
||||
default => []
|
||||
};
|
||||
|
@ -202,59 +420,62 @@ class AGIService
|
|||
*/
|
||||
private function makeHttpRequest(string $url, array $data, string $apiKey): array
|
||||
{
|
||||
$ch = curl_init();
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => json_encode($data),
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Bearer ' . $apiKey
|
||||
],
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_CONNECTTIMEOUT => 10,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => false,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_MAXREDIRS => 3,
|
||||
CURLOPT_USERAGENT => 'Hesabix-AGI-Service/1.0'
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
if ($error) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'خطا در ارتباط با سرور: ' . $error
|
||||
];
|
||||
}
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'خطای HTTP: ' . $this->getHttpErrorMessage($httpCode)
|
||||
];
|
||||
}
|
||||
|
||||
$responseData = json_decode($response, true);
|
||||
|
||||
if (!$responseData) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'پاسخ نامعتبر از سرور: ' . substr($response, 0, 200)
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => $responseData
|
||||
$debugInfo = [
|
||||
'request_url' => $url,
|
||||
'request_data' => $data,
|
||||
];
|
||||
|
||||
try {
|
||||
$response = $this->httpClient->request('POST', $url, [
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => 'Bearer ' . $apiKey,
|
||||
],
|
||||
'json' => $data,
|
||||
'timeout' => 15,
|
||||
'max_redirects' => 3,
|
||||
]);
|
||||
|
||||
$statusCode = $response->getStatusCode();
|
||||
$content = $response->getContent(false); // false: throw exception on 4xx/5xx نمیدهد
|
||||
$debugInfo['http_code'] = $statusCode;
|
||||
$debugInfo['raw_response'] = $content;
|
||||
|
||||
if ($statusCode !== 200) {
|
||||
$debugInfo['http_error_message'] = $this->getHttpErrorMessage($statusCode);
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'خطای HTTP: ' . $this->getHttpErrorMessage($statusCode),
|
||||
'debug_info' => $debugInfo
|
||||
];
|
||||
}
|
||||
|
||||
$responseData = json_decode($content, true);
|
||||
|
||||
if (!$responseData) {
|
||||
$debugInfo['json_error'] = json_last_error_msg();
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'پاسخ نامعتبر از سرور: ' . substr($content, 0, 200),
|
||||
'debug_info' => $debugInfo
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => $responseData,
|
||||
'debug_info' => $debugInfo
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
$debugInfo['exception'] = $e->getMessage();
|
||||
$debugInfo['exception_code'] = $e->getCode();
|
||||
$debugInfo['exception_trace'] = $e->getTraceAsString();
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'خطا در ارتباط با سرور: ' . $e->getMessage(),
|
||||
'debug_info' => $debugInfo
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -606,4 +827,12 @@ class AGIService
|
|||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* ساخت ابزارها از سرویسهای پرامپ
|
||||
*/
|
||||
private function buildToolsFromPromptServices(): array
|
||||
{
|
||||
return $this->promptService->getAllTools();
|
||||
}
|
||||
}
|
111
hesabixCore/src/Service/AGI/Promps/BankPromptService.php
Normal file
111
hesabixCore/src/Service/AGI/Promps/BankPromptService.php
Normal file
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service\AGI\Promps;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class BankPromptService
|
||||
{
|
||||
private $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->em = $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت تمام ابزارهای بخش بانکها برای function calling
|
||||
* @return array
|
||||
*/
|
||||
public function getTools(): array
|
||||
{
|
||||
$tools = [];
|
||||
|
||||
// ابزار getBankAccountInfo
|
||||
$bankAccountInfoPrompt = $this->getBankAccountInfoPrompt();
|
||||
$bankAccountInfoData = json_decode($bankAccountInfoPrompt, true);
|
||||
|
||||
if ($bankAccountInfoData) {
|
||||
$tools[] = [
|
||||
'type' => 'function',
|
||||
'function' => [
|
||||
'name' => $bankAccountInfoData['tool'],
|
||||
'description' => $bankAccountInfoData['description'],
|
||||
'parameters' => [
|
||||
'type' => 'object',
|
||||
'properties' => $bankAccountInfoData['input'],
|
||||
'required' => array_keys($bankAccountInfoData['input'])
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
return $tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* تولید تمام پرامپهای بخش بانکها
|
||||
* @return string
|
||||
*/
|
||||
public function getAllBankPrompts(): string
|
||||
{
|
||||
$prompts = [];
|
||||
|
||||
// اضافه کردن تمام پرامپهای موجود
|
||||
$prompts[] = $this->getBankAccountInfoPrompt();
|
||||
|
||||
// در آینده پرامپهای دیگر اضافه خواهند شد
|
||||
// $prompts[] = $this->getCreateBankAccountPrompt();
|
||||
// $prompts[] = $this->getUpdateBankAccountPrompt();
|
||||
// $prompts[] = $this->getBankTransactionPrompt();
|
||||
|
||||
// ترکیب تمام پرامپها
|
||||
return implode("\n\n", $prompts);
|
||||
}
|
||||
|
||||
/**
|
||||
* پرامپ برای دریافت اطلاعات کامل حساب بانکی
|
||||
* @return string
|
||||
*/
|
||||
public function getBankAccountInfoPrompt(): string
|
||||
{
|
||||
return '{
|
||||
"tool": "getBankAccountInfo",
|
||||
"description": "Get complete bank account information by code",
|
||||
"endpoint": "/api/bank/account/info/{code}",
|
||||
"method": "GET",
|
||||
"input": {
|
||||
"code": "string - Bank account code (e.g., 1001, 1002)"
|
||||
},
|
||||
"output": {
|
||||
"id": "integer - Account ID",
|
||||
"code": "string - Account code",
|
||||
"name": "string - Account name",
|
||||
"bankName": "string - Bank name",
|
||||
"accountNumber": "string - Account number",
|
||||
"shabaNumber": "string - Shaba number",
|
||||
"cardNumber": "string - Card number",
|
||||
"balance": "float - Current balance",
|
||||
"currency": "string - Currency type",
|
||||
"isActive": "boolean - Account active status",
|
||||
"description": "string - Account description"
|
||||
},
|
||||
"examples": {
|
||||
"input": {"code": "1001"},
|
||||
"output": {
|
||||
"id": 23,
|
||||
"code": "1001",
|
||||
"name": "حساب جاری اصلی",
|
||||
"bankName": "ملت",
|
||||
"accountNumber": "1234567890",
|
||||
"shabaNumber": "IR123456789012345678901234",
|
||||
"cardNumber": "6104337812345678",
|
||||
"balance": 150000000,
|
||||
"currency": "ریال",
|
||||
"isActive": true,
|
||||
"description": "حساب جاری اصلی شرکت"
|
||||
}
|
||||
}
|
||||
}';
|
||||
}
|
||||
}
|
|
@ -3,14 +3,18 @@
|
|||
namespace App\Service\AGI\Promps;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use App\Service\Access;
|
||||
use App\Entity\APIToken;
|
||||
|
||||
class BasePromptService
|
||||
{
|
||||
private $em;
|
||||
private $access;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
public function __construct(EntityManagerInterface $entityManager, Access $access)
|
||||
{
|
||||
$this->em = $entityManager;
|
||||
$this->access = $access;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,6 +23,29 @@ class BasePromptService
|
|||
*/
|
||||
public function getSystemIntroductionPrompt(): string
|
||||
{
|
||||
// دسترسی فعلی
|
||||
$acc = $this->access->hasRole('join');
|
||||
$apiToken = null;
|
||||
if ($acc && isset($acc['bid']) && isset($acc['user'])) {
|
||||
// جستجوی توکن AI معتبر برای این کاربر و کسبوکار
|
||||
$now = time();
|
||||
$apiToken = $this->em->getRepository(APIToken::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'submitter' => $acc['user'],
|
||||
'isForAi' => true
|
||||
]);
|
||||
if ($apiToken) {
|
||||
$expire = $apiToken->getDateExpire();
|
||||
if ($expire && $expire != '0' && $now > (int)$expire) {
|
||||
$apiToken = null; // منقضی شده
|
||||
}
|
||||
}
|
||||
if (!$apiToken) {
|
||||
// ساخت توکن جدید با اعتبار ۳۰ دقیقه
|
||||
$apiToken = $this->access->createAiToken($acc['bid'], $acc['user'], 1800);
|
||||
}
|
||||
}
|
||||
$apiKey = $apiToken ? $apiToken->getToken() : '';
|
||||
return '{
|
||||
"tool": "system_introduction",
|
||||
"description": "System introduction and authentication requirements",
|
||||
|
@ -29,7 +56,7 @@ class BasePromptService
|
|||
"authentication": {
|
||||
"method": "API Key or Session Token",
|
||||
"required_headers": {
|
||||
"api-key": "API token for AI access (required for AI operations)",
|
||||
"api-key": "' . $apiKey . ' (این کد را در هدر api-key قرار بده)",
|
||||
},
|
||||
},
|
||||
"language": "Persian (فارسی)",
|
||||
|
@ -87,6 +114,24 @@ class BasePromptService
|
|||
}';
|
||||
}
|
||||
|
||||
/**
|
||||
* پرامپ برای نمایش دامنه اصلی API
|
||||
* @return string
|
||||
*/
|
||||
public function getApiBaseUrlPrompt(): string
|
||||
{
|
||||
// دریافت اولین رکورد تنظیمات
|
||||
$settings = $this->em->getRepository(\App\Entity\Settings::class)->findAll();
|
||||
$appSite = isset($settings[0]) ? $settings[0]->getAppSite() : '';
|
||||
$domain = $appSite ? $appSite : '---';
|
||||
return '{
|
||||
"tool": "api_base_url",
|
||||
"description": "آدرس پایه API",
|
||||
"content": "تمام اندپوینتهای سیستم از طریق دامنه زیر قابل دسترسی هستند:",
|
||||
"base_url": "' . $domain . '"
|
||||
}';
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت تمام پرامپهای پایه
|
||||
* @return string
|
||||
|
@ -98,6 +143,7 @@ class BasePromptService
|
|||
$prompts[] = $this->getSystemIntroductionPrompt();
|
||||
$prompts[] = $this->getErrorHandlingPrompt();
|
||||
$prompts[] = $this->getHelpPrompt();
|
||||
$prompts[] = $this->getApiBaseUrlPrompt();
|
||||
|
||||
return implode("\n\n", $prompts);
|
||||
}
|
||||
|
|
116
hesabixCore/src/Service/AGI/Promps/InventoryPromptService.php
Normal file
116
hesabixCore/src/Service/AGI/Promps/InventoryPromptService.php
Normal file
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service\AGI\Promps;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class InventoryPromptService
|
||||
{
|
||||
private $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->em = $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت تمام ابزارهای بخش کالاها برای function calling
|
||||
* @return array
|
||||
*/
|
||||
public function getTools(): array
|
||||
{
|
||||
$tools = [];
|
||||
|
||||
// ابزار getItemInfo
|
||||
$itemInfoPrompt = $this->getItemInfoPrompt();
|
||||
$itemInfoData = json_decode($itemInfoPrompt, true);
|
||||
|
||||
if ($itemInfoData) {
|
||||
$tools[] = [
|
||||
'type' => 'function',
|
||||
'function' => [
|
||||
'name' => $itemInfoData['tool'],
|
||||
'description' => $itemInfoData['description'],
|
||||
'parameters' => [
|
||||
'type' => 'object',
|
||||
'properties' => $itemInfoData['input'],
|
||||
'required' => array_keys($itemInfoData['input'])
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
return $tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* تولید تمام پرامپهای بخش کالاها
|
||||
* @return string
|
||||
*/
|
||||
public function getAllInventoryPrompts(): string
|
||||
{
|
||||
$prompts = [];
|
||||
|
||||
// اضافه کردن تمام پرامپهای موجود
|
||||
$prompts[] = $this->getItemInfoPrompt();
|
||||
|
||||
// در آینده پرامپهای دیگر اضافه خواهند شد
|
||||
// $prompts[] = $this->getCreateItemPrompt();
|
||||
// $prompts[] = $this->getUpdateItemPrompt();
|
||||
// $prompts[] = $this->getSearchItemPrompt();
|
||||
// $prompts[] = $this->getItemStockPrompt();
|
||||
|
||||
// ترکیب تمام پرامپها
|
||||
return implode("\n\n", $prompts);
|
||||
}
|
||||
|
||||
/**
|
||||
* پرامپ برای دریافت اطلاعات کامل کالا
|
||||
* @return string
|
||||
*/
|
||||
public function getItemInfoPrompt(): string
|
||||
{
|
||||
return '{
|
||||
"tool": "getItemInfo",
|
||||
"description": "Get complete item information by code",
|
||||
"endpoint": "/api/item/info/{code}",
|
||||
"method": "GET",
|
||||
"input": {
|
||||
"code": "string - Item code (e.g., 1001, 1002)"
|
||||
},
|
||||
"output": {
|
||||
"id": "integer - Item ID",
|
||||
"code": "string - Item code",
|
||||
"name": "string - Item name",
|
||||
"description": "string - Item description",
|
||||
"category": "string - Item category",
|
||||
"unit": "string - Unit of measurement",
|
||||
"price": "float - Item price",
|
||||
"stock": "float - Current stock quantity",
|
||||
"minStock": "float - Minimum stock level",
|
||||
"maxStock": "float - Maximum stock level",
|
||||
"supplier": "string - Supplier name",
|
||||
"barcode": "string - Barcode",
|
||||
"isActive": "boolean - Item active status"
|
||||
},
|
||||
"examples": {
|
||||
"input": {"code": "1001"},
|
||||
"output": {
|
||||
"id": 45,
|
||||
"code": "1001",
|
||||
"name": "لپتاپ اپل",
|
||||
"description": "لپتاپ اپل مکبوک پرو 13 اینچ",
|
||||
"category": "الکترونیک",
|
||||
"unit": "عدد",
|
||||
"price": 45000000,
|
||||
"stock": 15,
|
||||
"minStock": 5,
|
||||
"maxStock": 50,
|
||||
"supplier": "شرکت اپل",
|
||||
"barcode": "1234567890123",
|
||||
"isActive": true
|
||||
}
|
||||
}
|
||||
}';
|
||||
}
|
||||
}
|
|
@ -13,6 +13,36 @@ class PersonPromptService
|
|||
$this->em = $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت تمام ابزارهای بخش اشخاص برای function calling
|
||||
* @return array
|
||||
*/
|
||||
public function getTools(): array
|
||||
{
|
||||
$tools = [];
|
||||
|
||||
// ابزار getPersonInfo
|
||||
$personInfoPrompt = $this->getPersonInfoPrompt();
|
||||
$personInfoData = json_decode($personInfoPrompt, true);
|
||||
|
||||
if ($personInfoData) {
|
||||
$tools[] = [
|
||||
'type' => 'function',
|
||||
'function' => [
|
||||
'name' => $personInfoData['tool'],
|
||||
'description' => $personInfoData['description'],
|
||||
'parameters' => [
|
||||
'type' => 'object',
|
||||
'properties' => $personInfoData['input'],
|
||||
'required' => array_keys($personInfoData['input'])
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
return $tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* تولید تمام پرامپهای بخش اشخاص
|
||||
* @return string
|
||||
|
|
|
@ -3,16 +3,65 @@
|
|||
namespace App\Service\AGI\Promps;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use App\Service\AGI\Promps\InventoryPromptService;
|
||||
use App\Service\AGI\Promps\BankPromptService;
|
||||
|
||||
class PromptService
|
||||
{
|
||||
private $em;
|
||||
private $personPromptService;
|
||||
private $basePromptService;
|
||||
private $inventoryPromptService;
|
||||
private $bankPromptService;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, PersonPromptService $personPromptService)
|
||||
{
|
||||
public function __construct(
|
||||
EntityManagerInterface $entityManager,
|
||||
PersonPromptService $personPromptService,
|
||||
BasePromptService $basePromptService,
|
||||
InventoryPromptService $inventoryPromptService,
|
||||
BankPromptService $bankPromptService
|
||||
) {
|
||||
$this->em = $entityManager;
|
||||
$this->personPromptService = $personPromptService;
|
||||
$this->basePromptService = $basePromptService;
|
||||
$this->inventoryPromptService = $inventoryPromptService;
|
||||
$this->bankPromptService = $bankPromptService;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت تمام ابزارهای موجود برای function calling
|
||||
* @return array
|
||||
*/
|
||||
public function getAllTools(): array
|
||||
{
|
||||
$tools = [];
|
||||
|
||||
// ابزارهای بخش اشخاص
|
||||
$personTools = $this->personPromptService->getTools();
|
||||
$tools = array_merge($tools, $personTools);
|
||||
|
||||
// ابزارهای بخش کالاها
|
||||
$inventoryTools = $this->inventoryPromptService->getTools();
|
||||
$tools = array_merge($tools, $inventoryTools);
|
||||
|
||||
// ابزارهای بخش بانکها
|
||||
$bankTools = $this->bankPromptService->getTools();
|
||||
$tools = array_merge($tools, $bankTools);
|
||||
|
||||
// در آینده ابزارهای بخشهای دیگر اضافه خواهند شد
|
||||
// $accountingTools = $this->accountingPromptService->getTools();
|
||||
// $tools = array_merge($tools, $accountingTools);
|
||||
|
||||
return $tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت تمام پرامپهای پایه (سیستم، API، و غیره)
|
||||
* @return string
|
||||
*/
|
||||
public function getAllBasePrompts(): string
|
||||
{
|
||||
return $this->basePromptService->getAllBasePrompts();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,6 +81,8 @@ class PromptService
|
|||
// return $this->inventoryPromptService->getAllInventoryPrompts();
|
||||
// case 'reports':
|
||||
// return $this->reportsPromptService->getAllReportsPrompts();
|
||||
// case 'bank':
|
||||
// return $this->bankPromptService->getAllBankPrompts();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@ -45,15 +96,24 @@ class PromptService
|
|||
{
|
||||
$prompts = [];
|
||||
|
||||
// پرامپهای پایه (سیستم، API، و غیره)
|
||||
$prompts['base'] = $this->basePromptService->getAllBasePrompts();
|
||||
|
||||
// پرامپهای بخش اشخاص
|
||||
$prompts['person'] = $this->personPromptService->getAllPersonPrompts();
|
||||
|
||||
// پرامپهای بخش کالاها
|
||||
$prompts['inventory'] = $this->inventoryPromptService->getAllInventoryPrompts();
|
||||
|
||||
// پرامپهای بخش بانکها
|
||||
$prompts['bank'] = $this->bankPromptService->getAllBankPrompts();
|
||||
|
||||
// در آینده بخشهای دیگر اضافه خواهند شد
|
||||
// $prompts['accounting'] = $this->accountingPromptService->getAllAccountingPrompts();
|
||||
// $prompts['inventory'] = $this->inventoryPromptService->getAllInventoryPrompts();
|
||||
// $prompts['reports'] = $this->reportsPromptService->getAllReportsPrompts();
|
||||
// $prompts['sales'] = $this->salesPromptService->getAllSalesPrompts();
|
||||
// $prompts['purchases'] = $this->purchasesPromptService->getAllPurchasesPrompts();
|
||||
// $prompts['bank'] = $this->bankPromptService->getAllBankPrompts();
|
||||
|
||||
return $prompts;
|
||||
}
|
||||
|
@ -67,4 +127,41 @@ class PromptService
|
|||
$allPrompts = $this->getAllSystemPrompts();
|
||||
return implode("\n\n", $allPrompts);
|
||||
}
|
||||
|
||||
/**
|
||||
* قوانین خروجی JSON و مثال برای پاسخ هوش مصنوعی
|
||||
* @return string
|
||||
*/
|
||||
public function getOutputFormatPrompt(): string
|
||||
{
|
||||
$prompt = "\n\nقوانین مهم: همیشه پاسخ را فقط در قالب یک شیء JSON با ساختار زیر ارسال کن و هیچ توضیح اضافهای ننویس:\n";
|
||||
$prompt .= <<<EOT
|
||||
{
|
||||
"type": ["text", "table", "chart"],
|
||||
"data": [
|
||||
{ "type": "text", "content": "..." },
|
||||
{ "type": "table", "headers": [...], "rows": [[...], ...] },
|
||||
{ "type": "chart", "chartType": "bar|line|pie", "title": "عنوان نمودار", "labels": ["برچسب۱", "برچسب۲"], "series": [{"name": "عنوان سری", "data": [100, 200]}], "values": [100, 200] }
|
||||
]
|
||||
}
|
||||
EOT;
|
||||
$prompt .= "\nدر صورت نیاز میتوانی چند نوع داده را همزمان ارسال کنی. اگر فقط متن است، فقط یک آبجکت با type=text و content بده.\n\nمثال خروجی صحیح برای نمودار (دقیقاً همین ساختار را رعایت کن و کلید type را فقط یک بار و مقدار آن را 'chart' قرار بده. نوع نمودار فقط در chartType باشد):\n";
|
||||
$prompt .= <<<EOT2
|
||||
{
|
||||
"type": ["chart"],
|
||||
"data": [
|
||||
{
|
||||
"type": "chart",
|
||||
"chartType": "bar",
|
||||
"title": "نمودار فروش ماهانه",
|
||||
"labels": ["فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد"],
|
||||
"series": [{"name": "فروش ماهانه", "data": [120, 150, 180, 200, 170]}],
|
||||
"values": [120, 150, 180, 200, 170]
|
||||
}
|
||||
]
|
||||
}
|
||||
EOT2;
|
||||
$prompt .= "\nحتماً کلیدهای type، title، labels، series، values و chartType را دقیقاً مطابق مثال بالا برای هر نمودار برگردان و فقط همین JSON را خروجی بده. کلید type را فقط یک بار و مقدار آن را 'chart' قرار بده و نوع نمودار را فقط در chartType بنویس.";
|
||||
return $prompt;
|
||||
}
|
||||
}
|
|
@ -17,25 +17,20 @@
|
|||
@click="refreshChart"
|
||||
:title="$t('chart.refresh')"
|
||||
></v-btn>
|
||||
<v-btn
|
||||
icon="mdi-fullscreen"
|
||||
variant="text"
|
||||
size="small"
|
||||
@click="toggleFullscreen"
|
||||
:title="$t('chart.fullscreen')"
|
||||
></v-btn>
|
||||
</div>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<div class="chart-container" :class="{ 'fullscreen': isFullscreen }">
|
||||
<div class="chart-container">
|
||||
<apexchart
|
||||
v-if="chartSeries && chartSeries.length > 0 && chartOptions && chartOptions.xaxis && Array.isArray(chartOptions.xaxis.categories)"
|
||||
ref="chart"
|
||||
:type="chartType"
|
||||
:height="chartHeight"
|
||||
:options="chartOptions"
|
||||
:series="chartSeries"
|
||||
></apexchart>
|
||||
/>
|
||||
<div v-else class="text-center pa-4" style="color: #888;">دادهای برای نمایش نمودار وجود ندارد.</div>
|
||||
</div>
|
||||
|
||||
<!-- اطلاعات نمودار -->
|
||||
|
@ -89,7 +84,6 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
isFullscreen: false,
|
||||
chartId: '',
|
||||
createdAt: new Date(),
|
||||
chartType: 'bar',
|
||||
|
@ -100,9 +94,6 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
chartHeight() {
|
||||
if (this.isFullscreen) {
|
||||
return window.innerHeight - 200;
|
||||
}
|
||||
return this.height;
|
||||
},
|
||||
dataPointsCount() {
|
||||
|
@ -124,6 +115,7 @@ export default {
|
|||
watch: {
|
||||
chartData: {
|
||||
handler(newData) {
|
||||
console.debug('AIChart.vue watch chartData', newData);
|
||||
this.initializeChart(newData);
|
||||
},
|
||||
immediate: true,
|
||||
|
@ -131,13 +123,15 @@ export default {
|
|||
}
|
||||
},
|
||||
mounted() {
|
||||
console.debug('AIChart.vue mounted', this.chartData);
|
||||
this.initializeChart(this.chartData);
|
||||
},
|
||||
methods: {
|
||||
initializeChart(data) {
|
||||
console.debug('AIChart.vue initializeChart data:', data);
|
||||
if (!data) return;
|
||||
|
||||
this.chartType = data.type || 'bar';
|
||||
this.chartType = data.chartType || 'bar'; // اصلاح مقداردهی نوع نمودار
|
||||
this.chartTitle = data.title || 'نمودار';
|
||||
this.chartId = data.chart_id || this.generateChartId();
|
||||
this.createdAt = new Date();
|
||||
|
@ -202,7 +196,7 @@ export default {
|
|||
},
|
||||
y: {
|
||||
formatter: function(value) {
|
||||
return this.$filters ? this.$filters.formatNumber(value) : value;
|
||||
return typeof value === 'number' ? value.toLocaleString('fa-IR') : value;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -218,10 +212,10 @@ export default {
|
|||
};
|
||||
|
||||
// تنظیمات خاص بر اساس نوع نمودار
|
||||
this.setupChartSpecificOptions(data.data);
|
||||
this.setupChartSpecificOptions(data);
|
||||
|
||||
// تنظیم سریهای داده
|
||||
this.setupChartSeries(data.data);
|
||||
this.setupChartSeries(data);
|
||||
},
|
||||
|
||||
setupChartSpecificOptions(data) {
|
||||
|
@ -247,7 +241,7 @@ export default {
|
|||
fontFamily: "'Vazirmatn FD', Arial, sans-serif"
|
||||
},
|
||||
formatter: function(value) {
|
||||
return this.$filters ? this.$filters.formatNumber(value) : value;
|
||||
return typeof value === 'number' ? value.toLocaleString('fa-IR') : value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -273,7 +267,7 @@ export default {
|
|||
fontSize: '16px',
|
||||
fontFamily: "'Vazirmatn FD', Arial, sans-serif",
|
||||
formatter: function(value) {
|
||||
return this.$filters ? this.$filters.formatNumber(value) : value;
|
||||
return typeof value === 'number' ? value.toLocaleString('fa-IR') : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -315,15 +309,55 @@ export default {
|
|||
},
|
||||
|
||||
setupChartSeries(data) {
|
||||
if (!data) {
|
||||
this.chartSeries = [];
|
||||
// مقداردهی پیشفرض به xaxis برای جلوگیری از خطا
|
||||
this.chartOptions = this.chartOptions || {};
|
||||
this.chartOptions.xaxis = { categories: [] };
|
||||
return;
|
||||
}
|
||||
if (this.chartType === 'pie' || this.chartType === 'doughnut') {
|
||||
this.chartSeries = data.series || [];
|
||||
if (data.values && Array.isArray(data.values)) {
|
||||
this.chartSeries = data.values;
|
||||
} else if (
|
||||
data.series &&
|
||||
Array.isArray(data.series) &&
|
||||
data.series.length > 0 &&
|
||||
Array.isArray(data.series[0].data)
|
||||
) {
|
||||
this.chartSeries = data.series[0].data;
|
||||
} else {
|
||||
this.chartSeries = [];
|
||||
}
|
||||
} else {
|
||||
this.chartSeries = data.series || [
|
||||
{
|
||||
name: 'دادهها',
|
||||
data: []
|
||||
if (data.series && Array.isArray(data.series) && data.series.length > 0) {
|
||||
this.chartSeries = data.series;
|
||||
// مقداردهی categories اگر وجود دارد
|
||||
if (data.labels && Array.isArray(data.labels)) {
|
||||
this.chartOptions = this.chartOptions || {};
|
||||
this.chartOptions.xaxis = this.chartOptions.xaxis || {};
|
||||
this.chartOptions.xaxis.categories = data.labels;
|
||||
}
|
||||
];
|
||||
} else if (data.labels && data.values && Array.isArray(data.labels) && Array.isArray(data.values)) {
|
||||
this.chartSeries = [
|
||||
{
|
||||
name: this.chartTitle || 'دادهها',
|
||||
data: data.values
|
||||
}
|
||||
];
|
||||
this.chartOptions = this.chartOptions || {};
|
||||
this.chartOptions.xaxis = this.chartOptions.xaxis || {};
|
||||
this.chartOptions.xaxis.categories = data.labels;
|
||||
} else {
|
||||
this.chartSeries = [
|
||||
{
|
||||
name: 'دادهها',
|
||||
data: []
|
||||
}
|
||||
];
|
||||
this.chartOptions = this.chartOptions || {};
|
||||
this.chartOptions.xaxis = { categories: [] };
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -356,24 +390,21 @@ export default {
|
|||
},
|
||||
|
||||
downloadChart() {
|
||||
if (this.$refs.chart) {
|
||||
this.$refs.chart.chart.downloadCSV();
|
||||
if (this.$refs.chart && this.$refs.chart.dataURI) {
|
||||
this.$refs.chart.dataURI().then(({ imgURI }) => {
|
||||
const link = document.createElement('a');
|
||||
link.href = imgURI;
|
||||
link.download = 'chart.png';
|
||||
link.click();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
refreshChart() {
|
||||
if (this.$refs.chart) {
|
||||
this.$refs.chart.chart.refresh();
|
||||
if (this.$refs.chart && this.$refs.chart.updateSeries) {
|
||||
// داده فعلی را دوباره ست میکنیم تا رفرش شود
|
||||
this.$refs.chart.updateSeries(this.chartSeries);
|
||||
}
|
||||
},
|
||||
|
||||
toggleFullscreen() {
|
||||
this.isFullscreen = !this.isFullscreen;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.chart) {
|
||||
this.$refs.chart.chart.resize();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -396,11 +427,20 @@ export default {
|
|||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
z-index: 9999;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.chart-container.fullscreen .apexcharts-canvas {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
min-width: 0 !important;
|
||||
min-height: 0 !important;
|
||||
}
|
||||
|
||||
.chart-details {
|
||||
|
|
|
@ -920,5 +920,15 @@ const fa_lang = {
|
|||
numberinput: {
|
||||
invalid_number: "فقط عدد انگلیسی مجاز است"
|
||||
},
|
||||
chart: {
|
||||
download: "دانلود نمودار",
|
||||
refresh: "نوسازی نمودار",
|
||||
fullscreen: "تمامصفحه",
|
||||
details: "جزئیات نمودار",
|
||||
type: "نوع نمودار",
|
||||
id: "شناسه نمودار",
|
||||
dataPoints: "تعداد نقاط داده",
|
||||
created: "تاریخ ایجاد"
|
||||
},
|
||||
};
|
||||
export default fa_lang
|
||||
|
|
|
@ -15,7 +15,28 @@
|
|||
</div>
|
||||
<div class="message-content">
|
||||
<div class="message-bubble">
|
||||
<!-- تغییر: رندر داینامیک بر اساس نوع داده -->
|
||||
<template v-if="message.type === 'ai' && message.data">
|
||||
<div v-for="(item, idx) in message.data.data" :key="idx">
|
||||
<div v-if="item.type === 'text'">{{ item.content }}</div>
|
||||
<v-table v-else-if="item.type === 'table'" class="my-2" density="compact">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="h in item.headers" :key="h">{{ h }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, rIdx) in item.rows" :key="rIdx">
|
||||
<td v-for="(cell, cIdx) in row" :key="cIdx">{{ cell }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
<AIChart v-else-if="item.type === 'chart' || item.chartType" :chartData="item" height="300" class="my-2" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p class="message-text">{{ message.text }}</p>
|
||||
</template>
|
||||
<span class="message-time">{{ formatTime(message.timestamp) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -96,16 +117,18 @@
|
|||
<script>
|
||||
import { useNavigationStore } from '@/stores/navigationStore';
|
||||
import axios from 'axios';
|
||||
import AIChart from '@/components/widgets/AIChart.vue';
|
||||
|
||||
export default {
|
||||
name: "WizardHome",
|
||||
components: { AIChart },
|
||||
data() {
|
||||
return {
|
||||
navigationStore: useNavigationStore(),
|
||||
messages: [
|
||||
{
|
||||
type: 'ai',
|
||||
text: 'سلام! من دستیار هوشمند شما هستم. چطور میتونم کمکتون کنم؟',
|
||||
data: { type: ['text'], data: [{ type: 'text', content: 'سلام! من دستیار هوشمند شما هستم. چطور میتونم کمکتون کنم؟' }] },
|
||||
timestamp: new Date()
|
||||
}
|
||||
],
|
||||
|
@ -137,7 +160,7 @@ export default {
|
|||
// تغییر پیام اولیه بر اساس وضعیت
|
||||
this.messages[0] = {
|
||||
type: 'ai',
|
||||
text: this.getStatusMessage(data.status, data.message),
|
||||
data: { type: ['text'], data: [{ type: 'text', content: this.getStatusMessage(data.status, data.message) }] },
|
||||
timestamp: new Date()
|
||||
};
|
||||
}
|
||||
|
@ -146,7 +169,7 @@ export default {
|
|||
this.aiStatus = 'error';
|
||||
this.messages[0] = {
|
||||
type: 'ai',
|
||||
text: 'خطا در بررسی وضعیت هوش مصنوعی. لطفاً دوباره تلاش کنید.',
|
||||
data: { type: ['text'], data: [{ type: 'text', content: 'خطا در بررسی وضعیت هوش مصنوعی. لطفاً دوباره تلاش کنید.' }] },
|
||||
timestamp: new Date()
|
||||
};
|
||||
}
|
||||
|
@ -155,7 +178,7 @@ export default {
|
|||
this.aiStatus = 'error';
|
||||
this.messages[0] = {
|
||||
type: 'ai',
|
||||
text: 'خطا در اتصال به سرور. لطفاً اتصال اینترنت خود را بررسی کنید.',
|
||||
data: { type: ['text'], data: [{ type: 'text', content: 'خطا در اتصال به سرور. لطفاً اتصال اینترنت خود را بررسی کنید.' }] },
|
||||
timestamp: new Date()
|
||||
};
|
||||
}
|
||||
|
@ -210,18 +233,48 @@ export default {
|
|||
this.isTyping = false;
|
||||
|
||||
if (response.data.success) {
|
||||
// اضافه کردن پاسخ AI
|
||||
// --- تغییر: پردازش پاسخ JSON ---
|
||||
let aiData = response.data.response;
|
||||
let parsed = null;
|
||||
try {
|
||||
// اگر پاسخ داخل بلاک کد markdown است، فقط بخش json را جدا کن
|
||||
if (typeof aiData === 'string' && aiData.trim().startsWith('```json')) {
|
||||
aiData = aiData.replace(/^```json[\r\n]*/i, '').replace(/```$/i, '').trim();
|
||||
}
|
||||
// پارس چند مرحلهای تا رسیدن به آبجکت واقعی
|
||||
parsed = aiData;
|
||||
let safety = 0;
|
||||
while (typeof parsed === 'string' && safety < 5) {
|
||||
parsed = JSON.parse(parsed);
|
||||
safety++;
|
||||
}
|
||||
// اگر باز هم data.data[0] رشته بود، دوباره پارس کن
|
||||
if (
|
||||
parsed &&
|
||||
parsed.data &&
|
||||
Array.isArray(parsed.data) &&
|
||||
typeof parsed.data[0] === 'string'
|
||||
) {
|
||||
let safety2 = 0;
|
||||
while (typeof parsed.data[0] === 'string' && safety2 < 5) {
|
||||
parsed.data[0] = JSON.parse(parsed.data[0]);
|
||||
safety2++;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// اگر JSON نبود، به صورت متن نمایش بده
|
||||
parsed = { type: ['text'], data: [{ type: 'text', content: aiData }] };
|
||||
}
|
||||
console.debug('home.vue AI message parsed:', parsed);
|
||||
this.messages.push({
|
||||
type: 'ai',
|
||||
text: response.data.response,
|
||||
data: parsed,
|
||||
timestamp: new Date()
|
||||
});
|
||||
|
||||
// ذخیره conversationId برای ادامه گفتگو
|
||||
if (response.data.conversationId) {
|
||||
this.conversationId = response.data.conversationId;
|
||||
}
|
||||
|
||||
// نمایش اطلاعات هزینه در صورت وجود
|
||||
if (response.data.cost) {
|
||||
console.log('هزینه استفاده:', response.data.cost);
|
||||
|
@ -230,11 +283,10 @@ export default {
|
|||
// نمایش خطا
|
||||
this.messages.push({
|
||||
type: 'ai',
|
||||
text: `خطا: ${response.data.error}`,
|
||||
data: { type: ['text'], data: [{ type: 'text', content: `خطا: ${response.data.error}` }] },
|
||||
timestamp: new Date()
|
||||
});
|
||||
}
|
||||
|
||||
// اسکرول به پایین بعد از دریافت پاسخ
|
||||
setTimeout(() => {
|
||||
this.scrollToBottom();
|
||||
|
@ -242,7 +294,6 @@ export default {
|
|||
|
||||
} catch (error) {
|
||||
this.isTyping = false;
|
||||
|
||||
let errorMessage = 'خطا در ارتباط با سرور';
|
||||
if (error.response) {
|
||||
if (error.response.data && error.response.data.error) {
|
||||
|
@ -255,13 +306,11 @@ export default {
|
|||
} else if (error.request) {
|
||||
errorMessage = 'خطا در اتصال به سرور';
|
||||
}
|
||||
|
||||
this.messages.push({
|
||||
type: 'ai',
|
||||
text: errorMessage,
|
||||
data: { type: ['text'], data: [{ type: 'text', content: errorMessage }] },
|
||||
timestamp: new Date()
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.scrollToBottom();
|
||||
}, 200);
|
||||
|
|
Loading…
Reference in a new issue