|
|
|
""" |
|
وحدة المساعد الذكي |
|
|
|
هذا الملف يحتوي على الفئة الرئيسية لتطبيق المساعد الذكي مع دعم نموذج Claude AI. |
|
""" |
|
|
|
import streamlit as st |
|
import pandas as pd |
|
import numpy as np |
|
import matplotlib.pyplot as plt |
|
import plotly.express as px |
|
import requests |
|
import json |
|
import time |
|
import base64 |
|
import logging |
|
import os |
|
from datetime import datetime, timedelta |
|
import io |
|
import tempfile |
|
import random |
|
from io import BytesIO |
|
from tempfile import NamedTemporaryFile |
|
from PIL import Image |
|
|
|
class ClaudeAIService: |
|
""" |
|
فئة خدمة Claude AI للتحليل الذكي |
|
""" |
|
def __init__(self): |
|
"""تهيئة خدمة Claude AI""" |
|
self.api_url = "https://api.anthropic.com/v1/messages" |
|
|
|
def get_api_key(self): |
|
"""الحصول على مفتاح API من متغيرات البيئة""" |
|
api_key = os.environ.get("anthropic") |
|
if not api_key: |
|
raise ValueError("مفتاح API لـ Claude غير موجود في متغيرات البيئة") |
|
return api_key |
|
|
|
def get_available_models(self): |
|
""" |
|
الحصول على قائمة بالنماذج المتاحة |
|
|
|
العوائد: |
|
dict: قائمة بالنماذج مع وصفها |
|
""" |
|
return { |
|
"claude-3-7-sonnet": "Claude 3.7 Sonnet - نموذج ذكي للمهام المتقدمة", |
|
"claude-3-5-haiku": "Claude 3.5 Haiku - أسرع نموذج للمهام اليومية" |
|
} |
|
|
|
def get_model_full_name(self, short_name): |
|
""" |
|
تحويل الاسم المختصر للنموذج إلى الاسم الكامل |
|
|
|
المعلمات: |
|
short_name: الاسم المختصر للنموذج |
|
|
|
العوائد: |
|
str: الاسم الكامل للنموذج |
|
""" |
|
valid_models = { |
|
"claude-3-7-sonnet": "claude-3-7-sonnet-20250219", |
|
"claude-3-5-haiku": "claude-3-5-haiku-20240307" |
|
} |
|
|
|
return valid_models.get(short_name, short_name) |
|
|
|
def analyze_image(self, image_path, prompt, model_name="claude-3-7-sonnet"): |
|
""" |
|
تحليل صورة باستخدام نموذج Claude AI |
|
|
|
المعلمات: |
|
image_path: مسار الصورة المراد تحليلها |
|
prompt: التوجيه للنموذج |
|
model_name: اسم نموذج Claude المراد استخدامه |
|
|
|
العوائد: |
|
dict: نتائج التحليل |
|
""" |
|
try: |
|
|
|
api_key = self.get_api_key() |
|
|
|
|
|
file_size = os.path.getsize(image_path) |
|
_, ext = os.path.splitext(image_path) |
|
ext = ext.lower() |
|
|
|
if ext in ['.jpg', '.jpeg', '.png', '.gif', '.webp'] and file_size > 5 * 1024 * 1024: |
|
|
|
try: |
|
with Image.open(image_path) as img: |
|
|
|
compressed_img_buffer = io.BytesIO() |
|
|
|
|
|
quality = min(95, int((5 * 1024 * 1024 / file_size) * 100)) |
|
|
|
|
|
img.save(compressed_img_buffer, format='JPEG', quality=quality) |
|
compressed_img_buffer.seek(0) |
|
|
|
|
|
file_content = compressed_img_buffer.read() |
|
logging.info(f"تم ضغط الصورة من {file_size/1024/1024:.2f} ميجابايت إلى {len(file_content)/1024/1024:.2f} ميجابايت") |
|
except Exception as e: |
|
logging.error(f"خطأ أثناء ضغط الصورة: {str(e)}") |
|
with open(image_path, 'rb') as f: |
|
file_content = f.read() |
|
else: |
|
|
|
with open(image_path, 'rb') as f: |
|
file_content = f.read() |
|
|
|
|
|
if len(file_content) > 5 * 1024 * 1024: |
|
raise ValueError(f"حجم الملف ({len(file_content)/1024/1024:.2f} ميجابايت) يتجاوز الحد الأقصى (5 ميجابايت). يرجى تقليل حجم الملف قبل الرفع.") |
|
|
|
|
|
file_base64 = base64.b64encode(file_content).decode('utf-8') |
|
|
|
|
|
_, ext = os.path.splitext(image_path) |
|
ext = ext.lower() |
|
|
|
if ext in ('.jpg', '.jpeg'): |
|
file_type = "image/jpeg" |
|
elif ext == '.png': |
|
file_type = "image/png" |
|
elif ext == '.gif': |
|
file_type = "image/gif" |
|
elif ext == '.webp': |
|
file_type = "image/webp" |
|
else: |
|
file_type = "image/jpeg" |
|
|
|
|
|
model_name = self.get_model_full_name(model_name) |
|
|
|
|
|
headers = { |
|
"Content-Type": "application/json", |
|
"x-api-key": api_key, |
|
"anthropic-version": "2023-06-01" |
|
} |
|
|
|
payload = { |
|
"model": model_name, |
|
"max_tokens": 4096, |
|
"messages": [ |
|
{ |
|
"role": "user", |
|
"content": [ |
|
{"type": "text", "text": prompt}, |
|
{ |
|
"type": "image", |
|
"source": { |
|
"type": "base64", |
|
"media_type": file_type, |
|
"data": file_base64 |
|
} |
|
} |
|
] |
|
} |
|
] |
|
} |
|
|
|
|
|
response = requests.post( |
|
self.api_url, |
|
headers=headers, |
|
json=payload, |
|
timeout=60 |
|
) |
|
|
|
|
|
if response.status_code != 200: |
|
error_message = f"فشل طلب API: {response.status_code}" |
|
try: |
|
error_details = response.json() |
|
error_message += f"\nتفاصيل: {error_details}" |
|
except: |
|
error_message += f"\nتفاصيل: {response.text}" |
|
|
|
return {"error": error_message} |
|
|
|
|
|
result = response.json() |
|
|
|
return { |
|
"success": True, |
|
"content": result["content"][0]["text"], |
|
"model": result["model"], |
|
"usage": result.get("usage", {}) |
|
} |
|
|
|
except Exception as e: |
|
logging.error(f"خطأ أثناء تحليل الصورة: {str(e)}") |
|
import traceback |
|
stack_trace = traceback.format_exc() |
|
return {"error": f"فشل في تحليل الصورة: {str(e)}\n{stack_trace}"} |
|
|
|
def chat_completion(self, messages, model_name="claude-3-7-sonnet"): |
|
""" |
|
إكمال محادثة باستخدام نموذج Claude AI |
|
|
|
المعلمات: |
|
messages: سجل المحادثة |
|
model_name: اسم نموذج Claude المراد استخدامه |
|
|
|
العوائد: |
|
dict: نتائج الإكمال |
|
""" |
|
try: |
|
|
|
api_key = self.get_api_key() |
|
|
|
|
|
claude_messages = [] |
|
for msg in messages: |
|
claude_messages.append({ |
|
"role": msg["role"], |
|
"content": msg["content"] |
|
}) |
|
|
|
|
|
model_name = self.get_model_full_name(model_name) |
|
|
|
|
|
headers = { |
|
"Content-Type": "application/json", |
|
"x-api-key": api_key, |
|
"anthropic-version": "2023-06-01" |
|
} |
|
|
|
payload = { |
|
"model": model_name, |
|
"max_tokens": 2048, |
|
"messages": claude_messages, |
|
"temperature": 0.7 |
|
} |
|
|
|
|
|
response = requests.post( |
|
self.api_url, |
|
headers=headers, |
|
json=payload, |
|
timeout=30 |
|
) |
|
|
|
|
|
if response.status_code != 200: |
|
error_message = f"فشل طلب API: {response.status_code}" |
|
try: |
|
error_details = response.json() |
|
error_message += f"\nتفاصيل: {error_details}" |
|
except: |
|
error_message += f"\nتفاصيل: {response.text}" |
|
|
|
return {"error": error_message} |
|
|
|
|
|
result = response.json() |
|
|
|
return { |
|
"success": True, |
|
"content": result["content"][0]["text"], |
|
"model": result["model"], |
|
"usage": result.get("usage", {}) |
|
} |
|
|
|
except Exception as e: |
|
logging.error(f"خطأ أثناء إكمال المحادثة: {str(e)}") |
|
import traceback |
|
stack_trace = traceback.format_exc() |
|
return {"error": f"فشل في إكمال المحادثة: {str(e)}\n{stack_trace}"} |
|
|
|
|
|
class AIAssistantApp: |
|
"""وحدة المساعد الذكي""" |
|
|
|
def __init__(self): |
|
"""تهيئة وحدة المساعد الذكي""" |
|
|
|
try: |
|
from pdf2image import convert_from_path |
|
pdf_conversion_available = True |
|
self.pdf_conversion_available = True |
|
self.convert_from_path = convert_from_path |
|
except ImportError: |
|
pdf_conversion_available = False |
|
self.pdf_conversion_available = False |
|
|
|
|
|
self.cost_model = self._load_cost_prediction_model() |
|
self.document_model = self._load_document_classifier_model() |
|
self.risk_model = self._load_risk_assessment_model() |
|
self.local_content_model = self._load_local_content_model() |
|
self.entity_model = self._load_entity_recognition_model() |
|
|
|
|
|
self.claude_service = ClaudeAIService() |
|
|
|
|
|
self.faqs = [ |
|
{ |
|
"question": "كيف يمكنني إضافة مشروع جديد؟", |
|
"answer": "يمكنك إضافة مشروع جديد من خلال الانتقال إلى وحدة إدارة المشاريع، ثم النقر على زر 'إضافة مشروع جديد'، وملء النموذج بالبيانات المطلوبة." |
|
}, |
|
{ |
|
"question": "ما هي خطوات تسعير المناقصة؟", |
|
"answer": "تتضمن خطوات تسعير المناقصة: 1) تحليل مستندات المناقصة، 2) تحديد بنود العمل، 3) تقدير التكاليف المباشرة، 4) إضافة المصاريف العامة والأرباح، 5) احتساب المحتوى المحلي، 6) مراجعة النتائج النهائية." |
|
}, |
|
{ |
|
"question": "كيف يتم حساب المحتوى المحلي؟", |
|
"answer": "يتم حساب المحتوى المحلي بتحديد نسبة المنتجات والخدمات والقوى العاملة المحلية من إجمالي التكاليف. يتم استخدام قاعدة بيانات الموردين المعتمدين وتطبيق معادلات خاصة حسب متطلبات هيئة المحتوى المحلي." |
|
}, |
|
{ |
|
"question": "كيف يمكنني تصدير التقارير؟", |
|
"answer": "يمكنك تصدير التقارير من وحدة التقارير والتحليلات، حيث يوجد زر 'تصدير' في كل تقرير. يمكن تصدير التقارير بتنسيقات مختلفة مثل Excel و PDF و CSV." |
|
}, |
|
{ |
|
"question": "كيف يمكنني تقييم المخاطر للمشروع؟", |
|
"answer": "يمكنك تقييم المخاطر للمشروع من خلال وحدة المخاطر، حيث يمكنك إضافة المخاطر المحتملة وتقييم تأثيرها واحتماليتها، ثم وضع خطة الاستجابة المناسبة." |
|
}, |
|
{ |
|
"question": "ما هي طرق التسعير المتاحة في النظام؟", |
|
"answer": "يوفر النظام أربع طرق للتسعير: 1) التسعير القياسي، 2) التسعير غير المتزن، 3) التسعير التنافسي، 4) التسعير الموجه بالربحية. يمكنك اختيار الطريقة المناسبة حسب طبيعة المشروع واستراتيجية الشركة." |
|
}, |
|
{ |
|
"question": "كيف يمكنني معالجة مستندات المناقصة ضخمة الحجم؟", |
|
"answer": "يمكنك استخدام وحدة تحليل المستندات لمعالجة مستندات المناقصة ضخمة الحجم، حيث تقوم الوحدة بتحليل المستندات واستخراج المعلومات المهمة مثل مواصفات المشروع ومتطلباته وشروطه تلقائياً." |
|
} |
|
] |
|
|
|
def _load_cost_prediction_model(self): |
|
"""تحميل نموذج التنبؤ بالتكاليف""" |
|
|
|
|
|
return {"name": "cost_prediction_model", "version": "1.0.0", "status": "loaded"} |
|
|
|
def _load_document_classifier_model(self): |
|
"""تحميل نموذج تصنيف المستندات""" |
|
return {"name": "document_classifier_model", "version": "1.0.0", "status": "loaded"} |
|
|
|
def _load_risk_assessment_model(self): |
|
"""تحميل نموذج تقييم المخاطر""" |
|
return {"name": "risk_assessment_model", "version": "1.0.0", "status": "loaded"} |
|
|
|
def _load_local_content_model(self): |
|
"""تحميل نموذج تحليل المحتوى المحلي""" |
|
return {"name": "local_content_model", "version": "1.0.0", "status": "loaded"} |
|
|
|
def _load_entity_recognition_model(self): |
|
"""تحميل نموذج التعرف على الكيانات""" |
|
return {"name": "entity_recognition_model", "version": "1.0.0", "status": "loaded"} |
|
|
|
def render(self): |
|
"""عرض واجهة وحدة المساعد الذكي""" |
|
|
|
st.markdown("<h1 class='module-title'>وحدة المساعد الذكي</h1>", unsafe_allow_html=True) |
|
|
|
tabs = st.tabs([ |
|
"المساعد الذكي", |
|
"التنبؤ بالتكاليف", |
|
"تحليل المخاطر", |
|
"تحليل المستندات", |
|
"المحتوى المحلي", |
|
"الأسئلة الشائعة" |
|
]) |
|
|
|
with tabs[0]: |
|
self._render_ai_assistant_tab() |
|
|
|
with tabs[1]: |
|
self._render_cost_prediction_tab() |
|
|
|
with tabs[2]: |
|
self._render_risk_analysis_tab() |
|
|
|
with tabs[3]: |
|
self._render_document_analysis_tab() |
|
|
|
with tabs[4]: |
|
self._render_local_content_tab() |
|
|
|
with tabs[5]: |
|
self._render_faq_tab() |
|
|
|
def _render_ai_assistant_tab(self): |
|
"""عرض تبويب المساعد الذكي مع دعم Claude AI""" |
|
|
|
st.markdown("### المساعد الذكي لتسعير المناقصات") |
|
|
|
|
|
claude_models = self.claude_service.get_available_models() |
|
|
|
selected_model = st.radio( |
|
"اختر نموذج الذكاء الاصطناعي", |
|
options=list(claude_models.keys()), |
|
format_func=lambda x: claude_models[x], |
|
horizontal=True, |
|
key="assistant_ai_model" |
|
) |
|
|
|
|
|
st.markdown(""" |
|
<div class="chat-container"> |
|
<div class="chat-header"> |
|
<h4>المساعد الذكي</h4> |
|
<p>تحدث مع المساعد الذكي للحصول على المساعدة في تسعير المناقصات وتحليل البيانات</p> |
|
</div> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
if 'ai_assistant_messages' not in st.session_state: |
|
st.session_state.ai_assistant_messages = [ |
|
{"role": "assistant", "content": "مرحباً! أنا المساعد الذكي لنظام تسعير المناقصات. كيف يمكنني مساعدتك اليوم؟"} |
|
] |
|
|
|
|
|
chat_container = st.container() |
|
with chat_container: |
|
st.markdown(""" |
|
<style> |
|
.chat-container { |
|
max-width: 800px; |
|
margin: 0 auto; |
|
padding: 20px; |
|
} |
|
.message { |
|
display: flex; |
|
margin-bottom: 20px; |
|
align-items: flex-start; |
|
} |
|
.user-message { |
|
justify-content: flex-end; |
|
} |
|
.assistant-message { |
|
justify-content: flex-start; |
|
} |
|
.avatar { |
|
width: 40px; |
|
height: 40px; |
|
border-radius: 50%; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
font-weight: bold; |
|
color: white; |
|
margin: 0 10px; |
|
} |
|
.user-avatar { |
|
background-color: #2196F3; |
|
} |
|
.assistant-avatar { |
|
background-color: #4CAF50; |
|
} |
|
.message-content { |
|
padding: 15px; |
|
border-radius: 15px; |
|
max-width: 70%; |
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1); |
|
} |
|
.user-content { |
|
background-color: #E3F2FD; |
|
border-top-right-radius: 5px; |
|
} |
|
.assistant-content { |
|
background-color: #F5F5F5; |
|
border-top-left-radius: 5px; |
|
} |
|
.message-time { |
|
font-size: 0.8em; |
|
color: #757575; |
|
margin-top: 5px; |
|
text-align: right; |
|
} |
|
</style> |
|
""", unsafe_allow_html=True) |
|
|
|
for message in st.session_state.ai_assistant_messages: |
|
if message["role"] == "user": |
|
st.markdown(f""" |
|
<div class="message user-message"> |
|
<div class="message-content user-content"> |
|
{message["content"]} |
|
<div class="message-time">أنت • الآن</div> |
|
</div> |
|
<div class="avatar user-avatar">أ</div> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
else: |
|
st.markdown(f""" |
|
<div class="message assistant-message"> |
|
<div class="avatar assistant-avatar">م</div> |
|
<div class="message-content assistant-content"> |
|
{message["content"]} |
|
<div class="message-time">المساعد • الآن</div> |
|
</div> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
uploaded_file = st.file_uploader( |
|
"اختياري: ارفع ملفًا للمساعدة (صورة، PDF)", |
|
type=["jpg", "jpeg", "png", "pdf"], |
|
key="assistant_file_upload" |
|
) |
|
|
|
|
|
user_input = st.text_input("اكتب رسالتك هنا", key="ai_assistant_input") |
|
|
|
|
|
api_available = True |
|
try: |
|
self.claude_service.get_api_key() |
|
except ValueError: |
|
api_available = False |
|
st.warning("مفتاح API لـ Claude غير متوفر. يرجى إضافته في إعدادات النظام.") |
|
|