forked from morrning/hesabixCore
redisign ai agent from over
This commit is contained in:
parent
39a2846ff6
commit
8a3ebc64cb
|
@ -3,7 +3,7 @@ namespace App\Controller;
|
|||
|
||||
use App\Entity\AIConversation;
|
||||
use App\Entity\AIMessage;
|
||||
use App\Service\AI\AIService;
|
||||
use App\Service\AGI\AGIService;
|
||||
use App\Service\Access;
|
||||
use App\Service\Extractor;
|
||||
use App\Service\Log;
|
||||
|
@ -17,11 +17,11 @@ use Symfony\Component\Security\Http\Attribute\CurrentUser;
|
|||
|
||||
class wizardController extends AbstractController
|
||||
{
|
||||
private AIService $aiService;
|
||||
private AGIService $agiService;
|
||||
|
||||
public function __construct(AIService $aiService)
|
||||
public function __construct(AGIService $agiService)
|
||||
{
|
||||
$this->aiService = $aiService;
|
||||
$this->agiService = $agiService;
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/talk', name: 'wizard_talk', methods: ['POST'])]
|
||||
|
@ -60,7 +60,8 @@ class wizardController extends AbstractController
|
|||
$conversationId = $params['conversationId'] ?? null;
|
||||
|
||||
// بررسی فعال بودن هوش مصنوعی
|
||||
if (!$this->aiService->isAIEnabled()) {
|
||||
$aiStatus = $this->agiService->checkAIServiceStatus();
|
||||
if (!$aiStatus['isEnabled']) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'سرویس هوش مصنوعی غیرفعال است'
|
||||
|
@ -84,226 +85,26 @@ class wizardController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
// دریافت یا ایجاد گفتگو
|
||||
$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']);
|
||||
|
||||
// استفاده از عنوان ساده برای جلوگیری از مشکل کدگذاری
|
||||
$title = substr($message, 0, 50);
|
||||
$title = preg_replace('/[^\x20-\x7E]/', '', $title); // حذف کاراکترهای غیر ASCII
|
||||
if (empty($title)) {
|
||||
$title = 'New Conversation';
|
||||
}
|
||||
$conversation->setTitle($title . '...');
|
||||
$conversation->setCategory('General');
|
||||
$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);
|
||||
|
||||
// دریافت تاریخچه گفتگو برای حفظ context
|
||||
$conversationHistory = [];
|
||||
if ($conversationId) {
|
||||
$previousMessages = $entityManager->getRepository(\App\Entity\AIMessage::class)
|
||||
->findBy(['conversation' => $conversation], ['id' => 'ASC']);
|
||||
|
||||
foreach ($previousMessages as $prevMessage) {
|
||||
$conversationHistory[] = [
|
||||
'role' => $prevMessage->getRole(),
|
||||
'content' => $prevMessage->getContent()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// بررسی درخواستهای مستقیم برای تیکت
|
||||
$directTicketCommands = [
|
||||
'وضعیت تیکت های من',
|
||||
'وضعیت تیکتهای من',
|
||||
'آمار تیکت های من',
|
||||
'آمار تیکتهای من',
|
||||
'تیکت های من',
|
||||
'تیکتهای من'
|
||||
];
|
||||
|
||||
$listTicketCommands = [
|
||||
'لیست تیکت های من',
|
||||
'لیست تیکتهای من',
|
||||
'مشاهده تیکت های من',
|
||||
'مشاهده تیکتهای من',
|
||||
'نمایش تیکت های من',
|
||||
'نمایش تیکتهای من'
|
||||
];
|
||||
|
||||
$messageLower = mb_strtolower(trim($message), 'UTF-8');
|
||||
$isDirectTicketRequest = false;
|
||||
$isListTicketRequest = false;
|
||||
|
||||
foreach ($directTicketCommands as $command) {
|
||||
if (strpos($messageLower, mb_strtolower($command, 'UTF-8')) !== false) {
|
||||
$isDirectTicketRequest = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($listTicketCommands as $command) {
|
||||
if (strpos($messageLower, mb_strtolower($command, 'UTF-8')) !== false) {
|
||||
$isListTicketRequest = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isDirectTicketRequest) {
|
||||
// پردازش مستقیم درخواست تیکت
|
||||
$ticketResult = $this->aiService->getTicketManagementService()->getTicketStatistics([], $business, $acc['user']);
|
||||
|
||||
if ($ticketResult['success']) {
|
||||
$stats = $ticketResult['statistics'];
|
||||
$responseContent = "📊 وضعیت تیکتهای شما:\n\n";
|
||||
$responseContent .= "📋 کل تیکتها: {$stats['total']}\n";
|
||||
$responseContent .= "⏳ در حال پیگیری: {$stats['pending']}\n";
|
||||
$responseContent .= "✅ پاسخ داده شده: {$stats['answered']}\n";
|
||||
$responseContent .= "🔒 خاتمه یافته: {$stats['closed']}\n\n";
|
||||
|
||||
if ($stats['total'] > 0) {
|
||||
$responseContent .= "💡 برای مشاهده جزئیات تیکتها، بگویید: 'لیست تیکتهای من'";
|
||||
} else {
|
||||
$responseContent .= "💡 برای ایجاد تیکت جدید، بگویید: 'تیکت جدید'";
|
||||
}
|
||||
|
||||
$result = [
|
||||
'success' => true,
|
||||
'response' => $responseContent,
|
||||
'requires_action' => false,
|
||||
'action_data' => null,
|
||||
'debug_info' => [
|
||||
'direct_ticket_request' => true,
|
||||
'statistics' => $stats
|
||||
]
|
||||
];
|
||||
} else {
|
||||
$result = [
|
||||
'success' => false,
|
||||
'error' => $ticketResult['error'] ?? 'خطا در دریافت آمار تیکتها'
|
||||
];
|
||||
}
|
||||
} elseif ($isListTicketRequest) {
|
||||
// پردازش مستقیم درخواست لیست تیکتها
|
||||
$ticketResult = $this->aiService->getTicketManagementService()->listUserTickets([], $business, $acc['user']);
|
||||
|
||||
if ($ticketResult['success']) {
|
||||
$tickets = $ticketResult['tickets'];
|
||||
$count = count($tickets);
|
||||
|
||||
$responseContent = "📋 لیست تیکتهای شما ({$count} تیکت):\n";
|
||||
$responseContent .= "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";
|
||||
|
||||
if ($count > 0) {
|
||||
foreach ($tickets as $index => $ticket) {
|
||||
$statusIcon = $this->getTicketStatusIcon($ticket['state']);
|
||||
$responseContent .= ($index + 1) . ". {$statusIcon} ";
|
||||
$responseContent .= $ticket['title'] . "\n";
|
||||
$responseContent .= " 📅 تاریخ: " . $ticket['date'] . "\n";
|
||||
$responseContent .= " 🔢 کد: " . $ticket['code'] . "\n";
|
||||
$responseContent .= " 📄 متن: " . $ticket['body'] . "\n";
|
||||
if ($ticket['has_file']) {
|
||||
$responseContent .= " 📎 فایل پیوست دارد\n";
|
||||
}
|
||||
$responseContent .= "\n";
|
||||
}
|
||||
|
||||
$responseContent .= "💡 برای مشاهده جزئیات یک تیکت خاص، بگویید: 'مشاهده تیکت [کد تیکت]'";
|
||||
} else {
|
||||
$responseContent .= "❌ هیچ تیکتی یافت نشد.\n\n";
|
||||
$responseContent .= "💡 برای ایجاد تیکت جدید، بگویید: 'تیکت جدید'";
|
||||
}
|
||||
|
||||
$result = [
|
||||
'success' => true,
|
||||
'response' => $responseContent,
|
||||
'requires_action' => false,
|
||||
'action_data' => null,
|
||||
'debug_info' => [
|
||||
'direct_ticket_request' => true,
|
||||
'tickets_count' => $count,
|
||||
'tickets' => $tickets
|
||||
]
|
||||
];
|
||||
} else {
|
||||
$result = [
|
||||
'success' => false,
|
||||
'error' => $ticketResult['error'] ?? 'خطا در دریافت لیست تیکتها'
|
||||
];
|
||||
}
|
||||
} else {
|
||||
// ارسال درخواست به سرویس هوش مصنوعی با تاریخچه
|
||||
$result = $this->aiService->sendRequest($message, $business, $acc['user'], $conversationHistory);
|
||||
}
|
||||
// استفاده از AGIService برای مدیریت گفتگو و ارسال درخواست
|
||||
$result = $this->agiService->sendRequest($message, $business, $acc['user'], $conversationId);
|
||||
|
||||
if ($result['success']) {
|
||||
// بررسی وجود کلید response
|
||||
$responseContent = $result['response'] ?? $result['message'] ?? 'عملیات با موفقیت انجام شد';
|
||||
|
||||
// ذخیره پاسخ هوش مصنوعی
|
||||
$aiMessage = new AIMessage();
|
||||
$aiMessage->setConversation($conversation);
|
||||
$aiMessage->setRole('assistant');
|
||||
$aiMessage->setContent($responseContent);
|
||||
$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' => $responseContent,
|
||||
'conversationId' => $conversation->getId(),
|
||||
'conversationId' => $result['conversation_id'] ?? null,
|
||||
'model' => $result['model'] ?? null,
|
||||
'usage' => $result['usage'] ?? null,
|
||||
'cost' => $result['cost'] ?? null,
|
||||
'debug_info' => $result['debug_info'] ?? null
|
||||
];
|
||||
|
||||
// محاسبه هزینه در صورت وجود اطلاعات usage
|
||||
if (isset($result['usage'])) {
|
||||
$response['cost'] = $cost;
|
||||
if (isset($result['cost'])) {
|
||||
$cost = $result['cost'];
|
||||
|
||||
// کسر اعتبار از کسب و کار
|
||||
$totalCost = $cost['total_cost'] ?? 0;
|
||||
|
@ -328,10 +129,6 @@ class wizardController extends AbstractController
|
|||
|
||||
return $this->json($response);
|
||||
} else {
|
||||
// حذف پیام کاربر در صورت خطا
|
||||
$entityManager->remove($userMessage);
|
||||
$entityManager->flush();
|
||||
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => $result['error'] ?? 'خطای نامشخص در سرویس هوش مصنوعی',
|
||||
|
@ -357,18 +154,16 @@ class wizardController extends AbstractController
|
|||
public function wizard_status(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$isEnabled = $this->aiService->isAIEnabled();
|
||||
$serviceStatus = $this->aiService->checkAIServiceStatus();
|
||||
$hasApiKey = $serviceStatus['hasApiKey'];
|
||||
$serviceStatus = $this->agiService->checkAIServiceStatus();
|
||||
|
||||
// بررسی وضعیت کامل
|
||||
$status = 'available';
|
||||
$message = 'سرویس هوش مصنوعی در دسترس است';
|
||||
|
||||
if (!$isEnabled) {
|
||||
if (!$serviceStatus['isEnabled']) {
|
||||
$status = 'disabled';
|
||||
$message = 'سرویس هوش مصنوعی غیرفعال است';
|
||||
} elseif (!$hasApiKey) {
|
||||
} elseif (!$serviceStatus['hasApiKey']) {
|
||||
$status = 'no_api_key';
|
||||
$message = 'کلید API تنظیم نشده است';
|
||||
}
|
||||
|
@ -393,8 +188,8 @@ class wizardController extends AbstractController
|
|||
public function wizard_models(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$agentSource = $this->aiService->getAIAgentSource();
|
||||
$currentModel = $this->aiService->getAIModel();
|
||||
$agentSource = $this->agiService->getAIAgentSource();
|
||||
$currentModel = $this->agiService->getAIModel();
|
||||
|
||||
// لیست مدلهای موجود بر اساس منبع ایجنت
|
||||
$models = [];
|
||||
|
@ -430,7 +225,7 @@ class wizardController extends AbstractController
|
|||
'success' => true,
|
||||
'models' => $models,
|
||||
'current_model' => $currentModel,
|
||||
'service_name' => $this->aiService->getServiceDisplayName($agentSource)
|
||||
'service_name' => $this->agiService->getServiceDisplayName($agentSource)
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
|
@ -444,16 +239,16 @@ class wizardController extends AbstractController
|
|||
public function wizard_settings(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$agentSource = $this->aiService->getAIAgentSource();
|
||||
$agentSource = $this->agiService->getAIAgentSource();
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'settings' => [
|
||||
'aiEnabled' => $this->aiService->isAIEnabled(),
|
||||
'serviceName' => $this->aiService->getServiceDisplayName($agentSource),
|
||||
'aiModel' => $this->aiService->getAIModel(),
|
||||
'inputTokenPrice' => $this->aiService->getInputTokenPrice(),
|
||||
'outputTokenPrice' => $this->aiService->getOutputTokenPrice(),
|
||||
'aiPrompt' => $this->aiService->getAIPrompt()
|
||||
'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) {
|
||||
|
@ -497,617 +292,4 @@ class wizardController extends AbstractController
|
|||
}
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/persons/search', name: 'wizard_persons_search', methods: ['POST'])]
|
||||
public function wizard_persons_search(Request $request, Access $access): 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) ?? [];
|
||||
$searchTerm = $params['search'] ?? '';
|
||||
|
||||
if (empty($searchTerm)) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'عبارت جستجو الزامی است'
|
||||
]);
|
||||
}
|
||||
|
||||
$business = $acc['bid'];
|
||||
$persons = $this->aiService->getPersonDataService()->searchPersons($business, $searchTerm);
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'persons' => $persons
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'خطا در جستجوی اشخاص: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/persons/{personId}', name: 'wizard_person_details', methods: ['GET'])]
|
||||
public function wizard_person_details($personId, Access $access): JsonResponse
|
||||
{
|
||||
try {
|
||||
$personId = (int)$personId;
|
||||
$acc = $access->hasRole('join');
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
// بررسی دسترسی هوش مصنوعی
|
||||
if (!$acc['ai']) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'شما دسترسی استفاده از هوش مصنوعی را ندارید'
|
||||
]);
|
||||
}
|
||||
|
||||
$business = $acc['bid'];
|
||||
$personData = $this->aiService->getPersonDataService()->getPersonData($business, $personId);
|
||||
|
||||
if (!$personData) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'شخص مورد نظر یافت نشد'
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'person' => $personData
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'خطا در دریافت اطلاعات شخص: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/persons/{personId}/transactions', name: 'wizard_person_transactions', methods: ['GET'])]
|
||||
public function wizard_person_transactions(int $personId, Request $request, Access $access, EntityManagerInterface $entityManager): JsonResponse
|
||||
{
|
||||
try {
|
||||
$acc = $access->hasRole('join');
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
// بررسی دسترسی هوش مصنوعی
|
||||
if (!$acc['ai']) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'شما دسترسی استفاده از هوش مصنوعی را ندارید'
|
||||
]);
|
||||
}
|
||||
|
||||
$limit = (int) ($request->query->get('limit') ?? 10);
|
||||
$business = $acc['bid'];
|
||||
|
||||
$person = $entityManager->getRepository(\App\Entity\Person::class)->findOneBy([
|
||||
'id' => $personId,
|
||||
'bid' => $business
|
||||
]);
|
||||
|
||||
if (!$person) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'شخص مورد نظر یافت نشد'
|
||||
]);
|
||||
}
|
||||
|
||||
$transactions = $this->aiService->getPersonDataService()->getPersonTransactions($business, $person, $limit);
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'transactions' => $transactions
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'خطا در دریافت تراکنشهای شخص: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/persons/guide', name: 'wizard_persons_guide', methods: ['GET'])]
|
||||
public function wizard_persons_guide(Access $access): JsonResponse
|
||||
{
|
||||
try {
|
||||
$acc = $access->hasRole('join');
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
// بررسی دسترسی هوش مصنوعی
|
||||
if (!$acc['ai']) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'شما دسترسی استفاده از هوش مصنوعی را ندارید'
|
||||
]);
|
||||
}
|
||||
|
||||
$guide = $this->aiService->getPersonManagementService()->getOperationsGuide();
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'guide' => $guide
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'خطا در دریافت راهنما: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/connection/test', name: 'wizard_connection_test', methods: ['GET'])]
|
||||
public function wizard_connection_test(Access $access): JsonResponse
|
||||
{
|
||||
try {
|
||||
$acc = $access->hasRole('join');
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
// بررسی دسترسی هوش مصنوعی
|
||||
if (!$acc['ai']) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'شما دسترسی استفاده از هوش مصنوعی را ندارید'
|
||||
]);
|
||||
}
|
||||
|
||||
$status = $this->aiService->checkAIServiceStatus();
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'status' => $status
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'خطا در تست اتصال: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/execute-command', name: 'wizard_execute_command', methods: ['POST'])]
|
||||
public function wizard_execute_command(
|
||||
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['tool']) || !isset($params['params'])) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'ابزار و پارامترها الزامی هستند'
|
||||
]);
|
||||
}
|
||||
|
||||
$tool = $params['tool'];
|
||||
$commandParams = $params['params'];
|
||||
$business = $acc['bid'];
|
||||
|
||||
// اجرای دستور
|
||||
$result = null;
|
||||
switch ($tool) {
|
||||
case 'add_person':
|
||||
$result = $this->aiService->getPersonManagementService()->addPerson($commandParams, $business, $acc['user']);
|
||||
break;
|
||||
case 'edit_person':
|
||||
$result = $this->aiService->getPersonManagementService()->editPerson($commandParams, $business, $acc['user']);
|
||||
break;
|
||||
case 'delete_person':
|
||||
$result = $this->aiService->getPersonManagementService()->deletePerson($commandParams, $business, $acc['user']);
|
||||
break;
|
||||
case 'show_person':
|
||||
$result = $this->aiService->getPersonManagementService()->showPerson($commandParams, $business, $acc['user']);
|
||||
break;
|
||||
case 'search_persons':
|
||||
$result = $this->aiService->getPersonManagementService()->searchPersons($commandParams, $business);
|
||||
break;
|
||||
case 'advanced_search_persons':
|
||||
$result = $this->aiService->getPersonManagementService()->advancedSearchPersons($commandParams, $business);
|
||||
break;
|
||||
case 'search_by_mobile':
|
||||
$result = $this->aiService->getPersonManagementService()->searchByMobile($commandParams, $business);
|
||||
break;
|
||||
case 'search_debtors':
|
||||
$result = $this->aiService->getPersonManagementService()->searchDebtors($commandParams, $business);
|
||||
break;
|
||||
case 'search_creditors':
|
||||
$result = $this->aiService->getPersonManagementService()->searchCreditors($commandParams, $business);
|
||||
break;
|
||||
|
||||
// ابزارهای مدیریت تیکت
|
||||
case 'create_ticket':
|
||||
$result = $this->aiService->getTicketManagementService()->createTicket($commandParams, $business, $acc['user']);
|
||||
break;
|
||||
|
||||
case 'list_tickets':
|
||||
$result = $this->aiService->getTicketManagementService()->listUserTickets($commandParams, $business, $acc['user']);
|
||||
break;
|
||||
|
||||
case 'view_ticket':
|
||||
$result = $this->aiService->getTicketManagementService()->viewTicket($commandParams, $business, $acc['user']);
|
||||
break;
|
||||
|
||||
case 'reply_ticket':
|
||||
$result = $this->aiService->getTicketManagementService()->replyToTicket($commandParams, $business, $acc['user']);
|
||||
break;
|
||||
|
||||
case 'search_tickets':
|
||||
$result = $this->aiService->getTicketManagementService()->searchTickets($commandParams, $business, $acc['user']);
|
||||
break;
|
||||
|
||||
case 'get_ticket_statistics':
|
||||
$result = $this->aiService->getTicketManagementService()->getTicketStatistics($commandParams, $business, $acc['user']);
|
||||
break;
|
||||
|
||||
default:
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => "ابزار '{$tool}' شناخته نشد"
|
||||
]);
|
||||
}
|
||||
|
||||
if ($result && $result['success']) {
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'response' => $result['message'] ?? 'عملیات با موفقیت انجام شد',
|
||||
'debug_info' => [
|
||||
'ai_response' => 'دستور اجرا شد',
|
||||
'tool_commands' => [
|
||||
[
|
||||
'tool' => $tool,
|
||||
'params' => $commandParams
|
||||
]
|
||||
],
|
||||
'has_commands' => true,
|
||||
'execution_result' => $result
|
||||
]
|
||||
]);
|
||||
} else {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => $result['error'] ?? 'خطا در اجرای دستور',
|
||||
'debug_info' => [
|
||||
'ai_response' => 'خطا در اجرای دستور',
|
||||
'tool_commands' => [
|
||||
[
|
||||
'tool' => $tool,
|
||||
'params' => $commandParams
|
||||
]
|
||||
],
|
||||
'has_commands' => true,
|
||||
'execution_result' => $result
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'خطا در اجرای دستور: ' . $e->getMessage(),
|
||||
'debug_info' => [
|
||||
'ai_response' => null,
|
||||
'tool_commands' => [],
|
||||
'has_commands' => false,
|
||||
'error_details' => $e->getMessage()
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/test-debug-info', name: 'wizard_test_debug_info', methods: ['POST'])]
|
||||
public function wizard_test_debug_info(Access $access): JsonResponse
|
||||
{
|
||||
try {
|
||||
$acc = $access->hasRole('join');
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
// بررسی دسترسی هوش مصنوعی
|
||||
if (!$acc['ai']) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'شما دسترسی استفاده از هوش مصنوعی را ندارید',
|
||||
'debug_info' => [
|
||||
'ai_response' => 'خطای دسترسی',
|
||||
'tool_commands' => [],
|
||||
'has_commands' => false,
|
||||
'error_details' => 'No AI access'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'response' => 'تست Debug Info با موفقیت انجام شد',
|
||||
'debug_info' => [
|
||||
'ai_response' => 'این یک تست برای Debug Info است',
|
||||
'tool_commands' => [
|
||||
[
|
||||
'tool' => 'test_tool',
|
||||
'params' => ['test_param' => 'test_value']
|
||||
]
|
||||
],
|
||||
'has_commands' => true,
|
||||
'test_data' => 'این داده تست است'
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'خطا در تست Debug Info: ' . $e->getMessage(),
|
||||
'debug_info' => [
|
||||
'ai_response' => null,
|
||||
'tool_commands' => [],
|
||||
'has_commands' => false,
|
||||
'error_details' => $e->getMessage()
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/test-interactive', name: 'wizard_test_interactive', methods: ['POST'])]
|
||||
public function wizard_test_interactive(Access $access): JsonResponse
|
||||
{
|
||||
try {
|
||||
$acc = $access->hasRole('join');
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
// بررسی دسترسی هوش مصنوعی
|
||||
if (!$acc['ai']) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'شما دسترسی استفاده از هوش مصنوعی را ندارید',
|
||||
'debug_info' => [
|
||||
'ai_response' => 'خطای دسترسی',
|
||||
'action_type' => 'error',
|
||||
'has_commands' => false,
|
||||
'error_details' => 'No AI access'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// تست سیستم تعاملی
|
||||
$testMessage = "تلفن 09123456789 را برای محسن اضافه کن";
|
||||
$business = $acc['bid'];
|
||||
|
||||
$result = $this->aiService->sendRequest($testMessage, $business, $acc['user']);
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'response' => $result['response'] ?? 'تست سیستم تعاملی',
|
||||
'debug_info' => $result['debug_info'] ?? [
|
||||
'ai_response' => 'تست سیستم تعاملی',
|
||||
'action_type' => 'test',
|
||||
'has_commands' => false
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'خطا در تست سیستم تعاملی: ' . $e->getMessage(),
|
||||
'debug_info' => [
|
||||
'ai_response' => null,
|
||||
'action_type' => 'error',
|
||||
'has_commands' => false,
|
||||
'error_details' => $e->getMessage()
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/test-interactive-system', name: 'wizard_test_interactive_system', methods: ['POST'])]
|
||||
public function wizard_test_interactive_system(Access $access): JsonResponse
|
||||
{
|
||||
try {
|
||||
$acc = $access->hasRole('join');
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
// بررسی دسترسی هوش مصنوعی
|
||||
if (!$acc['ai']) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'شما دسترسی استفاده از هوش مصنوعی را ندارید',
|
||||
'debug_info' => [
|
||||
'ai_response' => 'خطای دسترسی',
|
||||
'action_type' => 'error',
|
||||
'has_commands' => false,
|
||||
'error_details' => 'No AI access'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// تست سیستم تعاملی
|
||||
$testMessage = "تلفن 09123456789 را برای محسن اضافه کن";
|
||||
$business = $acc['bid'];
|
||||
|
||||
$result = $this->aiService->sendRequest($testMessage, $business, $acc['user']);
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'response' => $result['response'] ?? 'تست سیستم تعاملی',
|
||||
'debug_info' => $result['debug_info'] ?? [
|
||||
'ai_response' => 'تست سیستم تعاملی',
|
||||
'action_type' => 'test',
|
||||
'has_commands' => false
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'خطا در تست سیستم تعاملی: ' . $e->getMessage(),
|
||||
'debug_info' => [
|
||||
'ai_response' => null,
|
||||
'action_type' => 'error',
|
||||
'has_commands' => false,
|
||||
'error_details' => $e->getMessage()
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/test-continuous-operation', name: 'wizard_test_continuous_operation', methods: ['POST'])]
|
||||
public function wizard_test_continuous_operation(Access $access): JsonResponse
|
||||
{
|
||||
try {
|
||||
$acc = $access->hasRole('join');
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
// بررسی دسترسی هوش مصنوعی
|
||||
if (!$acc['ai']) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'شما دسترسی استفاده از هوش مصنوعی را ندارید',
|
||||
'debug_info' => [
|
||||
'ai_response' => 'خطای دسترسی',
|
||||
'action_type' => 'error',
|
||||
'has_commands' => false,
|
||||
'error_details' => 'No AI access'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// تست عملیات پیوسته
|
||||
$testMessage = "برای احمد موبایل 09123456789 تنظیم کن";
|
||||
$business = $acc['bid'];
|
||||
|
||||
$result = $this->aiService->sendRequest($testMessage, $business, $acc['user']);
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'response' => $result['response'] ?? 'تست عملیات پیوسته',
|
||||
'debug_info' => $result['debug_info'] ?? [
|
||||
'ai_response' => 'تست عملیات پیوسته',
|
||||
'action_type' => 'test',
|
||||
'has_commands' => false
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'خطا در تست عملیات پیوسته: ' . $e->getMessage(),
|
||||
'debug_info' => [
|
||||
'ai_response' => null,
|
||||
'action_type' => 'error',
|
||||
'has_commands' => false,
|
||||
'error_details' => $e->getMessage()
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/test-smart-operation', name: 'wizard_test_smart_operation', methods: ['POST'])]
|
||||
public function wizard_test_smart_operation(Access $access): JsonResponse
|
||||
{
|
||||
try {
|
||||
$acc = $access->hasRole('join');
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
// بررسی دسترسی هوش مصنوعی
|
||||
if (!$acc['ai']) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'شما دسترسی استفاده از هوش مصنوعی را ندارید',
|
||||
'debug_info' => [
|
||||
'ai_response' => 'خطای دسترسی',
|
||||
'action_type' => 'error',
|
||||
'has_commands' => false,
|
||||
'error_details' => 'No AI access'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// تست عملیات هوشمند
|
||||
$testMessage = "تلفن 09123456789 را برای محسن اضافه کن";
|
||||
$business = $acc['bid'];
|
||||
|
||||
$result = $this->aiService->sendRequest($testMessage, $business, $acc['user']);
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'response' => $result['response'] ?? 'تست عملیات هوشمند',
|
||||
'debug_info' => $result['debug_info'] ?? [
|
||||
'ai_response' => 'تست عملیات هوشمند',
|
||||
'action_type' => 'test',
|
||||
'has_commands' => false
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'خطا در تست عملیات هوشمند: ' . $e->getMessage(),
|
||||
'debug_info' => [
|
||||
'ai_response' => null,
|
||||
'action_type' => 'error',
|
||||
'has_commands' => false,
|
||||
'error_details' => $e->getMessage()
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت آیکون وضعیت تیکت
|
||||
*/
|
||||
private function getTicketStatusIcon(string $state): string
|
||||
{
|
||||
return match ($state) {
|
||||
'در حال پیگیری' => '⏳',
|
||||
'پاسخ داده شده' => '✅',
|
||||
'خاتمه یافته' => '🔒',
|
||||
default => '📋'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,9 @@ class APIToken
|
|||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?User $submitter = null;
|
||||
|
||||
#[ORM\Column(type: 'boolean', nullable: true)]
|
||||
private ?bool $isForAi = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
|
@ -78,4 +81,16 @@ class APIToken
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isForAi(): ?bool
|
||||
{
|
||||
return $this->isForAi;
|
||||
}
|
||||
|
||||
public function setIsForAi(?bool $isForAi): static
|
||||
{
|
||||
$this->isForAi = $isForAi;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
609
hesabixCore/src/Service/AGI/AGIService.php
Normal file
609
hesabixCore/src/Service/AGI/AGIService.php
Normal file
|
@ -0,0 +1,609 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service\AGI;
|
||||
|
||||
use App\Entity\Business;
|
||||
use App\Entity\AIConversation;
|
||||
use App\Entity\AIMessage;
|
||||
use App\Service\registryMGR;
|
||||
use App\Service\Log;
|
||||
use App\Service\Provider;
|
||||
use App\Service\AGI\Promps\PromptService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class AGIService
|
||||
{
|
||||
private $em;
|
||||
private $registryMGR;
|
||||
private $log;
|
||||
private $provider;
|
||||
private $promptService;
|
||||
|
||||
public function __construct(
|
||||
EntityManagerInterface $entityManager,
|
||||
registryMGR $registryMGR,
|
||||
Log $log,
|
||||
Provider $provider,
|
||||
PromptService $promptService
|
||||
) {
|
||||
$this->em = $entityManager;
|
||||
$this->registryMGR = $registryMGR;
|
||||
$this->log = $log;
|
||||
$this->provider = $provider;
|
||||
$this->promptService = $promptService;
|
||||
}
|
||||
|
||||
/**
|
||||
* ارسال درخواست به هوش مصنوعی
|
||||
* @param string $message پیام کاربر
|
||||
* @param Business|null $business کسب و کار
|
||||
* @param mixed $user کاربر
|
||||
* @param int|null $conversationId شناسه گفتگو (اختیاری)
|
||||
* @return array
|
||||
*/
|
||||
public function sendRequest(string $message, ?Business $business = null, $user = null, ?int $conversationId = null): array
|
||||
{
|
||||
// بررسی فعال بودن هوش مصنوعی
|
||||
$status = $this->checkAIServiceStatus();
|
||||
if (!$status['enabled']) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'سرویس هوش مصنوعی غیرفعال است.',
|
||||
'status' => $status
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
// مدیریت گفتگو و تاریخچه
|
||||
$conversation = $this->manageConversation($conversationId, $business, $user, $message);
|
||||
$conversationHistory = $this->getConversationHistory($conversation);
|
||||
|
||||
// ذخیره پیام کاربر
|
||||
$this->saveUserMessage($conversation, $message);
|
||||
|
||||
// ساخت پرامپ هوشمند
|
||||
$prompt = $this->buildSmartPrompt($message, $business, $conversationHistory);
|
||||
$service = $this->getAIAgentSource();
|
||||
$apiKey = $this->getAIApiKey($service);
|
||||
|
||||
if (!$apiKey) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'کلید API برای سرویس هوش مصنوعی تنظیم نشده است.'
|
||||
];
|
||||
}
|
||||
|
||||
// ارسال درخواست به سرویس هوش مصنوعی
|
||||
$response = $this->sendToAIService($prompt, $apiKey, $service, $conversationHistory);
|
||||
|
||||
if ($response['success']) {
|
||||
// پردازش پاسخ
|
||||
$aiResponse = $this->extractAIResponse($response['data']);
|
||||
$cost = $this->calculateCostFromResponse($response['data']);
|
||||
|
||||
// ذخیره پاسخ هوش مصنوعی
|
||||
$this->saveAIMessage($conversation, $aiResponse, $response['data'], $cost);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'response' => $aiResponse,
|
||||
'usage' => $response['data']['usage'] ?? null,
|
||||
'cost' => $cost,
|
||||
'service' => $service,
|
||||
'model' => $this->getAIModel(),
|
||||
'conversation_id' => $conversation->getId()
|
||||
];
|
||||
}
|
||||
|
||||
return $response;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'خطا در پردازش درخواست: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ساخت پرامپ هوشمند
|
||||
*/
|
||||
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
|
||||
{
|
||||
$urls = $this->getServiceUrls($service);
|
||||
$model = $this->getAIModel();
|
||||
|
||||
// ساخت messages با تاریخچه
|
||||
$messages = [];
|
||||
|
||||
// اضافه کردن تاریخچه گفتگو
|
||||
foreach ($conversationHistory as $historyItem) {
|
||||
$messages[] = [
|
||||
'role' => $historyItem['role'],
|
||||
'content' => $historyItem['content']
|
||||
];
|
||||
}
|
||||
|
||||
// اضافه کردن پیام فعلی
|
||||
$messages[] = [
|
||||
'role' => 'user',
|
||||
'content' => $prompt
|
||||
];
|
||||
|
||||
$data = [
|
||||
'model' => $model,
|
||||
'messages' => $messages,
|
||||
'max_tokens' => 12000,
|
||||
'temperature' => 0.1
|
||||
];
|
||||
|
||||
// تلاش برای ارسال به URL های مختلف
|
||||
foreach ($urls as $url) {
|
||||
$result = $this->makeHttpRequest($url, $data, $apiKey);
|
||||
if ($result['success']) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'خطا در ارتباط با سرور هوش مصنوعی.'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت URL های سرویس
|
||||
*/
|
||||
private function getServiceUrls(string $service): array
|
||||
{
|
||||
return match ($service) {
|
||||
'gapgpt' => [
|
||||
'https://api.gapgpt.app/v1/chat/completions',
|
||||
'https://api.gapgpt.ir/v1/chat/completions'
|
||||
],
|
||||
'avalai' => ['https://api.avalai.com/v1/chat/completions'],
|
||||
'local' => [$this->registryMGR->get('system', 'localModelAddress') ?? ''],
|
||||
default => []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* انجام درخواست HTTP
|
||||
*/
|
||||
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
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت پیام خطای HTTP
|
||||
*/
|
||||
private function getHttpErrorMessage(int $httpCode): string
|
||||
{
|
||||
$messages = [
|
||||
400 => 'درخواست نامعتبر',
|
||||
401 => 'عدم احراز هویت',
|
||||
403 => 'دسترسی ممنوع',
|
||||
404 => 'منبع یافت نشد',
|
||||
429 => 'تعداد درخواستها بیش از حد مجاز',
|
||||
500 => 'خطای داخلی سرور',
|
||||
502 => 'خطای دروازه',
|
||||
503 => 'سرویس در دسترس نیست',
|
||||
504 => 'خطای timeout دروازه'
|
||||
];
|
||||
|
||||
return $messages[$httpCode] ?? 'خطای نامشخص';
|
||||
}
|
||||
|
||||
/**
|
||||
* استخراج پاسخ از ساختارهای مختلف سرویسها
|
||||
*/
|
||||
private function extractAIResponse(array $responseData): ?string
|
||||
{
|
||||
if (isset($responseData['choices'][0]['message']['content'])) {
|
||||
return $responseData['choices'][0]['message']['content'];
|
||||
}
|
||||
|
||||
if (isset($responseData['response'])) {
|
||||
return $responseData['response'];
|
||||
}
|
||||
|
||||
if (isset($responseData['content'])) {
|
||||
return $responseData['content'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* محاسبه هزینه استفاده
|
||||
*/
|
||||
private function calculateCostFromResponse(array $responseData): array
|
||||
{
|
||||
$usage = $responseData['usage'] ?? [];
|
||||
return $this->calculateCost($usage);
|
||||
}
|
||||
|
||||
/**
|
||||
* محاسبه هزینه بر اساس usage
|
||||
*/
|
||||
public function calculateCost(array $usage): array
|
||||
{
|
||||
$promptTokens = $usage['prompt_tokens'] ?? 0;
|
||||
$completionTokens = $usage['completion_tokens'] ?? 0;
|
||||
|
||||
$inputCost = $promptTokens * $this->getInputTokenPrice();
|
||||
$outputCost = $completionTokens * $this->getOutputTokenPrice();
|
||||
|
||||
return [
|
||||
'input_cost' => round($inputCost, 4),
|
||||
'output_cost' => round($outputCost, 4),
|
||||
'total_cost' => round($inputCost + $outputCost, 4)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* بررسی وضعیت سرویس هوش مصنوعی
|
||||
*/
|
||||
public function checkAIServiceStatus(): array
|
||||
{
|
||||
$enabled = $this->registryMGR->get('system', 'aiEnabled') ?? false;
|
||||
$service = $this->registryMGR->get('system', 'aiAgentSource') ?? 'gapgpt';
|
||||
$apiKey = $this->getAIApiKey($service);
|
||||
|
||||
return [
|
||||
'isEnabled' => $enabled && $apiKey,
|
||||
'enabled' => $enabled && $apiKey,
|
||||
'service' => $this->getServiceDisplayName($service),
|
||||
'hasApiKey' => !empty($apiKey),
|
||||
'message' => $enabled && $apiKey ? 'سرویس فعال است' : 'سرویس غیرفعال است'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت نام نمایشی سرویس
|
||||
*/
|
||||
public function getServiceDisplayName(string $service): string
|
||||
{
|
||||
$displayNames = [
|
||||
'gapgpt' => 'گپجیپیتی',
|
||||
'avalai' => 'اولآی',
|
||||
'local' => 'سرویس محلی'
|
||||
];
|
||||
|
||||
return $displayNames[$service] ?? 'سرویس نامشخص';
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت کلید API برای سرویس مشخص
|
||||
*/
|
||||
private function getAIApiKey(string $service): ?string
|
||||
{
|
||||
return match ($service) {
|
||||
'gapgpt', 'avalai' => $this->registryMGR->get('system', 'aiApiKey') ?? null,
|
||||
'local' => $this->registryMGR->get('system', 'localModelAddress') ?? null,
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت مدل هوش مصنوعی
|
||||
*/
|
||||
public function getAIModel(): string
|
||||
{
|
||||
return $this->registryMGR->get('system', 'aiModel') ?? 'gpt-4o-2024-11-20';
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت منبع عامل هوش مصنوعی
|
||||
*/
|
||||
public function getAIAgentSource(): string
|
||||
{
|
||||
return $this->registryMGR->get('system', 'aiAgentSource') ?? 'gapgpt';
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت قیمت توکن ورودی
|
||||
*/
|
||||
public function getInputTokenPrice(): float
|
||||
{
|
||||
return (float) ($this->registryMGR->get('system', 'inputTokenPrice') ?? 0.00001);
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت قیمت توکن خروجی
|
||||
*/
|
||||
public function getOutputTokenPrice(): float
|
||||
{
|
||||
return (float) ($this->registryMGR->get('system', 'outputTokenPrice') ?? 0.00003);
|
||||
}
|
||||
|
||||
/**
|
||||
* بررسی فعال بودن هوش مصنوعی
|
||||
*/
|
||||
public function isAIEnabled(): bool
|
||||
{
|
||||
return (bool) ($this->registryMGR->get('system', 'aiEnabled') ?? false);
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت پرامپ هوش مصنوعی
|
||||
*/
|
||||
public function getAIPrompt(): string
|
||||
{
|
||||
return $this->registryMGR->get('system', 'aiPrompt') ?? 'شما یک دستیار هوشمند برای سیستم حسابداری هستید.';
|
||||
}
|
||||
|
||||
/**
|
||||
* مدیریت گفتگو - ایجاد یا بازیابی گفتگوی موجود
|
||||
*/
|
||||
private function manageConversation(?int $conversationId, ?Business $business, $user, string $message): AIConversation
|
||||
{
|
||||
if ($conversationId) {
|
||||
// بازیابی گفتگوی موجود
|
||||
$conversation = $this->em->getRepository(AIConversation::class)->find($conversationId);
|
||||
if ($conversation && $conversation->getUser() === $user && $conversation->getBusiness() === $business) {
|
||||
// بهروزرسانی زمان آخرین تغییر
|
||||
$conversation->setUpdatedAt(time());
|
||||
$this->em->persist($conversation);
|
||||
return $conversation;
|
||||
}
|
||||
}
|
||||
|
||||
// ایجاد گفتگوی جدید
|
||||
$conversation = new AIConversation();
|
||||
$conversation->setUser($user);
|
||||
$conversation->setBusiness($business);
|
||||
$conversation->setTitle($this->generateConversationTitle($message));
|
||||
$conversation->setCategory('عمومی');
|
||||
$conversation->setCreatedAt(time());
|
||||
$conversation->setUpdatedAt(time());
|
||||
$conversation->setIsActive(true);
|
||||
$conversation->setDeleted(false);
|
||||
|
||||
$this->em->persist($conversation);
|
||||
return $conversation;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت تاریخچه گفتگو از دیتابیس
|
||||
*/
|
||||
private function getConversationHistory(AIConversation $conversation): array
|
||||
{
|
||||
$messages = $this->em->getRepository(AIMessage::class)->findBy(
|
||||
['conversation' => $conversation],
|
||||
['createdAt' => 'ASC']
|
||||
);
|
||||
|
||||
$history = [];
|
||||
foreach ($messages as $message) {
|
||||
$history[] = [
|
||||
'role' => $message->getRole(),
|
||||
'content' => $message->getContent()
|
||||
];
|
||||
}
|
||||
|
||||
return $history;
|
||||
}
|
||||
|
||||
/**
|
||||
* ذخیره پیام کاربر
|
||||
*/
|
||||
private function saveUserMessage(AIConversation $conversation, string $message): void
|
||||
{
|
||||
$userMessage = new AIMessage();
|
||||
$userMessage->setConversation($conversation);
|
||||
$userMessage->setRole('user');
|
||||
$userMessage->setContent($message);
|
||||
$userMessage->setModel($this->getAIModel());
|
||||
$userMessage->setAgentSource($this->getAIAgentSource());
|
||||
$userMessage->setCreatedAt(time());
|
||||
|
||||
$this->em->persist($userMessage);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* ذخیره پاسخ هوش مصنوعی
|
||||
*/
|
||||
private function saveAIMessage(AIConversation $conversation, string $aiResponse, array $responseData, array $cost): void
|
||||
{
|
||||
$aiMessage = new AIMessage();
|
||||
$aiMessage->setConversation($conversation);
|
||||
$aiMessage->setRole('assistant');
|
||||
$aiMessage->setContent($aiResponse);
|
||||
$aiMessage->setModel($this->getAIModel());
|
||||
$aiMessage->setAgentSource($this->getAIAgentSource());
|
||||
$aiMessage->setCreatedAt(time());
|
||||
|
||||
// ذخیره اطلاعات usage
|
||||
if (isset($responseData['usage'])) {
|
||||
$aiMessage->setInputTokens($responseData['usage']['prompt_tokens'] ?? null);
|
||||
$aiMessage->setOutputTokens($responseData['usage']['completion_tokens'] ?? null);
|
||||
}
|
||||
|
||||
// ذخیره هزینهها
|
||||
$aiMessage->setInputCost($cost['input_cost'] ?? null);
|
||||
$aiMessage->setOutputCost($cost['output_cost'] ?? null);
|
||||
$aiMessage->setTotalCost($cost['total_cost'] ?? null);
|
||||
|
||||
$this->em->persist($aiMessage);
|
||||
|
||||
// بهروزرسانی زمان آخرین تغییر گفتگو
|
||||
$conversation->setUpdatedAt(time());
|
||||
$this->em->persist($conversation);
|
||||
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* تولید عنوان گفتگو بر اساس پیام اول
|
||||
*/
|
||||
private function generateConversationTitle(string $message): string
|
||||
{
|
||||
// حذف کاراکترهای اضافی و محدود کردن طول
|
||||
$title = trim($message);
|
||||
$title = preg_replace('/\s+/', ' ', $title); // حذف فاصلههای اضافی
|
||||
|
||||
// محدود کردن طول عنوان
|
||||
if (mb_strlen($title, 'UTF-8') > 50) {
|
||||
$title = mb_substr($title, 0, 47, 'UTF-8') . '...';
|
||||
}
|
||||
|
||||
return $title ?: 'گفتگوی جدید';
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت گفتگوهای کاربر
|
||||
*/
|
||||
public function getUserConversations($user, ?Business $business, int $limit = 20): array
|
||||
{
|
||||
$conversations = $this->em->getRepository(AIConversation::class)->findBy(
|
||||
[
|
||||
'user' => $user,
|
||||
'business' => $business,
|
||||
'isActive' => true,
|
||||
'deleted' => false
|
||||
],
|
||||
['updatedAt' => 'DESC'],
|
||||
$limit
|
||||
);
|
||||
|
||||
$result = [];
|
||||
foreach ($conversations as $conversation) {
|
||||
$messageCount = count($conversation->getMessages());
|
||||
$lastMessage = $this->em->getRepository(AIMessage::class)
|
||||
->findLastMessageByConversation($conversation);
|
||||
|
||||
$result[] = [
|
||||
'id' => $conversation->getId(),
|
||||
'title' => $conversation->getTitle(),
|
||||
'category' => $conversation->getCategory(),
|
||||
'createdAt' => $conversation->getCreatedAt(),
|
||||
'updatedAt' => $conversation->getUpdatedAt(),
|
||||
'messageCount' => $messageCount,
|
||||
'lastMessage' => $lastMessage ? $lastMessage->getContent() : ''
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت پیامهای یک گفتگو
|
||||
*/
|
||||
public function getConversationMessages(int $conversationId, $user, ?Business $business): array
|
||||
{
|
||||
$conversation = $this->em->getRepository(AIConversation::class)->find($conversationId);
|
||||
|
||||
if (!$conversation || $conversation->getUser() !== $user || $conversation->getBusiness() !== $business) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$messages = $this->em->getRepository(AIMessage::class)->findBy(
|
||||
['conversation' => $conversation],
|
||||
['createdAt' => 'ASC']
|
||||
);
|
||||
|
||||
$result = [];
|
||||
foreach ($messages as $message) {
|
||||
$result[] = [
|
||||
'id' => $message->getId(),
|
||||
'role' => $message->getRole(),
|
||||
'content' => $message->getContent(),
|
||||
'createdAt' => $message->getCreatedAt(),
|
||||
'inputTokens' => $message->getInputTokens(),
|
||||
'outputTokens' => $message->getOutputTokens(),
|
||||
'inputCost' => $message->getInputCost(),
|
||||
'outputCost' => $message->getOutputCost(),
|
||||
'totalCost' => $message->getTotalCost(),
|
||||
'model' => $message->getModel(),
|
||||
'agentSource' => $message->getAgentSource()
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
104
hesabixCore/src/Service/AGI/Promps/BasePromptService.php
Normal file
104
hesabixCore/src/Service/AGI/Promps/BasePromptService.php
Normal file
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service\AGI\Promps;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class BasePromptService
|
||||
{
|
||||
private $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->em = $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* پرامپ پایه برای معرفی سیستم
|
||||
* @return string
|
||||
*/
|
||||
public function getSystemIntroductionPrompt(): string
|
||||
{
|
||||
return '{
|
||||
"tool": "system_introduction",
|
||||
"description": "System introduction and authentication requirements",
|
||||
"content": "You are an AI assistant for Hesabix accounting system. This system manages businesses, persons, accounting entries, inventory, and financial reports. You can help users with various tasks using the available tools and APIs.",
|
||||
"capabilities": [
|
||||
"Person management (customers, suppliers, employees)",
|
||||
],
|
||||
"authentication": {
|
||||
"method": "API Key or Session Token",
|
||||
"required_headers": {
|
||||
"api-key": "API token for AI access (required for AI operations)",
|
||||
},
|
||||
},
|
||||
"language": "Persian (فارسی)",
|
||||
"currency": "Iranian Rial (ریال)"
|
||||
}';
|
||||
}
|
||||
|
||||
/**
|
||||
* پرامپ پایه برای خطاها
|
||||
* @return string
|
||||
*/
|
||||
public function getErrorHandlingPrompt(): string
|
||||
{
|
||||
return '{
|
||||
"tool": "error_handling",
|
||||
"description": "Error handling and authentication guidance",
|
||||
"instructions": "When encountering errors, provide clear explanations in Persian and suggest solutions. Focus on authentication and access control issues.",
|
||||
"error_types": {
|
||||
"access_denied": "دسترسی غیرمجاز - کاربر فاقد مجوز لازم است",
|
||||
"ai_permission_denied": "دسترسی هوش مصنوعی غیرمجاز - کاربر فاقد مجوز AI است",
|
||||
"invalid_api_key": "کلید API نامعتبر - لطفاً کلید صحیح را وارد کنید",
|
||||
"expired_token": "توکن منقضی شده - لطفاً توکن جدید دریافت کنید",
|
||||
"invalid_business": "کسب و کار نامعتبر - شناسه کسب و کار صحیح نیست",
|
||||
"invalid_year": "سال مالی نامعتبر - سال مالی انتخاب شده صحیح نیست",
|
||||
"invalid_currency": "واحد پول نامعتبر - واحد پول انتخاب شده صحیح نیست",
|
||||
"not_found": "مورد یافت نشد - کد یا شناسه وارد شده صحیح نیست",
|
||||
"validation_error": "خطای اعتبارسنجی - اطلاعات وارد شده صحیح نیست",
|
||||
"network_error": "خطای شبکه - اتصال اینترنت را بررسی کنید"
|
||||
},
|
||||
"authentication_solutions": {
|
||||
"missing_api_key": "کلید API را در هدر api-key قرار دهید",
|
||||
"expired_token": "توکن جدید از مدیر سیستم دریافت کنید",
|
||||
"no_ai_permission": "مجوز هوش مصنوعی از مدیر کسب و کار دریافت کنید",
|
||||
"wrong_business": "شناسه کسب و کار صحیح را در هدر activeBid قرار دهید",
|
||||
"wrong_year": "سال مالی صحیح را در هدر activeYear قرار دهید"
|
||||
},
|
||||
"response_format": "Explain error in Persian, provide specific solution, and suggest next steps"
|
||||
}';
|
||||
}
|
||||
|
||||
/**
|
||||
* پرامپ پایه برای راهنمایی کاربر
|
||||
* @return string
|
||||
*/
|
||||
public function getHelpPrompt(): string
|
||||
{
|
||||
return '{
|
||||
"tool": "help",
|
||||
"description": "User help and guidance",
|
||||
"instructions": "Provide helpful guidance to users about available features and how to use them. Be concise and clear in Persian.",
|
||||
"common_queries": {
|
||||
"person_info": "برای دریافت اطلاعات شخص، کد شخص را وارد کنید",
|
||||
},
|
||||
"response_format": "Provide step-by-step guidance in Persian with examples"
|
||||
}';
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت تمام پرامپهای پایه
|
||||
* @return string
|
||||
*/
|
||||
public function getAllBasePrompts(): string
|
||||
{
|
||||
$prompts = [];
|
||||
|
||||
$prompts[] = $this->getSystemIntroductionPrompt();
|
||||
$prompts[] = $this->getErrorHandlingPrompt();
|
||||
$prompts[] = $this->getHelpPrompt();
|
||||
|
||||
return implode("\n\n", $prompts);
|
||||
}
|
||||
}
|
155
hesabixCore/src/Service/AGI/Promps/PersonPromptService.php
Normal file
155
hesabixCore/src/Service/AGI/Promps/PersonPromptService.php
Normal file
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service\AGI\Promps;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class PersonPromptService
|
||||
{
|
||||
private $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->em = $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* تولید تمام پرامپهای بخش اشخاص
|
||||
* @return string
|
||||
*/
|
||||
public function getAllPersonPrompts(): string
|
||||
{
|
||||
$prompts = [];
|
||||
|
||||
// اضافه کردن تمام پرامپهای موجود
|
||||
$prompts[] = $this->getPersonInfoPrompt();
|
||||
|
||||
// در آینده پرامپهای دیگر اضافه خواهند شد
|
||||
// $prompts[] = $this->getCreatePersonPrompt();
|
||||
// $prompts[] = $this->getUpdatePersonPrompt();
|
||||
// $prompts[] = $this->getSearchPersonPrompt();
|
||||
// $prompts[] = $this->getDeletePersonPrompt();
|
||||
|
||||
// ترکیب تمام پرامپها
|
||||
return implode("\n\n", $prompts);
|
||||
}
|
||||
|
||||
/**
|
||||
* پرامپ برای دریافت اطلاعات کامل شخص
|
||||
* @return string
|
||||
*/
|
||||
public function getPersonInfoPrompt(): string
|
||||
{
|
||||
return '{
|
||||
"tool": "getPersonInfo",
|
||||
"description": "Get complete person information by code",
|
||||
"endpoint": "/api/person/info/{code}",
|
||||
"method": "GET",
|
||||
"input": {
|
||||
"code": "string - Person code (e.g., 1001, 1002)"
|
||||
},
|
||||
"output": {
|
||||
"id": "integer - Person ID",
|
||||
"code": "string - Person code",
|
||||
"nikename": "string - Person nickname",
|
||||
"name": "string - Person name",
|
||||
"tel": "string - Telephone number",
|
||||
"mobile": "string - Mobile number",
|
||||
"mobile2": "string|null - Secondary mobile",
|
||||
"des": "string - Description",
|
||||
"company": "string - Company name",
|
||||
"shenasemeli": "string - National ID",
|
||||
"sabt": "string - Registration number",
|
||||
"shahr": "string - City",
|
||||
"keshvar": "string - Country",
|
||||
"ostan": "string - Province",
|
||||
"postalcode": "string - Postal code",
|
||||
"codeeghtesadi": "string - Economic code",
|
||||
"email": "string - Email address",
|
||||
"website": "string - Website",
|
||||
"fax": "string - Fax number",
|
||||
"birthday": "string|null - Birthday",
|
||||
"speedAccess": "boolean - Quick access flag",
|
||||
"address": "string - Address",
|
||||
"prelabel": "string|null - Pre label",
|
||||
"accounts": "array - Bank accounts information",
|
||||
"types": "array - Person types with checked status",
|
||||
"bs": "float - Credit balance (بستانکار)",
|
||||
"bd": "float - Debit balance (بدهکار)",
|
||||
"balance": "float - Net balance (bs - bd)"
|
||||
},
|
||||
"examples": {
|
||||
"input": {"code": "1001"},
|
||||
"output": {
|
||||
"id": 65,
|
||||
"code": "1000",
|
||||
"nikename": "مشتری ۱",
|
||||
"name": "",
|
||||
"tel": "",
|
||||
"mobile": "09183282405",
|
||||
"mobile2": null,
|
||||
"des": "",
|
||||
"company": "",
|
||||
"shenasemeli": "0025",
|
||||
"sabt": "",
|
||||
"shahr": "",
|
||||
"keshvar": "",
|
||||
"ostan": "",
|
||||
"postalcode": "",
|
||||
"codeeghtesadi": "",
|
||||
"email": "",
|
||||
"website": "",
|
||||
"fax": "",
|
||||
"birthday": null,
|
||||
"speedAccess": true,
|
||||
"address": "",
|
||||
"prelabel": null,
|
||||
"accounts": [
|
||||
{
|
||||
"bank": "حساب ۲",
|
||||
"shabaNum": "شماره شبا",
|
||||
"cardNum": "شماره کارت",
|
||||
"accountNum": "شماره حساب"
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"label": "مشتری",
|
||||
"code": "customer",
|
||||
"checked": true
|
||||
},
|
||||
{
|
||||
"label": "بازاریاب",
|
||||
"code": "marketer",
|
||||
"checked": true
|
||||
},
|
||||
{
|
||||
"label": "کارمند",
|
||||
"code": "emplyee",
|
||||
"checked": false
|
||||
},
|
||||
{
|
||||
"label": "تامینکننده",
|
||||
"code": "supplier",
|
||||
"checked": false
|
||||
},
|
||||
{
|
||||
"label": "همکار",
|
||||
"code": "colleague",
|
||||
"checked": false
|
||||
},
|
||||
{
|
||||
"label": "فروشنده",
|
||||
"code": "salesman",
|
||||
"checked": false
|
||||
}
|
||||
],
|
||||
"bs": 752593632.55,
|
||||
"bd": 283007419,
|
||||
"balance": 469586213.54999995
|
||||
}
|
||||
}
|
||||
}';
|
||||
}
|
||||
|
||||
}
|
70
hesabixCore/src/Service/AGI/Promps/PromptService.php
Normal file
70
hesabixCore/src/Service/AGI/Promps/PromptService.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service\AGI\Promps;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class PromptService
|
||||
{
|
||||
private $em;
|
||||
private $personPromptService;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, PersonPromptService $personPromptService)
|
||||
{
|
||||
$this->em = $entityManager;
|
||||
$this->personPromptService = $personPromptService;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت پرامپ سیستمی بر اساس کلید
|
||||
* @param string $key کلید پرامپ
|
||||
* @return string|null
|
||||
*/
|
||||
public function getSystemPrompt(string $key): ?string
|
||||
{
|
||||
switch ($key) {
|
||||
case 'person':
|
||||
return $this->personPromptService->getAllPersonPrompts();
|
||||
// در آینده موارد بیشتر اضافه خواهند شد
|
||||
// case 'accounting':
|
||||
// return $this->accountingPromptService->getAllAccountingPrompts();
|
||||
// case 'inventory':
|
||||
// return $this->inventoryPromptService->getAllInventoryPrompts();
|
||||
// case 'reports':
|
||||
// return $this->reportsPromptService->getAllReportsPrompts();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت تمام پرامپهای سیستمی
|
||||
* @return array
|
||||
*/
|
||||
public function getAllSystemPrompts(): array
|
||||
{
|
||||
$prompts = [];
|
||||
|
||||
// پرامپهای بخش اشخاص
|
||||
$prompts['person'] = $this->personPromptService->getAllPersonPrompts();
|
||||
|
||||
// در آینده بخشهای دیگر اضافه خواهند شد
|
||||
// $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();
|
||||
|
||||
return $prompts;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت تمام پرامپها به صورت یک رشته واحد
|
||||
* @return string
|
||||
*/
|
||||
public function getAllPromptsAsString(): string
|
||||
{
|
||||
$allPrompts = $this->getAllSystemPrompts();
|
||||
return implode("\n\n", $allPrompts);
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -1,538 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service\AI;
|
||||
|
||||
use App\Entity\Business;
|
||||
use App\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use App\Service\Log;
|
||||
use App\Service\Provider;
|
||||
use App\Service\Jdate;
|
||||
|
||||
/**
|
||||
* سرویس مدیریت نمودارها و چارتها
|
||||
*/
|
||||
class ChartService
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
private Log $log;
|
||||
private Provider $provider;
|
||||
private Jdate $jdate;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, Log $log, Provider $provider, Jdate $jdate)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->log = $log;
|
||||
$this->provider = $provider;
|
||||
$this->jdate = $jdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* ایجاد نمودار بر اساس نوع و دادهها
|
||||
*/
|
||||
public function createChart(array $params, Business $business, $user): array
|
||||
{
|
||||
$chartType = $params['chart_type'] ?? 'bar';
|
||||
$title = $params['title'] ?? 'نمودار';
|
||||
$data = $params['data'] ?? [];
|
||||
$options = $params['options'] ?? [];
|
||||
|
||||
if (empty($data)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'دادهای برای نمودار ارائه نشده است'
|
||||
];
|
||||
}
|
||||
|
||||
// اعتبارسنجی نوع نمودار
|
||||
$validChartTypes = ['bar', 'line', 'pie', 'doughnut', 'area', 'radar', 'scatter', 'bubble'];
|
||||
if (!in_array($chartType, $validChartTypes)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => "نوع نمودار '{$chartType}' پشتیبانی نمیشود. انواع مجاز: " . implode(', ', $validChartTypes)
|
||||
];
|
||||
}
|
||||
|
||||
// پردازش دادهها بر اساس نوع نمودار
|
||||
$processedData = $this->processChartData($chartType, $data);
|
||||
|
||||
if (!$processedData['success']) {
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
// ایجاد تنظیمات نمودار
|
||||
$chartOptions = $this->buildChartOptions($chartType, $title, $processedData['data'], $options);
|
||||
|
||||
// ثبت لاگ
|
||||
$this->log->insert(
|
||||
'مدیریت نمودار',
|
||||
"ایجاد نمودار {$chartType}: {$title}",
|
||||
$user,
|
||||
$business
|
||||
);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'chart' => [
|
||||
'type' => $chartType,
|
||||
'title' => $title,
|
||||
'options' => $chartOptions,
|
||||
'data' => $processedData['data'],
|
||||
'chart_id' => $this->generateChartId()
|
||||
],
|
||||
'message' => "نمودار {$title} با موفقیت ایجاد شد"
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* پردازش دادهها بر اساس نوع نمودار
|
||||
*/
|
||||
private function processChartData(string $chartType, array $data): array
|
||||
{
|
||||
switch ($chartType) {
|
||||
case 'bar':
|
||||
case 'line':
|
||||
case 'area':
|
||||
return $this->processBarLineData($data);
|
||||
|
||||
case 'pie':
|
||||
case 'doughnut':
|
||||
return $this->processPieData($data);
|
||||
|
||||
case 'radar':
|
||||
return $this->processRadarData($data);
|
||||
|
||||
case 'scatter':
|
||||
case 'bubble':
|
||||
return $this->processScatterData($data);
|
||||
|
||||
default:
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'نوع نمودار پشتیبانی نمیشود'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* پردازش دادههای نمودار ستونی، خطی و ناحیهای
|
||||
*/
|
||||
private function processBarLineData(array $data): array
|
||||
{
|
||||
// فرمت مورد انتظار: [['category' => 'عنوان', 'value' => عدد], ...]
|
||||
// یا [['x' => 'عنوان', 'y' => عدد], ...]
|
||||
|
||||
$categories = [];
|
||||
$values = [];
|
||||
|
||||
foreach ($data as $item) {
|
||||
if (isset($item['category']) && isset($item['value'])) {
|
||||
$categories[] = $item['category'];
|
||||
$values[] = (float) $item['value'];
|
||||
} elseif (isset($item['x']) && isset($item['y'])) {
|
||||
$categories[] = $item['x'];
|
||||
$values[] = (float) $item['y'];
|
||||
} elseif (isset($item['label']) && isset($item['data'])) {
|
||||
$categories[] = $item['label'];
|
||||
$values[] = (float) $item['data'];
|
||||
} else {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'فرمت داده نامعتبر. فرمت مورد انتظار: [{"category": "عنوان", "value": عدد}, ...]'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'categories' => $categories,
|
||||
'series' => [
|
||||
[
|
||||
'name' => 'دادهها',
|
||||
'data' => $values
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* پردازش دادههای نمودار دایرهای
|
||||
*/
|
||||
private function processPieData(array $data): array
|
||||
{
|
||||
// فرمت مورد انتظار: [['label' => 'عنوان', 'value' => عدد], ...]
|
||||
// یا [['name' => 'عنوان', 'data' => عدد], ...]
|
||||
|
||||
$labels = [];
|
||||
$values = [];
|
||||
|
||||
foreach ($data as $item) {
|
||||
if (isset($item['label']) && isset($item['value'])) {
|
||||
$labels[] = $item['label'];
|
||||
$values[] = (float) $item['value'];
|
||||
} elseif (isset($item['name']) && isset($item['data'])) {
|
||||
$labels[] = $item['name'];
|
||||
$values[] = (float) $item['data'];
|
||||
} elseif (isset($item['category']) && isset($item['value'])) {
|
||||
$labels[] = $item['category'];
|
||||
$values[] = (float) $item['value'];
|
||||
} else {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'فرمت داده نامعتبر. فرمت مورد انتظار: [{"label": "عنوان", "value": عدد}, ...]'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'labels' => $labels,
|
||||
'series' => $values
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* پردازش دادههای نمودار راداری
|
||||
*/
|
||||
private function processRadarData(array $data): array
|
||||
{
|
||||
// فرمت مورد انتظار: [['category' => 'عنوان', 'value' => عدد], ...]
|
||||
|
||||
$categories = [];
|
||||
$values = [];
|
||||
|
||||
foreach ($data as $item) {
|
||||
if (isset($item['category']) && isset($item['value'])) {
|
||||
$categories[] = $item['category'];
|
||||
$values[] = (float) $item['value'];
|
||||
} elseif (isset($item['label']) && isset($item['data'])) {
|
||||
$categories[] = $item['label'];
|
||||
$values[] = (float) $item['data'];
|
||||
} else {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'فرمت داده نامعتبر. فرمت مورد انتظار: [{"category": "عنوان", "value": عدد}, ...]'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'categories' => $categories,
|
||||
'series' => [
|
||||
[
|
||||
'name' => 'دادهها',
|
||||
'data' => $values
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* پردازش دادههای نمودار پراکندگی
|
||||
*/
|
||||
private function processScatterData(array $data): array
|
||||
{
|
||||
// فرمت مورد انتظار: [['x' => عدد, 'y' => عدد], ...]
|
||||
|
||||
$series = [];
|
||||
|
||||
foreach ($data as $item) {
|
||||
if (isset($item['x']) && isset($item['y'])) {
|
||||
$series[] = [
|
||||
'x' => (float) $item['x'],
|
||||
'y' => (float) $item['y']
|
||||
];
|
||||
} elseif (isset($item['x_value']) && isset($item['y_value'])) {
|
||||
$series[] = [
|
||||
'x' => (float) $item['x_value'],
|
||||
'y' => (float) $item['y_value']
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'فرمت داده نامعتبر. فرمت مورد انتظار: [{"x": عدد, "y": عدد}, ...]'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'series' => [
|
||||
[
|
||||
'name' => 'دادهها',
|
||||
'data' => $series
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* ساخت تنظیمات نمودار
|
||||
*/
|
||||
private function buildChartOptions(string $chartType, string $title, array $data, array $customOptions = []): array
|
||||
{
|
||||
$baseOptions = [
|
||||
'chart' => [
|
||||
'id' => 'ai-chart-' . $this->generateChartId(),
|
||||
'type' => $chartType,
|
||||
'fontFamily' => "'Vazirmatn FD', Arial, sans-serif",
|
||||
'toolbar' => [
|
||||
'show' => true,
|
||||
'tools' => [
|
||||
'download' => true,
|
||||
'selection' => true,
|
||||
'zoom' => true,
|
||||
'zoomin' => true,
|
||||
'zoomout' => true,
|
||||
'pan' => true,
|
||||
'reset' => true
|
||||
]
|
||||
]
|
||||
],
|
||||
'title' => [
|
||||
'text' => $title,
|
||||
'align' => 'center',
|
||||
'style' => [
|
||||
'fontSize' => '16px',
|
||||
'fontWeight' => 'bold',
|
||||
'fontFamily' => "'Vazirmatn FD', Arial, sans-serif"
|
||||
]
|
||||
],
|
||||
'colors' => ['#2196F3', '#4CAF50', '#FFC107', '#F44336', '#9C27B0', '#00BCD4', '#FF9800', '#795548', '#607D8B', '#E91E63'],
|
||||
'legend' => [
|
||||
'position' => 'bottom',
|
||||
'fontSize' => '14px',
|
||||
'fontFamily' => "'Vazirmatn FD', Arial, sans-serif"
|
||||
],
|
||||
'tooltip' => [
|
||||
'theme' => 'light',
|
||||
'style' => [
|
||||
'fontSize' => '12px',
|
||||
'fontFamily' => "'Vazirmatn FD', Arial, sans-serif"
|
||||
]
|
||||
],
|
||||
'responsive' => [
|
||||
[
|
||||
'breakpoint' => 480,
|
||||
'options' => [
|
||||
'chart' => ['width' => '100%'],
|
||||
'legend' => ['position' => 'bottom']
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
// اضافه کردن تنظیمات خاص بر اساس نوع نمودار
|
||||
switch ($chartType) {
|
||||
case 'bar':
|
||||
case 'line':
|
||||
case 'area':
|
||||
if (isset($data['categories'])) {
|
||||
$baseOptions['xaxis'] = [
|
||||
'categories' => $data['categories'],
|
||||
'labels' => [
|
||||
'style' => [
|
||||
'fontSize' => '12px',
|
||||
'fontFamily' => "'Vazirmatn FD', Arial, sans-serif"
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
$baseOptions['yaxis'] = [
|
||||
'labels' => [
|
||||
'style' => [
|
||||
'fontSize' => '12px',
|
||||
'fontFamily' => "'Vazirmatn FD', Arial, sans-serif"
|
||||
]
|
||||
]
|
||||
];
|
||||
break;
|
||||
|
||||
case 'pie':
|
||||
case 'doughnut':
|
||||
if (isset($data['labels'])) {
|
||||
$baseOptions['labels'] = $data['labels'];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'radar':
|
||||
if (isset($data['categories'])) {
|
||||
$baseOptions['xaxis'] = [
|
||||
'categories' => $data['categories']
|
||||
];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// ادغام تنظیمات سفارشی
|
||||
return array_merge_recursive($baseOptions, $customOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* تولید شناسه منحصر به فرد برای نمودار
|
||||
*/
|
||||
private function generateChartId(): string
|
||||
{
|
||||
return 'chart_' . time() . '_' . rand(1000, 9999);
|
||||
}
|
||||
|
||||
/**
|
||||
* ابزار ایجاد نمودار ستونی
|
||||
*/
|
||||
public function createBarChart(array $params, Business $business, $user): array
|
||||
{
|
||||
$params['chart_type'] = 'bar';
|
||||
return $this->createChart($params, $business, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* ابزار ایجاد نمودار خطی
|
||||
*/
|
||||
public function createLineChart(array $params, Business $business, $user): array
|
||||
{
|
||||
$params['chart_type'] = 'line';
|
||||
return $this->createChart($params, $business, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* ابزار ایجاد نمودار دایرهای
|
||||
*/
|
||||
public function createPieChart(array $params, Business $business, $user): array
|
||||
{
|
||||
$params['chart_type'] = 'pie';
|
||||
return $this->createChart($params, $business, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* ابزار ایجاد نمودار ناحیهای
|
||||
*/
|
||||
public function createAreaChart(array $params, Business $business, $user): array
|
||||
{
|
||||
$params['chart_type'] = 'area';
|
||||
return $this->createChart($params, $business, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* ابزار ایجاد نمودار راداری
|
||||
*/
|
||||
public function createRadarChart(array $params, Business $business, $user): array
|
||||
{
|
||||
$params['chart_type'] = 'radar';
|
||||
return $this->createChart($params, $business, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* ابزار ایجاد نمودار پراکندگی
|
||||
*/
|
||||
public function createScatterChart(array $params, Business $business, $user): array
|
||||
{
|
||||
$params['chart_type'] = 'scatter';
|
||||
return $this->createChart($params, $business, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* ابزار ایجاد نمودار دونات
|
||||
*/
|
||||
public function createDoughnutChart(array $params, Business $business, $user): array
|
||||
{
|
||||
$params['chart_type'] = 'doughnut';
|
||||
return $this->createChart($params, $business, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* پردازش درخواست نمودار
|
||||
*/
|
||||
public function processRequest(string $message, Business $business, $user): array
|
||||
{
|
||||
// استخراج دستور از پیام
|
||||
$command = $this->extractCommand($message);
|
||||
if (!$command) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'دستور نامعتبر است. لطفاً واضحتر بیان کنید.',
|
||||
'guide' => $this->getOperationsGuide()
|
||||
];
|
||||
}
|
||||
|
||||
// اجرای دستور
|
||||
return $this->executeCommand($command, $business, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* استخراج دستور از پیام کاربر
|
||||
*/
|
||||
private function extractCommand(string $message): ?array
|
||||
{
|
||||
$message = mb_strtolower(trim($message), 'UTF-8');
|
||||
|
||||
// الگوهای دستورات
|
||||
$patterns = [
|
||||
'create_chart' => [
|
||||
'/(?:ایجاد|ساخت|ساز)\s+(?:نمودار|چارت)\s+(?:ستونی|خطی|دایرهای|ناحیهای|راداری|پراکندگی|دونات)\s+(?:با\s+عنوان\s+)?([^\n]+)/u',
|
||||
'/(?:نمودار|چارت)\s+(?:ستونی|خطی|دایرهای|ناحیهای|راداری|پراکندگی|دونات)\s+(?:با\s+عنوان\s+)?([^\n]+)\s+(?:ایجاد|ساخت|ساز)/u'
|
||||
],
|
||||
'bar_chart' => [
|
||||
'/(?:نمودار|چارت)\s+ستونی\s+(?:برای|از)\s+([^\n]+)/u',
|
||||
'/(?:ایجاد|ساخت)\s+نمودار\s+ستونی\s+(?:برای|از)\s+([^\n]+)/u'
|
||||
],
|
||||
'line_chart' => [
|
||||
'/(?:نمودار|چارت)\s+خطی\s+(?:برای|از)\s+([^\n]+)/u',
|
||||
'/(?:ایجاد|ساخت)\s+نمودار\s+خطی\s+(?:برای|از)\s+([^\n]+)/u'
|
||||
],
|
||||
'pie_chart' => [
|
||||
'/(?:نمودار|چارت)\s+دایرهای\s+(?:برای|از)\s+([^\n]+)/u',
|
||||
'/(?:ایجاد|ساخت)\s+نمودار\s+دایرهای\s+(?:برای|از)\s+([^\n]+)/u'
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($patterns as $commandType => $commandPatterns) {
|
||||
foreach ($commandPatterns as $pattern) {
|
||||
if (preg_match($pattern, $message, $matches)) {
|
||||
return [
|
||||
'type' => $commandType,
|
||||
'params' => $matches[1] ?? null
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* اجرای دستور
|
||||
*/
|
||||
private function executeCommand(array $command, Business $business, $user): array
|
||||
{
|
||||
// این بخش میتواند برای پردازش دستورات پیچیدهتر توسعه یابد
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'دستور نمودار نیاز به دادههای مشخص دارد'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* راهنمای عملیات
|
||||
*/
|
||||
public function getOperationsGuide(): string
|
||||
{
|
||||
return "📊 راهنمای ایجاد نمودار:\n\n" .
|
||||
"📈 نمودار ستونی: create_bar_chart{title:عنوان, data:[{category:عنوان, value:عدد}]}\n" .
|
||||
"📉 نمودار خطی: create_line_chart{title:عنوان, data:[{category:عنوان, value:عدد}]}\n" .
|
||||
"🥧 نمودار دایرهای: create_pie_chart{title:عنوان, data:[{label:عنوان, value:عدد}]}\n" .
|
||||
"🌊 نمودار ناحیهای: create_area_chart{title:عنوان, data:[{category:عنوان, value:عدد}]}\n" .
|
||||
"🎯 نمودار راداری: create_radar_chart{title:عنوان, data:[{category:عنوان, value:عدد}]}\n" .
|
||||
"🔵 نمودار پراکندگی: create_scatter_chart{title:عنوان, data:[{x:عدد, y:عدد}]}\n" .
|
||||
"🍩 نمودار دونات: create_doughnut_chart{title:عنوان, data:[{label:عنوان, value:عدد}]}\n\n" .
|
||||
"💡 مثال: 'نمودار ستونی فروش ماهانه با دادههای [{\"category\": \"فروردین\", \"value\": 1000}]'";
|
||||
}
|
||||
}
|
|
@ -1,172 +0,0 @@
|
|||
# سیستم مدیریت اشخاص از طریق هوش مصنوعی
|
||||
|
||||
## خلاصه
|
||||
این سیستم امکان مدیریت اشخاص (افزودن، ویرایش، حذف) را از طریق گفتگو با هوش مصنوعی فراهم میکند.
|
||||
|
||||
## ویژگیهای امنیتی
|
||||
|
||||
### 1. کنترل دسترسی
|
||||
- بررسی نقش کاربر قبل از انجام عملیات
|
||||
- محدودیت دسترسی بر اساس نوع عملیات:
|
||||
- **افزودن/ویرایش**: مدیران و مدیران ارشد
|
||||
- **حذف**: فقط مدیران ارشد
|
||||
|
||||
### 2. اعتبارسنجی دادهها
|
||||
- بررسی وجود شخص قبل از ویرایش/حذف
|
||||
- جلوگیری از حذف اشخاص دارای تراکنش
|
||||
- نرمالسازی شماره تلفن
|
||||
|
||||
### 3. ثبت لاگ
|
||||
- ثبت تمام عملیات در سیستم لاگ
|
||||
- ذخیره اطلاعات کاربر، عملیات و زمان
|
||||
|
||||
## عملیات پشتیبانی شده
|
||||
|
||||
### افزودن شخص جدید
|
||||
**دستورات نمونه:**
|
||||
- "شخص علی اضافه کن"
|
||||
- "مشتری جدید با نام احمد ایجاد کن"
|
||||
- "کارمند محسن اضافه کن"
|
||||
|
||||
**عملکرد:**
|
||||
- تولید کد خودکار
|
||||
- تنظیم نوع پیشفرض (مشتری)
|
||||
- بررسی عدم تکرار نام
|
||||
|
||||
### ویرایش اطلاعات شخص
|
||||
**دستورات نمونه:**
|
||||
- "تلفن 09123456789 را برای شخص علی اضافه کن"
|
||||
- "آدرس تهران، خیابان ولیعصر را برای محسن تغییر بده"
|
||||
- "ایمیل ali@example.com را برای احمد اضافه کن"
|
||||
|
||||
**فیلدهای قابل ویرایش:**
|
||||
- تلفن
|
||||
- آدرس
|
||||
- ایمیل
|
||||
|
||||
### حذف شخص
|
||||
**دستورات نمونه:**
|
||||
- "شخص علی را حذف کن"
|
||||
- "مشتری محسن را پاک کن"
|
||||
|
||||
**محدودیتها:**
|
||||
- فقط اشخاص بدون تراکنش قابل حذف هستند
|
||||
|
||||
## ساختار فایلها
|
||||
|
||||
### PersonManagementService.php
|
||||
سرویس اصلی مدیریت اشخاص که شامل:
|
||||
- تشخیص نوع عملیات از پیام کاربر
|
||||
- پردازش عملیاتهای مختلف
|
||||
- اعتبارسنجی و امنیت
|
||||
|
||||
### AIService.php
|
||||
بهروزرسانی شده برای:
|
||||
- تشخیص درخواستهای مدیریت اشخاص
|
||||
- هدایت درخواستها به سرویس مناسب
|
||||
|
||||
### wizardController.php
|
||||
کنترلر بهروزرسانی شده برای:
|
||||
- ارسال اطلاعات کاربر به AIService
|
||||
- endpoint جدید برای راهنمای عملیات
|
||||
|
||||
## نحوه استفاده
|
||||
|
||||
### در فرانتاند
|
||||
```javascript
|
||||
// ارسال درخواست مدیریت اشخاص
|
||||
const response = await axios.post('/api/wizard/talk', {
|
||||
message: 'شخص علی اضافه کن',
|
||||
conversationId: null
|
||||
});
|
||||
|
||||
// دریافت راهنمای عملیات
|
||||
const guide = await axios.get('/api/wizard/persons/guide');
|
||||
```
|
||||
|
||||
### در بکاند
|
||||
```php
|
||||
// استفاده مستقیم از سرویس
|
||||
$personManagementService = new PersonManagementService($entityManager, $log);
|
||||
$result = $personManagementService->processPersonManagementRequest($message, $business, $user);
|
||||
```
|
||||
|
||||
## پیامهای خطا
|
||||
|
||||
### خطاهای دسترسی
|
||||
- "شما دسترسی انجام این عملیات را ندارید"
|
||||
- "شما دسترسی استفاده از هوش مصنوعی را ندارید"
|
||||
|
||||
### خطاهای اعتبارسنجی
|
||||
- "شخصی با نام 'علی' قبلاً وجود دارد"
|
||||
- "شخصی با نام 'محسن' یافت نشد"
|
||||
- "نمیتوان شخص 'احمد' را حذف کرد زیرا تراکنشهای مرتبط دارد"
|
||||
|
||||
### خطاهای تشخیص
|
||||
- "نمیتوانم عملیات مورد نظر را تشخیص دهم. لطفاً واضحتر بیان کنید"
|
||||
- "عملیات نامعتبر است"
|
||||
|
||||
## امنیت
|
||||
|
||||
### بررسی دسترسی
|
||||
```php
|
||||
private function hasPermission($user, string $operationType): bool
|
||||
{
|
||||
$roles = $user->getRoles();
|
||||
|
||||
switch ($operationType) {
|
||||
case 'add':
|
||||
case 'edit':
|
||||
return in_array('ROLE_ADMIN', $roles) || in_array('ROLE_MANAGER', $roles);
|
||||
case 'delete':
|
||||
return in_array('ROLE_ADMIN', $roles);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ثبت لاگ
|
||||
```php
|
||||
$this->log->insert(
|
||||
'مدیریت اشخاص',
|
||||
"افزودن شخص جدید: {$name} (کد: {$code})",
|
||||
$user,
|
||||
$business
|
||||
);
|
||||
```
|
||||
|
||||
## توسعه آینده
|
||||
|
||||
### قابلیتهای پیشنهادی
|
||||
1. **مدیریت کارتهای بانکی**: افزودن/حذف کارتهای بانکی
|
||||
2. **مدیریت نوع شخص**: تغییر نوع شخص (مشتری، تامینکننده، کارمند)
|
||||
3. **عملیات دستهای**: حذف یا ویرایش چندین شخص همزمان
|
||||
4. **تایید عملیات**: درخواست تایید برای عملیات حساس
|
||||
5. **بازیابی**: امکان بازیابی اشخاص حذف شده
|
||||
|
||||
### بهبود تشخیص
|
||||
1. **تشخیص پیشرفته**: استفاده از NLP برای تشخیص بهتر
|
||||
2. **یادگیری**: بهبود تشخیص بر اساس استفاده کاربران
|
||||
3. **مترادفها**: پشتیبانی از مترادفهای بیشتر
|
||||
|
||||
## تست
|
||||
|
||||
### تستهای امنیتی
|
||||
- بررسی دسترسی کاربران مختلف
|
||||
- تست عملیات غیرمجاز
|
||||
- بررسی ثبت لاگ
|
||||
|
||||
### تستهای عملکردی
|
||||
- تست افزودن شخص جدید
|
||||
- تست ویرایش اطلاعات
|
||||
- تست حذف شخص
|
||||
- تست خطاهای مختلف
|
||||
|
||||
## نکات مهم
|
||||
|
||||
1. **همیشه از دسترسی کاربر اطمینان حاصل کنید**
|
||||
2. **تمام عملیات را در لاگ ثبت کنید**
|
||||
3. **قبل از حذف، وجود تراکنشها را بررسی کنید**
|
||||
4. **پیامهای خطا را واضح و مفید ارائه دهید**
|
||||
5. **کدهای تولید شده را منحصر به فرد کنید**
|
|
@ -1,267 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service\AI;
|
||||
|
||||
use App\Entity\Person;
|
||||
use App\Entity\Business;
|
||||
use App\Entity\HesabdariRow;
|
||||
use App\Entity\HesabdariDoc;
|
||||
use App\Entity\PersonCard;
|
||||
use App\Entity\PersonType;
|
||||
use App\Entity\PersonPrelabel;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
class PersonDataService
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* جستجوی اشخاص بر اساس معیارهای مختلف
|
||||
*/
|
||||
public function searchPersons(Business $business, $searchTerm): array
|
||||
{
|
||||
// اگر searchTerm آرایه است، از متد قبلی استفاده کن
|
||||
if (is_array($searchTerm)) {
|
||||
return $this->searchPersons($business, $searchTerm);
|
||||
}
|
||||
|
||||
// اگر searchTerm رشته است، جستجوی ساده انجام بده
|
||||
$qb = $this->entityManager->createQueryBuilder();
|
||||
$qb->select('p')
|
||||
->from(Person::class, 'p')
|
||||
->where('p.bid = :business')
|
||||
->andWhere('(p.name LIKE :search OR p.nikename LIKE :search OR p.code LIKE :search OR p.mobile LIKE :search OR p.tel LIKE :search)')
|
||||
->setParameter('business', $business)
|
||||
->setParameter('search', '%' . $searchTerm . '%')
|
||||
->orderBy('p.name', 'ASC')
|
||||
->setMaxResults(20);
|
||||
|
||||
$persons = $qb->getQuery()->getResult();
|
||||
$result = [];
|
||||
|
||||
foreach ($persons as $person) {
|
||||
$result[] = [
|
||||
'id' => $person->getId(),
|
||||
'code' => $person->getCode(),
|
||||
'name' => $person->getName(),
|
||||
'nikename' => $person->getNikename(),
|
||||
'mobile' => $person->getMobile(),
|
||||
'tel' => $person->getTel(),
|
||||
'address' => $person->getAddress(),
|
||||
'email' => $person->getEmail(),
|
||||
'company' => $person->getCompany(),
|
||||
'is_employee' => $person->isEmploye()
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت اطلاعات شخص
|
||||
*/
|
||||
public function getPersonData(Business $business, int $personId): ?array
|
||||
{
|
||||
$person = $this->entityManager->getRepository(Person::class)->findOneBy([
|
||||
'id' => $personId,
|
||||
'bid' => $business
|
||||
]);
|
||||
|
||||
if (!$person) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getPersonDetails($person);
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت تراکنشهای شخص
|
||||
*/
|
||||
public function getPersonTransactions(Business $business, Person $person, int $limit = 10): array
|
||||
{
|
||||
return $this->getRecentTransactions($person, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت اطلاعات کامل یک شخص
|
||||
*/
|
||||
public function getPersonDetails(Person $person): array
|
||||
{
|
||||
$details = [
|
||||
'id' => $person->getId(),
|
||||
'code' => $person->getCode(),
|
||||
'name' => $person->getName(),
|
||||
'nikename' => $person->getNikename(),
|
||||
'mobile' => $person->getMobile(),
|
||||
'tel' => $person->getTel(),
|
||||
'email' => $person->getEmail(),
|
||||
'address' => $person->getAddress(),
|
||||
'company' => $person->getCompany(),
|
||||
'shenasemeli' => $person->getShenasemeli(),
|
||||
'codeeghtesadi' => $person->getCodeeghtesadi(),
|
||||
'sabt' => $person->getSabt(),
|
||||
'website' => $person->getWebsite(),
|
||||
'fax' => $person->getFax(),
|
||||
'birthday' => $person->getBirthday(),
|
||||
'is_employee' => $person->isEmploye(),
|
||||
'cards' => $this->getPersonCards($person),
|
||||
'transactions' => $this->getRecentTransactions($person)
|
||||
];
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* دریافت کارتهای بانکی شخص
|
||||
*/
|
||||
private function getPersonCards(Person $person): array
|
||||
{
|
||||
$cards = $person->getPersonCards();
|
||||
$cardData = [];
|
||||
|
||||
foreach ($cards as $card) {
|
||||
$cardData[] = [
|
||||
'id' => $card->getId(),
|
||||
'bank' => $card->getBank(),
|
||||
'card_number' => $card->getCardNum(),
|
||||
'account_number' => $card->getAccountNum(),
|
||||
'shaba_number' => $card->getShabaNum()
|
||||
];
|
||||
}
|
||||
|
||||
return $cardData;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت تراکنشهای اخیر شخص
|
||||
*/
|
||||
private function getRecentTransactions(Person $person, int $limit = 10): array
|
||||
{
|
||||
$qb = $this->entityManager->createQueryBuilder();
|
||||
$qb->select('hr')
|
||||
->from(HesabdariRow::class, 'hr')
|
||||
->where('hr.person = :person')
|
||||
->setParameter('person', $person)
|
||||
->orderBy('hr.id', 'DESC')
|
||||
->setMaxResults($limit);
|
||||
|
||||
$rows = $qb->getQuery()->getResult();
|
||||
$transactions = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$doc = $row->getDoc();
|
||||
$transactions[] = [
|
||||
'id' => $row->getId(),
|
||||
'description' => $row->getDes(),
|
||||
'bs' => $row->getBs(),
|
||||
'bd' => $row->getBd(),
|
||||
'document_code' => $doc ? $doc->getCode() : null,
|
||||
'document_description' => $doc ? $doc->getDes() : null
|
||||
];
|
||||
}
|
||||
|
||||
return $transactions;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* محاسبه آمار کلی اشخاص
|
||||
*/
|
||||
public function getPersonsStatistics(Business $business): array
|
||||
{
|
||||
$qb = $this->entityManager->createQueryBuilder();
|
||||
$qb->select('COUNT(p.id) as total')
|
||||
->from(Person::class, 'p')
|
||||
->where('p.bid = :business')
|
||||
->setParameter('business', $business);
|
||||
|
||||
$result = $qb->getQuery()->getSingleResult();
|
||||
|
||||
return [
|
||||
'total_persons' => (int)$result['total']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* تولید خلاصه اطلاعات اشخاص برای هوش مصنوعی
|
||||
*/
|
||||
public function generatePersonsSummaryForAI(Business $business): string
|
||||
{
|
||||
$stats = $this->getPersonsStatistics($business);
|
||||
|
||||
$summary = "اطلاعات اشخاص کسب و کار:\n";
|
||||
$summary .= "- تعداد کل اشخاص: {$stats['total_persons']}\n\n";
|
||||
|
||||
// اضافه کردن لیست اشخاص اخیر
|
||||
$recentPersons = $this->getRecentPersons($business, 10);
|
||||
if (!empty($recentPersons)) {
|
||||
$summary .= "اشخاص اخیر:\n";
|
||||
foreach ($recentPersons as $person) {
|
||||
$summary .= "- {$person->getCode()}: {$person->getName()}\n";
|
||||
}
|
||||
$summary .= "\n";
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت اشخاص اخیر
|
||||
*/
|
||||
private function getRecentPersons(Business $business, int $limit = 10): array
|
||||
{
|
||||
$qb = $this->entityManager->createQueryBuilder();
|
||||
$qb->select('p')
|
||||
->from(Person::class, 'p')
|
||||
->where('p.bid = :business')
|
||||
->setParameter('business', $business)
|
||||
->orderBy('p.id', 'DESC')
|
||||
->setMaxResults($limit);
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* جستجوی پیشرفته اشخاص
|
||||
*/
|
||||
public function advancedSearch(Business $business, array $filters): array
|
||||
{
|
||||
$qb = $this->entityManager->createQueryBuilder();
|
||||
$qb->select('p')
|
||||
->from(Person::class, 'p')
|
||||
->where('p.bid = :business')
|
||||
->setParameter('business', $business);
|
||||
|
||||
// فیلتر بر اساس نام یا کد
|
||||
if (!empty($filters['search'])) {
|
||||
$qb->andWhere('(p.name LIKE :search OR p.nikename LIKE :search OR p.code LIKE :search)')
|
||||
->setParameter('search', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
// فیلتر بر اساس موبایل
|
||||
if (!empty($filters['mobile'])) {
|
||||
$qb->andWhere('p.mobile LIKE :mobile')
|
||||
->setParameter('mobile', '%' . $filters['mobile'] . '%');
|
||||
}
|
||||
|
||||
// فیلتر بر اساس ایمیل
|
||||
if (!empty($filters['email'])) {
|
||||
$qb->andWhere('p.email LIKE :email')
|
||||
->setParameter('email', '%' . $filters['email'] . '%');
|
||||
}
|
||||
|
||||
$qb->orderBy('p.name', 'ASC');
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,61 +0,0 @@
|
|||
# سرویسهای هوش مصنوعی
|
||||
|
||||
این پوشه شامل سرویسهای مربوط به هوش مصنوعی و پردازش دادههای اشخاص است.
|
||||
|
||||
## ساختار فایلها
|
||||
|
||||
### AIService.php
|
||||
سرویس اصلی هوش مصنوعی که مسئول:
|
||||
- ارتباط با سرویسهای مختلف هوش مصنوعی (GapGPT، AvalAI، LocalAI)
|
||||
- مدیریت درخواستها و پاسخها
|
||||
- محاسبه هزینهها
|
||||
- بررسی وضعیت سرویس
|
||||
|
||||
### PersonDataService.php
|
||||
سرویس دادههای اشخاص که مسئول:
|
||||
- جستجو و بازیابی اطلاعات اشخاص
|
||||
- تولید خلاصه اطلاعات برای هوش مصنوعی
|
||||
- مدیریت تراکنشها و موجودیها
|
||||
- گزارشگیری از دادههای اشخاص
|
||||
|
||||
## نحوه استفاده
|
||||
|
||||
### در کنترلرها
|
||||
```php
|
||||
use App\Service\AI\AIService;
|
||||
|
||||
class MyController extends AbstractController
|
||||
{
|
||||
public function __construct(AIService $aiService)
|
||||
{
|
||||
$this->aiService = $aiService;
|
||||
}
|
||||
|
||||
public function someAction()
|
||||
{
|
||||
// ارسال درخواست به هوش مصنوعی
|
||||
$result = $this->aiService->sendRequest($message, $business);
|
||||
|
||||
// دسترسی به سرویس دادههای اشخاص
|
||||
$personData = $this->aiService->getPersonDataService();
|
||||
$persons = $personData->searchPersons($business, $searchTerm);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### تنظیمات مورد نیاز
|
||||
در جدول `registry` باید تنظیمات زیر موجود باشد:
|
||||
- `ai_settings`: تنظیمات کلی هوش مصنوعی
|
||||
- `gapgpt_api_key`: کلید API برای GapGPT
|
||||
- `avalai_api_key`: کلید API برای AvalAI
|
||||
- `local_api_key`: کلید API برای LocalAI
|
||||
|
||||
## امنیت
|
||||
- تمام درخواستها از طریق کنترلرها انجام میشود
|
||||
- دسترسی به دادههای اشخاص محدود به کسب و کار کاربر است
|
||||
- کلیدهای API در تنظیمات امن ذخیره میشوند
|
||||
|
||||
## توسعه آینده
|
||||
- اضافه کردن سرویسهای جدید هوش مصنوعی
|
||||
- بهبود پردازش دادهها
|
||||
- اضافه کردن قابلیتهای گزارشگیری پیشرفته
|
|
@ -1,519 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service\AI;
|
||||
|
||||
use App\Entity\Support;
|
||||
use App\Entity\Business;
|
||||
use App\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use App\Service\Log;
|
||||
use App\Service\Provider;
|
||||
use App\Service\Jdate;
|
||||
|
||||
/**
|
||||
* سرویس مدیریت تیکتهای پشتیبانی
|
||||
*/
|
||||
class TicketManagementService
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
private Log $log;
|
||||
private Provider $provider;
|
||||
private Jdate $jdate;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, Log $log, Provider $provider, Jdate $jdate)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->log = $log;
|
||||
$this->provider = $provider;
|
||||
$this->jdate = $jdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* ابزار ایجاد تیکت جدید
|
||||
*/
|
||||
public function createTicket(array $params, Business $business, $user): array
|
||||
{
|
||||
$title = $params['title'] ?? '';
|
||||
$body = $params['body'] ?? '';
|
||||
$priority = $params['priority'] ?? 'عادی'; // عادی، مهم، فوری
|
||||
|
||||
if (empty($title) || empty($body)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'عنوان و متن تیکت الزامی است'
|
||||
];
|
||||
}
|
||||
|
||||
// تولید کد منحصر به فرد
|
||||
$code = $this->generateTicketCode();
|
||||
|
||||
// ایجاد تیکت جدید
|
||||
$ticket = new Support();
|
||||
$ticket->setTitle($title)
|
||||
->setBody($body)
|
||||
->setDateSubmit(time())
|
||||
->setSubmitter($user)
|
||||
->setMain(0)
|
||||
->setCode($code)
|
||||
->setState('در حال پیگیری')
|
||||
->setBid($business);
|
||||
|
||||
$this->entityManager->persist($ticket);
|
||||
$this->entityManager->flush();
|
||||
|
||||
// ثبت لاگ
|
||||
$this->log->insert(
|
||||
'مدیریت تیکت',
|
||||
"ایجاد تیکت جدید: {$title} (کد: {$code})",
|
||||
$user,
|
||||
$business
|
||||
);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => "تیکت با موفقیت ایجاد شد. کد تیکت: {$code}",
|
||||
'ticket' => [
|
||||
'id' => $ticket->getId(),
|
||||
'code' => $ticket->getCode(),
|
||||
'title' => $ticket->getTitle(),
|
||||
'state' => $ticket->getState(),
|
||||
'date' => $this->jdate->jdate('Y/m/d H:i', $ticket->getDateSubmit())
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* ابزار مشاهده تیکتهای کاربر
|
||||
*/
|
||||
public function listUserTickets(array $params, Business $business, $user): array
|
||||
{
|
||||
$state = $params['state'] ?? null;
|
||||
$limit = (int) ($params['limit'] ?? 10);
|
||||
$page = (int) ($params['page'] ?? 1);
|
||||
|
||||
$queryBuilder = $this->entityManager->getRepository(Support::class)
|
||||
->createQueryBuilder('s')
|
||||
->where('s.submitter = :user')
|
||||
->andWhere('s.main = 0')
|
||||
->setParameter('user', $user)
|
||||
->orderBy('s.dateSubmit', 'DESC');
|
||||
|
||||
// فیلتر بر اساس وضعیت
|
||||
if ($state && in_array($state, ['در حال پیگیری', 'پاسخ داده شده', 'خاتمه یافته'])) {
|
||||
$queryBuilder->andWhere('s.state = :state')
|
||||
->setParameter('state', $state);
|
||||
}
|
||||
|
||||
// محاسبه تعداد کل
|
||||
$totalQuery = clone $queryBuilder;
|
||||
$total = (int) $totalQuery->select('COUNT(s.id)')->getQuery()->getSingleScalarResult();
|
||||
|
||||
// اعمال صفحهبندی
|
||||
$queryBuilder->setFirstResult(($page - 1) * $limit)
|
||||
->setMaxResults($limit);
|
||||
|
||||
$tickets = $queryBuilder->getQuery()->getResult();
|
||||
|
||||
$ticketsArray = array_map(function ($ticket) {
|
||||
return [
|
||||
'id' => $ticket->getId(),
|
||||
'code' => $ticket->getCode(),
|
||||
'title' => $ticket->getTitle(),
|
||||
'body' => mb_substr($ticket->getBody(), 0, 100) . '...',
|
||||
'state' => $ticket->getState(),
|
||||
'date' => $this->jdate->jdate('Y/m/d H:i', $ticket->getDateSubmit()),
|
||||
'has_file' => !empty($ticket->getFileName())
|
||||
];
|
||||
}, $tickets);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'tickets' => $ticketsArray,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'message' => "تعداد {$total} تیکت یافت شد"
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* ابزار مشاهده جزئیات تیکت
|
||||
*/
|
||||
public function viewTicket(array $params, Business $business, $user): array
|
||||
{
|
||||
$ticketId = $params['ticket_id'] ?? null;
|
||||
|
||||
if (!$ticketId) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'شناسه تیکت الزامی است'
|
||||
];
|
||||
}
|
||||
|
||||
$ticket = $this->entityManager->getRepository(Support::class)->find($ticketId);
|
||||
|
||||
if (!$ticket) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'تیکت یافت نشد'
|
||||
];
|
||||
}
|
||||
|
||||
// بررسی دسترسی کاربر
|
||||
if ($ticket->getSubmitter() !== $user) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'شما دسترسی به این تیکت را ندارید'
|
||||
];
|
||||
}
|
||||
|
||||
// دریافت پاسخها
|
||||
$replies = $this->entityManager->getRepository(Support::class)
|
||||
->findBy(['main' => $ticket->getId()], ['dateSubmit' => 'ASC']);
|
||||
|
||||
$repliesArray = array_map(function ($reply) {
|
||||
return [
|
||||
'id' => $reply->getId(),
|
||||
'body' => $reply->getBody(),
|
||||
'is_admin' => $reply->getSubmitter()->getRoles()[0] === 'ROLE_ADMIN',
|
||||
'date' => $this->jdate->jdate('Y/m/d H:i', $reply->getDateSubmit()),
|
||||
'has_file' => !empty($reply->getFileName())
|
||||
];
|
||||
}, $replies);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'ticket' => [
|
||||
'id' => $ticket->getId(),
|
||||
'code' => $ticket->getCode(),
|
||||
'title' => $ticket->getTitle(),
|
||||
'body' => $ticket->getBody(),
|
||||
'state' => $ticket->getState(),
|
||||
'date' => $this->jdate->jdate('Y/m/d H:i', $ticket->getDateSubmit()),
|
||||
'has_file' => !empty($ticket->getFileName())
|
||||
],
|
||||
'replies' => $repliesArray,
|
||||
'message' => "جزئیات تیکت {$ticket->getCode()}"
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* ابزار پاسخ به تیکت
|
||||
*/
|
||||
public function replyToTicket(array $params, Business $business, $user): array
|
||||
{
|
||||
$ticketId = $params['ticket_id'] ?? null;
|
||||
$body = $params['body'] ?? '';
|
||||
|
||||
if (!$ticketId || empty($body)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'شناسه تیکت و متن پاسخ الزامی است'
|
||||
];
|
||||
}
|
||||
|
||||
$mainTicket = $this->entityManager->getRepository(Support::class)->find($ticketId);
|
||||
|
||||
if (!$mainTicket) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'تیکت یافت نشد'
|
||||
];
|
||||
}
|
||||
|
||||
// بررسی دسترسی کاربر
|
||||
if ($mainTicket->getSubmitter() !== $user) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'شما دسترسی به این تیکت را ندارید'
|
||||
];
|
||||
}
|
||||
|
||||
// ایجاد پاسخ جدید
|
||||
$reply = new Support();
|
||||
$reply->setMain($mainTicket->getId())
|
||||
->setBody($body)
|
||||
->setTitle($mainTicket->getTitle())
|
||||
->setDateSubmit(time())
|
||||
->setSubmitter($user)
|
||||
->setState('در حال پیگیری');
|
||||
|
||||
$this->entityManager->persist($reply);
|
||||
|
||||
// بهروزرسانی وضعیت تیکت اصلی
|
||||
$mainTicket->setState('در حال پیگیری');
|
||||
$this->entityManager->persist($mainTicket);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
// ثبت لاگ
|
||||
$this->log->insert(
|
||||
'مدیریت تیکت',
|
||||
"پاسخ به تیکت: {$mainTicket->getCode()}",
|
||||
$user,
|
||||
$business
|
||||
);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'پاسخ با موفقیت ارسال شد',
|
||||
'reply' => [
|
||||
'id' => $reply->getId(),
|
||||
'body' => $reply->getBody(),
|
||||
'date' => $this->jdate->jdate('Y/m/d H:i', $reply->getDateSubmit())
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* ابزار جستجوی تیکتها
|
||||
*/
|
||||
public function searchTickets(array $params, Business $business, $user): array
|
||||
{
|
||||
$search = $params['search'] ?? '';
|
||||
$state = $params['state'] ?? null;
|
||||
$limit = (int) ($params['limit'] ?? 10);
|
||||
|
||||
if (empty($search)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'متن جستجو الزامی است'
|
||||
];
|
||||
}
|
||||
|
||||
$queryBuilder = $this->entityManager->getRepository(Support::class)
|
||||
->createQueryBuilder('s')
|
||||
->where('s.submitter = :user')
|
||||
->andWhere('s.main = 0')
|
||||
->andWhere(
|
||||
's.title LIKE :search OR s.body LIKE :search OR s.code LIKE :search'
|
||||
)
|
||||
->setParameter('user', $user)
|
||||
->setParameter('search', '%' . $search . '%')
|
||||
->orderBy('s.dateSubmit', 'DESC');
|
||||
|
||||
// فیلتر بر اساس وضعیت
|
||||
if ($state && in_array($state, ['در حال پیگیری', 'پاسخ داده شده', 'خاتمه یافته'])) {
|
||||
$queryBuilder->andWhere('s.state = :state')
|
||||
->setParameter('state', $state);
|
||||
}
|
||||
|
||||
$queryBuilder->setMaxResults($limit);
|
||||
$tickets = $queryBuilder->getQuery()->getResult();
|
||||
|
||||
$ticketsArray = array_map(function ($ticket) {
|
||||
return [
|
||||
'id' => $ticket->getId(),
|
||||
'code' => $ticket->getCode(),
|
||||
'title' => $ticket->getTitle(),
|
||||
'body' => mb_substr($ticket->getBody(), 0, 100) . '...',
|
||||
'state' => $ticket->getState(),
|
||||
'date' => $this->jdate->jdate('Y/m/d H:i', $ticket->getDateSubmit())
|
||||
];
|
||||
}, $tickets);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'tickets' => $ticketsArray,
|
||||
'count' => count($tickets),
|
||||
'message' => "تعداد " . count($tickets) . " تیکت برای \"{$search}\" یافت شد"
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* ابزار آمار تیکتها
|
||||
*/
|
||||
public function getTicketStatistics(array $params, Business $business, $user): array
|
||||
{
|
||||
$queryBuilder = $this->entityManager->getRepository(Support::class)
|
||||
->createQueryBuilder('s')
|
||||
->where('s.submitter = :user')
|
||||
->andWhere('s.main = 0')
|
||||
->setParameter('user', $user);
|
||||
|
||||
// تعداد کل تیکتها
|
||||
$totalQuery = clone $queryBuilder;
|
||||
$total = (int) $totalQuery->select('COUNT(s.id)')->getQuery()->getSingleScalarResult();
|
||||
|
||||
// تعداد تیکتهای در حال پیگیری
|
||||
$pendingQuery = clone $queryBuilder;
|
||||
$pending = (int) $pendingQuery->andWhere('s.state = :state')
|
||||
->setParameter('state', 'در حال پیگیری')
|
||||
->select('COUNT(s.id)')
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
|
||||
// تعداد تیکتهای پاسخ داده شده
|
||||
$answeredQuery = clone $queryBuilder;
|
||||
$answered = (int) $answeredQuery->andWhere('s.state = :state')
|
||||
->setParameter('state', 'پاسخ داده شده')
|
||||
->select('COUNT(s.id)')
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
|
||||
// تعداد تیکتهای خاتمه یافته
|
||||
$closedQuery = clone $queryBuilder;
|
||||
$closed = (int) $closedQuery->andWhere('s.state = :state')
|
||||
->setParameter('state', 'خاتمه یافته')
|
||||
->select('COUNT(s.id)')
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'statistics' => [
|
||||
'total' => $total,
|
||||
'pending' => $pending,
|
||||
'answered' => $answered,
|
||||
'closed' => $closed
|
||||
],
|
||||
'message' => "آمار تیکتهای شما: {$total} کل، {$pending} در حال پیگیری، {$answered} پاسخ داده شده، {$closed} خاتمه یافته"
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* تولید کد منحصر به فرد برای تیکت
|
||||
*/
|
||||
private function generateTicketCode(): string
|
||||
{
|
||||
$characters = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
|
||||
$code = '';
|
||||
|
||||
for ($i = 0; $i < 8; $i++) {
|
||||
$code .= $characters[rand(0, strlen($characters) - 1)];
|
||||
}
|
||||
|
||||
// بررسی یکتا بودن کد
|
||||
$existing = $this->entityManager->getRepository(Support::class)->findOneBy(['code' => $code]);
|
||||
if ($existing) {
|
||||
return $this->generateTicketCode(); // بازگشت بازگشتی
|
||||
}
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* پردازش درخواست مدیریت تیکت
|
||||
*/
|
||||
public function processRequest(string $message, Business $business, $user): array
|
||||
{
|
||||
// استخراج دستور از پیام
|
||||
$command = $this->extractCommand($message);
|
||||
if (!$command) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'دستور نامعتبر است. لطفاً واضحتر بیان کنید.',
|
||||
'guide' => $this->getOperationsGuide()
|
||||
];
|
||||
}
|
||||
|
||||
// اجرای دستور
|
||||
return $this->executeCommand($command, $business, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* استخراج دستور از پیام کاربر
|
||||
*/
|
||||
private function extractCommand(string $message): ?array
|
||||
{
|
||||
$message = mb_strtolower(trim($message), 'UTF-8');
|
||||
|
||||
// الگوهای دستورات
|
||||
$patterns = [
|
||||
'create' => [
|
||||
'/(?:ایجاد|ساخت|ثبت)\s+(?:کن|کنید|بکن|بکنید)\s+(?:تیکت|درخواست|پشتیبانی)\s+(?:جدید\s+)?(?:با\s+عنوان\s+)?([^\n]+)/u',
|
||||
'/(?:تیکت|درخواست|پشتیبانی)\s+(?:جدید\s+)?(?:با\s+عنوان\s+)?([^\n]+)\s+(?:ایجاد|ساخت|ثبت)\s+(?:کن|کنید|بکن|بکنید)/u'
|
||||
],
|
||||
|
||||
'list' => [
|
||||
'/(?:لیست|مشاهده|نشون\s+بده)\s+(?:تیکت|درخواست|پشتیبانی)\s*(?:ها|های\s+من)?/u',
|
||||
'/(?:تیکت|درخواست|پشتیبانی)\s*(?:ها|های\s+من)?\s+(?:رو\s+)?(?:لیست|مشاهده|نشون\s+بده)/u'
|
||||
],
|
||||
|
||||
'view' => [
|
||||
'/(?:مشاهده|ببین|جزئیات)\s+(?:تیکت|درخواست)\s+(?:با\s+کد\s+)?([A-Z0-9]+)/u',
|
||||
'/(?:تیکت|درخواست)\s+([A-Z0-9]+)\s+(?:رو\s+)?(?:مشاهده|ببین)/u'
|
||||
],
|
||||
|
||||
'search' => [
|
||||
'/(?:جستجو|پیدا\s+کن)\s+(?:تیکت|درخواست)\s+(?:برای\s+)?([^\n]+)/u',
|
||||
'/(?:تیکت|درخواست)\s+(?:برای\s+)?([^\n]+)\s+(?:رو\s+)?(?:جستجو|پیدا\s+کن)/u'
|
||||
],
|
||||
|
||||
'statistics' => [
|
||||
'/(?:آمار|وضعیت)\s+(?:تیکت|درخواست|پشتیبانی)\s*(?:ها|های\s+من)?/u',
|
||||
'/(?:تیکت|درخواست|پشتیبانی)\s*(?:ها|های\s+من)?\s+(?:رو\s+)?(?:آمار|وضعیت)/u'
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($patterns as $commandType => $commandPatterns) {
|
||||
foreach ($commandPatterns as $pattern) {
|
||||
if (preg_match($pattern, $message, $matches)) {
|
||||
return [
|
||||
'type' => $commandType,
|
||||
'params' => $matches[1] ?? null
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* اجرای دستور
|
||||
*/
|
||||
private function executeCommand(array $command, Business $business, $user): array
|
||||
{
|
||||
switch ($command['type']) {
|
||||
case 'create':
|
||||
// استخراج عنوان و متن از پیام
|
||||
$content = $command['params'];
|
||||
$lines = explode("\n", $content);
|
||||
$title = trim($lines[0]);
|
||||
$body = implode("\n", array_slice($lines, 1));
|
||||
|
||||
return $this->createTicket([
|
||||
'title' => $title,
|
||||
'body' => $body
|
||||
], $business, $user);
|
||||
|
||||
case 'list':
|
||||
return $this->listUserTickets([], $business, $user);
|
||||
|
||||
case 'view':
|
||||
return $this->viewTicket([
|
||||
'ticket_id' => $command['params']
|
||||
], $business, $user);
|
||||
|
||||
case 'search':
|
||||
return $this->searchTickets([
|
||||
'search' => $command['params']
|
||||
], $business, $user);
|
||||
|
||||
case 'statistics':
|
||||
return $this->getTicketStatistics([], $business, $user);
|
||||
|
||||
default:
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'دستور نامعتبر است'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* راهنمای عملیات
|
||||
*/
|
||||
public function getOperationsGuide(): string
|
||||
{
|
||||
return "🔧 راهنمای مدیریت تیکتها:\n\n" .
|
||||
"📝 ایجاد تیکت: 'تیکت جدید با عنوان [عنوان] [متن]'\n" .
|
||||
"📋 مشاهده لیست: 'لیست تیکتهای من'\n" .
|
||||
"👁️ مشاهده جزئیات: 'مشاهده تیکت [کد]'\n" .
|
||||
"🔍 جستجو: 'جستجو تیکت [متن]'\n" .
|
||||
"📊 آمار: 'آمار تیکتهای من'\n\n" .
|
||||
"💡 مثال: 'تیکت جدید با عنوان مشکل در ورود به سیستم\n" .
|
||||
"من نمیتونم وارد حسابم بشم و خطای 404 میگیرم'";
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ use App\Entity\APIToken;
|
|||
use App\Entity\Business;
|
||||
use App\Entity\Money;
|
||||
use App\Entity\Permission;
|
||||
use App\Entity\User;
|
||||
use App\Entity\UserToken;
|
||||
use App\Entity\Year;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
@ -54,6 +55,17 @@ class Access
|
|||
'token'=>$this->request->headers->get('api-key')
|
||||
]);
|
||||
if(!$token) { return false; }
|
||||
|
||||
// بررسی تاریخ انقضا
|
||||
$dateExpire = $token->getDateExpire();
|
||||
if($dateExpire && $dateExpire != '0') {
|
||||
$expireTimestamp = (int)$dateExpire;
|
||||
$currentTimestamp = time();
|
||||
if($currentTimestamp > $expireTimestamp) {
|
||||
return false; // توکن منقضی شده
|
||||
}
|
||||
}
|
||||
|
||||
$bid = $token->getBid();
|
||||
}
|
||||
if ($this->request->headers->get('activeYear')) {
|
||||
|
@ -125,4 +137,36 @@ class Access
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* ایجاد توکن API برای هوش مصنوعی
|
||||
* @param Business $business کسب و کار
|
||||
* @param User $user کاربر ایجاد کننده
|
||||
* @param int|null $expireSeconds زمان انقضا به ثانیه (null برای همیشه معتبر)
|
||||
* @return APIToken
|
||||
*/
|
||||
public function createAiToken(Business $business, User $user, ?int $expireSeconds = null): APIToken
|
||||
{
|
||||
$token = new APIToken();
|
||||
$token->setBid($business);
|
||||
$token->setSubmitter($user);
|
||||
$token->setIsForAi(true);
|
||||
|
||||
// ایجاد توکن تصادفی
|
||||
$randomToken = bin2hex(random_bytes(32));
|
||||
$token->setToken($randomToken);
|
||||
|
||||
// تنظیم تاریخ انقضا
|
||||
if ($expireSeconds !== null) {
|
||||
$expireTimestamp = time() + $expireSeconds;
|
||||
$token->setDateExpire((string)$expireTimestamp);
|
||||
} else {
|
||||
$token->setDateExpire('0'); // همیشه معتبر
|
||||
}
|
||||
|
||||
$this->em->persist($token);
|
||||
$this->em->flush();
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
33
webUI/src/stores/navigationStore.ts
Normal file
33
webUI/src/stores/navigationStore.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useNavigationStore = defineStore('navigation', () => {
|
||||
const drawer = ref(true)
|
||||
const previousDrawerState = ref(true)
|
||||
|
||||
const closeDrawer = () => {
|
||||
previousDrawerState.value = drawer.value
|
||||
drawer.value = false
|
||||
}
|
||||
|
||||
const openDrawer = () => {
|
||||
drawer.value = previousDrawerState.value
|
||||
}
|
||||
|
||||
const toggleDrawer = () => {
|
||||
drawer.value = !drawer.value
|
||||
}
|
||||
|
||||
const setDrawer = (value: boolean) => {
|
||||
drawer.value = value
|
||||
}
|
||||
|
||||
return {
|
||||
drawer,
|
||||
previousDrawerState,
|
||||
closeDrawer,
|
||||
openDrawer,
|
||||
toggleDrawer,
|
||||
setDrawer
|
||||
}
|
||||
})
|
|
@ -4,6 +4,7 @@ import axios from "axios";
|
|||
import Swal from "sweetalert2";
|
||||
import { getApiUrl, getBasePath, getSiteName, getSiteSlogan } from "@/hesabixConfig";
|
||||
import { ref } from 'vue';
|
||||
import { useNavigationStore } from '@/stores/navigationStore';
|
||||
import Profile_btn from '@/components/application/buttons/profile_btn.vue';
|
||||
import Notifications_btn from '@/components/application/buttons/notifications_btn.vue';
|
||||
import Year_cob from '@/components/application/combobox/year_cob.vue';
|
||||
|
@ -15,7 +16,7 @@ import ShortcutsButton from '@/components/application/buttons/ShortcutsButton.vu
|
|||
export default {
|
||||
data() {
|
||||
return {
|
||||
drawer: ref(null),
|
||||
navigationStore: useNavigationStore(),
|
||||
plugins: [],
|
||||
business: { id: '', name: '' },
|
||||
timeNow: '',
|
||||
|
@ -35,6 +36,16 @@ export default {
|
|||
canFreeAccounting: true,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
drawer: {
|
||||
get() {
|
||||
return this.navigationStore.drawer;
|
||||
},
|
||||
set(value) {
|
||||
this.navigationStore.setDrawer(value);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
axios.post('/api/plugin/get/actives').then((response) => {
|
||||
this.plugins = response.data;
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue