progress in ai

This commit is contained in:
Hesabix 2025-07-18 16:29:35 +00:00
parent a87d479e82
commit fc6f286e0e
14 changed files with 3046 additions and 456 deletions

127
AI_PERSON_INTEGRATION.md Normal file
View file

@ -0,0 +1,127 @@
# هوش مصنوعی حسابیکس - یکپارچه‌سازی با اطلاعات اشخاص
## خلاصه
این پروژه قابلیت‌های جدیدی به سیستم هوش مصنوعی حسابیکس اضافه کرده است که به کاربران امکان دسترسی پویا به اطلاعات اشخاص را می‌دهد. هوش مصنوعی حالا می‌تواند به سوالات مربوط به اشخاص، موجودی‌ها و تراکنش‌های مالی پاسخ دهد.
## ویژگی‌های جدید
### 1. دسترسی به اطلاعات اشخاص
- نمایش اطلاعات کامل اشخاص شامل نام، کد، آدرس، تلفن و غیره
- محاسبه و نمایش موجودی مالی اشخاص
- نمایش تراکنش‌های اخیر هر شخص
- نمایش کارت‌های بانکی و اطلاعات مالی
### 2. جستجوی هوشمند
- جستجو بر اساس نام، کد یا شماره تلفن
- پیشنهادات جستجو
- فیلتر بر اساس نوع اشخاص (مشتری، تامین‌کننده، کارمند)
### 3. امنیت و حریم خصوصی
- هر کاربر فقط به اطلاعات اشخاص کسب و کار خود دسترسی دارد
- بررسی دسترسی‌ها قبل از نمایش اطلاعات
- محافظت از اطلاعات حساس
## ساختار فایل‌ها
### Backend (PHP/Symfony)
#### سرویس‌های جدید:
- `PersonDataService.php`: مدیریت داده‌های اشخاص
- `AIService.php`: به‌روزرسانی شده برای پشتیبانی از اطلاعات اشخاص
#### کنترلرهای جدید:
- `wizardController.php`: اضافه شدن endpoint های جدید برای اشخاص
#### API Endpoints جدید:
- `POST /api/wizard/persons/search`: جستجوی اشخاص
- `GET /api/wizard/persons/{personId}`: دریافت اطلاعات شخص
- `GET /api/wizard/persons/{personId}/transactions`: دریافت تراکنش‌های شخص
### Frontend (Vue.js)
#### کامپوننت‌های جدید:
- `PersonInfo.vue`: نمایش اطلاعات کامل شخص
- `home.vue`: به‌روزرسانی شده برای پشتیبانی از قابلیت‌های جدید
## نحوه استفاده
### 1. سوالات مربوط به اشخاص
کاربران می‌توانند سوالاتی مانند موارد زیر بپرسند:
- "اطلاعات شخص احمد محمدی"
- "موجودی مشتری علی رضایی"
- "تراکنش‌های تامین‌کننده شرکت ABC"
- "لیست کارمندان"
### 2. جستجوی مستقیم
- استفاده از پیشنهادات موجود در رابط کاربری
- تایپ نام یا کد شخص در چت
### 3. نمایش اطلاعات
- اطلاعات شخص در دیالوگ جداگانه نمایش داده می‌شود
- شامل موجودی مالی، تراکنش‌ها و اطلاعات تماس
- امکان مشاهده جزئیات کامل
## امنیت
### بررسی دسترسی‌ها:
- هر درخواست ابتدا بررسی می‌شود که کاربر دسترسی لازم را داشته باشد
- اطلاعات فقط برای کسب و کار مربوطه نمایش داده می‌شود
- API endpoints محافظت شده با سیستم احراز هویت
### محافظت از داده‌ها:
- شماره کارت‌های بانکی ماسک می‌شوند
- اطلاعات حساس فیلتر می‌شوند
- لاگ تمام درخواست‌ها ثبت می‌شود
## تنظیمات
### پرامپ هوش مصنوعی:
سیستم به طور خودکار اطلاعات اشخاص را به پرامپ اضافه می‌کند تا هوش مصنوعی بتواند به سوالات مربوطه پاسخ دهد.
### محدودیت‌ها:
- حداکثر 20 نتیجه در جستجو
- حداکثر 10 تراکنش در نمایش
- محدودیت دسترسی بر اساس کسب و کار
## نمونه استفاده
```javascript
// جستجوی شخص
const persons = await this.searchPersons('احمد محمدی');
// دریافت اطلاعات شخص
const personDetails = await this.getPersonDetails(personId);
// دریافت تراکنش‌ها
const transactions = await this.getPersonTransactions(personId, 10);
```
## آینده‌نگری
### قابلیت‌های پیشنهادی:
1. گزارش‌گیری پیشرفته از اشخاص
2. تحلیل روند تراکنش‌ها
3. پیش‌بینی موجودی بر اساس الگوهای گذشته
4. یکپارچه‌سازی با سیستم اعلان‌ها
5. پشتیبانی از تصاویر پروفایل اشخاص
### بهبودهای فنی:
1. کش کردن اطلاعات پرکاربرد
2. بهینه‌سازی کوئری‌های دیتابیس
3. پشتیبانی از pagination برای لیست‌های بزرگ
4. اضافه کردن فیلترهای پیشرفته
## عیب‌یابی
### مشکلات رایج:
1. **خطای دسترسی**: بررسی کنید که کاربر دسترسی AI داشته باشد
2. **عدم یافتن شخص**: نام یا کد را بررسی کنید
3. **خطای شبکه**: اتصال اینترنت را بررسی کنید
### لاگ‌ها:
تمام خطاها در console مرورگر و لاگ‌های سرور ثبت می‌شوند.
## پشتیبانی
برای گزارش مشکلات یا درخواست ویژگی‌های جدید، لطفاً با تیم توسعه تماس بگیرید.

View file

