diff --git a/hesabixCore/config/packages/framework.yaml b/hesabixCore/config/packages/framework.yaml index 0621d265..79c02b2d 100644 --- a/hesabixCore/config/packages/framework.yaml +++ b/hesabixCore/config/packages/framework.yaml @@ -21,6 +21,9 @@ framework: #esi: true #fragments: true + http_client: + default_options: + timeout: 30 php_errors: log: true diff --git a/hesabixCore/config/services.yaml b/hesabixCore/config/services.yaml index 9272c0ae..b4549d28 100644 --- a/hesabixCore/config/services.yaml +++ b/hesabixCore/config/services.yaml @@ -40,6 +40,10 @@ services: - '../src/Entity/' - '../src/Kernel.php' + App\Controller\System\DebugController: + arguments: + $kernelLogsDir: '%kernel.logs_dir%' + doctrine.orm.default_attribute_driver: class: Doctrine\ORM\Mapping\Driver\AttributeDriver arguments: @@ -122,7 +126,37 @@ services: arguments: $entityManager: '@doctrine.orm.entity_manager' + App\Cog\TicketService: + arguments: + $entityManager: '@doctrine.orm.entity_manager' + $explore: '@App\Service\Explore' + $jdate: '@Jdate' + $registryMGR: '@registryMGR' + $sms: '@SMS' + $uploadDirectory: '%SupportFilesDir%' + + App\Service\Explore: ~ + App\AiTool\AccountingDocService: arguments: $em: '@doctrine.orm.entity_manager' $cogAccountingDocService: '@App\Cog\AccountingDocService' + + App\AiTool\TicketService: + arguments: + $em: '@doctrine.orm.entity_manager' + $cogTicketService: '@App\Cog\TicketService' + + App\Service\AGI\AGIService: + arguments: + $entityManager: '@doctrine.orm.entity_manager' + $registryMGR: '@registryMGR' + $log: '@Log' + $provider: '@Provider' + $promptService: '@App\Service\AGI\Promps\PromptService' + $httpClient: '@http_client' + $httpKernel: '@kernel' + $explore: '@App\Service\Explore' + $jdate: '@Jdate' + $sms: '@SMS' + $uploadDirectory: '%SupportFilesDir%' diff --git a/hesabixCore/src/AiTool/TicketService.php b/hesabixCore/src/AiTool/TicketService.php index 7b7fd99d..2b4133b8 100644 --- a/hesabixCore/src/AiTool/TicketService.php +++ b/hesabixCore/src/AiTool/TicketService.php @@ -3,13 +3,115 @@ namespace App\AiTool; use App\Cog\TicketService as CogTicketService; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Security\Core\User\UserInterface; class TicketService { - public function __construct( - private readonly CogTicketService $cogTicketService - ) { + private EntityManagerInterface $em; + private CogTicketService $cogTicketService; + + public function __construct(EntityManagerInterface $em, CogTicketService $cogTicketService) + { + $this->em = $em; + $this->cogTicketService = $cogTicketService; + } + + /** + * دریافت لیست تیکت‌ها برای ابزار هوش مصنوعی + */ + public function getTicketsListAi(array $params, $acc = null): array + { + $acc = $acc ?? ($params['acc'] ?? null); + if (!$acc) { + return [ + 'error' => 'اطلاعات دسترسی (acc) الزامی است' + ]; + } + try { + // اینجا باید منطق دریافت لیست تیکت‌ها پیاده‌سازی شود + // فعلاً یک پیام موقت برمی‌گردانیم + return [ + 'error' => 'این قابلیت در حال توسعه است' + ]; + } catch (\Exception $e) { + return [ + 'error' => 'خطا در دریافت لیست تیکت‌ها: ' . $e->getMessage() + ]; + } + } + + /** + * دریافت اطلاعات تیکت بر اساس کد + */ + public function getTicketInfoByCode($code, $acc): array + { + if (!$code) { + return [ + 'error' => 'کد تیکت الزامی است' + ]; + } + if (!$acc) { + return [ + 'error' => 'اطلاعات دسترسی (acc) الزامی است' + ]; + } + try { + // اینجا باید منطق دریافت اطلاعات تیکت پیاده‌سازی شود + return [ + 'error' => 'این قابلیت در حال توسعه است' + ]; + } catch (\Exception $e) { + return [ + 'error' => 'خطا در دریافت اطلاعات تیکت: ' . $e->getMessage() + ]; + } + } + + /** + * افزودن یا ویرایش تیکت برای ابزار هوش مصنوعی + */ + public function addOrUpdateTicketAi(array $params, $acc = null, $code = 0): array + { + $acc = $acc ?? ($params['acc'] ?? null); + if (!$acc) { + return [ + 'error' => 'اطلاعات دسترسی (acc) الزامی است' + ]; + } + try { + // اینجا باید منطق افزودن/ویرایش تیکت پیاده‌سازی شود + return [ + 'error' => 'این قابلیت در حال توسعه است' + ]; + } catch (\Exception $e) { + return [ + 'error' => 'خطا در افزودن/ویرایش تیکت: ' . $e->getMessage() + ]; + } + } + + /** + * پاسخ به تیکت برای ابزار هوش مصنوعی + */ + public function replyToTicketAi(array $params, $acc = null): array + { + $acc = $acc ?? ($params['acc'] ?? null); + if (!$acc) { + return [ + 'error' => 'اطلاعات دسترسی (acc) الزامی است' + ]; + } + try { + // اینجا باید منطق پاسخ به تیکت پیاده‌سازی شود + return [ + 'error' => 'این قابلیت در حال توسعه است' + ]; + } catch (\Exception $e) { + return [ + 'error' => 'خطا در پاسخ به تیکت: ' . $e->getMessage() + ]; + } } /** diff --git a/hesabixCore/src/Controller/SupportController.php b/hesabixCore/src/Controller/SupportController.php index 0c1e278d..ebcb4467 100644 --- a/hesabixCore/src/Controller/SupportController.php +++ b/hesabixCore/src/Controller/SupportController.php @@ -14,6 +14,7 @@ use App\Service\Notification; use App\Service\Provider; use App\Service\registryMGR; use App\Service\SMS; +use App\AiTool\TicketService; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\BinaryFileResponse; diff --git a/hesabixCore/src/Controller/System/DebugController.php b/hesabixCore/src/Controller/System/DebugController.php new file mode 100644 index 00000000..15adcd42 --- /dev/null +++ b/hesabixCore/src/Controller/System/DebugController.php @@ -0,0 +1,684 @@ +logsDir = $kernelLogsDir; + $this->filesystem = new Filesystem(); + $this->environment = $kernel->getEnvironment(); + } + + #[Route('/logs', name: 'debug_logs_list', methods: ['GET'])] + public function getLogs(Request $request): JsonResponse + { + try { + $page = (int) $request->query->get('page', 1); + $limit = (int) $request->query->get('limit', 50); + $search = (string) $request->query->get('search', ''); + $level = (string) $request->query->get('level', ''); + $date = (string) $request->query->get('date', ''); + + // Handle sorting parameters safely + $sortBy = 'timestamp'; + $sortDesc = true; + + // Get sortBy parameter safely + $sortByParam = $request->query->get('sortBy'); + if (is_string($sortByParam) && !empty($sortByParam)) { + $sortBy = $sortByParam; + } + + // Get sortDesc parameter safely + $sortDescParam = $request->query->get('sortDesc'); + if (is_string($sortDescParam)) { + $sortDesc = $sortDescParam === 'true' || $sortDescParam === '1'; + } elseif (is_bool($sortDescParam)) { + $sortDesc = $sortDescParam; + } + + // محدود کردن تعداد آیتم‌ها برای جلوگیری از مصرف حافظه زیاد + $limit = min($limit, 100); + + $logs = $this->parseLogFilesOptimized($page, $limit, $search, $level, $date, $sortBy, $sortDesc); + + return $this->json([ + 'success' => true, + 'data' => $logs['items'], + 'total' => $logs['total'], + 'page' => $page, + 'limit' => $limit, + 'totalPages' => ceil($logs['total'] / $limit), + 'environment' => $this->environment + ]); + } catch (\Exception $e) { + return $this->json([ + 'success' => false, + 'message' => 'خطا در دریافت لاگ‌ها: ' . $e->getMessage() + ], 500); + } + } + + #[Route('/logs/{id}', name: 'debug_log_detail', methods: ['GET'])] + public function getLogDetail(string $id): JsonResponse + { + try { + $logDetail = $this->getLogDetailById($id); + + if (!$logDetail) { + return $this->json([ + 'success' => false, + 'message' => 'لاگ مورد نظر یافت نشد' + ], 404); + } + + return $this->json([ + 'success' => true, + 'data' => $logDetail + ]); + } catch (\Exception $e) { + return $this->json([ + 'success' => false, + 'message' => 'خطا در دریافت جزئیات لاگ: ' . $e->getMessage() + ], 500); + } + } + + #[Route('/logs', name: 'debug_logs_delete', methods: ['DELETE'])] + public function deleteLogs(Request $request): JsonResponse + { + try { + $data = json_decode($request->getContent(), true); + $logIds = $data['ids'] ?? []; + $deleteAll = $data['deleteAll'] ?? false; + + if ($deleteAll) { + $this->clearAllLogs(); + return $this->json([ + 'success' => true, + 'message' => 'تمام لاگ‌ها با موفقیت حذف شدند' + ]); + } + + if (empty($logIds)) { + return $this->json([ + 'success' => false, + 'message' => 'هیچ لاگی برای حذف انتخاب نشده' + ], 400); + } + + $deletedCount = $this->deleteLogsByIds($logIds); + + return $this->json([ + 'success' => true, + 'message' => "{$deletedCount} لاگ با موفقیت حذف شد", + 'deletedCount' => $deletedCount + ]); + } catch (\Exception $e) { + return $this->json([ + 'success' => false, + 'message' => 'خطا در حذف لاگ‌ها: ' . $e->getMessage() + ], 500); + } + } + + #[Route('/logs/export', name: 'debug_logs_export', methods: ['GET'])] + public function exportLogs(Request $request): JsonResponse + { + try { + $format = $request->query->get('format', 'json'); + $date = $request->query->get('date', ''); + $level = $request->query->get('level', ''); + + $logs = $this->getLogsForExport($date, $level); + + if ($format === 'csv') { + $csvData = $this->convertToCsv($logs); + return new JsonResponse($csvData, 200, [ + 'Content-Type' => 'text/csv', + 'Content-Disposition' => 'attachment; filename="logs_' . $this->environment . '_' . date('Y-m-d') . '.csv"' + ]); + } + + return $this->json([ + 'success' => true, + 'data' => $logs, + 'total' => count($logs), + 'environment' => $this->environment + ]); + } catch (\Exception $e) { + return $this->json([ + 'success' => false, + 'message' => 'خطا در صادرات لاگ‌ها: ' . $e->getMessage() + ], 500); + } + } + + #[Route('/system-info', name: 'debug_system_info', methods: ['GET'])] + public function getSystemInfo(): JsonResponse + { + try { + $info = [ + 'environment' => $this->environment, + 'php_version' => PHP_VERSION, + 'symfony_version' => \Symfony\Component\HttpKernel\Kernel::VERSION, + 'memory_usage' => memory_get_usage(true), + 'memory_peak' => memory_get_peak_usage(true), + 'disk_free_space' => disk_free_space($this->logsDir), + 'disk_total_space' => disk_total_space($this->logsDir), + 'log_files_count' => $this->getLogFilesCount(), + 'log_files_size' => $this->getLogFilesSize(), + 'last_error_log' => $this->getLastErrorLog(), + 'server_info' => [ + 'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown', + 'php_sapi' => php_sapi_name(), + 'max_execution_time' => ini_get('max_execution_time'), + 'memory_limit' => ini_get('memory_limit'), + ] + ]; + + return $this->json([ + 'success' => true, + 'data' => $info + ]); + } catch (\Exception $e) { + return $this->json([ + 'success' => false, + 'message' => 'خطا در دریافت اطلاعات سیستم: ' . $e->getMessage() + ], 500); + } + } + + private function parseLogFilesOptimized(int $page, int $limit, string $search, string $level, string $date, string $sortBy = 'timestamp', bool $sortDesc = true): array + { + $finder = new Finder(); + + // فقط فایل‌های لاگ مربوط به محیط فعلی را پیدا کن + $finder->files() + ->in($this->logsDir) + ->name('*.log') + ->filter(function ($file) { + // فایل‌های مربوط به محیط فعلی + $filename = $file->getFilename(); + return strpos($filename, $this->environment . '.log') !== false || + strpos($filename, $this->environment) !== false || + $filename === 'dev.log' || $filename === 'prod.log' || + $filename === 'test.log'; + }) + ->sortByModifiedTime(); + + $allLogs = []; + $id = 1; + $maxLogs = 5000; // محدود کردن تعداد کل لاگ‌ها برای جلوگیری از مصرف حافظه + + foreach ($finder as $file) { + // بررسی اندازه فایل قبل از خواندن + if ($file->getSize() > 50 * 1024 * 1024) { // فایل‌های بزرگتر از 50MB را رد کن + continue; + } + + $handle = fopen($file->getPathname(), 'r'); + if (!$handle) { + continue; + } + + $lineNumber = 0; + while (($line = fgets($handle)) !== false && count($allLogs) < $maxLogs) { + $lineNumber++; + + // محدود کردن تعداد خطوط خوانده شده + if ($lineNumber > 10000) { + break; + } + + if (empty(trim($line))) continue; + + $logEntry = $this->parseLogLine($line, $file->getFilename(), $id++); + + if ($logEntry) { + // فیلتر بر اساس جستجو + if ($search && !$this->matchesSearch($logEntry, $search)) { + continue; + } + + // فیلتر بر اساس سطح + if ($level && $logEntry['level'] !== $level) { + continue; + } + + // فیلتر بر اساس تاریخ + if ($date && $logEntry['date'] !== $date) { + continue; + } + + $allLogs[] = $logEntry; + } + + // بررسی مصرف حافظه + if (memory_get_usage() > 100 * 1024 * 1024) { // بیش از 100MB + break 2; + } + } + + fclose($handle); + } + + // اعمال مرتب‌سازی + usort($allLogs, function($a, $b) use ($sortBy, $sortDesc) { + $aValue = $a[$sortBy] ?? ''; + $bValue = $b[$sortBy] ?? ''; + + // برای تاریخ و زمان، از timestamp استفاده کن + if ($sortBy === 'timestamp') { + $aValue = strtotime($aValue); + $bValue = strtotime($bValue); + } + + if ($sortDesc) { + return $aValue < $bValue ? 1 : -1; + } else { + return $aValue > $bValue ? 1 : -1; + } + }); + + $totalCount = count($allLogs); + $offset = ($page - 1) * $limit; + $items = array_slice($allLogs, $offset, $limit); + + return [ + 'items' => $items, + 'total' => $totalCount + ]; + } + + private function parseLogLine(string $line, string $filename, int $id): ?array + { + // فرمت لاگ Symfony: [timestamp] level: message {"context"} [] + $pattern = '/^\[([^\]]+)\]\s+([^:]+):\s+(.+?)(?:\s+\{([^}]*)\}\s+\[\])?$/'; + + if (preg_match($pattern, $line, $matches)) { + $timestamp = $matches[1]; + $level = strtoupper(trim($matches[2])); + $message = trim($matches[3]); + $context = isset($matches[4]) ? $matches[4] : ''; + + // محدود کردن طول پیام + if (strlen($message) > 1000) { + $message = substr($message, 0, 1000) . '...'; + } + + // پردازش context اگر وجود داشته باشد + $extra = []; + if (!empty($context)) { + // تلاش برای پارس کردن context به عنوان JSON + $contextData = json_decode('{' . $context . '}', true); + if ($contextData) { + $extra = $contextData; + } else { + $extra = ['context' => $context]; + } + } + + return [ + 'id' => $id, + 'timestamp' => $timestamp, + 'date' => date('Y-m-d', strtotime($timestamp)), + 'time' => date('H:i:s', strtotime($timestamp)), + 'level' => $level, + 'message' => $message, + 'filename' => $filename, + 'environment' => $this->environment, + 'extra' => $extra, + 'raw' => substr($line, 0, 500) + ]; + } + + // اگر فرمت استاندارد تطبیق نکرد، تلاش برای فرمت‌های دیگر + $patterns = [ + // فرمت JSON + '/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z)\s+(\w+)\s+(.+)$/', + // فرمت استاندارد + '/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] (\w+): (.+)$/', + // فرمت ساده + '/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+(\w+)\s+(.+)$/' + ]; + + foreach ($patterns as $pattern) { + if (preg_match($pattern, $line, $matches)) { + $timestamp = $matches[1]; + $level = strtoupper($matches[2]); + $message = $matches[3]; + + // محدود کردن طول پیام + if (strlen($message) > 1000) { + $message = substr($message, 0, 1000) . '...'; + } + + // استخراج اطلاعات اضافی از JSON + $extra = []; + if (strpos($message, '{') === 0) { + $jsonData = json_decode($message, true); + if ($jsonData) { + $message = $jsonData['message'] ?? $message; + $extra = $jsonData; + } + } + + return [ + 'id' => $id, + 'timestamp' => $timestamp, + 'date' => date('Y-m-d', strtotime($timestamp)), + 'time' => date('H:i:s', strtotime($timestamp)), + 'level' => $level, + 'message' => $message, + 'filename' => $filename, + 'environment' => $this->environment, + 'extra' => $extra, + 'raw' => substr($line, 0, 500) + ]; + } + } + + // اگر هیچ الگویی تطبیق نکرد، لاگ را با اطلاعات حداقلی برگردان + return [ + 'id' => $id, + 'timestamp' => date('Y-m-d H:i:s'), + 'date' => date('Y-m-d'), + 'time' => date('H:i:s'), + 'level' => 'UNKNOWN', + 'message' => substr($line, 0, 500), + 'filename' => $filename, + 'environment' => $this->environment, + 'extra' => [], + 'raw' => substr($line, 0, 500) + ]; + } + + private function matchesSearch(array $logEntry, string $search): bool + { + $search = strtolower($search); + return strpos(strtolower($logEntry['message']), $search) !== false || + strpos(strtolower($logEntry['level']), $search) !== false || + strpos(strtolower($logEntry['filename']), $search) !== false; + } + + private function getLogDetailById(string $id): ?array + { + $finder = new Finder(); + $finder->files() + ->in($this->logsDir) + ->name('*.log') + ->filter(function ($file) { + $filename = $file->getFilename(); + return strpos($filename, $this->environment . '.log') !== false || + strpos($filename, $this->environment) !== false || + $filename === 'dev.log' || $filename === 'prod.log' || + $filename === 'test.log'; + }); + + foreach ($finder as $file) { + if ($file->getSize() > 10 * 1024 * 1024) { // فایل‌های بزرگتر از 10MB را رد کن + continue; + } + + $handle = fopen($file->getPathname(), 'r'); + if (!$handle) { + continue; + } + + $lineId = 1; + while (($line = fgets($handle)) !== false) { + if (empty(trim($line))) { + $lineId++; + continue; + } + + if ($lineId == $id) { + fclose($handle); + return $this->parseLogLine($line, $file->getFilename(), $lineId); + } + $lineId++; + } + + fclose($handle); + } + + return null; + } + + private function deleteLogsByIds(array $ids): int + { + $deletedCount = 0; + $finder = new Finder(); + $finder->files() + ->in($this->logsDir) + ->name('*.log') + ->filter(function ($file) { + $filename = $file->getFilename(); + return strpos($filename, $this->environment . '.log') !== false || + strpos($filename, $this->environment) !== false || + $filename === 'dev.log' || $filename === 'prod.log' || + $filename === 'test.log'; + }); + + foreach ($finder as $file) { + if ($file->getSize() > 50 * 1024 * 1024) { // فایل‌های بزرگتر از 50MB را رد کن + continue; + } + + $handle = fopen($file->getPathname(), 'r'); + if (!$handle) { + continue; + } + + $lines = []; + $lineId = 1; + $fileModified = false; + + while (($line = fgets($handle)) !== false) { + if (!in_array($lineId, $ids)) { + $lines[] = $line; + } else { + $deletedCount++; + $fileModified = true; + } + $lineId++; + } + + fclose($handle); + + if ($fileModified) { + $this->filesystem->dumpFile($file->getPathname(), implode('', $lines)); + } + } + + return $deletedCount; + } + + private function clearAllLogs(): void + { + $finder = new Finder(); + $finder->files() + ->in($this->logsDir) + ->name('*.log') + ->filter(function ($file) { + $filename = $file->getFilename(); + return strpos($filename, $this->environment . '.log') !== false || + strpos($filename, $this->environment) !== false || + $filename === 'dev.log' || $filename === 'prod.log' || + $filename === 'test.log'; + }); + + foreach ($finder as $file) { + $this->filesystem->remove($file->getPathname()); + } + } + + private function getLogsForExport(string $date, string $level): array + { + $finder = new Finder(); + $finder->files() + ->in($this->logsDir) + ->name('*.log') + ->filter(function ($file) { + $filename = $file->getFilename(); + return strpos($filename, $this->environment . '.log') !== false || + strpos($filename, $this->environment) !== false || + $filename === 'dev.log' || $filename === 'prod.log' || + $filename === 'test.log'; + }); + + $logs = []; + $id = 1; + $maxLogs = 1000; // محدود کردن تعداد لاگ‌های صادر شده + + foreach ($finder as $file) { + if ($file->getSize() > 10 * 1024 * 1024) { // فایل‌های بزرگتر از 10MB را رد کن + continue; + } + + $handle = fopen($file->getPathname(), 'r'); + if (!$handle) { + continue; + } + + while (($line = fgets($handle)) !== false && count($logs) < $maxLogs) { + if (empty(trim($line))) continue; + + $logEntry = $this->parseLogLine($line, $file->getFilename(), $id++); + + if ($logEntry) { + if ($date && $logEntry['date'] !== $date) continue; + if ($level && $logEntry['level'] !== $level) continue; + + $logs[] = $logEntry; + } + } + + fclose($handle); + } + + return $logs; + } + + private function convertToCsv(array $logs): string + { + $csv = "ID,Date,Time,Level,Message,Filename,Environment\n"; + + foreach ($logs as $log) { + $csv .= sprintf( + "%d,%s,%s,%s,%s,%s,%s\n", + $log['id'], + $log['date'], + $log['time'], + $log['level'], + str_replace(',', ';', $log['message']), + $log['filename'], + $log['environment'] + ); + } + + return $csv; + } + + private function getLogFilesCount(): int + { + $finder = new Finder(); + $finder->files() + ->in($this->logsDir) + ->name('*.log') + ->filter(function ($file) { + $filename = $file->getFilename(); + return strpos($filename, $this->environment . '.log') !== false || + strpos($filename, $this->environment) !== false || + $filename === 'dev.log' || $filename === 'prod.log' || + $filename === 'test.log'; + }); + return iterator_count($finder); + } + + private function getLogFilesSize(): int + { + $size = 0; + $finder = new Finder(); + $finder->files() + ->in($this->logsDir) + ->name('*.log') + ->filter(function ($file) { + $filename = $file->getFilename(); + return strpos($filename, $this->environment . '.log') !== false || + strpos($filename, $this->environment) !== false || + $filename === 'dev.log' || $filename === 'prod.log' || + $filename === 'test.log'; + }); + + foreach ($finder as $file) { + $size += $file->getSize(); + } + + return $size; + } + + private function getLastErrorLog(): ?array + { + $finder = new Finder(); + $finder->files() + ->in($this->logsDir) + ->name('*.log') + ->filter(function ($file) { + $filename = $file->getFilename(); + return strpos($filename, $this->environment . '.log') !== false || + strpos($filename, $this->environment) !== false || + $filename === 'dev.log' || $filename === 'prod.log' || + $filename === 'test.log'; + }) + ->sortByModifiedTime(); + + $lastError = null; + $lastTimestamp = 0; + + foreach ($finder as $file) { + if ($file->getSize() > 5 * 1024 * 1024) { // فایل‌های بزرگتر از 5MB را رد کن + continue; + } + + $handle = fopen($file->getPathname(), 'r'); + if (!$handle) { + continue; + } + + while (($line = fgets($handle)) !== false) { + if (empty(trim($line))) continue; + + $logEntry = $this->parseLogLine($line, $file->getFilename(), 1); + + if ($logEntry && in_array($logEntry['level'], ['ERROR', 'CRITICAL', 'ALERT', 'EMERGENCY'])) { + $timestamp = strtotime($logEntry['timestamp']); + if ($timestamp > $lastTimestamp) { + $lastTimestamp = $timestamp; + $lastError = $logEntry; + } + } + } + + fclose($handle); + } + + return $lastError; + } +} \ No newline at end of file diff --git a/hesabixCore/src/Service/AGI/AGIService.php b/hesabixCore/src/Service/AGI/AGIService.php index 78fcbcbb..b14bb155 100644 --- a/hesabixCore/src/Service/AGI/AGIService.php +++ b/hesabixCore/src/Service/AGI/AGIService.php @@ -9,6 +9,9 @@ use App\Service\registryMGR; use App\Service\Log; use App\Service\Provider; use App\Service\AGI\Promps\PromptService; +use App\Service\Explore; +use App\Service\Jdate; +use App\Service\SMS; use Doctrine\ORM\EntityManagerInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Component\HttpKernel\HttpKernelInterface; @@ -23,6 +26,10 @@ class AGIService private $promptService; private $httpClient; private $httpKernel; + private $explore; + private $jdate; + private $sms; + private $uploadDirectory; public function __construct( EntityManagerInterface $entityManager, @@ -31,7 +38,11 @@ class AGIService Provider $provider, PromptService $promptService, HttpClientInterface $httpClient, - HttpKernelInterface $httpKernel + HttpKernelInterface $httpKernel, + Explore $explore, + Jdate $jdate, + SMS $sms, + string $uploadDirectory ) { $this->em = $entityManager; $this->registryMGR = $registryMGR; @@ -40,6 +51,10 @@ class AGIService $this->promptService = $promptService; $this->httpClient = $httpClient; $this->httpKernel = $httpKernel; + $this->explore = $explore; + $this->jdate = $jdate; + $this->sms = $sms; + $this->uploadDirectory = $uploadDirectory; } /** @@ -337,19 +352,19 @@ class AGIService return $accountingDocService->searchRowsAi($params, $params['acc'] ?? null); // ابزارهای مربوط به تیکت case 'getTicketsList': - $cogTicketService = new \App\Cog\TicketService($this->em); + $cogTicketService = new \App\Cog\TicketService($this->em, $this->explore, $this->jdate, $this->registryMGR, $this->sms, $this->uploadDirectory); $ticketService = new \App\AiTool\TicketService($this->em, $cogTicketService); return $ticketService->getTicketsListAi($params, $params['acc'] ?? null); case 'getTicketInfo': - $cogTicketService = new \App\Cog\TicketService($this->em); + $cogTicketService = new \App\Cog\TicketService($this->em, $this->explore, $this->jdate, $this->registryMGR, $this->sms, $this->uploadDirectory); $ticketService = new \App\AiTool\TicketService($this->em, $cogTicketService); return $ticketService->getTicketInfoByCode($params['code'] ?? null, $params['acc'] ?? null); case 'addOrUpdateTicket': - $cogTicketService = new \App\Cog\TicketService($this->em); + $cogTicketService = new \App\Cog\TicketService($this->em, $this->explore, $this->jdate, $this->registryMGR, $this->sms, $this->uploadDirectory); $ticketService = new \App\AiTool\TicketService($this->em, $cogTicketService); return $ticketService->addOrUpdateTicketAi($params, $params['acc'] ?? null, $params['code'] ?? 0); case 'replyToTicket': - $cogTicketService = new \App\Cog\TicketService($this->em); + $cogTicketService = new \App\Cog\TicketService($this->em, $this->explore, $this->jdate, $this->registryMGR, $this->sms, $this->uploadDirectory); $ticketService = new \App\AiTool\TicketService($this->em, $cogTicketService); return $ticketService->replyToTicketAi($params, $params['acc'] ?? null); default: diff --git a/hesabixCore/src/Service/AGI/Promps/PromptService.php b/hesabixCore/src/Service/AGI/Promps/PromptService.php index 5ed80ed5..f7ae0098 100644 --- a/hesabixCore/src/Service/AGI/Promps/PromptService.php +++ b/hesabixCore/src/Service/AGI/Promps/PromptService.php @@ -86,6 +86,8 @@ class PromptService switch ($key) { case 'person': return $this->personPromptService->getAllPersonPrompts(); + case 'ticket': + return $this->ticketPromptService->getAllTicketPrompts(); // در آینده موارد بیشتر اضافه خواهند شد // case 'accounting': // return $this->accountingPromptService->getAllAccountingPrompts(); diff --git a/hesabixCore/src/Service/AGI/Promps/TicketService.php b/hesabixCore/src/Service/AGI/Promps/TicketService.php index 47343e04..44a8ce7a 100644 --- a/hesabixCore/src/Service/AGI/Promps/TicketService.php +++ b/hesabixCore/src/Service/AGI/Promps/TicketService.php @@ -2,204 +2,292 @@ namespace App\Service\AGI\Promps; +use Doctrine\ORM\EntityManagerInterface; + class TicketService { + private $em; + + public function __construct(EntityManagerInterface $entityManager) + { + $this->em = $entityManager; + } + /** - * دریافت ابزارهای مربوط به تیکت‌ها + * دریافت تمام ابزارهای بخش تیکت‌ها برای function calling * @return array */ public function getTools(): array { - return [ - [ - 'name' => 'analyze_ticket', - 'description' => 'تحلیل و دسته‌بندی تیکت', - 'parameters' => [ - 'type' => 'object', - 'properties' => [ - 'ticket_body' => [ - 'type' => 'string', - 'description' => 'متن تیکت' - ] - ], - 'required' => ['ticket_body'] + $tools = []; + + // ابزار getTicketsList + $ticketsListPrompt = $this->getTicketsListPrompt(); + $ticketsListData = json_decode($ticketsListPrompt, true); + if ($ticketsListData) { + $tools[] = [ + 'type' => 'function', + 'function' => [ + 'name' => $ticketsListData['tool'], + 'description' => $ticketsListData['description'], + 'parameters' => $ticketsListData['parameters'] ] - ], - [ - 'name' => 'draft_ticket_response', - 'description' => 'تهیه پیش‌نویس پاسخ تیکت', - 'parameters' => [ - 'type' => 'object', - 'properties' => [ - 'ticket_body' => [ - 'type' => 'string', - 'description' => 'متن تیکت' - ], - 'ticket_title' => [ - 'type' => 'string', - 'description' => 'عنوان تیکت' - ], - 'history' => [ - 'type' => 'array', - 'description' => 'تاریخچه مکالمات قبلی', - 'items' => [ - 'type' => 'object', - 'properties' => [ - 'sender' => [ - 'type' => 'string', - 'description' => 'فرستنده پیام' - ], - 'message' => [ - 'type' => 'string', - 'description' => 'متن پیام' - ] - ] - ] - ] - ], - 'required' => ['ticket_body', 'ticket_title'] + ]; + } + + // ابزار getTicketInfo + $ticketInfoPrompt = $this->getTicketInfoPrompt(); + $ticketInfoData = json_decode($ticketInfoPrompt, true); + if ($ticketInfoData) { + $tools[] = [ + 'type' => 'function', + 'function' => [ + 'name' => $ticketInfoData['tool'], + 'description' => $ticketInfoData['description'], + 'parameters' => $ticketInfoData['parameters'] ] - ] - ]; - } - - /** - * پرامپت برای بررسی متن تیکت و دسته‌بندی آن - */ - public function getTicketAnalysisPrompt(string $ticketBody): string - { - return <<getAddOrUpdateTicketPrompt(); + $addOrUpdateData = json_decode($addOrUpdatePrompt, true); + if ($addOrUpdateData) { + $tools[] = [ + 'type' => 'function', + 'function' => [ + 'name' => $addOrUpdateData['tool'], + 'description' => $addOrUpdateData['description'], + 'parameters' => $addOrUpdateData['parameters'] + ] + ]; + } + + // ابزار replyToTicket + $replyPrompt = $this->getReplyToTicketPrompt(); + $replyData = json_decode($replyPrompt, true); + if ($replyData) { + $tools[] = [ + 'type' => 'function', + 'function' => [ + 'name' => $replyData['tool'], + 'description' => $replyData['description'], + 'parameters' => $replyData['parameters'] + ] + ]; + } + + return $tools; } /** - * پرامپت برای پیشنهاد اقدامات بعدی برای تیکت + * تولید تمام پرامپ‌های بخش تیکت‌ها + * @return string */ - public function getNextActionPrompt(string $ticketBody, string $currentStatus, array $previousActions = []): string + public function getAllTicketPrompts(): string { - $previousActionsText = ''; - if (!empty($previousActions)) { - $previousActionsText = "اقدامات قبلی:\n"; - foreach ($previousActions as $action) { - $previousActionsText .= "- {$action}\n"; - } - } - - return <<getTicketsListPrompt(); + $prompts[] = $this->getTicketInfoPrompt(); + $prompts[] = $this->getAddOrUpdateTicketPrompt(); + $prompts[] = $this->getReplyToTicketPrompt(); + return implode("\n\n", $prompts); } /** - * پرامپت برای خلاصه‌سازی تیکت و تاریخچه آن + * پرامپ برای دریافت لیست تیکت‌ها */ - public function getTicketSummaryPrompt(array $ticketHistory): string + public function getTicketsListPrompt(): string { - $historyText = ''; - foreach ($ticketHistory as $entry) { - $historyText .= sprintf( - "- %s (%s): %s\n", - $entry['date'], - $entry['user'], - $entry['message'] - ); + return '{ + "tool": "getTicketsList", + "description": "دریافت لیست تیکت‌های پشتیبانی با فیلتر و صفحه‌بندی", + "endpoint": "/api/ticket/list", + "method": "POST", + "parameters": { + "type": "object", + "properties": { + "page": {"type": "integer", "description": "شماره صفحه"}, + "itemsPerPage": {"type": "integer", "description": "تعداد آیتم در هر صفحه"}, + "search": {"type": "string", "description": "متن جست‌وجو (عنوان، کد، و غیره)"}, + "status": {"type": "array", "items": {"type": "string"}, "description": "فیلتر وضعیت تیکت‌ها (اختیاری)"}, + "priority": {"type": "array", "items": {"type": "string"}, "description": "فیلتر اولویت تیکت‌ها (اختیاری)"}, + "sortBy": {"type": ["string", "null"], "description": "فیلد مرتب‌سازی (اختیاری)"}, + "acc": {"type": "object", "description": "اطلاعات دسترسی (مورد نیاز برای backend)"} + }, + "required": ["page", "itemsPerPage", "search"] + }, + "output": { + "items": [ + { + "id": "integer", + "code": "string", + "title": "string", + "body": "string", + "status": "string", + "priority": "string", + "dateSubmit": "integer", + "submitter": "object", + "main": "integer", + "fileName": "string|null" + } + ], + "total": "integer", + "unfilteredTotal": "integer" + }, + "examples": { + "input": {"page":1,"itemsPerPage":10,"search":"مشکل","status":["در حال پیگیری","بسته شده"],"priority":["کم","متوسط","زیاد"],"sortBy":null,"acc":{"bid":2,"user":2,"year":2,"access":true,"money":1,"ai":true}}, + "output": { + "items": [ + { + "id": 1, + "code": "TKT001", + "title": "مشکل در ورود به سیستم", + "body": "نمی‌توانم وارد سیستم شوم", + "status": "در حال پیگیری", + "priority": "متوسط", + "dateSubmit": 1703123456, + "submitter": {"id": 1, "name": "کاربر نمونه"}, + "main": 0, + "fileName": null } - - return << import('../views/user/manager/debug/debug.vue'), + meta: { + 'title': 'دیباگ سیستم', + 'login': true + } + }, { path: 'manager/changes/mod/:id', component: () => import('../views/user/manager/reportchange/mod.vue'), diff --git a/webUI/src/views/user/manager/debug/debug.vue b/webUI/src/views/user/manager/debug/debug.vue new file mode 100644 index 00000000..fec83fb7 --- /dev/null +++ b/webUI/src/views/user/manager/debug/debug.vue @@ -0,0 +1,669 @@ + + + + + diff --git a/webUI/src/views/user/profile/profile-main.vue b/webUI/src/views/user/profile/profile-main.vue index 276ccdb8..fabd5902 100755 --- a/webUI/src/views/user/profile/profile-main.vue +++ b/webUI/src/views/user/profile/profile-main.vue @@ -113,6 +113,7 @@ export default defineComponent({ { text: 'تاریخچه سیستم', url: '/profile/manager/logs/list', icon: 'mdi-history', visible: true }, { text: 'کیف پول', url: '/profile/manager/wallet/list', icon: 'mdi-wallet', visible: true }, { text: 'اطلاعیه‌ها', url: '/profile/manager/statments/list', icon: 'mdi-bell', visible: true }, + { text: 'دیباگ سیستم', url: '/profile/manager/debug', icon: 'mdi-bug', visible: true }, ], adminSettings: [ { text: 'پیامک', url: '/profile/manager/system/sms/settings', icon: 'mdi-message-alert', visible: true },