diff --git a/hesabixCore/src/Controller/wizardController.php b/hesabixCore/src/Controller/wizardController.php index 1f9c036c..9e2b806a 100644 --- a/hesabixCore/src/Controller/wizardController.php +++ b/hesabixCore/src/Controller/wizardController.php @@ -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 => '📋' - }; - } } diff --git a/hesabixCore/src/Entity/APIToken.php b/hesabixCore/src/Entity/APIToken.php index 27f2d1e8..7c005080 100644 --- a/hesabixCore/src/Entity/APIToken.php +++ b/hesabixCore/src/Entity/APIToken.php @@ -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; + } } diff --git a/hesabixCore/src/Service/AGI/AGIService.php b/hesabixCore/src/Service/AGI/AGIService.php new file mode 100644 index 00000000..d7e9feac --- /dev/null +++ b/hesabixCore/src/Service/AGI/AGIService.php @@ -0,0 +1,609 @@ +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; + } +} \ No newline at end of file diff --git a/hesabixCore/src/Service/AGI/Promps/BasePromptService.php b/hesabixCore/src/Service/AGI/Promps/BasePromptService.php new file mode 100644 index 00000000..e9280865 --- /dev/null +++ b/hesabixCore/src/Service/AGI/Promps/BasePromptService.php @@ -0,0 +1,104 @@ +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); + } +} \ No newline at end of file diff --git a/hesabixCore/src/Service/AGI/Promps/PersonPromptService.php b/hesabixCore/src/Service/AGI/Promps/PersonPromptService.php new file mode 100644 index 00000000..0441c024 --- /dev/null +++ b/hesabixCore/src/Service/AGI/Promps/PersonPromptService.php @@ -0,0 +1,155 @@ +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 + } + } +}'; + } + +} \ No newline at end of file diff --git a/hesabixCore/src/Service/AGI/Promps/PromptService.php b/hesabixCore/src/Service/AGI/Promps/PromptService.php new file mode 100644 index 00000000..dfeaf575 --- /dev/null +++ b/hesabixCore/src/Service/AGI/Promps/PromptService.php @@ -0,0 +1,70 @@ +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); + } +} \ No newline at end of file diff --git a/hesabixCore/src/Service/AI/AIService.php b/hesabixCore/src/Service/AI/AIService.php deleted file mode 100644 index edcfb359..00000000 --- a/hesabixCore/src/Service/AI/AIService.php +++ /dev/null @@ -1,1960 +0,0 @@ -registryMGR = $registryMGR; - $this->entityManager = $entityManager; - $this->log = $log; - $this->provider = $provider; - } - - /** - * ارسال درخواست به هوش مصنوعی با تشخیص خودکار ابزارها - */ - public function sendRequest(string $message, ?Business $business = null, $user = null, array $conversationHistory = []): array - { - // بررسی فعال بودن هوش مصنوعی - $status = $this->checkAIServiceStatus(); - if (!$status['enabled']) { - return [ - 'success' => false, - 'error' => 'سرویس هوش مصنوعی غیرفعال است.', - 'status' => $status - ]; - } - - try { - // ساخت پرامپ هوشمند با معرفی ابزارها و تاریخچه گفتگو - $prompt = $this->buildSmartPrompt($message, $business, $conversationHistory); - $service = $this->getAIAgentSource(); - $apiKey = $this->getAIApiKey($service); - - if (!$apiKey) { - return [ - 'success' => false, - 'error' => 'کلید API برای سرویس هوش مصنوعی تنظیم نشده است.' - ]; - } - - $response = match ($service) { - 'gapgpt' => $this->sendToGapGPT($prompt, $apiKey, $conversationHistory), - 'avalai' => $this->sendToAvalAI($prompt, $apiKey, $conversationHistory), - 'local' => $this->sendToLocalAI($prompt, $apiKey, $conversationHistory), - default => [ - 'success' => false, - 'error' => 'سرویس هوش مصنوعی نامعتبر است.' - ] - }; - - if ($response['success']) { - // بررسی وجود کلید data - if (isset($response['data'])) { - $aiResponse = $this->extractAIResponse($response['data']); - $cost = $this->calculateCostFromResponse($response['data']); - - // پردازش پاسخ هوش مصنوعی برای تشخیص دستورات ابزار - $processedResponse = $this->processAIResponse($aiResponse, $business, $user); - - // اضافه کردن debug logging - error_log('AIService Debug - AI Response: ' . $aiResponse); - error_log('AIService Debug - Processed Response: ' . json_encode($processedResponse)); - error_log('AIService Debug - Usage Data: ' . json_encode($response['data']['usage'] ?? 'No usage data')); - error_log('AIService Debug - Cost Data: ' . json_encode($cost)); - - return [ - 'success' => true, - 'response' => $processedResponse['response'], - 'usage' => $response['data']['usage'] ?? null, - 'cost' => $cost, - 'service' => $service, - 'model' => $this->getAIModel(), - 'requires_action' => $processedResponse['requires_action'] ?? false, - 'action_data' => $processedResponse['action_data'] ?? null, - 'debug_info' => $processedResponse['debug_info'] ?? null - ]; - } else { - // اگر کلید data وجود ندارد، احتمالاً پاسخ از ابزار است - return $response; - } - } - - return $response; - - } catch (\Exception $e) { - return [ - 'success' => false, - 'error' => 'خطا در پردازش درخواست: ' . $e->getMessage() - ]; - } - } - - /** - * ساخت پرامپ هوشمند با معرفی ابزارها و تاریخچه گفتگو - */ - private function buildSmartPrompt(string $message, ?Business $business, array $conversationHistory = []): string - { - $basePrompt = $this->getSystemPrompt(); - $customPrompt = $this->getAIPrompt(); - - $prompt = $basePrompt; - - // اضافه کردن پرامپ سفارشی مدیر سیستم - if (!empty($customPrompt) && $customPrompt !== 'شما یک دستیار هوشمند برای سیستم حسابداری هستید.') { - $prompt .= "\n\n" . $customPrompt; - } - - 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; - - // اضافه کردن اطلاعات اشخاص اگر کسب و کار موجود باشد - if ($business) { - try { - $personsSummary = $this->getPersonDataService()->generatePersonsSummaryForAI($business); - $prompt .= "\n\n" . $personsSummary; - } catch (\Exception $e) { - error_log('خطا در دریافت اطلاعات اشخاص: ' . $e->getMessage()); - } - } - - return $prompt; - } - - /** - * پرامپ سیستمی هوشمند - */ - private function getSystemPrompt(): string - { - return "شما یک دستیار هوشمند برای سیستم حسابداری حسابیکس هستید.\n\n🔧 ابزارهای موجود:\n\n1. **مدیریت اشخاص**:\n - افزودن شخص جدید: add_person{name:نام مستعار, full_name:نام کامل}\n - حذف شخص: delete_person{name:نام مستعار}\n - ویرایش شخص: edit_person{name:نام مستعار, mobile:موبایل, address:آدرس, email:ایمیل}\n - نمایش مشخصات: show_person{name:نام مستعار}\n - جستجوی اشخاص: search_persons{search:متن جستجو, limit:تعداد نتایج}\n\n2. **جستجوی پیشرفته اشخاص**:\n - جستجوی پیشرفته: advanced_search_persons{search:متن جستجو, search_type:نوع جستجو, limit:تعداد نتایج}\n * search_type: all, mobile, name, code, debtor, creditor, balanced\n - جستجو بر اساس موبایل: search_by_mobile{mobile:شماره موبایل}\n - جستجوی بدهکاران: search_debtors{search:متن جستجو, limit:تعداد نتایج}\n - جستجوی بستانکاران: search_creditors{search:متن جستجو, limit:تعداد نتایج}\n\n3. **مدیریت تیکت‌های پشتیبانی**:\n - ایجاد تیکت جدید: create_ticket{title:عنوان تیکت, body:متن تیکت, priority:اولویت}\n - مشاهده لیست تیکت‌ها: list_tickets{state:وضعیت, limit:تعداد نتایج}\n - مشاهده جزئیات تیکت: view_ticket{ticket_id:شناسه تیکت}\n - پاسخ به تیکت: reply_ticket{ticket_id:شناسه تیکت, body:متن پاسخ}\n - جستجوی تیکت‌ها: search_tickets{search:متن جستجو, state:وضعیت}\n - آمار تیکت‌ها: get_ticket_statistics{}\n\n4. **مدیریت نمودارها و چارت‌ها**:\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📋 قوانین مهم:\n- حتماً از دستورات بالا استفاده کنید\n- نام شخص = نام مستعار (nikename) - فیلد الزامی\n- نام کامل (full_name) = نام و نام خانوادگی - فیلد اختیاری\n- برای جستجو بر اساس موبایل، از search_by_mobile استفاده کنید\n- برای جستجوی بدهکاران/بستانکاران، از ابزارهای مخصوص استفاده کنید\n- برای سوالات پشتیبانی، ابتدا راهنمایی ارائه دهید، سپس پیشنهاد تیکت بدهید\n\n🔍 راهنمای جستجوی هوشمند:\n\n**جستجو بر اساس موبایل:**\n- وقتی کاربر شماره موبایل می‌دهد: \"کی موبایلش 09183282405 هست؟\"\n- از ابزار search_by_mobile استفاده کنید\n\n**جستجوی بدهکاران:**\n- وقتی کاربر می‌گوید: \"بدهکاران رو بهم معرفی کن\"\n- از ابزار search_debtors استفاده کنید\n\n**جستجوی بستانکاران:**\n- وقتی کاربر می‌گوید: \"بستانکاران رو نشون بده\"\n- از ابزار search_creditors استفاده کنید\n\n**جستجوی پیشرفته:**\n- برای جستجوهای خاص: advanced_search_persons{search_type:debtor, search:علی}\n- برای همه بدهکاران: advanced_search_persons{search_type:debtor}\n- برای همه بستانکاران: advanced_search_persons{search_type:creditor}\n\n**مدیریت تیکت‌های پشتیبانی:**\n- وقتی کاربر مشکل یا سوالی دارد: ابتدا راهنمایی ارائه دهید، سپس پیشنهاد تیکت بدهید\n- برای ایجاد تیکت: create_ticket{title:عنوان, body:متن مشکل}\n- برای مشاهده تیکت‌ها: list_tickets{state:در حال پیگیری}\n- برای مشاهده جزئیات: view_ticket{ticket_id:شناسه}\n- برای پاسخ: reply_ticket{ticket_id:شناسه, body:متن پاسخ}\n- برای جستجو: search_tickets{search:متن جستجو}\n- برای آمار: get_ticket_statistics{}\n\n**راهنمای پشتیبانی هوشمند:**\n1. **سوالات عمومی**: ابتدا پاسخ دهید، سپس پیشنهاد تیکت بدهید\n2. **مشکلات فنی**: راهنمایی ارائه دهید، سپس تیکت ایجاد کنید\n3. **درخواست‌های پیچیده**: ابتدا توضیح دهید، سپس تیکت پیشنهاد دهید\n4. **مشاهده وضعیت**: از ابزارهای تیکت استفاده کنید\n\n**مثال‌های تیکت:**\n- 'مشکل در ورود به سیستم' → راهنمایی + پیشنهاد تیکت\n- 'لیست تیکت‌های من' → list_tickets\n- 'مشاهده تیکت ABC123' → view_ticket{ticket_id:ABC123}\n- 'پاسخ به تیکت 123' → reply_ticket{ticket_id:123, body:متن پاسخ}\n\n**مدیریت نمودارها و چارت‌ها:**\n- وقتی کاربر می‌خواهد نمودار ببیند: ابتدا داده‌ها را جمع‌آوری کنید، سپس نمودار مناسب ایجاد کنید\n- برای نمودار ستونی: create_bar_chart{title:عنوان, data:[{category:عنوان, value:عدد}]}\n- برای نمودار دایره‌ای: create_pie_chart{title:عنوان, data:[{label:عنوان, value:عدد}]}\n- برای نمودار خطی: create_line_chart{title:عنوان, data:[{category:عنوان, 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**راهنمای نمودار هوشمند:**\n1. **تحلیل داده‌ها**: ابتدا داده‌ها را جمع‌آوری و تحلیل کنید\n2. **انتخاب نوع نمودار**: بر اساس نوع داده، نمودار مناسب انتخاب کنید\n3. **ایجاد نمودار**: از ابزارهای نمودار استفاده کنید\n4. **نمایش تعاملی**: نمودار را با قابلیت‌های تعاملی نمایش دهید\n\n**مثال‌های نمودار:**\n- 'نمودار فروش ماهانه' → جمع‌آوری داده‌ها + create_bar_chart\n- 'نمودار دایره‌ای سهم بازار' → جمع‌آوری داده‌ها + create_pie_chart\n- 'نمودار خطی روند رشد' → جمع‌آوری داده‌ها + create_line_chart\n- 'نمودار پراکندگی سن و درآمد' → جمع‌آوری داده‌ها + create_scatter_chart\n\n🎯 تحلیل و نتیجه‌گیری هوشمند:\n\n**برای سوالات تحلیلی، حتماً نتیجه‌گیری کنید:**\n\n1. **بیشترین بدهکار:**\n - وقتی کاربر می‌گوید: \"بیشترین فرد بدهکار رو بهم مشخصاتشو بده\"\n - ابتدا لیست بدهکاران را بگیرید\n - سپس تحلیل کنید و شخص با بیشترین بدهی را مشخص کنید\n - در نهایت مشخصات کامل آن شخص را نمایش دهید\n\n2. **بیشترین بستانکار:**\n - وقتی کاربر می‌گوید: \"بیشترین بستانکار کیه؟\"\n - ابتدا لیست بستانکاران را بگیرید\n - سپس تحلیل کنید و شخص با بیشترین بستانکاری را مشخص کنید\n\n3. **تحلیل آماری:**\n - وقتی کاربر می‌خواهد تحلیل کند: \"بدهکاران رو تحلیل کن\"\n - لیست را بگیرید و آمار ارائه دهید\n - مثلاً: تعداد کل، میانگین بدهی، بیشترین و کمترین بدهی\n\n4. **مقایسه و رتبه‌بندی:**\n - وقتی کاربر می‌خواهد مقایسه کند: \"بدهکاران رو بر اساس میزان بدهی مرتب کن\"\n - لیست را بگیرید و رتبه‌بندی ارائه دهید\n\n**نکات مهم تحلیل:**\n- همیشه نتایج جستجو را تحلیل کنید\n- اگر کاربر می‌خواهد \"بیشترین\" یا \"کمترین\"، حتماً مشخص کنید\n- اگر کاربر می‌خواهد \"مشخصات\"، از ابزار show_person استفاده کنید\n- برای سوالات تحلیلی، requires_followup را true کنید تا بتوانید تحلیل ارائه دهید\n- در user_message، ابتدا نتایج را نشان دهید، سپس تحلیل کنید\n\n🔄 سیستم تعاملی چندمرحله‌ای:\nبرای عملیات پیچیده که نیاز به چند مرحله دارند، از ساختار JSON استفاده کنید:\n\n```json\n{ - \"action\": \"tool_command\", - \"tool\": \"نام_ابزار\", - \"params\": { - \"param1\": \"value1\", - \"param2\": \"value2\" - }, - \"user_message\": \"پیام نمایشی برای کاربر\", - \"debug_info\": \"اطلاعات دیباگ\", - \"requires_followup\": true, - \"next_step\": \"توضیح مرحله بعدی\" -} -```\n\n🔄 سیستم عملیات پیوسته تا حصول نتیجه:\nبرای عملیات‌هایی که ممکن است نیاز به چندین تلاش داشته باشند، از متغیر `operation_complete` استفاده کنید:\n\n```json\n{ - \"action\": \"continuous_operation\", - \"operation_type\": \"نوع_عملیات\", - \"current_step\": \"مرحله_فعلی\", - \"steps\": [ - { - \"tool\": \"search_persons\", - \"params\": {\"search\": \"محسن\"}, - \"purpose\": \"جستجوی شخص محسن\" - } - ], - \"user_message\": \"در حال جستجوی شخص محسن...\", - \"debug_info\": \"عملیات پیوسته برای یافتن و ویرایش محسن\", - \"operation_complete\": false, - \"next_action\": \"اگر محسن پیدا نشد، شخص جدید بساز\", - \"max_attempts\": 3 -} -```\n\n🔄 سیستم عملیات هوشمند چندمرحله‌ای (جدید):\nبرای عملیات‌هایی که نیاز به تصمیم‌گیری هوشمند دارند، از ساختار زیر استفاده کنید:\n\n```json\n{ - \"action\": \"smart_operation\", - \"operation_type\": \"add_phone_to_person\", - \"target_person\": \"محسن\", - \"target_phone\": \"09123456789\", - \"steps\": [ - { - \"tool\": \"search_persons\", - \"params\": {\"search\": \"محسن\"}, - \"purpose\": \"جستجوی شخص محسن\", - \"on_success\": \"edit_person\", - \"on_failure\": \"add_person\" - } - ], - \"user_message\": \"در حال جستجوی شخص محسن...\", - \"debug_info\": \"عملیات هوشمند برای افزودن تلفن\", - \"operation_complete\": false, - \"next_action\": \"بر اساس نتیجه جستجو، شخص را ویرایش یا اضافه کن\" -} -```\n\n📝 انواع پاسخ‌ها:\n\n1. **پاسخ مستقیم** (برای عملیات ساده):\n```json\n{ - \"action\": \"direct_response\", - \"user_message\": \"پیام نمایشی برای کاربر\", - \"debug_info\": \"اطلاعات دیباگ\" -} -```\n\n2. **دستور ابزار** (برای عملیات تک مرحله‌ای):\n```json\n{ - \"action\": \"tool_command\", - \"tool\": \"edit_person\", - \"params\": { - \"name\": \"محسن\", - \"mobile\": \"09123456789\" - }, - \"user_message\": \"در حال ویرایش موبایل محسن...\", - \"debug_info\": \"اجرای دستور edit_person\" -} -```\n\n3. **عملیات چندمرحله‌ای** (برای عملیات پیچیده):\n```json\n{ - \"action\": \"multi_step\", - \"steps\": [ - { - \"tool\": \"search_persons\", - \"params\": {\"search\": \"محسن\"}, - \"purpose\": \"جستجوی شخص محسن\" - }, - { - \"tool\": \"edit_person\", - \"params\": {\"name\": \"محسن\", \"mobile\": \"09123456789\"}, - \"purpose\": \"ویرایش موبایل\" - } - ], - \"user_message\": \"در حال جستجو و ویرایش اطلاعات محسن...\", - \"debug_info\": \"عملیات چندمرحله‌ای\", - \"requires_followup\": true -} -```\n\n4. **عملیات هوشمند چندمرحله‌ای** (جدید):\n```json\n{ - \"action\": \"smart_operation\", - \"operation_type\": \"add_phone_to_person\", - \"target_person\": \"محسن\", - \"target_phone\": \"09123456789\", - \"steps\": [ - { - \"tool\": \"search_persons\", - \"params\": {\"search\": \"محسن\"}, - \"purpose\": \"جستجوی شخص محسن\", - \"on_success\": \"edit_person\", - \"on_failure\": \"add_person\" - } - ], - \"user_message\": \"در حال جستجوی شخص محسن...\", - \"debug_info\": \"عملیات هوشمند برای افزودن تلفن\", - \"operation_complete\": false, - \"next_action\": \"بر اساس نتیجه جستجو، شخص را ویرایش یا اضافه کن\" -} -```\n\n5. **نتیجه نهایی** (وقتی عملیات کامل شد):\n```json\n{ - \"action\": \"operation_complete\", - \"operation_type\": \"add_phone_to_person\", - \"final_result\": \"عملیات با موفقیت انجام شد\", - \"user_message\": \"موبایل 09123456789 برای محسن محمودی با موفقیت اضافه شد\", - \"debug_info\": \"عملیات پیوسته کامل شد\", - \"operation_complete\": true, - \"total_steps\": 2, - \"final_data\": { - \"person_id\": 139, - \"person_name\": \"محسن محمودی\", - \"new_mobile\": \"09123456789\" - } -} -```\n\n💡 مثال‌های استفاده:\n\n**مثال 1 - عملیات ساده:**\n- 'علی رو حذف کن' → دستور مستقیم delete_person\n\n**مثال 2 - عملیات با جستجو:**\n- 'تلفن 09123456789 را برای محسن اضافه کن' → ابتدا جستجو، سپس ویرایش\n\n**مثال 3 - عملیات هوشمند:**\n- 'شخصی با نام محسن پیدا کن و موبایلش رو 09123456789 کن' → جستجو + اگر پیدا شد ویرایش + اگر پیدا نشد بساز\n\n**مثال 4 - عملیات پیچیده:**\n- 'برای احمد موبایل 09123456789 تنظیم کن' → جستجو + اگر پیدا نشد بساز + ویرایش موبایل\n\n**مثال 5 - نمایش نتایج جستجو:**\n- '5 نفر اخیر را نشان بده' → جستجو + نمایش مستقیم نتایج بدون followup\n\n**مثال 6 - جستجو بر اساس موبایل:**\n- 'کی موبایلش 09183282405 هست؟' → search_by_mobile{mobile:09183282405}\n\n**مثال 7 - جستجوی بدهکاران:**\n- 'بدهکاران رو بهم معرفی کن' → search_debtors{limit:20}\n\n**مثال 8 - جستجوی بستانکاران:**\n- 'بستانکاران رو نشون بده' → search_creditors{limit:20}\n\n**مثال 9 - جستجوی پیشرفته:**\n- 'بدهکاران علی رو پیدا کن' → advanced_search_persons{search_type:debtor, search:علی}\n\n**مثال 10 - تحلیل و نتیجه‌گیری:**\n- 'بیشترین فرد بدهکار رو بهم مشخصاتشو بده' → search_debtors + تحلیل + show_person\n- 'بیشترین بستانکار کیه؟' → search_creditors + تحلیل + نمایش نتیجه\n- 'بدهکاران رو تحلیل کن' → search_debtors + تحلیل آماری\n\n⚠️ نکات مهم:\n- حتماً از ساختار JSON استفاده کنید\n- برای عملیات پیچیده، ابتدا جستجو کنید\n- اگر شخصی پیدا نشد، شخص جدید بسازید\n- پیام‌های کاربر را واضح و مفید بنویسید\n- اطلاعات دیباگ را کامل ارائه دهید\n- برای عملیات‌هایی که فقط نیاز به نمایش نتایج دارند، requires_followup را false کنید\n- برای عملیات پیوسته، operation_complete را false نگه دارید تا به نتیجه برسید\n- وقتی عملیات کامل شد، operation_complete را true کنید و نتیجه نهایی را ارائه دهید\n- برای عملیات هوشمند، از on_success و on_failure استفاده کنید تا مرحله بعدی مشخص شود\n- برای جستجو و نمایش نتایج، مستقیماً نتایج را در user_message قرار دهید و requires_followup را false کنید\n- برای جستجو بر اساس موبایل، حتماً از ابزار search_by_mobile استفاده کنید\n- برای جستجوی بدهکاران/بستانکاران، از ابزارهای مخصوص استفاده کنید\n- **برای سوالات تحلیلی، حتماً تحلیل کنید و نتیجه‌گیری کنید**\n- **برای سوالات \"بیشترین\" یا \"کمترین\"، حتماً مشخص کنید**\n- **برای سوالات \"مشخصات\"، از ابزار show_person استفاده کنید**\n\n🔄 منطق عملیات هوشمند:\n1. ابتدا جستجو کنید\n2. اگر پیدا شد، عملیات مورد نظر را انجام دهید\n3. اگر پیدا نشد، شخص جدید بسازید\n4. سپس عملیات مورد نظر را انجام دهید\n5. در نهایت operation_complete را true کنید\n\n🔄 منطق عملیات پیوسته:\n1. ابتدا جستجو کنید\n2. اگر پیدا شد، عملیات مورد نظر را انجام دهید\n3. اگر پیدا نشد، شخص جدید بسازید\n4. سپس عملیات مورد نظر را انجام دهید\n5. در نهایت operation_complete را true کنید\n\n🔄 منطق نمایش نتایج:\n1. جستجو را انجام دهید\n2. نتایج را مستقیماً در user_message قرار دهید\n3. requires_followup را false کنید\n4. اطلاعات کامل را در debug_info قرار دهید\n\n🔄 منطق پاسخ‌دهی عمومی:\n1. سوال را بررسی کنید\n2. اگر سوال عمومی است، پاسخ آموزشی ارائه دهید\n3. از direct_response استفاده کنید\n4. اطلاعات مفید و کامل ارائه دهید\n\n🔄 منطق جستجوی هوشمند:\n1. اگر کاربر شماره موبایل می‌دهد، از search_by_mobile استفاده کنید\n2. اگر کاربر می‌خواهد بدهکاران را ببیند، از search_debtors استفاده کنید\n3. اگر کاربر می‌خواهد بستانکاران را ببیند، از search_creditors استفاده کنید\n4. برای جستجوهای پیشرفته، از advanced_search_persons استفاده کنید\n5. برای جستجوهای ساده، از search_persons استفاده کنید\n\n🔄 منطق تحلیل و نتیجه‌گیری:\n1. اگر کاربر می‌خواهد \"بیشترین\" یا \"کمترین\"، ابتدا لیست را بگیرید\n2. سپس تحلیل کنید و شخص مورد نظر را مشخص کنید\n3. اگر کاربر می‌خواهد \"مشخصات\"، از show_person استفاده کنید\n4. در نهایت نتیجه نهایی را ارائه دهید\n5. برای سوالات تحلیلی، requires_followup را true کنید\n\nلطفاً درخواست کاربر را بررسی کرده و پاسخ مناسب را با ساختار JSON ارائه دهید."; - } - - /** - * پردازش پاسخ هوش مصنوعی و تشخیص دستورات ابزار - */ - private function processAIResponse(string $aiResponse, ?Business $business, $user): array - { - // ابتدا سعی کن JSON را از پاسخ استخراج کن - $jsonResponse = $this->extractJSONFromResponse($aiResponse); - - if ($jsonResponse) { - return $this->processJSONResponse($jsonResponse, $aiResponse, $business, $user); - } - - // اگر JSON یافت نشد، از روش قدیمی استفاده کن - return $this->processLegacyResponse($aiResponse, $business, $user); - } - - /** - * استخراج JSON از پاسخ AI - */ - private function extractJSONFromResponse(string $aiResponse): ?array - { - // جستجوی JSON در پاسخ - if (preg_match('/```json\s*(\{.*?\})\s*```/s', $aiResponse, $matches)) { - $jsonString = $matches[1]; - } elseif (preg_match('/(\{.*\})/s', $aiResponse, $matches)) { - $jsonString = $matches[1]; - } else { - return null; - } - - $decoded = json_decode($jsonString, true); - return $decoded ?: null; - } - - /** - * پردازش پاسخ JSON - */ - private function processJSONResponse(array $jsonResponse, string $originalResponse, ?Business $business, $user): array - { - $action = $jsonResponse['action'] ?? 'direct_response'; - - switch ($action) { - case 'direct_response': - return [ - 'response' => $jsonResponse['user_message'] ?? $originalResponse, - 'requires_action' => false, - 'action_data' => null, - 'debug_info' => [ - 'ai_response' => $originalResponse, - 'json_response' => $jsonResponse, - 'action_type' => 'direct_response', - 'has_commands' => false - ] - ]; - - case 'tool_command': - return $this->executeSingleToolCommand($jsonResponse, $originalResponse, $business, $user); - - case 'multi_step': - return $this->executeMultiStepCommand($jsonResponse, $originalResponse, $business, $user); - - case 'continuous_operation': - return $this->executeContinuousOperation($jsonResponse, $originalResponse, $business, $user); - - case 'smart_operation': - return $this->executeSmartOperation($jsonResponse, $originalResponse, $business, $user); - - case 'operation_complete': - return [ - 'response' => $jsonResponse['user_message'] ?? 'عملیات با موفقیت کامل شد', - 'requires_action' => false, - 'action_data' => null, - 'debug_info' => [ - 'ai_response' => $originalResponse, - 'json_response' => $jsonResponse, - 'action_type' => 'operation_complete', - 'has_commands' => false, - 'final_result' => $jsonResponse['final_result'] ?? null, - 'final_data' => $jsonResponse['final_data'] ?? null - ] - ]; - - default: - return [ - 'response' => $jsonResponse['user_message'] ?? $originalResponse, - 'requires_action' => false, - 'action_data' => null, - 'debug_info' => [ - 'ai_response' => $originalResponse, - 'json_response' => $jsonResponse, - 'action_type' => 'unknown', - 'has_commands' => false, - 'error' => 'نوع عملیات نامعتبر' - ] - ]; - } - } - - /** - * اجرای دستور ابزار تک مرحله‌ای - */ - private function executeSingleToolCommand(array $jsonResponse, string $originalResponse, ?Business $business, $user): array - { - $tool = $jsonResponse['tool'] ?? ''; - $params = $jsonResponse['params'] ?? []; - - if (empty($tool)) { - return [ - 'response' => $jsonResponse['user_message'] ?? 'خطا: ابزار مشخص نشده است', - 'requires_action' => false, - 'action_data' => null, - 'debug_info' => [ - 'ai_response' => $originalResponse, - 'json_response' => $jsonResponse, - 'action_type' => 'tool_command', - 'has_commands' => false, - 'error' => 'ابزار مشخص نشده است' - ] - ]; - } - - $result = $this->executeToolCommand(['tool' => $tool, 'params' => $params], $business, $user); - - $finalResponse = $jsonResponse['user_message'] ?? 'عملیات انجام شد'; - - // برای جستجو، نتایج را مستقیماً نمایش بده - if (in_array($tool, ['search_persons', 'advanced_search_persons', 'search_debtors', 'search_creditors']) && $result['success'] && isset($result['persons'])) { - $persons = $result['persons']; - $count = count($persons); - - if ($count > 0) { - $finalResponse .= "\n\n"; - - // تعیین نوع جستجو - if ($tool === 'search_debtors') { - $finalResponse .= "🔴 لیست بدهکاران"; - if (isset($params['search']) && !empty($params['search'])) { - $finalResponse .= " برای \"{$params['search']}\""; - } - $finalResponse .= ":\n"; - if (isset($result['total_debt'])) { - $finalResponse .= "💰 مجموع بدهی: " . number_format($result['total_debt']) . " ریال\n\n"; - } - } elseif ($tool === 'search_creditors') { - $finalResponse .= "🟢 لیست بستانکاران"; - if (isset($params['search']) && !empty($params['search'])) { - $finalResponse .= " برای \"{$params['search']}\""; - } - $finalResponse .= ":\n"; - if (isset($result['total_credit'])) { - $finalResponse .= "💰 مجموع بستانکاری: " . number_format($result['total_credit']) . " ریال\n\n"; - } - } elseif ($tool === 'advanced_search_persons') { - $searchType = $params['search_type'] ?? 'all'; - $searchTypeNames = [ - 'debtor' => 'بدهکاران', - 'creditor' => 'بستانکاران', - 'balanced' => 'تسویه شده‌ها', - 'mobile' => 'بر اساس موبایل', - 'name' => 'بر اساس نام', - 'code' => 'بر اساس کد' - ]; - $finalResponse .= "🔍 نتایج جستجوی " . ($searchTypeNames[$searchType] ?? 'پیشرفته'); - if (isset($params['search']) && !empty($params['search'])) { - $finalResponse .= " برای \"{$params['search']}\""; - } - $finalResponse .= ":\n"; - } else { - if (isset($params['search']) && !empty($params['search'])) { - $finalResponse .= "🔍 نتایج جستجو برای \"{$params['search']}\":\n"; - } else { - $finalResponse .= "📋 لیست {$count} نفر اخیر:\n"; - } - } - - foreach ($persons as $index => $person) { - $finalResponse .= "\n" . ($index + 1) . ". "; - if (!empty($person['nikename'])) { - $finalResponse .= $person['nikename']; - } elseif (!empty($person['name'])) { - $finalResponse .= $person['name']; - } else { - $finalResponse .= "شخص " . $person['code']; - } - - if (!empty($person['mobile'])) { - $finalResponse .= " - موبایل: " . $person['mobile']; - } - - // نمایش تراز برای جستجوهای مالی - if (isset($person['balance_formatted'])) { - $status = $person['status'] ?? ''; - $finalResponse .= " - تراز: " . $person['balance_formatted'] . " ({$status})"; - } - - $finalResponse .= " (کد: " . $person['code'] . ")"; - } - } else { - $finalResponse .= "\n\n❌ هیچ شخصی یافت نشد."; - } - - // برای جستجو، followup را غیرفعال کن مگر اینکه سوال تحلیلی باشد - $requiresFollowup = false; - - // بررسی اینکه آیا سوال تحلیلی است - $userMessage = $jsonResponse['user_message'] ?? ''; - $isAnalyticalQuestion = false; - - // کلمات کلیدی برای سوالات تحلیلی - $analyticalKeywords = [ - 'بیشترین', 'کمترین', 'بزرگترین', 'کوچکترین', - 'مشخصات', 'تحلیل', 'رتبه', 'مرتب', 'مقایسه', - 'اول', 'آخر', 'برتر', 'بدترین', 'بهترین' - ]; - - foreach ($analyticalKeywords as $keyword) { - if (strpos($userMessage, $keyword) !== false) { - $isAnalyticalQuestion = true; - break; - } - } - - // اگر سوال تحلیلی است، followup را فعال کن - if ($isAnalyticalQuestion) { - $requiresFollowup = true; - } - } elseif ($tool === 'search_by_mobile' && $result['success'] && isset($result['person'])) { - $person = $result['person']; - $finalResponse .= "\n\n📱 اطلاعات شخص با موبایل {$params['mobile']}:\n\n"; - $finalResponse .= "👤 نام: " . ($person['nikename'] ?: $person['name']) . "\n"; - $finalResponse .= "📞 موبایل: " . $person['mobile'] . "\n"; - $finalResponse .= "🏢 کد: " . $person['code'] . "\n"; - if (isset($person['balance_formatted'])) { - $finalResponse .= "💰 تراز: " . $person['balance_formatted'] . " ({$person['status']})\n"; - } - if (!empty($person['address'])) { - $finalResponse .= "📍 آدرس: " . $person['address'] . "\n"; - } - if (!empty($person['email'])) { - $finalResponse .= "📧 ایمیل: " . $person['email'] . "\n"; - } - - $requiresFollowup = false; - } elseif (in_array($tool, ['list_tickets', 'search_tickets', 'get_ticket_statistics']) && $result['success']) { - // برای ابزارهای تیکت، نتایج را مستقیماً نمایش بده - if ($tool === 'list_tickets' && isset($result['tickets'])) { - $tickets = $result['tickets']; - $count = count($tickets); - - $finalResponse .= "\n\n📋 لیست تیکت‌های شما ({$count} تیکت):\n"; - $finalResponse .= "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"; - - foreach ($tickets as $index => $ticket) { - $statusIcon = $this->getTicketStatusIcon($ticket['state']); - $finalResponse .= "\n" . ($index + 1) . ". {$statusIcon} "; - $finalResponse .= $ticket['title'] . "\n"; - $finalResponse .= " 📅 تاریخ: " . $ticket['date'] . "\n"; - $finalResponse .= " 🔢 کد: " . $ticket['code'] . "\n"; - $finalResponse .= " 📄 متن: " . $ticket['body'] . "\n"; - if ($ticket['has_file']) { - $finalResponse .= " 📎 فایل پیوست دارد\n"; - } - } - } elseif ($tool === 'search_tickets' && isset($result['tickets'])) { - $tickets = $result['tickets']; - $count = count($tickets); - - $finalResponse .= "\n\n🔍 نتایج جستجوی تیکت‌ها ({$count} تیکت):\n"; - $finalResponse .= "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"; - - foreach ($tickets as $index => $ticket) { - $statusIcon = $this->getTicketStatusIcon($ticket['state']); - $finalResponse .= "\n" . ($index + 1) . ". {$statusIcon} "; - $finalResponse .= $ticket['title'] . "\n"; - $finalResponse .= " 📅 تاریخ: " . $ticket['date'] . "\n"; - $finalResponse .= " 🔢 کد: " . $ticket['code'] . "\n"; - } - } elseif ($tool === 'get_ticket_statistics' && isset($result['statistics'])) { - $stats = $result['statistics']; - $finalResponse .= "\n\n📊 آمار تیکت‌های شما:\n"; - $finalResponse .= "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"; - $finalResponse .= "📋 کل تیکت‌ها: {$stats['total']}\n"; - $finalResponse .= "⏳ در حال پیگیری: {$stats['pending']}\n"; - $finalResponse .= "✅ پاسخ داده شده: {$stats['answered']}\n"; - $finalResponse .= "🔒 خاتمه یافته: {$stats['closed']}\n"; - } - - $requiresFollowup = false; - } elseif ($tool === 'view_ticket' && $result['success'] && isset($result['ticket'])) { - // نمایش جزئیات تیکت - $ticket = $result['ticket']; - $replies = $result['replies'] ?? []; - - $finalResponse .= "\n\n📋 جزئیات تیکت:\n"; - $finalResponse .= "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"; - $finalResponse .= "🔢 کد: " . $ticket['code'] . "\n"; - $finalResponse .= "📝 عنوان: " . $ticket['title'] . "\n"; - $finalResponse .= "📅 تاریخ: " . $ticket['date'] . "\n"; - $finalResponse .= "📄 متن: " . $ticket['body'] . "\n"; - $finalResponse .= "📊 وضعیت: " . $ticket['state'] . "\n"; - if ($ticket['has_file']) { - $finalResponse .= "📎 فایل پیوست دارد\n"; - } - - if (!empty($replies)) { - $finalResponse .= "\n💬 پاسخ‌ها (" . count($replies) . " پاسخ):\n"; - $finalResponse .= "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"; - - foreach ($replies as $index => $reply) { - $role = $reply['is_admin'] ? '👨‍💼 پشتیبان' : '👤 شما'; - $finalResponse .= "\n" . ($index + 1) . ". {$role}\n"; - $finalResponse .= " 📅 تاریخ: " . $reply['date'] . "\n"; - $finalResponse .= " 📄 متن: " . $reply['body'] . "\n"; - if ($reply['has_file']) { - $finalResponse .= " 📎 فایل پیوست دارد\n"; - } - } - } - - $requiresFollowup = false; - } elseif ($tool === 'create_ticket' && $result['success']) { - // نمایش نتیجه ایجاد تیکت - $finalResponse .= "\n\n✅ تیکت با موفقیت ایجاد شد!\n"; - $finalResponse .= "🔢 کد تیکت: " . $result['ticket']['code'] . "\n"; - $finalResponse .= "📝 عنوان: " . $result['ticket']['title'] . "\n"; - $finalResponse .= "📅 تاریخ: " . $result['ticket']['date'] . "\n"; - $finalResponse .= "📊 وضعیت: " . $result['ticket']['state'] . "\n"; - - $requiresFollowup = false; - } elseif ($tool === 'reply_ticket' && $result['success']) { - // نمایش نتیجه پاسخ به تیکت - $finalResponse .= "\n\n✅ پاسخ با موفقیت ارسال شد!\n"; - $finalResponse .= "📅 تاریخ: " . $result['reply']['date'] . "\n"; - $finalResponse .= "📄 متن: " . $result['reply']['body'] . "\n"; - - $requiresFollowup = false; - } elseif ($tool === 'show_person' && $result['success'] && isset($result['person'])) { - // برای نمایش اطلاعات شخص، نتایج را مستقیماً نمایش بده - $person = $result['person']; - - $finalResponse .= "\n\n"; - $finalResponse .= "📋 اطلاعات شخص:\n"; - $finalResponse .= "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"; - - // نمایش اطلاعات اصلی - if (isset($person['نام مستعار']) && !empty($person['نام مستعار'])) { - $finalResponse .= "👤 نام مستعار: " . $person['نام مستعار'] . "\n"; - } - if (isset($person['نام کامل']) && !empty($person['نام کامل'])) { - $finalResponse .= "📝 نام کامل: " . $person['نام کامل'] . "\n"; - } - if (isset($person['کد']) && !empty($person['کد'])) { - $finalResponse .= "🔢 کد: " . $person['کد'] . "\n"; - } - - // نمایش اطلاعات تماس - if (isset($person['موبایل']) && !empty($person['موبایل']) && $person['موبایل'] !== 'تعیین نشده') { - $finalResponse .= "📱 موبایل: " . $person['موبایل'] . "\n"; - } - if (isset($person['تلفن']) && !empty($person['تلفن']) && $person['تلفن'] !== 'تعیین نشده') { - $finalResponse .= "☎️ تلفن: " . $person['تلفن'] . "\n"; - } - if (isset($person['ایمیل']) && !empty($person['ایمیل']) && $person['ایمیل'] !== 'تعیین نشده') { - $finalResponse .= "📧 ایمیل: " . $person['ایمیل'] . "\n"; - } - - // نمایش اطلاعات آدرس - if (isset($person['آدرس']) && !empty($person['آدرس']) && $person['آدرس'] !== 'تعیین نشده') { - $finalResponse .= "📍 آدرس: " . $person['آدرس'] . "\n"; - } - - // نمایش اطلاعات شرکت - if (isset($person['شرکت']) && !empty($person['شرکت']) && $person['شرکت'] !== 'تعیین نشده') { - $finalResponse .= "🏢 شرکت: " . $person['شرکت'] . "\n"; - } - - // نمایش اطلاعات مالی - if (isset($person['تراز حساب']) && !empty($person['تراز حساب'])) { - $finalResponse .= "💰 تراز حساب: " . $person['تراز حساب'] . "\n"; - } - if (isset($person['وضعیت حساب']) && !empty($person['وضعیت حساب'])) { - $finalResponse .= "📊 وضعیت حساب: " . $person['وضعیت حساب'] . "\n"; - } - - // نمایش اطلاعات شناسایی - if (isset($person['شناسه ملی']) && !empty($person['شناسه ملی']) && $person['شناسه ملی'] !== 'تعیین نشده') { - $finalResponse .= "🆔 شناسه ملی: " . $person['شناسه ملی'] . "\n"; - } - if (isset($person['کد اقتصادی']) && !empty($person['کد اقتصادی']) && $person['کد اقتصادی'] !== 'تعیین نشده') { - $finalResponse .= "🏛️ کد اقتصادی: " . $person['کد اقتصادی'] . "\n"; - } - - // نمایش انواع - if (isset($person['انواع']) && !empty($person['انواع']) && $person['انواع'] !== 'تعیین نشده') { - $finalResponse .= "🏷️ انواع: " . $person['انواع'] . "\n"; - } - - $finalResponse .= "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; - - // برای نمایش اطلاعات شخص، followup را غیرفعال کن - $requiresFollowup = false; - } else { - // برای سایر عملیات، از تنظیمات AI استفاده کن - if ($result['success']) { - $finalResponse .= "\n\n" . ($result['message'] ?? 'عملیات با موفقیت انجام شد'); - } else { - $finalResponse .= "\n\nخطا: " . ($result['error'] ?? 'خطای نامشخص'); - } - - $requiresFollowup = $jsonResponse['requires_followup'] ?? false; - } - - return [ - 'response' => $finalResponse, - 'requires_action' => $requiresFollowup, - 'action_data' => $jsonResponse['next_step'] ?? null, - 'debug_info' => [ - 'ai_response' => $originalResponse, - 'json_response' => $jsonResponse, - 'action_type' => 'tool_command', - 'tool_commands' => [['tool' => $tool, 'params' => $params]], - 'has_commands' => true, - 'execution_result' => $result - ] - ]; - } - - /** - * اجرای دستور چندمرحله‌ای - */ - private function executeMultiStepCommand(array $jsonResponse, string $originalResponse, ?Business $business, $user): array - { - $steps = $jsonResponse['steps'] ?? []; - $results = []; - $allCommands = []; - - foreach ($steps as $step) { - $tool = $step['tool'] ?? ''; - $params = $step['params'] ?? []; - - if (!empty($tool)) { - $allCommands[] = ['tool' => $tool, 'params' => $params]; - $result = $this->executeToolCommand(['tool' => $tool, 'params' => $params], $business, $user); - $results[] = $result; - } - } - - $finalResponse = $jsonResponse['user_message'] ?? 'عملیات چندمرحله‌ای انجام شد'; - - // اضافه کردن نتایج به پاسخ - foreach ($results as $index => $result) { - if ($result['success']) { - $finalResponse .= "\n\nمرحله " . ($index + 1) . ": " . ($result['message'] ?? 'موفق'); - } else { - $finalResponse .= "\n\nمرحله " . ($index + 1) . ": خطا - " . ($result['error'] ?? 'خطای نامشخص'); - } - } - - return [ - 'response' => $finalResponse, - 'requires_action' => $jsonResponse['requires_followup'] ?? false, - 'action_data' => $jsonResponse['next_step'] ?? null, - 'debug_info' => [ - 'ai_response' => $originalResponse, - 'json_response' => $jsonResponse, - 'action_type' => 'multi_step', - 'tool_commands' => $allCommands, - 'has_commands' => true, - 'execution_results' => $results - ] - ]; - } - - /** - * اجرای عملیات پیوسته تا حصول نتیجه - */ - private function executeContinuousOperation(array $jsonResponse, string $originalResponse, ?Business $business, $user): array - { - $operationType = $jsonResponse['operation_type'] ?? ''; - $currentStep = $jsonResponse['current_step'] ?? 1; - $steps = $jsonResponse['steps'] ?? []; - $maxAttempts = $jsonResponse['max_attempts'] ?? 3; - $attemptCount = $jsonResponse['attempt_count'] ?? 1; - $operationComplete = $jsonResponse['operation_complete'] ?? false; - - // اگر عملیات کامل شده، نتیجه نهایی را برگردان - if ($operationComplete) { - return [ - 'response' => $jsonResponse['user_message'] ?? 'عملیات با موفقیت کامل شد', - 'requires_action' => false, - 'action_data' => null, - 'debug_info' => [ - 'ai_response' => $originalResponse, - 'json_response' => $jsonResponse, - 'action_type' => 'operation_complete', - 'has_commands' => false, - 'final_result' => $jsonResponse['final_result'] ?? null, - 'final_data' => $jsonResponse['final_data'] ?? null - ] - ]; - } - - // اگر تعداد تلاش‌ها از حد مجاز بیشتر شد - if ($attemptCount > $maxAttempts) { - return [ - 'response' => 'تعداد تلاش‌ها از حد مجاز بیشتر شد. لطفاً درخواست خود را واضح‌تر بیان کنید.', - 'requires_action' => false, - 'action_data' => null, - 'debug_info' => [ - 'ai_response' => $originalResponse, - 'json_response' => $jsonResponse, - 'action_type' => 'continuous_operation', - 'has_commands' => false, - 'error' => 'تعداد تلاش‌ها از حد مجاز بیشتر شد', - 'attempt_count' => $attemptCount, - 'max_attempts' => $maxAttempts - ] - ]; - } - - // اجرای مراحل فعلی - $results = []; - $allCommands = []; - - foreach ($steps as $step) { - $tool = $step['tool'] ?? ''; - $params = $step['params'] ?? []; - - if (!empty($tool)) { - $allCommands[] = ['tool' => $tool, 'params' => $params]; - $result = $this->executeToolCommand(['tool' => $tool, 'params' => $params], $business, $user); - $results[] = $result; - } - } - - // بررسی نتایج و تصمیم‌گیری برای مرحله بعدی - $nextAction = $this->determineNextAction($operationType, $results, $jsonResponse); - - $finalResponse = $jsonResponse['user_message'] ?? 'عملیات پیوسته در حال اجرا...'; - - // اضافه کردن نتایج به پاسخ - foreach ($results as $index => $result) { - if ($result['success']) { - $finalResponse .= "\n\nمرحله " . ($index + 1) . ": " . ($result['message'] ?? 'موفق'); - } else { - $finalResponse .= "\n\nمرحله " . ($index + 1) . ": خطا - " . ($result['error'] ?? 'خطای نامشخص'); - } - } - - return [ - 'response' => $finalResponse, - 'requires_action' => true, - 'action_data' => [ - 'operation_type' => $operationType, - 'current_step' => $currentStep, - 'next_action' => $nextAction, - 'attempt_count' => $attemptCount, - 'max_attempts' => $maxAttempts, - 'results' => $results - ], - 'debug_info' => [ - 'ai_response' => $originalResponse, - 'json_response' => $jsonResponse, - 'action_type' => 'continuous_operation', - 'tool_commands' => $allCommands, - 'has_commands' => true, - 'execution_results' => $results, - 'operation_type' => $operationType, - 'current_step' => $currentStep, - 'attempt_count' => $attemptCount, - 'max_attempts' => $maxAttempts, - 'next_action' => $nextAction - ] - ]; - } - - /** - * اجرای عملیات هوشمند چندمرحله‌ای - */ - private function executeSmartOperation(array $jsonResponse, string $originalResponse, ?Business $business, $user): array - { - $operationType = $jsonResponse['operation_type'] ?? ''; - $targetPerson = $jsonResponse['target_person'] ?? ''; - $targetPhone = $jsonResponse['target_phone'] ?? ''; - $steps = $jsonResponse['steps'] ?? []; - - // اجرای مرحله اول (معمولاً جستجو) - $firstStep = $steps[0] ?? null; - if (!$firstStep) { - return [ - 'response' => 'خطا: مرحله اول تعریف نشده است', - 'requires_action' => false, - 'action_data' => null, - 'debug_info' => [ - 'ai_response' => $originalResponse, - 'json_response' => $jsonResponse, - 'action_type' => 'smart_operation', - 'has_commands' => false, - 'error' => 'مرحله اول تعریف نشده' - ] - ]; - } - - $tool = $firstStep['tool'] ?? ''; - $params = $firstStep['params'] ?? []; - $onSuccess = $firstStep['on_success'] ?? ''; - $onFailure = $firstStep['on_failure'] ?? ''; - - // اجرای مرحله اول - $result = $this->executeToolCommand(['tool' => $tool, 'params' => $params], $business, $user); - - if ($result['success']) { - // بررسی نتیجه جستجو - if (isset($result['persons'])) { - if (!empty($result['persons'])) { - // شخص پیدا شد، عملیات on_success را انجام بده - return $this->executeNextStep($onSuccess, $targetPerson, $targetPhone, $result, $business, $user, $originalResponse, $jsonResponse); - } else { - // شخص پیدا نشد، عملیات on_failure را انجام بده - return $this->executeNextStep($onFailure, $targetPerson, $targetPhone, $result, $business, $user, $originalResponse, $jsonResponse); - } - } - } - - // اگر نتیجه ناموفق بود - return [ - 'response' => 'خطا در اجرای عملیات: ' . ($result['error'] ?? 'خطای نامشخص'), - 'requires_action' => false, - 'action_data' => null, - 'debug_info' => [ - 'ai_response' => $originalResponse, - 'json_response' => $jsonResponse, - 'action_type' => 'smart_operation', - 'has_commands' => true, - 'execution_result' => $result, - 'error' => 'خطا در اجرای مرحله اول' - ] - ]; - } - - /** - * اجرای مرحله بعدی بر اساس نتیجه - */ - private function executeNextStep(string $action, string $targetPerson, string $targetPhone, array $searchResult, ?Business $business, $user, string $originalResponse, array $jsonResponse): array - { - switch ($action) { - case 'edit_person': - // ویرایش شخص موجود - $person = $searchResult['persons'][0] ?? null; - if ($person) { - $editParams = [ - 'name' => $person['nikename'], - 'mobile' => $targetPhone - ]; - - $editResult = $this->executeToolCommand(['tool' => 'edit_person', 'params' => $editParams], $business, $user); - - if ($editResult['success']) { - return [ - 'response' => "موبایل {$targetPhone} برای {$targetPerson} با موفقیت تنظیم شد", - 'requires_action' => false, - 'action_data' => null, - 'debug_info' => [ - 'ai_response' => $originalResponse, - 'json_response' => $jsonResponse, - 'action_type' => 'smart_operation_complete', - 'has_commands' => true, - 'search_result' => $searchResult, - 'edit_result' => $editResult, - 'final_result' => 'عملیات ویرایش با موفقیت انجام شد' - ] - ]; - } else { - return [ - 'response' => 'خطا در ویرایش موبایل: ' . ($editResult['error'] ?? 'خطای نامشخص'), - 'requires_action' => false, - 'action_data' => null, - 'debug_info' => [ - 'ai_response' => $originalResponse, - 'json_response' => $jsonResponse, - 'action_type' => 'smart_operation', - 'has_commands' => true, - 'search_result' => $searchResult, - 'edit_result' => $editResult, - 'error' => 'خطا در ویرایش' - ] - ]; - } - } - break; - - case 'add_person': - // افزودن شخص جدید - $addParams = [ - 'name' => $targetPerson, - 'mobile' => $targetPhone - ]; - - $addResult = $this->executeToolCommand(['tool' => 'add_person', 'params' => $addParams], $business, $user); - - if ($addResult['success']) { - return [ - 'response' => "شخص جدید {$targetPerson} با موبایل {$targetPhone} با موفقیت ایجاد شد", - 'requires_action' => false, - 'action_data' => null, - 'debug_info' => [ - 'ai_response' => $originalResponse, - 'json_response' => $jsonResponse, - 'action_type' => 'smart_operation_complete', - 'has_commands' => true, - 'search_result' => $searchResult, - 'add_result' => $addResult, - 'final_result' => 'عملیات افزودن با موفقیت انجام شد' - ] - ]; - } else { - return [ - 'response' => 'خطا در افزودن شخص: ' . ($addResult['error'] ?? 'خطای نامشخص'), - 'requires_action' => false, - 'action_data' => null, - 'debug_info' => [ - 'ai_response' => $originalResponse, - 'json_response' => $jsonResponse, - 'action_type' => 'smart_operation', - 'has_commands' => true, - 'search_result' => $searchResult, - 'add_result' => $addResult, - 'error' => 'خطا در افزودن' - ] - ]; - } - break; - } - - return [ - 'response' => 'عملیات نامشخص: ' . $action, - 'requires_action' => false, - 'action_data' => null, - 'debug_info' => [ - 'ai_response' => $originalResponse, - 'json_response' => $jsonResponse, - 'action_type' => 'smart_operation', - 'has_commands' => false, - 'error' => 'عملیات نامشخص: ' . $action - ] - ]; - } - - /** - * تعیین مرحله بعدی بر اساس نتایج - */ - private function determineNextAction(string $operationType, array $results, array $jsonResponse): string - { - switch ($operationType) { - case 'add_phone_to_person': - // بررسی نتایج جستجو - foreach ($results as $result) { - if (isset($result['persons']) && empty($result['persons'])) { - // شخص پیدا نشد، باید شخص جدید بسازیم - return "شخص پیدا نشد. در حال ساخت شخص جدید..."; - } elseif (isset($result['persons']) && !empty($result['persons'])) { - // شخص پیدا شد، موبایل را ویرایش کن - return "شخص پیدا شد. در حال ویرایش موبایل..."; - } - } - break; - - case 'edit_person_info': - // بررسی نتایج جستجو - foreach ($results as $result) { - if (isset($result['persons']) && empty($result['persons'])) { - return "شخص پیدا نشد. لطفاً نام صحیح را وارد کنید."; - } elseif (isset($result['persons']) && !empty($result['persons'])) { - return "شخص پیدا شد. در حال ویرایش اطلاعات..."; - } - } - break; - - case 'delete_person': - // بررسی نتایج جستجو - foreach ($results as $result) { - if (isset($result['persons']) && empty($result['persons'])) { - return "شخص پیدا نشد. لطفاً نام صحیح را وارد کنید."; - } elseif (isset($result['persons']) && !empty($result['persons'])) { - return "شخص پیدا شد. در حال حذف..."; - } - } - break; - } - - return $jsonResponse['next_action'] ?? "مرحله بعدی تعیین نشده"; - } - - /** - * پردازش پاسخ قدیمی (برای سازگاری) - */ - private function processLegacyResponse(string $aiResponse, ?Business $business, $user): array - { - // تشخیص دستورات ابزار در پاسخ - $toolCommands = $this->extractToolCommands($aiResponse); - - // اضافه کردن debug info - $debugInfo = [ - 'ai_response' => $aiResponse, - 'tool_commands' => $toolCommands, - 'has_commands' => !empty($toolCommands), - 'response_type' => 'legacy' - ]; - - // اگر دستور ابزاری یافت نشد، سعی کن دستورات را از متن استخراج کن - if (empty($toolCommands)) { - $extractedCommands = $this->extractCommandsFromText($aiResponse); - if (!empty($extractedCommands)) { - $toolCommands = $extractedCommands; - $debugInfo['extracted_commands'] = $extractedCommands; - $debugInfo['has_commands'] = true; - } - } - - if (!empty($toolCommands)) { - // اجرای دستورات ابزار - $results = []; - foreach ($toolCommands as $command) { - $result = $this->executeToolCommand($command, $business, $user); - $results[] = $result; - } - - // ساخت پاسخ نهایی - $finalResponse = $this->buildFinalResponse($aiResponse, $results); - - return [ - 'response' => $finalResponse, - 'requires_action' => false, - 'action_data' => null, - 'debug_info' => $debugInfo - ]; - } - - // اگر دستور ابزاری یافت نشد، پاسخ عادی برگردان - return [ - 'response' => $aiResponse, - 'requires_action' => false, - 'action_data' => null, - 'debug_info' => $debugInfo - ]; - } - - /** - * استخراج دستورات ابزار از پاسخ هوش مصنوعی - */ - private function extractToolCommands(string $aiResponse): array - { - $commands = []; - - // الگوهای دستورات ابزار - ساده شده - $patterns = [ - 'add_person' => '/add_person\{([^}]+)\}/u', - 'delete_person' => '/delete_person\{name:([^}]+)\}/u', - 'edit_person' => '/edit_person\{([^}]+)\}/u', - 'show_person' => '/show_person\{name:([^}]+)\}/u', - 'search_persons' => '/search_persons\{search:([^}]+)\}/u', - 'advanced_search_persons' => '/advanced_search_persons\{([^}]+)\}/u', - 'search_by_mobile' => '/search_by_mobile\{mobile:([^}]+)\}/u', - 'search_debtors' => '/search_debtors\{([^}]+)\}/u', - 'search_creditors' => '/search_creditors\{([^}]+)\}/u', - 'create_ticket' => '/create_ticket\{([^}]+)\}/u', - 'list_tickets' => '/list_tickets\{([^}]+)\}/u', - 'view_ticket' => '/view_ticket\{ticket_id:([^}]+)\}/u', - 'reply_ticket' => '/reply_ticket\{([^}]+)\}/u', - 'search_tickets' => '/search_tickets\{([^}]+)\}/u', - 'get_ticket_statistics' => '/get_ticket_statistics\{([^}]*)\}/u' - ]; - - foreach ($patterns as $tool => $pattern) { - if (preg_match_all($pattern, $aiResponse, $matches, PREG_SET_ORDER)) { - foreach ($matches as $match) { - $params = $this->parseToolParameters($match[0]); - $commands[] = [ - 'tool' => $tool, - 'params' => $params - ]; - } - } - } - - return $commands; - } - - /** - * استخراج دستورات از متن پاسخ AI - */ - private function extractCommandsFromText(string $text): array - { - $commands = []; - $text = mb_strtolower($text, 'UTF-8'); - - // الگوهای تشخیص دستورات برای فارسی - $patterns = [ - 'add_person' => [ - '/(?:افزودن|اضافه|ایجاد|ساخت)\s+(?:شخص|مشتری|کارمند)\s+(?:جدید\s+)?(?:با\s+نام\s+)?([^\s،]+)/u', - '/(?:شخص|مشتری|کارمند)\s+(?:جدید\s+)?(?:با\s+نام\s+)?([^\s،]+)\s+(?:اضافه|افزودن|ایجاد|ساخت)/u', - '/([^\s،]+)\s+(?:اضافه|افزودن|ایجاد|ساخت)/u', - '/(?:برای|به)\s+([^\s،]+)\s+(?:اضافه|افزودن|ایجاد|ساخت)/u' - ], - 'edit_person' => [ - '/(?:ویرایش|تغییر|تنظیم)\s+(?:موبایل|شماره|تلفن)\s+(?:برای|به)\s+([^\s،]+)\s+(?:به\s+)?([0-9]+)/u', - '/([^\s،]+)\s+(?:موبایل|شماره|تلفن)\s+(?:به\s+)?([0-9]+)/u', - '/(?:موبایل|شماره|تلفن)\s+([0-9]+)\s+(?:را\s+)?(?:برای|به)\s+([^\s،]+)/u', - '/(?:برای|به)\s+([^\s،]+)\s+(?:موبایل|شماره|تلفن)\s+([0-9]+)/u' - ] - ]; - - foreach ($patterns as $tool => $toolPatterns) { - foreach ($toolPatterns as $pattern) { - if (preg_match($pattern, $text, $matches)) { - if ($tool === 'add_person') { - $commands[] = [ - 'tool' => $tool, - 'params' => ['name' => $matches[1]] - ]; - } elseif ($tool === 'edit_person') { - // تشخیص اینکه کدام پارامتر نام و کدام موبایل است - if (preg_match('/^[0-9]+$/', $matches[1])) { - // اگر اولین پارامتر عدد است، پس موبایل است - $commands[] = [ - 'tool' => $tool, - 'params' => ['name' => $matches[2], 'mobile' => $matches[1]] - ]; - } else { - // اگر اولین پارامتر عدد نیست، پس نام است - $commands[] = [ - 'tool' => $tool, - 'params' => ['name' => $matches[1], 'mobile' => $matches[2]] - ]; - } - } - break; - } - } - } - - return $commands; - } - - /** - * تجزیه پارامترهای دستور ابزار - */ - private function parseToolParameters(string $command): array - { - $params = []; - - // حذف نام ابزار از ابتدای دستور - $command = preg_replace('/^\w+\{/', '', $command); - $command = rtrim($command, '}'); - - // استخراج پارامترها از فرمت name:value,key:value - if (preg_match_all('/([^:,}]+):([^,}]+)/u', $command, $matches, PREG_SET_ORDER)) { - foreach ($matches as $match) { - $key = trim($match[1]); - $value = trim($match[2]); - - // حذف کاراکترهای اضافی - $value = rtrim($value, ',}'); - - // حذف فاصله‌های اضافی - $key = trim($key); - $value = trim($value); - - // اصلاح کلیدهای نامعتبر - if (strpos($key, '{') !== false) { - $key = str_replace('{', '', $key); - } - if (strpos($key, 'edit_person') !== false) { - $key = 'name'; - } - - $params[$key] = $value; - } - } - - return $params; - } - - /** - * اجرای دستور ابزار - */ - private function executeToolCommand(array $command, ?Business $business, $user): array - { - $tool = $command['tool']; - $params = $command['params']; - - switch ($tool) { - case 'add_person': - return $this->getPersonManagementService()->addPerson($params, $business, $user); - - case 'delete_person': - return $this->getPersonManagementService()->deletePerson($params, $business, $user); - - case 'edit_person': - return $this->getPersonManagementService()->editPerson($params, $business, $user); - - case 'show_person': - return $this->getPersonManagementService()->showPerson($params, $business, $user); - - case 'search_persons': - return $this->getPersonManagementService()->searchPersons($params, $business); - - case 'advanced_search_persons': - return $this->getPersonManagementService()->advancedSearchPersons($params, $business); - - case 'search_by_mobile': - return $this->getPersonManagementService()->searchByMobile($params, $business); - - case 'search_debtors': - return $this->getPersonManagementService()->searchDebtors($params, $business); - - case 'search_creditors': - return $this->getPersonManagementService()->searchCreditors($params, $business); - - // ابزارهای مدیریت تیکت - case 'create_ticket': - return $this->getTicketManagementService()->createTicket($params, $business, $user); - - case 'list_tickets': - return $this->getTicketManagementService()->listUserTickets($params, $business, $user); - - case 'view_ticket': - return $this->getTicketManagementService()->viewTicket($params, $business, $user); - - case 'reply_ticket': - return $this->getTicketManagementService()->replyToTicket($params, $business, $user); - - case 'search_tickets': - return $this->getTicketManagementService()->searchTickets($params, $business, $user); - - case 'get_ticket_statistics': - return $this->getTicketManagementService()->getTicketStatistics($params, $business, $user); - - // ابزارهای مدیریت نمودار - case 'create_bar_chart': - return $this->getChartService()->createBarChart($params, $business, $user); - - case 'create_line_chart': - return $this->getChartService()->createLineChart($params, $business, $user); - - case 'create_pie_chart': - return $this->getChartService()->createPieChart($params, $business, $user); - - case 'create_area_chart': - return $this->getChartService()->createAreaChart($params, $business, $user); - - case 'create_radar_chart': - return $this->getChartService()->createRadarChart($params, $business, $user); - - case 'create_scatter_chart': - return $this->getChartService()->createScatterChart($params, $business, $user); - - case 'create_doughnut_chart': - return $this->getChartService()->createDoughnutChart($params, $business, $user); - - case 'create_chart': - return $this->getChartService()->createChart($params, $business, $user); - - default: - return [ - 'success' => false, - 'error' => "ابزار '{$tool}' شناخته نشد." - ]; - } - } - - /** - * ساخت پاسخ نهایی با ترکیب پاسخ هوش مصنوعی و نتایج ابزارها - */ - private function buildFinalResponse(string $aiResponse, array $toolResults): string - { - $response = $aiResponse; - - // حذف دستورات ابزار از پاسخ - $response = preg_replace('/\w+\{[^}]+\}/', '', $response); - $response = trim($response); - - // اضافه کردن نتایج ابزارها - $toolMessages = []; - foreach ($toolResults as $result) { - if ($result['success']) { - // اگر نتیجه شامل اطلاعات شخص است، آن را به صورت ساختاریافته نمایش ده - if (isset($result['person']) && is_array($result['person'])) { - $personInfo = $result['person']; - $personMessage = "\n📋 اطلاعات شخص:\n"; - foreach ($personInfo as $key => $value) { - $personMessage .= "• {$key}: {$value}\n"; - } - $toolMessages[] = $personMessage; - } - // اگر نتیجه شامل نمودار است، آن را به صورت ساختاریافته نمایش ده - elseif (isset($result['chart']) && is_array($result['chart'])) { - $chartInfo = $result['chart']; - $chartMessage = "\n📊 نمودار ایجاد شد:\n"; - $chartMessage .= "• نوع: {$chartInfo['type']}\n"; - $chartMessage .= "• عنوان: {$chartInfo['title']}\n"; - $chartMessage .= "• شناسه: {$chartInfo['chart_id']}\n"; - - // اضافه کردن اطلاعات داده‌ها - if (isset($chartInfo['data'])) { - $data = $chartInfo['data']; - if (isset($data['categories'])) { - $chartMessage .= "• دسته‌بندی‌ها: " . implode(', ', $data['categories']) . "\n"; - } - if (isset($data['labels'])) { - $chartMessage .= "• برچسب‌ها: " . implode(', ', $data['labels']) . "\n"; - } - if (isset($data['series'])) { - $chartMessage .= "• تعداد سری‌ها: " . count($data['series']) . "\n"; - } - } - - $toolMessages[] = $chartMessage; - } else { - $toolMessages[] = $result['message'] ?? 'عملیات با موفقیت انجام شد'; - } - } else { - $toolMessages[] = 'خطا: ' . ($result['error'] ?? 'خطای نامشخص'); - } - } - - if (!empty($toolMessages)) { - $response .= "\n\n" . implode("\n", $toolMessages); - } - - return $response; - } - - /** - * ارسال درخواست به GapGPT - */ - private function sendToGapGPT(string $prompt, string $apiKey, array $conversationHistory = []): array - { - $urls = [ - 'https://api.gapgpt.app/v1/chat/completions', - 'https://api.gapgpt.ir/v1/chat/completions' - ]; - - $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' => 1500, - 'temperature' => 0.7 - ]; - - foreach ($urls as $url) { - $result = $this->makeHttpRequest($url, $data, $apiKey); - if ($result['success']) { - return $result; - } - } - - return [ - 'success' => false, - 'error' => 'خطا در ارتباط با سرور هوش مصنوعی.' - ]; - } - - /** - * ارسال درخواست به AvalAI - */ - private function sendToAvalAI(string $prompt, string $apiKey, array $conversationHistory = []): array - { - $url = 'https://api.avalai.com/v1/chat/completions'; - $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' => 1500, - 'temperature' => 0.7 - ]; - - return $this->makeHttpRequest($url, $data, $apiKey); - } - - /** - * ارسال درخواست به LocalAI - */ - private function sendToLocalAI(string $prompt, string $apiKey, array $conversationHistory = []): array - { - $url = $apiKey; - $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' => 1500, - 'temperature' => 0.7 - ]; - - return $this->makeHttpRequest($url, $data, ''); - } - - /** - * انجام درخواست 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-AI-Service/2.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); - } - - /** - * دریافت کلید API برای سرویس مشخص - */ - private function getAIApiKey(string $service): ?string - { - switch ($service) { - case 'gapgpt': - case 'avalai': - return $this->registryMGR->get('system', 'aiApiKey') ?? null; - case 'local': - return $this->registryMGR->get('system', 'localModelAddress') ?? null; - default: - return null; - } - } - - /** - * دریافت سرویس داده‌های اشخاص - */ - public function getPersonDataService(): PersonDataService - { - if ($this->personDataService === null) { - $this->personDataService = new PersonDataService($this->entityManager); - } - return $this->personDataService; - } - - /** - * دریافت سرویس مدیریت اشخاص - */ - public function getPersonManagementService(): PersonManagementService - { - if ($this->personManagementService === null) { - $this->personManagementService = new PersonManagementService($this->entityManager, $this->log, $this->provider); - } - return $this->personManagementService; - } - - public function getTicketManagementService(): TicketManagementService - { - if ($this->ticketManagementService === null) { - $this->ticketManagementService = new TicketManagementService( - $this->entityManager, - $this->log, - $this->provider, - new \App\Service\Jdate() - ); - } - return $this->ticketManagementService; - } - - public function getChartService(): ChartService - { - if ($this->chartService === null) { - $this->chartService = new ChartService( - $this->entityManager, - $this->log, - $this->provider, - new \App\Service\Jdate() - ); - } - return $this->chartService; - } - - /** - * بررسی وضعیت سرویس هوش مصنوعی - */ - public function checkAIServiceStatus(): array - { - $enabled = $this->registryMGR->get('system', 'aiEnabled') ?? false; - $service = $this->registryMGR->get('system', 'aiAgentSource') ?? 'gapgpt'; - $apiKey = $this->getAIApiKey($service); - - $connectionTest = $this->testConnection($service); - - return [ - 'enabled' => $enabled && $apiKey, - 'service' => $this->getServiceDisplayName($service), - 'hasApiKey' => !empty($apiKey), - 'connection_status' => $connectionTest['status'], - 'connection_message' => $connectionTest['message'], - 'message' => $enabled && $apiKey ? 'سرویس فعال است' : 'سرویس غیرفعال است' - ]; - } - - /** - * دریافت نام نمایشی سرویس - */ - public function getServiceDisplayName(string $service): string - { - $displayNames = [ - 'gapgpt' => 'سرویس هوش مصنوعی', - 'avalai' => 'سرویس هوش مصنوعی', - 'local' => 'سرویس محلی' - ]; - - return $displayNames[$service] ?? 'سرویس نامشخص'; - } - - /** - * تست اتصال به سرور - */ - private function testConnection(string $service): array - { - $testUrls = []; - - switch ($service) { - case 'gapgpt': - $testUrls = [ - 'https://api.gapgpt.app', - 'https://api.gapgpt.ir' - ]; - break; - case 'avalai': - $testUrls = ['https://api.avalai.com']; - break; - case 'local': - $localAddress = $this->registryMGR->get('system', 'localModelAddress'); - if ($localAddress) { - $testUrls = [$localAddress]; - } - break; - } - - foreach ($testUrls as $url) { - $ch = curl_init(); - curl_setopt_array($ch, [ - CURLOPT_URL => $url, - CURLOPT_RETURNTRANSFER => true, - CURLOPT_NOBODY => true, - CURLOPT_TIMEOUT => 5, - CURLOPT_CONNECTTIMEOUT => 3, - CURLOPT_SSL_VERIFYPEER => false, - CURLOPT_SSL_VERIFYHOST => false - ]); - - $result = curl_exec($ch); - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - $error = curl_error($ch); - curl_close($ch); - - if (!$error && $httpCode < 500) { - return [ - 'status' => 'connected', - 'message' => 'اتصال به سرور برقرار است', - 'working_url' => $url - ]; - } - } - - return [ - 'status' => 'disconnected', - 'message' => 'خطا در اتصال به سرور هوش مصنوعی.', - 'tested_urls' => $testUrls - ]; - } - - /** - * بررسی فعال بودن هوش مصنوعی - */ - public function isAIEnabled(): bool - { - $status = $this->checkAIServiceStatus(); - return $status['enabled']; - } - - /** - * دریافت مدل هوش مصنوعی - */ - 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 getAIPrompt(): string - { - return $this->registryMGR->get('system', 'aiPrompt') ?? 'شما یک دستیار هوشمند برای سیستم حسابداری هستید.'; - } - - /** - * دریافت قیمت توکن ورودی - */ - 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); - } - - /** - * محاسبه هزینه بر اساس 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) - ]; - } - - /** - * دریافت آیکون وضعیت تیکت - */ - private function getTicketStatusIcon(string $state): string - { - return match ($state) { - 'در حال پیگیری' => '⏳', - 'پاسخ داده شده' => '✅', - 'خاتمه یافته' => '🔒', - default => '📋' - }; - } - - /** - * دریافت لیست ابزارهای موجود - */ - public function getAvailableTools(): array - { - return [ - 'person_management' => [ - 'name' => 'مدیریت اشخاص', - 'description' => 'افزودن، ویرایش، حذف و نمایش اطلاعات اشخاص', - 'commands' => [ - 'add_person{name:نام شخص}', - 'delete_person{name:نام شخص}', - 'edit_person{name:نام شخص, phone:موبایل, address:آدرس, email:ایمیل}', - 'show_person{name:نام شخص}', - 'search_persons{search:متن جستجو, limit:تعداد نتایج}' - ], - 'examples' => [ - 'علی رو حذف کن', - 'شخص جدید با نام احمد اضافه کن', - 'مشخصات محسن رو نشون بده' - ] - ], - 'advanced_search' => [ - 'name' => 'جستجوی پیشرفته اشخاص', - 'description' => 'جستجوی هوشمند اشخاص بر اساس معیارهای مختلف', - 'commands' => [ - 'advanced_search_persons{search:متن جستجو, search_type:نوع جستجو, limit:تعداد نتایج}', - 'search_by_mobile{mobile:شماره موبایل}', - 'search_debtors{search:متن جستجو, limit:تعداد نتایج}', - 'search_creditors{search:متن جستجو, limit:تعداد نتایج}' - ], - 'examples' => [ - 'کی موبایلش 09183282405 هست؟', - 'بدهکاران رو بهم معرفی کن', - 'بستانکاران رو نشون بده', - 'بدهکاران علی رو پیدا کن' - ] - ], - 'ticket_management' => [ - 'name' => 'مدیریت تیکت‌های پشتیبانی', - 'description' => 'ایجاد، مشاهده و مدیریت تیکت‌های پشتیبانی', - 'commands' => [ - 'create_ticket{title:عنوان تیکت, body:متن تیکت, priority:اولویت}', - 'list_tickets{state:وضعیت, limit:تعداد نتایج}', - 'view_ticket{ticket_id:شناسه تیکت}', - 'reply_ticket{ticket_id:شناسه تیکت, body:متن پاسخ}', - 'search_tickets{search:متن جستجو, state:وضعیت}', - 'get_ticket_statistics{}' - ], - 'examples' => [ - 'تیکت جدید با عنوان مشکل در ورود به سیستم', - 'لیست تیکت‌های من', - 'مشاهده تیکت ABC123', - 'پاسخ به تیکت 123', - 'جستجو تیکت مشکل', - 'آمار تیکت‌های من' - ] - ], - 'chart_management' => [ - 'name' => 'مدیریت نمودارها و چارت‌ها', - 'description' => 'ایجاد نمودارهای مختلف با داده‌های پویا', - 'commands' => [ - 'create_bar_chart{title:عنوان, data:[{category:عنوان, value:عدد}]}', - 'create_line_chart{title:عنوان, data:[{category:عنوان, value:عدد}]}', - 'create_pie_chart{title:عنوان, data:[{label:عنوان, value:عدد}]}', - 'create_area_chart{title:عنوان, data:[{category:عنوان, value:عدد}]}', - 'create_radar_chart{title:عنوان, data:[{category:عنوان, value:عدد}]}', - 'create_scatter_chart{title:عنوان, data:[{x:عدد, y:عدد}]}', - 'create_doughnut_chart{title:عنوان, data:[{label:عنوان, value:عدد}]}', - 'create_chart{chart_type:نوع, title:عنوان, data:داده‌ها}' - ], - 'examples' => [ - 'نمودار ستونی فروش ماهانه', - 'نمودار دایره‌ای سهم بازار', - 'نمودار خطی روند رشد', - 'نمودار ناحیه‌ای درآمد سالانه', - 'نمودار راداری عملکرد بخش‌ها', - 'نمودار پراکندگی سن و درآمد', - 'نمودار دونات توزیع محصولات' - ] - ] - ]; - } -} \ No newline at end of file diff --git a/hesabixCore/src/Service/AI/ChartService.php b/hesabixCore/src/Service/AI/ChartService.php deleted file mode 100644 index f6aff8f2..00000000 --- a/hesabixCore/src/Service/AI/ChartService.php +++ /dev/null @@ -1,538 +0,0 @@ -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}]'"; - } -} \ No newline at end of file diff --git a/hesabixCore/src/Service/AI/PERSON_MANAGEMENT_README.md b/hesabixCore/src/Service/AI/PERSON_MANAGEMENT_README.md deleted file mode 100644 index 6f5dbb32..00000000 --- a/hesabixCore/src/Service/AI/PERSON_MANAGEMENT_README.md +++ /dev/null @@ -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. **کدهای تولید شده را منحصر به فرد کنید** \ No newline at end of file diff --git a/hesabixCore/src/Service/AI/PersonDataService.php b/hesabixCore/src/Service/AI/PersonDataService.php deleted file mode 100644 index f235a4c0..00000000 --- a/hesabixCore/src/Service/AI/PersonDataService.php +++ /dev/null @@ -1,267 +0,0 @@ -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(); - } - -} \ No newline at end of file diff --git a/hesabixCore/src/Service/AI/PersonManagementService.php b/hesabixCore/src/Service/AI/PersonManagementService.php deleted file mode 100644 index 041b7a85..00000000 --- a/hesabixCore/src/Service/AI/PersonManagementService.php +++ /dev/null @@ -1,1148 +0,0 @@ -entityManager = $entityManager; - $this->log = $log; - $this->provider = $provider; - } - - /** - * ابزار افزودن شخص جدید - */ - public function addPerson(array $params, Business $business, $user): array - { - $name = $params['name'] ?? ''; - $fullName = $params['full_name'] ?? $name; // نام کامل (اختیاری) - - if (empty($name)) { - return [ - 'success' => false, - 'error' => 'نام شخص الزامی است' - ]; - } - - // بررسی وجود شخص با همین نام مستعار - $existingPerson = $this->entityManager->getRepository(Person::class)->findOneBy([ - 'nikename' => $name, - 'bid' => $business - ]); - - if ($existingPerson) { - return [ - 'success' => false, - 'error' => "شخصی با نام مستعار '{$name}' قبلاً وجود دارد." - ]; - } - - // ایجاد شخص جدید - $person = new Person(); - $person->setNikename($name); // نام مستعار (الزامی) - $person->setName($fullName); // نام کامل (اختیاری) - $person->setBid($business); - - // تولید کد خودکار - $maxAttempts = 10; - $code = null; - - for ($i = 0; $i < $maxAttempts; $i++) { - $code = $this->provider->getAccountingCode($business, 'person'); - $exist = $this->entityManager->getRepository(Person::class)->findOneBy([ - 'code' => $code - ]); - if (!$exist) { - break; - } - } - - if ($code === null) { - return [ - 'success' => false, - 'error' => 'نمی‌توان کد جدیدی برای شخص تولید کرد' - ]; - } - - $person->setCode($code); - - // تنظیم نوع پیش‌فرض - $defaultType = $this->entityManager->getRepository(PersonType::class)->findOneBy(['code' => 'CUST']); - if ($defaultType) { - $person->addType($defaultType); - } - - $this->entityManager->persist($person); - $this->entityManager->flush(); - - // ثبت لاگ - $this->log->insert( - 'مدیریت اشخاص', - "افزودن شخص جدید: {$name} (کد: {$code})", - $user, - $business - ); - - return [ - 'success' => true, - 'message' => "شخص '{$name}' با موفقیت اضافه شد. کد: {$code}", - 'person' => [ - 'id' => $person->getId(), - 'name' => $person->getName(), - 'code' => $person->getCode() - ] - ]; - } - - /** - * ابزار ویرایش شخص - */ - public function editPerson(array $params, Business $business, $user): array - { - $name = $params['name'] ?? ''; - if (empty($name)) { - return [ - 'success' => false, - 'error' => 'نام شخص الزامی است' - ]; - } - - // یافتن شخص - $person = $this->findPersonByName($name, $business); - if (!$person) { - return [ - 'success' => false, - 'error' => "شخصی با نام '{$name}' یافت نشد." - ]; - } - - $changes = []; - - // اعمال تغییرات - if (isset($params['phone'])) { - $person->setMobile($params['phone']); - $changes[] = "موبایل: {$params['phone']}"; - } - - if (isset($params['mobile'])) { - $person->setMobile($params['mobile']); - $changes[] = "موبایل: {$params['mobile']}"; - } - - if (isset($params['address'])) { - $person->setAddress($params['address']); - $changes[] = "آدرس: {$params['address']}"; - } - - if (isset($params['email'])) { - $person->setEmail($params['email']); - $changes[] = "ایمیل: {$params['email']}"; - } - - if (empty($changes)) { - return [ - 'success' => false, - 'error' => 'هیچ تغییری برای اعمال یافت نشد.' - ]; - } - - $this->entityManager->persist($person); - $this->entityManager->flush(); - - // ثبت لاگ - $this->log->insert( - 'مدیریت اشخاص', - "ویرایش شخص: {$name} - تغییرات: " . implode(', ', $changes), - $user, - $business - ); - - return [ - 'success' => true, - 'message' => "اطلاعات شخص '{$name}' با موفقیت به‌روزرسانی شد.", - 'changes' => $changes - ]; - } - - /** - * ابزار حذف شخص - */ - public function deletePerson(array $params, Business $business, $user): array - { - $name = $params['name'] ?? ''; - if (empty($name)) { - return [ - 'success' => false, - 'error' => 'نام شخص الزامی است' - ]; - } - - // یافتن شخص - $person = $this->findPersonByName($name, $business); - if (!$person) { - return [ - 'success' => false, - 'error' => "شخصی با نام '{$name}' یافت نشد." - ]; - } - - // بررسی وجود تراکنش‌های مرتبط - $hasTransactions = $this->hasRelatedTransactions($person); - if ($hasTransactions) { - return [ - 'success' => false, - 'error' => "نمی‌توان شخص '{$name}' را حذف کرد زیرا تراکنش‌های مرتبط دارد." - ]; - } - - $personCode = $person->getCode(); - $personName = $person->getName(); - - $this->entityManager->remove($person); - $this->entityManager->flush(); - - // ثبت لاگ - $this->log->insert( - 'مدیریت اشخاص', - "حذف شخص: {$personName} (کد: {$personCode})", - $user, - $business - ); - - return [ - 'success' => true, - 'message' => "شخص '{$personName}' با موفقیت حذف شد." - ]; - } - - /** - * ابزار نمایش مشخصات شخص - */ - public function showPerson(array $params, Business $business, $user): array - { - $name = $params['name'] ?? ''; - if (empty($name)) { - return [ - 'success' => false, - 'error' => 'نام شخص الزامی است' - ]; - } - - // یافتن شخص - $person = $this->findPersonByName($name, $business); - if (!$person) { - return [ - 'success' => false, - 'error' => "شخصی با نام '{$name}' یافت نشد." - ]; - } - - // محاسبه تراز حساب - $rows = $this->entityManager->getRepository(\App\Entity\HesabdariRow::class)->findBy([ - 'person' => $person, - 'bid' => $business - ]); - - $bs = 0; - $bd = 0; - foreach ($rows as $row) { - $doc = $row->getDoc(); - if ($doc && $doc->getMoney() && $doc->getYear()) { - $bs += (float) $row->getBs(); - $bd += (float) $row->getBd(); - } - } - $balance = $bs - $bd; - - // ساخت اطلاعات شخص - $personInfo = [ - 'کد' => $person->getCode(), - 'نام مستعار' => $person->getNikename(), - 'نام کامل' => $person->getName() ?: 'تعیین نشده', - 'موبایل' => $person->getMobile() ?: 'تعیین نشده', - 'تلفن' => $person->getTel() ?: 'تعیین نشده', - 'ایمیل' => $person->getEmail() ?: 'تعیین نشده', - 'آدرس' => $person->getAddress() ?: 'تعیین نشده', - 'شرکت' => $person->getCompany() ?: 'تعیین نشده', - 'شناسه ملی' => $person->getShenasemeli() ?: 'تعیین نشده', - 'کد اقتصادی' => $person->getCodeeghtesadi() ?: 'تعیین نشده', - 'تراز حساب' => number_format($balance) . ' ریال', - 'وضعیت حساب' => $balance > 0 ? 'بستانکار' : ($balance < 0 ? 'بدهکار' : 'تسویه شده') - ]; - - // اضافه کردن انواع شخص - $types = []; - foreach ($person->getType() as $type) { - $types[] = $type->getName(); - } - $personInfo['انواع'] = !empty($types) ? implode('، ', $types) : 'تعیین نشده'; - - return [ - 'success' => true, - 'person' => $personInfo - ]; - } - - /** - * ابزار جستجوی اشخاص - */ - public function searchPersons(array $params, Business $business): array - { - $search = $params['search'] ?? ''; - $limit = $params['limit'] ?? 10; - - $queryBuilder = $this->entityManager->getRepository(Person::class) - ->createQueryBuilder('p') - ->where('p.bid = :bid') - ->setParameter('bid', $business) - ->setMaxResults($limit); - - if (!empty($search)) { - $queryBuilder->andWhere('p.nikename LIKE :search OR p.name LIKE :search OR p.code LIKE :search') - ->setParameter('search', "%{$search}%"); - } - - $persons = $queryBuilder->getQuery()->getResult(); - - $result = []; - foreach ($persons as $person) { - $result[] = [ - 'id' => $person->getId(), - 'code' => $person->getCode(), - 'nikename' => $person->getNikename(), - 'name' => $person->getName(), - 'mobile' => $person->getMobile() - ]; - } - - return [ - 'success' => true, - 'persons' => $result, - 'count' => count($result) - ]; - } - - /** - * ابزار جستجوی پیشرفته اشخاص - */ - public function advancedSearchPersons(array $params, Business $business): array - { - $search = $params['search'] ?? ''; - $limit = $params['limit'] ?? 20; - $searchType = $params['search_type'] ?? 'all'; // all, mobile, name, code, debtor, creditor - - $queryBuilder = $this->entityManager->getRepository(Person::class) - ->createQueryBuilder('p') - ->leftJoin('p.hesabdariRows', 'hr') - ->leftJoin('hr.doc', 'd') - ->where('p.bid = :bid') - ->setParameter('bid', $business); - - // جستجو بر اساس نوع - switch ($searchType) { - case 'mobile': - $queryBuilder->andWhere('p.mobile LIKE :search OR p.tel LIKE :search') - ->setParameter('search', "%{$search}%"); - break; - - case 'name': - $queryBuilder->andWhere('p.nikename LIKE :search OR p.name LIKE :search') - ->setParameter('search', "%{$search}%"); - break; - - case 'code': - $queryBuilder->andWhere('p.code LIKE :search') - ->setParameter('search', "%{$search}%"); - break; - - case 'debtor': - // بدهکاران (کسانی که تراز منفی دارند) - $dql = " - SELECT p, SUM(hr.bs - hr.bd) as balance - FROM App\Entity\Person p - LEFT JOIN p.hesabdariRows hr - WHERE p.bid = :bid - GROUP BY p.id - HAVING SUM(hr.bs - hr.bd) < 0 - "; - if (!empty($search)) { - $dql .= " AND (p.nikename LIKE :search OR p.name LIKE :search)"; - } - $dql .= " ORDER BY balance ASC"; - - $query = $this->entityManager->createQuery($dql); - $query->setParameter('bid', $business); - if (!empty($search)) { - $query->setParameter('search', "%{$search}%"); - } - $query->setMaxResults($limit); - - $results = $query->getResult(); - $result = []; - foreach ($results as $row) { - $person = $row[0]; - $balance = $row['balance']; - $result[] = [ - 'id' => $person->getId(), - 'code' => $person->getCode(), - 'nikename' => $person->getNikename(), - 'name' => $person->getName(), - 'mobile' => $person->getMobile(), - 'tel' => $person->getTel(), - 'email' => $person->getEmail(), - 'address' => $person->getAddress(), - 'balance' => $balance, - 'balance_formatted' => number_format($balance) . ' ریال', - 'status' => 'بدهکار' - ]; - } - return [ - 'success' => true, - 'persons' => $result, - 'count' => count($result), - 'search_type' => $searchType - ]; - - case 'creditor': - // بستانکاران (کسانی که تراز مثبت دارند) - $dql = " - SELECT p, SUM(hr.bs - hr.bd) as balance - FROM App\Entity\Person p - LEFT JOIN p.hesabdariRows hr - WHERE p.bid = :bid - GROUP BY p.id - HAVING SUM(hr.bs - hr.bd) > 0 - "; - if (!empty($search)) { - $dql .= " AND (p.nikename LIKE :search OR p.name LIKE :search)"; - } - $dql .= " ORDER BY balance DESC"; - - $query = $this->entityManager->createQuery($dql); - $query->setParameter('bid', $business); - if (!empty($search)) { - $query->setParameter('search', "%{$search}%"); - } - $query->setMaxResults($limit); - - $results = $query->getResult(); - $result = []; - foreach ($results as $row) { - $person = $row[0]; - $balance = $row['balance']; - $result[] = [ - 'id' => $person->getId(), - 'code' => $person->getCode(), - 'nikename' => $person->getNikename(), - 'name' => $person->getName(), - 'mobile' => $person->getMobile(), - 'tel' => $person->getTel(), - 'email' => $person->getEmail(), - 'address' => $person->getAddress(), - 'balance' => $balance, - 'balance_formatted' => number_format($balance) . ' ریال', - 'status' => 'بستانکار' - ]; - } - return [ - 'success' => true, - 'persons' => $result, - 'count' => count($result), - 'search_type' => $searchType - ]; - - case 'balanced': - // تسویه شده‌ها (کسانی که تراز صفر دارند) - $dql = " - SELECT p, SUM(hr.bs - hr.bd) as balance - FROM App\Entity\Person p - LEFT JOIN p.hesabdariRows hr - WHERE p.bid = :bid - GROUP BY p.id - HAVING SUM(hr.bs - hr.bd) = 0 - "; - if (!empty($search)) { - $dql .= " AND (p.nikename LIKE :search OR p.name LIKE :search)"; - } - $dql .= " ORDER BY p.name ASC"; - - $query = $this->entityManager->createQuery($dql); - $query->setParameter('bid', $business); - if (!empty($search)) { - $query->setParameter('search', "%{$search}%"); - } - $query->setMaxResults($limit); - - $results = $query->getResult(); - $result = []; - foreach ($results as $row) { - $person = $row[0]; - $balance = $row['balance']; - $result[] = [ - 'id' => $person->getId(), - 'code' => $person->getCode(), - 'nikename' => $person->getNikename(), - 'name' => $person->getName(), - 'mobile' => $person->getMobile(), - 'tel' => $person->getTel(), - 'email' => $person->getEmail(), - 'address' => $person->getAddress(), - 'balance' => $balance, - 'balance_formatted' => number_format($balance) . ' ریال', - 'status' => 'تسویه شده' - ]; - } - return [ - 'success' => true, - 'persons' => $result, - 'count' => count($result), - 'search_type' => $searchType - ]; - - default: - // جستجوی عمومی - if (!empty($search)) { - $queryBuilder->andWhere('p.nikename LIKE :search OR p.name LIKE :search OR p.code LIKE :search OR p.mobile LIKE :search OR p.tel LIKE :search') - ->setParameter('search', "%{$search}%"); - } - } - - $queryBuilder->groupBy('p.id') - ->orderBy('p.name', 'ASC') - ->setMaxResults($limit); - - $persons = $queryBuilder->getQuery()->getResult(); - - $result = []; - foreach ($persons as $person) { - // محاسبه تراز - $balance = $this->calculatePersonBalance($person, $business); - - $result[] = [ - 'id' => $person->getId(), - 'code' => $person->getCode(), - 'nikename' => $person->getNikename(), - 'name' => $person->getName(), - 'mobile' => $person->getMobile(), - 'tel' => $person->getTel(), - 'email' => $person->getEmail(), - 'address' => $person->getAddress(), - 'balance' => $balance, - 'balance_formatted' => number_format($balance) . ' ریال', - 'status' => $balance > 0 ? 'بستانکار' : ($balance < 0 ? 'بدهکار' : 'تسویه شده') - ]; - } - - return [ - 'success' => true, - 'persons' => $result, - 'count' => count($result), - 'search_type' => $searchType - ]; - } - - /** - * ابزار جستجو بر اساس شماره موبایل - */ - public function searchByMobile(array $params, Business $business): array - { - $mobile = $params['mobile'] ?? ''; - - if (empty($mobile)) { - return [ - 'success' => false, - 'error' => 'شماره موبایل الزامی است' - ]; - } - - $person = $this->entityManager->getRepository(Person::class)->findOneBy([ - 'mobile' => $mobile, - 'bid' => $business - ]); - - if (!$person) { - return [ - 'success' => false, - 'error' => "شخصی با شماره موبایل {$mobile} یافت نشد" - ]; - } - - $balance = $this->calculatePersonBalance($person, $business); - - return [ - 'success' => true, - 'person' => [ - 'id' => $person->getId(), - 'code' => $person->getCode(), - 'nikename' => $person->getNikename(), - 'name' => $person->getName(), - 'mobile' => $person->getMobile(), - 'tel' => $person->getTel(), - 'email' => $person->getEmail(), - 'address' => $person->getAddress(), - 'balance' => $balance, - 'balance_formatted' => number_format($balance) . ' ریال', - 'status' => $balance > 0 ? 'بستانکار' : ($balance < 0 ? 'بدهکار' : 'تسویه شده') - ] - ]; - } - - /** - * ابزار جستجوی بدهکاران - */ - public function searchDebtors(array $params, Business $business): array - { - $limit = $params['limit'] ?? 20; - $search = $params['search'] ?? ''; - - // استفاده از DQL برای محاسبه تراز - $dql = " - SELECT p, SUM(hr.bs - hr.bd) as balance - FROM App\Entity\Person p - LEFT JOIN p.hesabdariRows hr - WHERE p.bid = :bid - GROUP BY p.id - HAVING SUM(hr.bs - hr.bd) < 0 - "; - - if (!empty($search)) { - $dql .= " AND (p.nikename LIKE :search OR p.name LIKE :search)"; - } - - $dql .= " ORDER BY balance ASC"; - - $query = $this->entityManager->createQuery($dql); - $query->setParameter('bid', $business); - - if (!empty($search)) { - $query->setParameter('search', "%{$search}%"); - } - - $query->setMaxResults($limit); - - $results = $query->getResult(); - - $result = []; - $totalDebt = 0; - - foreach ($results as $row) { - $person = $row[0]; - $balance = $row['balance']; - - $result[] = [ - 'id' => $person->getId(), - 'code' => $person->getCode(), - 'nikename' => $person->getNikename(), - 'name' => $person->getName(), - 'mobile' => $person->getMobile(), - 'balance' => $balance, - 'balance_formatted' => number_format($balance) . ' ریال', - 'debt_amount' => abs($balance) - ]; - - $totalDebt += abs($balance); - } - - return [ - 'success' => true, - 'persons' => $result, - 'count' => count($result), - 'total_debt' => $totalDebt - ]; - } - - /** - * ابزار جستجوی بستانکاران - */ - public function searchCreditors(array $params, Business $business): array - { - $limit = $params['limit'] ?? 20; - $search = $params['search'] ?? ''; - - // استفاده از DQL برای محاسبه تراز - $dql = " - SELECT p, SUM(hr.bs - hr.bd) as balance - FROM App\Entity\Person p - LEFT JOIN p.hesabdariRows hr - WHERE p.bid = :bid - GROUP BY p.id - HAVING SUM(hr.bs - hr.bd) > 0 - "; - - if (!empty($search)) { - $dql .= " AND (p.nikename LIKE :search OR p.name LIKE :search)"; - } - - $dql .= " ORDER BY balance DESC"; - - $query = $this->entityManager->createQuery($dql); - $query->setParameter('bid', $business); - - if (!empty($search)) { - $query->setParameter('search', "%{$search}%"); - } - - $query->setMaxResults($limit); - - $results = $query->getResult(); - - $result = []; - $totalCredit = 0; - - foreach ($results as $row) { - $person = $row[0]; - $balance = $row['balance']; - - $result[] = [ - 'id' => $person->getId(), - 'code' => $person->getCode(), - 'nikename' => $person->getNikename(), - 'name' => $person->getName(), - 'mobile' => $person->getMobile(), - 'balance' => $balance, - 'balance_formatted' => number_format($balance) . ' ریال', - 'credit_amount' => $balance - ]; - - $totalCredit += $balance; - } - - return [ - 'success' => true, - 'persons' => $result, - 'count' => count($result), - 'total_credit' => $totalCredit - ]; - } - - /** - * محاسبه تراز شخص - */ - private function calculatePersonBalance(Person $person, Business $business): float - { - $hesabdariRows = $this->entityManager->getRepository(\App\Entity\HesabdariRow::class) - ->findBy(['person' => $person, 'bid' => $business]); - - $balance = 0; - foreach ($hesabdariRows as $row) { - $balance += $row->getBs() - $row->getBd(); - } - - return $balance; - } - - /** - * یافتن شخص با نام - */ - private function findPersonByName(string $name, Business $business): ?Person - { - // ابتدا با nikename جستجو کن - $person = $this->entityManager->getRepository(Person::class)->findOneBy([ - 'nikename' => $name, - 'bid' => $business - ]); - - // اگر پیدا نشد، با name جستجو کن - if (!$person) { - $person = $this->entityManager->getRepository(Person::class)->findOneBy([ - 'name' => $name, - 'bid' => $business - ]); - } - - return $person; - } - - /** - * بررسی وجود تراکنش‌های مرتبط - */ - private function hasRelatedTransactions(Person $person): bool - { - $hesabdariRows = $this->entityManager->getRepository(\App\Entity\HesabdariRow::class) - ->count(['person' => $person]); - - return $hesabdariRows > 0; - } -} - -/** - * سرویس مدیریت اشخاص با استفاده از ابزارها - */ -class PersonManagementService -{ - private PersonTools $tools; - - public function __construct(EntityManagerInterface $entityManager, Log $log, Provider $provider) - { - $this->tools = new PersonTools($entityManager, $log, $provider); - } - - /** - * افزودن شخص جدید - */ - public function addPerson(array $params, Business $business, $user): array - { - return $this->tools->addPerson($params, $business, $user); - } - - /** - * حذف شخص - */ - public function deletePerson(array $params, Business $business, $user): array - { - return $this->tools->deletePerson($params, $business, $user); - } - - /** - * ویرایش شخص - */ - public function editPerson(array $params, Business $business, $user): array - { - return $this->tools->editPerson($params, $business, $user); - } - - /** - * نمایش مشخصات شخص - */ - public function showPerson(array $params, Business $business, $user): array - { - return $this->tools->showPerson($params, $business, $user); - } - - /** - * جستجوی اشخاص - */ - public function searchPersons(array $params, Business $business): array - { - return $this->tools->searchPersons($params, $business); - } - - /** - * جستجوی پیشرفته اشخاص - */ - public function advancedSearchPersons(array $params, Business $business): array - { - return $this->tools->advancedSearchPersons($params, $business); - } - - /** - * جستجو بر اساس شماره موبایل - */ - public function searchByMobile(array $params, Business $business): array - { - return $this->tools->searchByMobile($params, $business); - } - - /** - * جستجوی بدهکاران - */ - public function searchDebtors(array $params, Business $business): array - { - return $this->tools->searchDebtors($params, $business); - } - - /** - * جستجوی بستانکاران - */ - public function searchCreditors(array $params, Business $business): array - { - return $this->tools->searchCreditors($params, $business); - } - - /** - * تحلیل نتایج جستجو - */ - public function analyzeSearchResults(array $params, Business $business): array - { - $analysisType = $params['analysis_type'] ?? 'highest_debtor'; // highest_debtor, highest_creditor, statistics - $searchResults = $params['search_results'] ?? []; - - if (empty($searchResults)) { - return [ - 'success' => false, - 'error' => 'نتایج جستجو برای تحلیل موجود نیست' - ]; - } - - switch ($analysisType) { - case 'highest_debtor': - return $this->findHighestDebtor($searchResults); - - case 'highest_creditor': - return $this->findHighestCreditor($searchResults); - - case 'statistics': - return $this->generateStatistics($searchResults); - - default: - return [ - 'success' => false, - 'error' => 'نوع تحلیل نامعتبر است' - ]; - } - } - - /** - * یافتن شخص با بیشترین بدهی - */ - private function findHighestDebtor(array $searchResults): array - { - $highestDebtor = null; - $maxDebt = 0; - - foreach ($searchResults as $person) { - $balance = $person['balance'] ?? 0; - if ($balance < 0 && abs($balance) > $maxDebt) { - $maxDebt = abs($balance); - $highestDebtor = $person; - } - } - - if ($highestDebtor) { - return [ - 'success' => true, - 'analysis_type' => 'highest_debtor', - 'person' => $highestDebtor, - 'debt_amount' => $maxDebt, - 'debt_formatted' => number_format($maxDebt) . ' ریال', - 'message' => "بیشترین بدهکار: {$highestDebtor['nikename']} با بدهی {$maxDebt} ریال" - ]; - } else { - return [ - 'success' => false, - 'error' => 'هیچ بدهکاری یافت نشد' - ]; - } - } - - /** - * یافتن شخص با بیشترین بستانکاری - */ - private function findHighestCreditor(array $searchResults): array - { - $highestCreditor = null; - $maxCredit = 0; - - foreach ($searchResults as $person) { - $balance = $person['balance'] ?? 0; - if ($balance > 0 && $balance > $maxCredit) { - $maxCredit = $balance; - $highestCreditor = $person; - } - } - - if ($highestCreditor) { - return [ - 'success' => true, - 'analysis_type' => 'highest_creditor', - 'person' => $highestCreditor, - 'credit_amount' => $maxCredit, - 'credit_formatted' => number_format($maxCredit) . ' ریال', - 'message' => "بیشترین بستانکار: {$highestCreditor['nikename']} با بستانکاری {$maxCredit} ریال" - ]; - } else { - return [ - 'success' => false, - 'error' => 'هیچ بستانکاری یافت نشد' - ]; - } - } - - /** - * تولید آمار از نتایج جستجو - */ - private function generateStatistics(array $searchResults): array - { - $totalCount = count($searchResults); - $totalDebt = 0; - $totalCredit = 0; - $debtors = []; - $creditors = []; - $balanced = []; - - foreach ($searchResults as $person) { - $balance = $person['balance'] ?? 0; - - if ($balance < 0) { - $totalDebt += abs($balance); - $debtors[] = $person; - } elseif ($balance > 0) { - $totalCredit += $balance; - $creditors[] = $person; - } else { - $balanced[] = $person; - } - } - - $avgDebt = count($debtors) > 0 ? $totalDebt / count($debtors) : 0; - $avgCredit = count($creditors) > 0 ? $totalCredit / count($creditors) : 0; - - return [ - 'success' => true, - 'analysis_type' => 'statistics', - 'statistics' => [ - 'total_count' => $totalCount, - 'debtors_count' => count($debtors), - 'creditors_count' => count($creditors), - 'balanced_count' => count($balanced), - 'total_debt' => $totalDebt, - 'total_credit' => $totalCredit, - 'avg_debt' => $avgDebt, - 'avg_credit' => $avgCredit, - 'total_debt_formatted' => number_format($totalDebt) . ' ریال', - 'total_credit_formatted' => number_format($totalCredit) . ' ریال', - 'avg_debt_formatted' => number_format($avgDebt) . ' ریال', - 'avg_credit_formatted' => number_format($avgCredit) . ' ریال' - ], - 'message' => "آمار کلی: {$totalCount} نفر - {$totalDebt} ریال بدهی کل - {$totalCredit} ریال بستانکاری کل" - ]; - } - - /** - * پردازش درخواست مدیریت اشخاص (برای سازگاری با سیستم قدیمی) - */ - public function processRequest(string $message, Business $business, $user): array - { - // بررسی دسترسی - if (!$this->hasPermission($user)) { - return [ - 'success' => false, - 'error' => 'شما مجوز انجام این عملیات را ندارید.' - ]; - } - - // استخراج دستور از پیام - $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 = [ - 'add' => [ - '/(?:اضافه|افزودن|ایجاد|ساخت)\s+(?:کن|کنید|بکن|بکنید)\s+(?:شخص|مشتری|تامین‌کننده|کارمند)\s+(?:جدید\s+)?(?:با\s+نام\s+)?([^\s]+)/u', - '/(?:شخص|مشتری|تامین‌کننده|کارمند)\s+(?:جدید\s+)?(?:با\s+نام\s+)?([^\s]+)\s+(?:ایجاد|اضافه|افزودن|ساخت)\s+(?:کن|کنید|بکن|بکنید)/u', - '/([^\s]+)\s+ایجاد\s+کن/u', - '/یک\s+شخص\s+جدید\s+با\s+نام\s+([^\s]+)\s+ایجاد\s+کن/u' - ], - - 'delete' => [ - '/(?:حذف|پاک|حذف\s+کن|حذف\s+کنید)\s+(?:شخص|مشتری|تامین‌کننده|کارمند)\s+(?:با\s+نام\s+)?([^\s]+)/u', - '/(?:شخص|مشتری|تامین‌کننده|کارمند)\s+([^\s]+)\s+را\s+(?:حذف|پاک)\s+(?:کن|کنید|بکن|بکنید)/u', - '/([^\s]+)\s+رو\s+(?:حذف|پاک)\s+(?:کن|کنید|بکن|بکنید)/u', - '/([^\s]+\s+[^\s]+)\s+رو\s+(?:حذف|پاک)\s+(?:کن|کنید|بکن|بکنید)/u' - ], - - 'show' => [ - '/(?:نشون\s+بده|نمایش\s+ده|ببین|مشخصات)\s+(?:شخص|مشتری|تامین‌کننده|کارمند)\s+(?:با\s+نام\s+)?([^\s]+)/u', - '/([^\s]+)\s+(?:رو\s+)?(?:نشون\s+بده|نمایش\s+ده|ببین)/u' - ] - ]; - - foreach ($patterns as $action => $actionPatterns) { - foreach ($actionPatterns as $pattern) { - if (preg_match($pattern, $message, $matches)) { - return [ - 'action' => $action, - 'params' => ['name' => $matches[1]] - ]; - } - } - } - - return null; - } - - /** - * اجرای دستور (برای سازگاری با سیستم قدیمی) - */ - private function executeCommand(array $command, Business $business, $user): array - { - $action = $command['action']; - $params = $command['params']; - - switch ($action) { - case 'add': - return $this->tools->addPerson($params, $business, $user); - case 'delete': - return $this->tools->deletePerson($params, $business, $user); - case 'show': - return $this->tools->showPerson($params, $business, $user); - default: - return [ - 'success' => false, - 'error' => 'عملیات نامعتبر است.' - ]; - } - } - - /** - * بررسی دسترسی کاربر - */ - private function hasPermission($user): bool - { - $roles = $user->getRoles(); - return in_array('ROLE_ADMIN', $roles) || in_array('ROLE_MANAGER', $roles) || in_array('ROLE_USER', $roles); - } - - /** - * دریافت راهنمای عملیات - */ - public function getOperationsGuide(): string - { - return "راهنمای عملیات مدیریت اشخاص: - -افزودن شخص جدید: -• 'شخص علی اضافه کن' -• 'مشتری جدید با نام احمد ایجاد کن' -• 'علی ایجاد کن' - -حذف شخص: -• 'علی رو حذف کن' -• 'شخص محسن محمودی رو حذف کن' -• 'حذف کن شخص احمد' - -نمایش مشخصات: -• 'مشخصات علی رو نشون بده' -• 'علی رو ببین' -• 'نمایش ده شخص احمد' - -نکات: -- نام شخص می‌تواند نام مستعار یا نام کامل باشد -- فقط اشخاص بدون تراکنش قابل حذف هستند -- تمام عملیات در لاگ ثبت می‌شود"; - } - - -} \ No newline at end of file diff --git a/hesabixCore/src/Service/AI/README.md b/hesabixCore/src/Service/AI/README.md deleted file mode 100644 index ac9bd7e4..00000000 --- a/hesabixCore/src/Service/AI/README.md +++ /dev/null @@ -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 در تنظیمات امن ذخیره می‌شوند - -## توسعه آینده -- اضافه کردن سرویس‌های جدید هوش مصنوعی -- بهبود پردازش داده‌ها -- اضافه کردن قابلیت‌های گزارش‌گیری پیشرفته \ No newline at end of file diff --git a/hesabixCore/src/Service/AI/TicketManagementService.php b/hesabixCore/src/Service/AI/TicketManagementService.php deleted file mode 100644 index f2d65441..00000000 --- a/hesabixCore/src/Service/AI/TicketManagementService.php +++ /dev/null @@ -1,519 +0,0 @@ -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 می‌گیرم'"; - } -} \ No newline at end of file diff --git a/hesabixCore/src/Service/Access.php b/hesabixCore/src/Service/Access.php index 8e0a83f0..971569cd 100644 --- a/hesabixCore/src/Service/Access.php +++ b/hesabixCore/src/Service/Access.php @@ -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; + } } \ No newline at end of file diff --git a/webUI/src/stores/navigationStore.ts b/webUI/src/stores/navigationStore.ts new file mode 100644 index 00000000..536a2a03 --- /dev/null +++ b/webUI/src/stores/navigationStore.ts @@ -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 + } +}) \ No newline at end of file diff --git a/webUI/src/views/acc/App.vue b/webUI/src/views/acc/App.vue index 37efb7ce..38e47980 100644 --- a/webUI/src/views/acc/App.vue +++ b/webUI/src/views/acc/App.vue @@ -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; diff --git a/webUI/src/views/wizard/home.vue b/webUI/src/views/wizard/home.vue index a98f5b89..80e66b73 100644 --- a/webUI/src/views/wizard/home.vue +++ b/webUI/src/views/wizard/home.vue @@ -1,1924 +1,423 @@