@ -0,0 +1,261 @@
# سیستم هوشمند هوش مصنوعی حسابیکس - نسخه 2.0
## مقدمه
سیستم جدید هوشمند هوش مصنوعی حسابیکس با رویکردی کاملاً متفاوت طراحی شده است. در این نسخه، به جای تشخیص دستی دستورات، به هوش مصنوعی گفته می‌شود که چه ابزارهایی دارد و اجازه داده می‌شود خودش تصمیم بگیرد که از کدام ابزار استفاده کند.
## ویژگی‌های کلیدی
### 🔧 تشخیص خودکار ابزارها
- هوش مصنوعی خودش تشخیص می‌دهد که چه ابزاری مناسب است
- نیازی به تشخیص دستی دستورات نیست
- انعطاف‌پذیری بالا در درک درخواست‌های کاربر
### 📝 پرامپ‌های سیستمی هوشمند
- پرامپ‌های جامع که تمام ابزارها را معرفی می‌کنند
- مثال‌های کاربردی برای هر ابزار
- قوانین و محدودیت‌های استفاده
### 🎯 تعامل چندمرحله‌ای
- امکان انجام عملیات پیچیده در چند مرحله
- جمع‌آوری اطلاعات تدریجی
- تجربه کاربری بهتر
## معماری سیستم
### 1. AIService (سرویس اصلی)
```php
class AIService {
// پرامپ سیستمی هوشمند
private function getSystemPrompt(): string
// پردازش پاسخ هوش مصنوعی
private function processAIResponse(string $aiResponse, ?Business $business, $user): array
// استخراج دستورات ابزار
private function extractToolCommands(string $aiResponse): array
// اجرای دستورات ابزار
private function executeToolCommand(array $command, ?Business $business, $user): array
}
```
### 2. PersonManagementService (مدیریت اشخاص)
```php
class PersonManagementService {
// ابزارهای مدیریت اشخاص
public function addPerson(array $params, Business $business, $user): array
public function deletePerson(array $params, Business $business, $user): array
public function editPerson(array $params, Business $business, $user): array
public function showPerson(array $params, Business $business, $user): array
public function searchPersons(array $params, Business $business): array
}
```
## ابزارهای موجود
### مدیریت اشخاص (person_management)
#### 1. افزودن شخص جدید
```bash
add_person{name:نام شخص}
```
**مثال‌ها:**
- `add_person{name:علی}`
- `add_person{name:احمد محمدی}`
#### 2. حذف شخص
```bash
delete_person{name:نام شخص}
```
**مثال‌ها:**
- `delete_person{name:علی}`
- `delete_person{name:محسن محمودی}`
#### 3. ویرایش شخص
```bash
edit_person{name:نام شخص, phone:موبایل, address:آدرس, email:ایمیل}
```
**مثال‌ها:**
- `edit_person{name:علی, phone:09123456789}`
- `edit_person{name:احمد, address:تهران، خیابان ولیعصر}`
#### 4. نمایش مشخصات
```bash
show_person{name:نام شخص}
```
**مثال‌ها:**
- `show_person{name:علی}`
- `show_person{name:محسن محمودی}`
#### 5. جستجوی اشخاص
```bash
search_persons{search:متن جستجو, limit:تعداد نتایج}
```
**مثال‌ها:**
- `search_persons{search:علی}`
- `search_persons{search:محمد, limit:5}`
## پرامپ سیستمی
پرامپ سیستمی شامل موارد زیر است:
### معرفی ابزارها
```
شما یک دستیار هوشمند برای سیستم حسابداری حسابیکس هستید. شما دسترسی به ابزارهای زیر دارید:
🔧 ابزارهای موجود:
1. **مدیریت اشخاص** (person_management):
- افزودن شخص جدید: add_person{name:نام شخص}
- حذف شخص: delete_person{name:نام شخص}
- ویرایش شخص: edit_person{name:نام شخص, phone:موبایل, address:آدرس, email:ایمیل}
- نمایش مشخصات: show_person{name:نام شخص}
- جستجوی اشخاص: search_persons{search:متن جستجو, limit:تعداد نتایج}
```
### قوانین استفاده
```
📋 قوانین استفاده:
- اگر کاربر درخواست عملیات مدیریت اشخاص دارد، از دستورات بالا استفاده کنید
- نام شخص می‌تواند نام مستعار یا نام کامل باشد
- برای عملیات پیچیده، ابتدا اطلاعات را جمع‌آوری کنید
- همیشه پاسخ فارسی و واضح ارائه دهید
```
### مثال‌های کاربردی
```
💡 مثال‌های استفاده:
- 'علی رو حذف کن' → delete_person{name:علی}
- 'شخص جدید با نام احمد اضافه کن' → add_person{name:احمد}
- 'مشخصات محسن رو نشون بده' → show_person{name:محسن}
```
## جریان کار
### 1. دریافت درخواست کاربر
```
کاربر: "شخص علی را حذف کن"
```
### 2. ساخت پرامپ هوشمند
```
پرامپ = پرامپ سیستمی + اطلاعات کسب و کار + سوال کاربر
```
### 3. ارسال به هوش مصنوعی
```
هوش مصنوعی پرامپ را دریافت کرده و تصمیم می‌گیرد که از ابزار مناسب استفاده کند
```
### 4. تشخیص دستورات ابزار
```
پاسخ هوش مصنوعی: "برای حذف شخص علی، از دستور delete_person{name:علی} استفاده می‌کنم."
```
### 5. استخراج و اجرای دستورات
```
دستور استخراج شده: delete_person{name:علی}
نتیجه اجرا: "شخص علی با موفقیت حذف شد."
```
### 6. ساخت پاسخ نهایی
```
پاسخ نهایی = پاسخ هوش مصنوعی + نتایج ابزارها
```
## مزایای سیستم جدید
### 🚀 هوشمندی بیشتر
- هوش مصنوعی خودش تصمیم می‌گیرد
- نیازی به تشخیص دستی دستورات نیست
- انعطاف‌پذیری بالا در درک درخواست‌ها
### 🔧 قابلیت توسعه
- افزودن ابزارهای جدید آسان است
- پرامپ‌ها قابل به‌روزرسانی هستند
- معماری مقیاس‌پذیر
### 🎯 تجربه کاربری بهتر
- تعامل طبیعی‌تر
- پاسخ‌های هوشمندانه‌تر
- پشتیبانی از عملیات پیچیده
### 🛡️ امنیت و کنترل
- تمام عملیات در لاگ ثبت می‌شود
- بررسی دسترسی کاربران
- کنترل خطاها
## توسعه آینده
### ابزارهای پیشنهادی
1. **مدیریت محصولات**
- افزودن، ویرایش، حذف محصولات
- مدیریت موجودی
- قیمت‌گذاری
2. **مدیریت تراکنش‌ها**
- ثبت تراکنش‌های مالی
- گزارش‌گیری
- تحلیل داده‌ها
3. **گزارش‌گیری هوشمند**
- گزارش‌های مالی
- تحلیل‌های آماری
- پیش‌بینی‌ها
4. **مدیریت حساب‌ها**
- مدیریت حساب‌های بانکی
- صندوق‌ها
- حقوق‌ها
### بهبودهای پیشنهادی
1. **یادگیری ماشین**
- بهبود تشخیص دستورات
- شخصی‌سازی پاسخ‌ها
- پیش‌بینی نیازهای کاربر
2. **پشتیبانی چندزبانه**
- پشتیبانی از زبان‌های مختلف
- تشخیص خودکار زبان
- ترجمه خودکار
3. **یکپارچه‌سازی پیشرفته**
- اتصال به سرویس‌های خارجی
- API های پیشرفته
- وب‌هوک‌ها
## نکات فنی
### مدیریت خطاها
- بررسی وجود کلیدهای مورد نیاز
- مدیریت خطاهای شبکه
- لاگ‌گیری کامل
### بهینه‌سازی عملکرد
- کش‌گذاری پاسخ‌ها
- کاهش درخواست‌های تکراری
- بهینه‌سازی پرامپ‌ها
### امنیت
- بررسی دسترسی کاربران
- اعتبارسنجی ورودی‌ها
- محافظت از داده‌های حساس
## نتیجه‌گیری
سیستم جدید هوشمند هوش مصنوعی حسابیکس با رویکردی نوآورانه و انعطاف‌پذیر طراحی شده است. این سیستم قابلیت توسعه بالایی دارد و می‌تواند به راحتی با نیازهای آینده سازگار شود.
مزایای اصلی این سیستم عبارتند از:
- هوشمندی بیشتر در تشخیص دستورات
- انعطاف‌پذیری بالا
- قابلیت توسعه آسان
- تجربه کاربری بهتر
- امنیت و کنترل بیشتر
این سیستم پایه‌ای محکم برای توسعه‌های آینده فراهم می‌کند و می‌تواند به عنوان یک دستیار هوشمند واقعی برای کاربران حسابیکس عمل کند.

View file

@ -3,7 +3,7 @@ namespace App\Controller;
use App\Entity\AIConversation;
use App\Entity\AIMessage;
use App\Service\AIService;
use App\Service\AI\AIService;
use App\Service\Access;
use App\Service\Extractor;
use App\Service\Log;
@ -101,8 +101,15 @@ class wizardController extends AbstractController
$conversation = new AIConversation();
$conversation->setUser($acc['user']);
$conversation->setBusiness($acc['bid']);
$conversation->setTitle(substr($message, 0, 50) . '...');
$conversation->setCategory('عمومی');
// استفاده از عنوان ساده برای جلوگیری از مشکل کدگذاری
$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);
}
@ -115,15 +122,32 @@ class wizardController extends AbstractController
$userMessage->setAgentSource($this->aiService->getAIAgentSource());
$entityManager->persist($userMessage);
// ارسال درخواست به سرویس هوش مصنوعی
$result = $this->aiService->sendRequest($message, $options);
// دریافت تاریخچه گفتگو برای حفظ 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()
];
}
}
// ارسال درخواست به سرویس هوش مصنوعی با تاریخچه
$result = $this->aiService->sendRequest($message, $business, $acc['user'], $conversationHistory);
if ($result['success']) {
// بررسی وجود کلید response
$responseContent = $result['response'] ?? $result['message'] ?? 'عملیات با موفقیت انجام شد';
// ذخیره پاسخ هوش مصنوعی
$aiMessage = new AIMessage();
$aiMessage->setConversation($conversation);
$aiMessage->setRole('assistant');
$aiMessage->setContent($result['response']);
$aiMessage->setContent($responseContent);
$aiMessage->setModel($result['model'] ?? $this->aiService->getAIModel());
$aiMessage->setAgentSource($this->aiService->getAIAgentSource());
@ -149,7 +173,7 @@ class wizardController extends AbstractController
$response = [
'success' => true,
'response' => $result['response'],
'response' => $responseContent,
'conversationId' => $conversation->getId(),
'model' => $result['model'] ?? null,
'usage' => $result['usage'] ?? null
@ -205,9 +229,8 @@ class wizardController extends AbstractController
{
try {
$isEnabled = $this->aiService->isAIEnabled();
$agentSource = $this->aiService->getAIAgentSource();
$model = $this->aiService->getAIModel();
$apiKey = $this->aiService->getAIApiKey();
$serviceStatus = $this->aiService->checkAIServiceStatus();
$hasApiKey = $serviceStatus['hasApiKey'];
// بررسی وضعیت کامل
$status = 'available';
@ -216,7 +239,7 @@ class wizardController extends AbstractController
if (!$isEnabled) {
$status = 'disabled';
$message = 'سرویس هوش مصنوعی غیرفعال است';
} elseif (empty($apiKey)) {
} elseif (!$hasApiKey) {
$status = 'no_api_key';
$message = 'کلید API تنظیم نشده است';
}
@ -224,15 +247,10 @@ class wizardController extends AbstractController
return $this->json([
'success' => true,
'status' => $status,
'agent_source' => $agentSource,
'model' => $model,
'service_name' => $serviceStatus['service'],
'message' => $message,
'debug_info' => [
'ai_enabled' => $isEnabled,
'has_api_key' => !empty($apiKey),
'agent_source' => $agentSource,
'model' => $model
]
'connection_status' => $serviceStatus['connection_status'] ?? 'unknown',
'connection_message' => $serviceStatus['connection_message'] ?? ''
]);
} catch (\Exception $e) {
return $this->json([
@ -255,25 +273,25 @@ class wizardController extends AbstractController
switch ($agentSource) {
case 'gapgpt':
$models = [
'gpt-4o' => 'GPT-4 Omni',
'gpt-4-turbo' => 'GPT-4 Turbo',
'gpt-3.5-turbo' => 'GPT-3.5 Turbo',
'claude-3-opus' => 'Claude 3 Opus',
'claude-3-sonnet' => 'Claude 3 Sonnet',
'gemini-pro' => 'Gemini Pro'
'gpt-4o' => 'مدل پیشرفته',
'gpt-4-turbo' => 'مدل سریع',
'gpt-3.5-turbo' => 'مدل استاندارد',
'claude-3-opus' => 'مدل تحلیلی',
'claude-3-sonnet' => 'مدل متعادل',
'gemini-pro' => 'مدل چندمنظوره'
];
break;
case 'avalai':
$models = [
'gpt-4' => 'GPT-4',
'gpt-3.5-turbo' => 'GPT-3.5 Turbo',
'claude-3' => 'Claude 3',
'gemini-pro' => 'Gemini Pro'
'gpt-4' => 'مدل پیشرفته',
'gpt-3.5-turbo' => 'مدل استاندارد',
'claude-3' => 'مدل تحلیلی',
'gemini-pro' => 'مدل چندمنظوره'
];
break;
case 'local':
$models = [
'local-model' => 'مدل لوکال',
'local-model' => 'مدل محلی',
'custom-model' => 'مدل سفارشی'
];
break;
@ -283,7 +301,7 @@ class wizardController extends AbstractController
'success' => true,
'models' => $models,
'current_model' => $currentModel,
'agent_source' => $agentSource
'service_name' => $this->aiService->getServiceDisplayName($agentSource)
]);
} catch (\Exception $e) {
return $this->json([
@ -297,11 +315,12 @@ class wizardController extends AbstractController
public function wizard_settings(): JsonResponse
{
try {
$agentSource = $this->aiService->getAIAgentSource();
return $this->json([
'success' => true,
'settings' => [
'aiEnabled' => $this->aiService->isAIEnabled(),
'aiAgentSource' => $this->aiService->getAIAgentSource(),
'serviceName' => $this->aiService->getServiceDisplayName($agentSource),
'aiModel' => $this->aiService->getAIModel(),
'inputTokenPrice' => $this->aiService->getInputTokenPrice(),
'outputTokenPrice' => $this->aiService->getOutputTokenPrice(),
@ -348,4 +367,194 @@ 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()
]);
}
}
}

View file

@ -23,10 +23,10 @@ class AIConversation
#[ORM\JoinColumn(nullable: false)]
private ?Business $business = null;
#[ORM\Column(length: 255)]
#[ORM\Column(length: 255, options: ["charset" => "utf8mb4", "collation" => "utf8mb4_unicode_ci"])]
private ?string $title = null;
#[ORM\Column(length: 255, nullable: true)]
#[ORM\Column(length: 255, nullable: true, options: ["charset" => "utf8mb4", "collation" => "utf8mb4_unicode_ci"])]
private ?string $category = null;
#[ORM\Column]

View file

@ -21,7 +21,7 @@ class AIMessage
#[ORM\Column(length: 20)]
private ?string $role = null; // 'user' یا 'assistant'
#[ORM\Column(type: Types::TEXT)]
#[ORM\Column(type: Types::TEXT, options: ["charset" => "utf8mb4", "collation" => "utf8mb4_unicode_ci"])]
private ?string $content = null;
#[ORM\Column]
@ -42,10 +42,10 @@ class AIMessage
#[ORM\Column(nullable: true)]
private ?float $totalCost = null;
#[ORM\Column(length: 255, nullable: true)]
#[ORM\Column(length: 255, nullable: true, options: ["charset" => "utf8mb4", "collation" => "utf8mb4_unicode_ci"])]
private ?string $model = null;
#[ORM\Column(length: 255, nullable: true)]
#[ORM\Column(length: 255, nullable: true, options: ["charset" => "utf8mb4", "collation" => "utf8mb4_unicode_ci"])]
private ?string $agentSource = null;
public function __construct()

View file

@ -0,0 +1,802 @@
<?php
namespace App\Service\AI;
use App\Entity\Business;
use App\Service\registryMGR;
use App\Service\Log;
use App\Service\Provider;
use Doctrine\ORM\EntityManagerInterface;
/**
* سرویس هوشمند هوش مصنوعی با تشخیص خودکار ابزارها
*/
class AIService
{
private registryMGR $registryMGR;
private EntityManagerInterface $entityManager;
private Log $log;
private Provider $provider;
private ?PersonDataService $personDataService = null;
private ?PersonManagementService $personManagementService = null;
public function __construct(registryMGR $registryMGR, EntityManagerInterface $entityManager, Log $log, Provider $provider)
{
$this->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);
return [
'success' => true,
'response' => $processedResponse['response'],
'cost' => $cost,
'service' => $service,
'model' => $this->getAIModel(),
'requires_action' => $processedResponse['requires_action'] ?? false,
'action_data' => $processedResponse['action_data'] ?? 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();
$prompt = $basePrompt;
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 "شما یک دستیار هوشمند برای سیستم حسابداری حسابیکس هستید. شما دسترسی به ابزارهای زیر دارید:
🔧 ابزارهای موجود:
1. **مدیریت اشخاص** (person_management):
- افزودن شخص جدید: add_person{name:نام مستعار}
- حذف شخص: delete_person{name:نام مستعار}
- ویرایش شخص: edit_person{name:نام مستعار, phone:موبایل, address:آدرس, email:ایمیل}
- نمایش مشخصات: show_person{name:نام مستعار}
- جستجوی اشخاص: search_persons{search:متن جستجو, limit:تعداد نتایج}
📋 قوانین استفاده:
- اگر کاربر درخواست عملیات مدیریت اشخاص دارد، از دستورات بالا استفاده کنید
- نام شخص = نام مستعار (nikename) که در دیتابیس فیلد الزامی است
- نام مستعار معمولاً همان نامی است که کاربران استفاده می‌کنند
- برای عملیات پیچیده، اطلاعات را جمع‌آوری کنید
- همیشه پاسخ فارسی و واضح ارائه دهید
- حتماً دستور ابزار را در پاسخ خود قرار دهید
🔄 حفظ context:
- همیشه تاریخچه گفتگو را در نظر بگیرید
- اگر کاربر به سوال قبلی اشاره می‌کند، آن را در نظر بگیرید
- اگر نام شخص در سوال قبلی ذکر شده، از آن استفاده کنید
- برای سوالات کوتاه، به context قبلی مراجعه کنید
💡 مثال‌های استفاده:
- 'علی رو حذف کن' ابتدا بگویید: 'برای حذف شخص علی، از دستور delete_person{name:علی} استفاده می‌کنم.'
- 'شخص جدید با نام احمد اضافه کن' ابتدا بگویید: 'برای افزودن شخص جدید، از دستور add_person{name:احمد} استفاده می‌کنم.'
- 'مشخصات محسن رو نشون بده' ابتدا بگویید: 'برای نمایش مشخصات محسن، از دستور show_person{name:محسن} استفاده می‌کنم.'
- 'اطلاعات رضا کمری رو بده' ابتدا بگویید: 'برای نمایش مشخصات رضا کمری، از دستور show_person{name:رضا کمری} استفاده می‌کنم.'
💬 مثال حفظ context:
- کاربر: 'اطلاعات رضا رو بهم بده'
- دستیار: 'کدام رضا؟ لطفاً نام کامل یا نام مستعار را مشخص کنید.'
- کاربر: 'کمری'
- دستیار: 'برای نمایش مشخصات رضا کمری، از دستور show_person{name:رضا کمری} استفاده می‌کنم.'
⚠️ نکات مهم:
- نام شخص = نام مستعار (nikename) در دیتابیس
- حتماً دستور ابزار را در پاسخ خود قرار دهید
- فقط از ابزارهایی که معرفی شده‌اند استفاده کنید
- اگر ابزار مناسب وجود ندارد، به کاربر بگویید
- برای عملیات پیچیده، مرحله به مرحله پیش بروید
- همیشه context گفتگو را حفظ کنید
لطفاً درخواست کاربر را بررسی کرده و در صورت نیاز از ابزارهای مناسب استفاده کنید. حتماً دستور ابزار را در پاسخ خود قرار دهید و context گفتگو را حفظ کنید.";
}
/**
* پردازش پاسخ هوش مصنوعی و تشخیص دستورات ابزار
*/
private function processAIResponse(string $aiResponse, ?Business $business, $user): array
{
// تشخیص دستورات ابزار در پاسخ
$toolCommands = $this->extractToolCommands($aiResponse);
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
];
}
// اگر دستور ابزاری یافت نشد، پاسخ عادی برگردان
return [
'response' => $aiResponse,
'requires_action' => false,
'action_data' => null
];
}
/**
* استخراج دستورات ابزار از پاسخ هوش مصنوعی
*/
private function extractToolCommands(string $aiResponse): array
{
$commands = [];
// الگوهای دستورات ابزار
$patterns = [
'add_person' => '/add_person\{name:([^}]+)\}/u',
'delete_person' => '/delete_person\{name:([^}]+)\}/u',
'edit_person' => '/edit_person\{name:([^}]+)(?:,([^}]+))*\}/u',
'show_person' => '/show_person\{name:([^}]+)\}/u',
'search_persons' => '/search_persons\{search:([^}]+)(?:,limit:([^}]+))*\}/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;
}
/**
* تجزیه پارامترهای دستور ابزار
*/
private function parseToolParameters(string $command): array
{
$params = [];
// استخراج پارامترها از فرمت {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, ',}');
$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);
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;
} 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 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)
];
}
/**
* دریافت لیست ابزارهای موجود
*/
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' => [
'علی رو حذف کن',
'شخص جدید با نام احمد اضافه کن',
'مشخصات محسن رو نشون بده'
]
]
];
}
}

View file

@ -0,0 +1,172 @@
# سیستم مدیریت اشخاص از طریق هوش مصنوعی
## خلاصه
این سیستم امکان مدیریت اشخاص (افزودن، ویرایش، حذف) را از طریق گفتگو با هوش مصنوعی فراهم می‌کند.
## ویژگی‌های امنیتی
### 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. **کدهای تولید شده را منحصر به فرد کنید**

View file

@ -0,0 +1,267 @@
<?php
namespace App\Service\AI;
use App\Entity\Person;
use App\Entity\Business;
use App\Entity\HesabdariRow;
use App\Entity\HesabdariDoc;
use App\Entity\PersonCard;
use App\Entity\PersonType;
use App\Entity\PersonPrelabel;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
class PersonDataService
{
private EntityManagerInterface $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* جستجوی اشخاص بر اساس معیارهای مختلف
*/
public function searchPersons(Business $business, $searchTerm): array
{
// اگر searchTerm آرایه است، از متد قبلی استفاده کن
if (is_array($searchTerm)) {
return $this->searchPersons($business, $searchTerm);
}
// اگر searchTerm رشته است، جستجوی ساده انجام بده
$qb = $this->entityManager->createQueryBuilder();
$qb->select('p')
->from(Person::class, 'p')
->where('p.bid = :business')
->andWhere('(p.name LIKE :search OR p.nikename LIKE :search OR p.code LIKE :search OR p.mobile LIKE :search OR p.tel LIKE :search)')
->setParameter('business', $business)
->setParameter('search', '%' . $searchTerm . '%')
->orderBy('p.name', 'ASC')
->setMaxResults(20);
$persons = $qb->getQuery()->getResult();
$result = [];
foreach ($persons as $person) {
$result[] = [
'id' => $person->getId(),
'code' => $person->getCode(),
'name' => $person->getName(),
'nikename' => $person->getNikename(),
'mobile' => $person->getMobile(),
'tel' => $person->getTel(),
'address' => $person->getAddress(),
'email' => $person->getEmail(),
'company' => $person->getCompany(),
'is_employee' => $person->isEmploye()
];
}
return $result;
}
/**
* دریافت اطلاعات شخص
*/
public function getPersonData(Business $business, int $personId): ?array
{
$person = $this->entityManager->getRepository(Person::class)->findOneBy([
'id' => $personId,
'bid' => $business
]);
if (!$person) {
return null;
}
return $this->getPersonDetails($person);
}
/**
* دریافت تراکنش‌های شخص
*/
public function getPersonTransactions(Business $business, Person $person, int $limit = 10): array
{
return $this->getRecentTransactions($person, $limit);
}
/**
* دریافت اطلاعات کامل یک شخص
*/
public function getPersonDetails(Person $person): array
{
$details = [
'id' => $person->getId(),
'code' => $person->getCode(),
'name' => $person->getName(),
'nikename' => $person->getNikename(),
'mobile' => $person->getMobile(),
'tel' => $person->getTel(),
'email' => $person->getEmail(),
'address' => $person->getAddress(),
'company' => $person->getCompany(),
'shenasemeli' => $person->getShenasemeli(),
'codeeghtesadi' => $person->getCodeeghtesadi(),
'sabt' => $person->getSabt(),
'website' => $person->getWebsite(),
'fax' => $person->getFax(),
'birthday' => $person->getBirthday(),
'is_employee' => $person->isEmploye(),
'cards' => $this->getPersonCards($person),
'transactions' => $this->getRecentTransactions($person)
];
return $details;
}
/**
* دریافت کارت‌های بانکی شخص
*/
private function getPersonCards(Person $person): array
{
$cards = $person->getPersonCards();
$cardData = [];
foreach ($cards as $card) {
$cardData[] = [
'id' => $card->getId(),
'bank' => $card->getBank(),
'card_number' => $card->getCardNum(),
'account_number' => $card->getAccountNum(),
'shaba_number' => $card->getShabaNum()
];
}
return $cardData;
}
/**
* دریافت تراکنش‌های اخیر شخص
*/
private function getRecentTransactions(Person $person, int $limit = 10): array
{
$qb = $this->entityManager->createQueryBuilder();
$qb->select('hr')
->from(HesabdariRow::class, 'hr')
->where('hr.person = :person')
->setParameter('person', $person)
->orderBy('hr.id', 'DESC')
->setMaxResults($limit);
$rows = $qb->getQuery()->getResult();
$transactions = [];
foreach ($rows as $row) {
$doc = $row->getDoc();
$transactions[] = [
'id' => $row->getId(),
'description' => $row->getDes(),
'bs' => $row->getBs(),
'bd' => $row->getBd(),
'document_code' => $doc ? $doc->getCode() : null,
'document_description' => $doc ? $doc->getDes() : null
];
}
return $transactions;
}
/**
* محاسبه آمار کلی اشخاص
*/
public function getPersonsStatistics(Business $business): array
{
$qb = $this->entityManager->createQueryBuilder();
$qb->select('COUNT(p.id) as total')
->from(Person::class, 'p')
->where('p.bid = :business')
->setParameter('business', $business);
$result = $qb->getQuery()->getSingleResult();
return [
'total_persons' => (int)$result['total']
];
}
/**
* تولید خلاصه اطلاعات اشخاص برای هوش مصنوعی
*/
public function generatePersonsSummaryForAI(Business $business): string
{
$stats = $this->getPersonsStatistics($business);
$summary = "اطلاعات اشخاص کسب و کار:\n";
$summary .= "- تعداد کل اشخاص: {$stats['total_persons']}\n\n";
// اضافه کردن لیست اشخاص اخیر
$recentPersons = $this->getRecentPersons($business, 10);
if (!empty($recentPersons)) {
$summary .= "اشخاص اخیر:\n";
foreach ($recentPersons as $person) {
$summary .= "- {$person->getCode()}: {$person->getName()}\n";
}
$summary .= "\n";
}
return $summary;
}
/**
* دریافت اشخاص اخیر
*/
private function getRecentPersons(Business $business, int $limit = 10): array
{
$qb = $this->entityManager->createQueryBuilder();
$qb->select('p')
->from(Person::class, 'p')
->where('p.bid = :business')
->setParameter('business', $business)
->orderBy('p.id', 'DESC')
->setMaxResults($limit);
return $qb->getQuery()->getResult();
}
/**
* جستجوی پیشرفته اشخاص
*/
public function advancedSearch(Business $business, array $filters): array
{
$qb = $this->entityManager->createQueryBuilder();
$qb->select('p')
->from(Person::class, 'p')
->where('p.bid = :business')
->setParameter('business', $business);
// فیلتر بر اساس نام یا کد
if (!empty($filters['search'])) {
$qb->andWhere('(p.name LIKE :search OR p.nikename LIKE :search OR p.code LIKE :search)')
->setParameter('search', '%' . $filters['search'] . '%');
}
// فیلتر بر اساس موبایل
if (!empty($filters['mobile'])) {
$qb->andWhere('p.mobile LIKE :mobile')
->setParameter('mobile', '%' . $filters['mobile'] . '%');
}
// فیلتر بر اساس ایمیل
if (!empty($filters['email'])) {
$qb->andWhere('p.email LIKE :email')
->setParameter('email', '%' . $filters['email'] . '%');
}
$qb->orderBy('p.name', 'ASC');
return $qb->getQuery()->getResult();
}
}

View file

@ -0,0 +1,549 @@
<?php
namespace App\Service\AI;
use App\Entity\Person;
use App\Entity\Business;
use App\Entity\PersonType;
use Doctrine\ORM\EntityManagerInterface;
use App\Service\Log;
use App\Service\Provider;
/**
* کلاس ابزارهای مدیریت اشخاص
*/
class PersonTools
{
private EntityManagerInterface $entityManager;
private Log $log;
private Provider $provider;
public function __construct(EntityManagerInterface $entityManager, Log $log, Provider $provider)
{
$this->entityManager = $entityManager;
$this->log = $log;
$this->provider = $provider;
}
/**
* ابزار افزودن شخص جدید
*/
public function addPerson(array $params, Business $business, $user): array
{
$name = $params['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->setName($name);
$person->setNikename($name);
$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['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)
];
}
/**
* یافتن شخص با نام
*/
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 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 "راهنمای عملیات مدیریت اشخاص:
افزودن شخص جدید:
'شخص علی اضافه کن'
'مشتری جدید با نام احمد ایجاد کن'
'علی ایجاد کن'
حذف شخص:
'علی رو حذف کن'
'شخص محسن محمودی رو حذف کن'
'حذف کن شخص احمد'
نمایش مشخصات:
'مشخصات علی رو نشون بده'
'علی رو ببین'
'نمایش ده شخص احمد'
نکات:
- نام شخص می‌تواند نام مستعار یا نام کامل باشد
- فقط اشخاص بدون تراکنش قابل حذف هستند
- تمام عملیات در لاگ ثبت می‌شود";
}
}

View file

@ -0,0 +1,61 @@
# سرویس‌های هوش مصنوعی
این پوشه شامل سرویس‌های مربوط به هوش مصنوعی و پردازش داده‌های اشخاص است.
## ساختار فایل‌ها
### 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 در تنظیمات امن ذخیره می‌شوند
## توسعه آینده
- اضافه کردن سرویس‌های جدید هوش مصنوعی
- بهبود پردازش داده‌ها
- اضافه کردن قابلیت‌های گزارش‌گیری پیشرفته

View file

@ -1,388 +0,0 @@
<?php
namespace App\Service;
use App\Service\registryMGR;
use Exception;
use Symfony\Component\HttpFoundation\JsonResponse;
class AIService
{
private registryMGR $registryMGR;
public function __construct(registryMGR $registryMGR)
{
$this->registryMGR = $registryMGR;
}
/**
* ارسال درخواست به سرویس هوش مصنوعی
*/
public function sendRequest(string $message, array $options = []): array
{
try {
// بررسی فعال بودن هوش مصنوعی
if (!$this->isAIEnabled()) {
return [
'success' => false,
'error' => 'سرویس هوش مصنوعی غیرفعال است'
];
}
// ترکیب پرامپ با پیام کاربر
$enhancedMessage = $this->combinePromptWithMessage($message);
$agentSource = $this->getAIAgentSource();
switch ($agentSource) {
case 'gapgpt':
return $this->sendToGapGPT($enhancedMessage, $options);
case 'avalai':
return $this->sendToAvalAI($enhancedMessage, $options);
case 'local':
return $this->sendToLocalModel($enhancedMessage, $options);
default:
return [
'success' => false,
'error' => 'سرویس هوش مصنوعی نامعتبر است'
];
}
} catch (Exception $e) {
return [
'success' => false,
'error' => 'خطا در ارتباط با سرویس هوش مصنوعی: ' . $e->getMessage()
];
} catch (\Throwable $e) {
return [
'success' => false,
'error' => 'خطای غیرمنتظره در سرویس هوش مصنوعی: ' . $e->getMessage()
];
}
}
/**
* ترکیب پرامپ با پیام کاربر
*/
private function combinePromptWithMessage(string $message): string
{
$prompt = $this->getAIPrompt();
if (empty($prompt)) {
return $message;
}
// اگر پرامپ وجود دارد، آن را با پیام کاربر ترکیب کن
return $prompt . "\n\nسوال کاربر: " . $message;
}
/**
* ارسال درخواست به GapGPT
*/
private function sendToGapGPT(string $message, array $options = []): array
{
$apiKey = $this->getAIApiKey();
if (empty($apiKey)) {
return [
'success' => false,
'error' => 'کلید API GapGPT تنظیم نشده است'
];
}
$url = 'https://api.gapgpt.app/v1/chat/completions';
$model = $options['model'] ?? $this->getAIModel();
$data = [
'model' => $model,
'messages' => [
[
'role' => 'user',
'content' => $message
]
]
];
// اضافه کردن تنظیمات اختیاری
if (isset($options['temperature'])) {
$data['temperature'] = $options['temperature'];
}
if (isset($options['max_tokens'])) {
$data['max_tokens'] = $options['max_tokens'];
}
$headers = [
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey
];
$response = $this->makeHttpRequest($url, $data, $headers);
if ($response['success']) {
return [
'success' => true,
'response' => $response['data']['choices'][0]['message']['content'] ?? '',
'usage' => $response['data']['usage'] ?? null,
'model' => $response['data']['model'] ?? $model
];
}
return [
'success' => false,
'error' => $response['error'] ?? 'خطا در ارتباط با GapGPT: پاسخ نامعتبر'
];
}
/**
* ارسال درخواست به AvalAI
*/
private function sendToAvalAI(string $message, array $options = []): array
{
$apiKey = $this->getAIApiKey();
if (empty($apiKey)) {
return [
'success' => false,
'error' => 'کلید API AvalAI تنظیم نشده است'
];
}
$url = 'https://api.avalai.ir/v1/chat/completions';
$model = $options['model'] ?? $this->getAIModel();
$data = [
'model' => $model,
'messages' => [
[
'role' => 'user',
'content' => $message
]
]
];
// اضافه کردن تنظیمات اختیاری
if (isset($options['temperature'])) {
$data['temperature'] = $options['temperature'];
}
if (isset($options['max_tokens'])) {
$data['max_tokens'] = $options['max_tokens'];
}
$headers = [
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey
];
$response = $this->makeHttpRequest($url, $data, $headers);
if ($response['success']) {
return [
'success' => true,
'response' => $response['data']['choices'][0]['message']['content'] ?? '',
'usage' => $response['data']['usage'] ?? null,
'model' => $response['data']['model'] ?? $model
];
}
return [
'success' => false,
'error' => $response['error'] ?? 'خطا در ارتباط با AvalAI: پاسخ نامعتبر'
];
}
/**
* ارسال درخواست به مدل لوکال
*/
private function sendToLocalModel(string $message, array $options = []): array
{
$localAddress = $this->getLocalModelAddress();
if (empty($localAddress)) {
return [
'success' => false,
'error' => 'آدرس مدل لوکال تنظیم نشده است'
];
}
$url = rtrim($localAddress, '/') . '/v1/chat/completions';
$model = $options['model'] ?? 'local-model';
$data = [
'model' => $model,
'messages' => [
[
'role' => 'user',
'content' => $message
]
]
];
// اضافه کردن تنظیمات اختیاری
if (isset($options['temperature'])) {
$data['temperature'] = $options['temperature'];
}
if (isset($options['max_tokens'])) {
$data['max_tokens'] = $options['max_tokens'];
}
$headers = [
'Content-Type: application/json'
];
// اضافه کردن کلید API در صورت وجود
$apiKey = $this->getAIApiKey();
if (!empty($apiKey)) {
$headers[] = 'Authorization: Bearer ' . $apiKey;
}
$response = $this->makeHttpRequest($url, $data, $headers);
if ($response['success']) {
return [
'success' => true,
'response' => $response['data']['choices'][0]['message']['content'] ?? '',
'usage' => $response['data']['usage'] ?? null,
'model' => $response['data']['model'] ?? $model
];
}
return [
'success' => false,
'error' => $response['error'] ?? 'خطا در ارتباط با مدل لوکال: پاسخ نامعتبر'
];
}
/**
* انجام درخواست HTTP
*/
private function makeHttpRequest(string $url, array $data, array $headers): array
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_FOLLOWLOCATION => true
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
return [
'success' => false,
'error' => 'خطا در cURL: ' . $error
];
}
if ($httpCode !== 200) {
return [
'success' => false,
'error' => 'خطای HTTP: ' . $httpCode . ' - ' . $response
];
}
$responseData = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return [
'success' => false,
'error' => 'خطا در پردازش پاسخ JSON'
];
}
return [
'success' => true,
'data' => $responseData
];
}
/**
* بررسی فعال بودن هوش مصنوعی
*/
public function isAIEnabled(): bool
{
return $this->registryMGR->get('system', 'aiEnabled') === '1';
}
/**
* دریافت منبع ایجنت هوش مصنوعی
*/
public function getAIAgentSource(): string
{
return $this->registryMGR->get('system', 'aiAgentSource') ?: 'gapgpt';
}
/**
* دریافت مدل هوش مصنوعی
*/
public function getAIModel(): string
{
return $this->registryMGR->get('system', 'aiModel') ?: 'gpt-4o';
}
/**
* دریافت کلید API هوش مصنوعی
*/
public function getAIApiKey(): string
{
return $this->registryMGR->get('system', 'aiApiKey') ?: '';
}
/**
* دریافت آدرس مدل لوکال
*/
public function getLocalModelAddress(): string
{
return $this->registryMGR->get('system', 'localModelAddress') ?: '';
}
/**
* دریافت قیمت توکن ورودی
*/
public function getInputTokenPrice(): float
{
return (float) ($this->registryMGR->get('system', 'inputTokenPrice') ?: 0);
}
/**
* دریافت قیمت توکن خروجی
*/
public function getOutputTokenPrice(): float
{
return (float) ($this->registryMGR->get('system', 'outputTokenPrice') ?: 0);
}
/**
* دریافت پرامپ هوش مصنوعی
*/
public function getAIPrompt(): string
{
return $this->registryMGR->get('system', 'aiPrompt') ?: '';
}
/**
* محاسبه هزینه بر اساس تعداد توکن‌ها
*/
public function calculateCost(array $usage): array
{
$inputTokenPrice = (float) ($this->registryMGR->get('system', 'inputTokenPrice') ?: 0);
$outputTokenPrice = (float) ($this->registryMGR->get('system', 'outputTokenPrice') ?: 0);
$inputTokens = $usage['prompt_tokens'] ?? 0;
$outputTokens = $usage['completion_tokens'] ?? 0;
$inputCost = ($inputTokens / 1000) * $inputTokenPrice;
$outputCost = ($outputTokens / 1000) * $outputTokenPrice;
$totalCost = $inputCost + $outputCost;
// گرد کردن هزینه‌ها به اعداد صحیح
return [
'input_cost' => (int) round($inputCost),
'output_cost' => (int) round($outputCost),
'total_cost' => (int) round($totalCost)
];
}
}

View file

@ -24,6 +24,13 @@ class registryMGR
}
$item->setRoot($root);
$item->setName($key);
// محدود کردن طول داده برای جلوگیری از خطای value_of_key
$maxLength = 65535; // حداکثر طول برای TEXT در MySQL
if (strlen($v) > $maxLength) {
$v = substr($v, 0, $maxLength - 100) . '... [برش داده شده]';
}
$item->setValueOfKey($v);
$this->em->persist($item);
$this->em->flush();
@ -36,12 +43,7 @@ class registryMGR
'name'=>$key
]);
if(! $item){
$item = new Registry();
$item->setRoot($root);
$item->setName($key);
$item->setValueOfKey('');
$this->em->persist($item);
$this->em->flush();
return null; // برگرداندن null به جای ایجاد آیتم جدید
}
return $item->getValueOfKey();
}

View file

@ -0,0 +1,322 @@
<template>
<div class="person-info">
<v-card v-if="person" class="person-card" elevation="2">
<v-card-title class="d-flex align-center">
<v-avatar color="primary" size="40" class="mr-3">
<v-icon color="white">mdi-account</v-icon>
</v-avatar>
<div>
<div class="text-h6">{{ person.nikename }}</div>
<div class="text-caption text-medium-emphasis">{{ person.code }}</div>
</div>
<v-spacer></v-spacer>
<v-chip
:color="getBalanceColor(person.balance.balance_status)"
size="small"
variant="flat"
>
{{ person.balance.balance_status }}
</v-chip>
</v-card-title>
<v-card-text>
<!-- اطلاعات اصلی -->
<div class="info-section mb-4">
<h4 class="text-subtitle-1 font-weight-medium mb-2">اطلاعات اصلی</h4>
<v-row>
<v-col cols="12" md="6">
<div class="info-item">
<span class="info-label">نام کامل:</span>
<span class="info-value">{{ person.name || 'نامشخص' }}</span>
</div>
<div class="info-item" v-if="person.company">
<span class="info-label">شرکت:</span>
<span class="info-value">{{ person.company }}</span>
</div>
<div class="info-item" v-if="person.mobile">
<span class="info-label">موبایل:</span>
<span class="info-value">{{ person.mobile }}</span>
</div>
<div class="info-item" v-if="person.tel">
<span class="info-label">تلفن:</span>
<span class="info-value">{{ person.tel }}</span>
</div>
</v-col>
<v-col cols="12" md="6">
<div class="info-item" v-if="person.email">
<span class="info-label">ایمیل:</span>
<span class="info-value">{{ person.email }}</span>
</div>
<div class="info-item" v-if="person.address">
<span class="info-label">آدرس:</span>
<span class="info-value">{{ person.address }}</span>
</div>
<div class="info-item" v-if="person.shahr">
<span class="info-label">شهر:</span>
<span class="info-value">{{ person.shahr }}</span>
</div>
<div class="info-item" v-if="person.ostan">
<span class="info-label">استان:</span>
<span class="info-value">{{ person.ostan }}</span>
</div>
</v-col>
</v-row>
</div>
<!-- موجودی مالی -->
<div class="info-section mb-4">
<h4 class="text-subtitle-1 font-weight-medium mb-2">موجودی مالی</h4>
<v-row>
<v-col cols="12" md="4">
<v-card variant="outlined" class="balance-card">
<v-card-text class="text-center">
<div class="text-caption text-medium-emphasis">بستانکار</div>
<div class="text-h6 text-success">{{ formatNumber(person.balance.total_bs) }} ریال</div>
</v-card-text>
</v-card>
</v-col>
<v-col cols="12" md="4">
<v-card variant="outlined" class="balance-card">
<v-card-text class="text-center">
<div class="text-caption text-medium-emphasis">بدهکار</div>
<div class="text-h6 text-error">{{ formatNumber(person.balance.total_bd) }} ریال</div>
</v-card-text>
</v-card>
</v-col>
<v-col cols="12" md="4">
<v-card variant="outlined" class="balance-card">
<v-card-text class="text-center">
<div class="text-caption text-medium-emphasis">موجودی</div>
<div class="text-h6" :class="getBalanceTextColor(person.balance.balance_status)">
{{ formatNumber(person.balance.balance) }} ریال
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
<div class="text-caption text-medium-emphasis mt-2">
تعداد تراکنشها: {{ person.balance.transaction_count }}
</div>
</div>
<!-- نوع اشخاص -->
<div class="info-section mb-4" v-if="person.types && person.types.length > 0">
<h4 class="text-subtitle-1 font-weight-medium mb-2">نوع اشخاص</h4>
<div class="d-flex flex-wrap gap-1">
<v-chip
v-for="type in person.types"
:key="type.id"
size="small"
variant="tonal"
color="primary"
>
{{ type.name }}
</v-chip>
</div>
</div>
<!-- کارتهای بانکی -->
<div class="info-section mb-4" v-if="person.cards && person.cards.length > 0">
<h4 class="text-subtitle-1 font-weight-medium mb-2">کارتهای بانکی</h4>
<v-row>
<v-col cols="12" md="6" v-for="card in person.cards" :key="card.bank">
<v-card variant="outlined" class="card-item">
<v-card-text>
<div class="d-flex align-center mb-2">
<v-icon color="primary" class="mr-2">mdi-credit-card</v-icon>
<span class="font-weight-medium">{{ card.bank }}</span>
</div>
<div class="text-caption" v-if="card.card_number">
شماره کارت: {{ maskCardNumber(card.card_number) }}
</div>
<div class="text-caption" v-if="card.account_number">
شماره حساب: {{ card.account_number }}
</div>
<div class="text-caption" v-if="card.shaba_number">
شماره شبا: {{ card.shaba_number }}
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
</div>
<!-- تراکنشهای اخیر -->
<div class="info-section" v-if="transactions.length > 0">
<h4 class="text-subtitle-1 font-weight-medium mb-2">تراکنشهای اخیر</h4>
<v-table density="compact">
<thead>
<tr>
<th>تاریخ</th>
<th>نوع</th>
<th>بستانکار</th>
<th>بدهکار</th>
<th>توضیحات</th>
</tr>
</thead>
<tbody>
<tr v-for="transaction in transactions" :key="transaction.id">
<td>{{ formatDate(transaction.doc_date) }}</td>
<td>
<v-chip size="x-small" variant="tonal" color="primary">
{{ transaction.doc_type }}
</v-chip>
</td>
<td class="text-success" v-if="transaction.bs > 0">
{{ formatNumber(transaction.bs) }}
</td>
<td v-else>-</td>
<td class="text-error" v-if="transaction.bd > 0">
{{ formatNumber(transaction.bd) }}
</td>
<td v-else>-</td>
<td>{{ transaction.description || transaction.doc_description }}</td>
</tr>
</tbody>
</v-table>
</div>
</v-card-text>
</v-card>
<v-card v-else class="no-data-card" elevation="1">
<v-card-text class="text-center">
<v-icon size="48" color="grey" class="mb-3">mdi-account-off</v-icon>
<div class="text-h6 text-grey">اطلاعات شخص یافت نشد</div>
<div class="text-caption text-grey">لطفاً نام یا کد شخص را بررسی کنید</div>
</v-card-text>
</v-card>
</div>
</template>
<script>
export default {
name: 'PersonInfo',
props: {
person: {
type: Object,
default: null
},
transactions: {
type: Array,
default: () => []
}
},
methods: {
formatNumber(number) {
return new Intl.NumberFormat('fa-IR').format(number);
},
formatDate(dateString) {
if (!dateString) return '-';
// تبدیل تاریخ شمسی به میلادی و نمایش
return dateString;
},
maskCardNumber(cardNumber) {
if (!cardNumber) return '-';
return cardNumber.replace(/(\d{4})(\d{4})(\d{4})(\d{4})/, '$1-$2-$3-$4');
},
getBalanceColor(status) {
switch (status) {
case 'بستانکار':
return 'success';
case 'بدهکار':
return 'error';
default:
return 'grey';
}
},
getBalanceTextColor(status) {
switch (status) {
case 'بستانکار':
return 'text-success';
case 'بدهکار':
return 'text-error';
default:
return 'text-grey';
}
}
}
};
</script>
<style scoped>
.person-info {
max-width: 100%;
}
.person-card {
border-radius: 12px;
}
.info-section {
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
padding-bottom: 16px;
}
.info-section:last-child {
border-bottom: none;
padding-bottom: 0;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
}
.info-item:last-child {
border-bottom: none;
}
.info-label {
font-weight: 500;
color: rgba(0, 0, 0, 0.7);
min-width: 80px;
}
.info-value {
color: rgba(0, 0, 0, 0.9);
text-align: left;
}
.balance-card {
border-radius: 8px;
transition: all 0.3s ease;
}
.balance-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.card-item {
border-radius: 8px;
transition: all 0.3s ease;
}
.card-item:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.no-data-card {
border-radius: 12px;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.gap-1 {
gap: 4px;
}
.gap-2 {
gap: 8px;
}
</style>

View file

@ -76,6 +76,23 @@
</template>
<span>مشاهده آرشیو گفتگوها</span>
</v-tooltip>
<!-- دکمه راهنمای عملیات -->
<v-tooltip location="bottom">
<template v-slot:activator="{ props }">
<v-btn
v-bind="props"
icon
@click="showOperationsGuide = true"
class="mr-2 guide-btn"
color="info"
variant="tonal"
>
<v-icon>mdi-help-circle</v-icon>
</v-btn>
</template>
<span>راهنمای عملیات مدیریت اشخاص</span>
</v-tooltip>
</v-toolbar>
<div class="page-container">
<!-- کارت اطلاعات قیمتگذاری و آمار استفاده -->
@ -397,15 +414,64 @@
</v-card-actions>
</v-card>
</v-dialog>
<!-- دیالوگ نمایش اطلاعات اشخاص -->
<v-dialog v-model="showPersonInfo" max-width="900px">
<v-card>
<v-card-title class="d-flex align-center justify-space-between">
<span>اطلاعات شخص</span>
<v-btn icon @click="showPersonInfo = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-card-text>
<PersonInfo
:person="selectedPerson"
:transactions="personTransactions"
/>
</v-card-text>
</v-card>
</v-dialog>
<!-- دیالوگ راهنمای عملیات -->
<v-dialog v-model="showOperationsGuide" max-width="800px">
<v-card>
<v-card-title class="d-flex align-center justify-space-between">
<span>راهنمای عملیات مدیریت اشخاص</span>
<v-btn icon @click="showOperationsGuide = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-card-text>
<div v-if="operationsGuide" v-html="renderMarkdown(operationsGuide)"></div>
<div v-else class="text-center pa-4">
<v-progress-circular indeterminate color="primary"></v-progress-circular>
</div>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" @click="showOperationsGuide = false">
بستن
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
import { marked } from 'marked';
import DOMPurify from 'dompurify';
import axios from 'axios';
import PersonInfo from '@/components/PersonInfo.vue';
export default {
name: 'WizardHome',
components: {
PersonInfo
},
data() {
return {
userMessage: '',
@ -431,7 +497,13 @@ export default {
'راهنمای انبارداری',
'ثبت خرید و فروش',
'گزارش‌های مالی',
'تنظیمات کاربران'
'تنظیمات کاربران',
'اطلاعات اشخاص',
'جستجو در اشخاص',
'موجودی اشخاص',
'شخص علی اضافه کن',
'تلفن 09123456789 را برای محسن اضافه کن',
'شخص احمد را حذف کن'
],
// متغیرهای جدید برای گفتگوها
currentConversation: null,
@ -445,7 +517,14 @@ export default {
userBalance: 0,
// متغیرهای دیالوگ حذف
showDeleteDialog: false,
conversationToDelete: null
conversationToDelete: null,
// متغیرهای اطلاعات اشخاص
selectedPerson: null,
personTransactions: [],
showPersonInfo: false,
// متغیرهای راهنمای عملیات
showOperationsGuide: false,
operationsGuide: null
};
},
computed: {
@ -485,6 +564,7 @@ export default {
await this.checkConnection();
await this.loadConversations();
await this.loadBalance();
await this.loadOperationsGuide(); // Add this line
this.setWelcomeMessage();
},
methods: {
@ -544,19 +624,24 @@ export default {
async loadConversations() {
try {
const response = await axios.post('/api/ai/conversations/list');
this.conversations = response.data;
this.filteredConversations = [...this.conversations];
this.conversations = Array.isArray(response.data) ? response.data : [];
this.filteredConversations = Array.isArray(this.conversations) ? [...this.conversations] : [];
// استخراج دستهبندیها
const categorySet = new Set();
this.conversations.forEach(conv => {
if (conv.category) {
categorySet.add(conv.category);
}
});
if (Array.isArray(this.conversations)) {
this.conversations.forEach(conv => {
if (conv.category) {
categorySet.add(conv.category);
}
});
}
this.categories = Array.from(categorySet);
} catch (error) {
console.error('خطا در بارگذاری گفتگوها:', error);
this.conversations = [];
this.filteredConversations = [];
this.categories = [];
}
},
@ -571,6 +656,17 @@ export default {
}
},
async loadOperationsGuide() {
try {
const response = await axios.get('/api/wizard/persons/guide');
if (response.data.success) {
this.operationsGuide = response.data.guide;
}
} catch (error) {
console.error('خطا در بارگذاری راهنما:', error);
}
},
formatBalance(balance) {
// تبدیل به عدد صحیح
const intBalance = Math.round(balance);
@ -588,7 +684,8 @@ export default {
this.userMessages = [];
// تبدیل پیامهای دیتابیس به فرمت نمایش
response.data.forEach(msg => {
const messages = Array.isArray(response.data) ? response.data : [];
messages.forEach(msg => {
if (msg.role === 'user') {
this.userMessages.push(msg.content);
} else if (msg.role === 'assistant') {
@ -669,21 +766,21 @@ export default {
searchConversations() {
if (!this.searchTerm) {
this.filteredConversations = [...this.conversations];
this.filteredConversations = Array.isArray(this.conversations) ? [...this.conversations] : [];
} else {
this.filteredConversations = this.conversations.filter(conv =>
conv.title.toLowerCase().includes(this.searchTerm.toLowerCase())
);
this.filteredConversations = Array.isArray(this.conversations) ? this.conversations.filter(conv =>
conv.title && conv.title.toLowerCase().includes(this.searchTerm.toLowerCase())
) : [];
}
},
filterByCategory() {
if (!this.selectedCategory) {
this.filteredConversations = [...this.conversations];
this.filteredConversations = Array.isArray(this.conversations) ? [...this.conversations] : [];
} else {
this.filteredConversations = this.conversations.filter(conv =>
this.filteredConversations = Array.isArray(this.conversations) ? this.conversations.filter(conv =>
conv.category === this.selectedCategory
);
) : [];
}
},
@ -714,8 +811,8 @@ export default {
// بررسی اعتبار قبل از ارسال
if (this.userBalance < 100) {
this.userMessages.push({
isAI: true,
this.userMessages.push({
isAI: true,
text: `خطا: اعتبار شما کافی نیست (${this.formatBalance(this.userBalance)}). برای شارژ حساب خود روی دکمه زیر کلیک کنید:`,
isError: true,
showChargeButton: true
@ -737,26 +834,36 @@ export default {
this.scrollToBottom();
});
let response = null;
try {
const response = await axios.post('/api/wizard/talk', {
// بررسی درخواستهای مربوط به اشخاص
const personKeywords = ['شخص', 'مشتری', 'تامین‌کننده', 'کارمند', 'موجودی', 'تراکنش'];
const isPersonRequest = personKeywords.some(keyword =>
message.toLowerCase().includes(keyword.toLowerCase())
);
if (isPersonRequest) {
// اگر درخواست مربوط به اشخاص است، ابتدا اطلاعات شخص را نمایش دهیم
await this.showPersonDetails(message);
}
// در هر صورت درخواست را به سرور ارسال کن
response = await axios.post('/api/wizard/talk', {
message: message,
conversationId: this.currentConversation?.id || null
});
if (response.data.success) {
if (response && response.data && response.data.success) {
this.userMessages.push({
isAI: true,
text: response.data.response,
usage: response.data.usage,
cost: response.data.cost
});
// بهروزرسانی اعتبار کاربر
await this.loadBalance();
// بهروزرسانی لیست گفتگوها
await this.loadConversations();
} else {
} else if (response && response.data) {
// بررسی خطای اعتبار
if (response.data.error && response.data.error.includes('اعتبار')) {
this.userMessages.push({
@ -765,7 +872,6 @@ export default {
isError: true,
showChargeButton: response.data.showChargeButton || false
});
// بهروزرسانی اعتبار کاربر
await this.loadBalance();
} else {
this.userMessages.push({
@ -774,6 +880,13 @@ export default {
isError: true
});
}
} else {
// اگر هیچ پاسخی از سرور دریافت نشد
this.userMessages.push({
isAI: true,
text: 'خطا: خطا در ارتباط با سرور',
isError: true
});
}
} catch (error) {
console.error('خطا در ارسال پیام:', error);
@ -823,6 +936,99 @@ export default {
goToChargePage() {
// هدایت به صفحه شارژ با vue-router
this.$router.push('/acc/sms/panel');
},
async searchPersons(searchTerm) {
try {
const response = await axios.post('/api/wizard/persons/search', {
search: searchTerm
});
if (response.data.success) {
return response.data.persons;
} else {
console.error('خطا در جستجوی اشخاص:', response.data.error);
return [];
}
} catch (error) {
console.error('خطا در جستجوی اشخاص:', error);
return [];
}
},
async getPersonDetails(personId) {
try {
const response = await axios.get(`/api/wizard/persons/${personId}`);
if (response.data.success) {
return response.data.person;
} else {
console.error('خطا در دریافت اطلاعات شخص:', response.data.error);
return null;
}
} catch (error) {
console.error('خطا در دریافت اطلاعات شخص:', error);
return null;
}
},
async getPersonTransactions(personId, limit = 10) {
try {
const response = await axios.get(`/api/wizard/persons/${personId}/transactions?limit=${limit}`);
if (response.data.success) {
return response.data.transactions;
} else {
console.error('خطا در دریافت تراکنش‌های شخص:', response.data.error);
return [];
}
} catch (error) {
console.error('خطا در دریافت تراکنش‌های شخص:', error);
return [];
}
},
async showPersonDetails(searchTerm) {
try {
// جستجوی شخص
const persons = await this.searchPersons(searchTerm);
if (persons.length === 0) {
this.$emit('show-snackbar', {
text: 'شخصی با این نام یافت نشد',
color: 'warning'
});
return;
}
// انتخاب اولین شخص (یا میتوانیم لیست انتخاب نمایش دهیم)
const person = persons[0];
// دریافت اطلاعات کامل شخص
const personDetails = await this.getPersonDetails(person.id);
if (!personDetails) {
this.$emit('show-snackbar', {
text: 'خطا در دریافت اطلاعات شخص',
color: 'error'
});
return;
}
// دریافت تراکنشهای شخص
const transactions = await this.getPersonTransactions(person.id, 10);
// نمایش اطلاعات
this.selectedPerson = personDetails;
this.personTransactions = transactions;
this.showPersonInfo = true;
} catch (error) {
console.error('خطا در نمایش اطلاعات شخص:', error);
this.$emit('show-snackbar', {
text: 'خطا در نمایش اطلاعات شخص',
color: 'error'
});
}
}
}
};