diff --git "a/modules/ai_assistant/ai_app.py" "b/modules/ai_assistant/ai_app.py" --- "a/modules/ai_assistant/ai_app.py" +++ "b/modules/ai_assistant/ai_app.py" @@ -1,1381 +1,1330 @@ -""" -تعديل ملف ai_app.py لدمج وحدة التحقق من الصور مباشرة وإصلاح مشكلة الاستيراد -""" import os -import sys -import logging +import streamlit as st +import pandas as pd +import numpy as np +import plotly.express as px +import plotly.graph_objects as go +import matplotlib.pyplot as plt +import seaborn as sns import base64 +import io import json +import re import time from datetime import datetime -import io -import tempfile -from pathlib import Path -import streamlit as st -import requests from PIL import Image -import pandas as pd -import numpy as np -import plotly.express as px +import anthropic +import requests +from io import BytesIO +import tempfile +import PyPDF2 +import docx +import arabic_reshaper +from bidi.algorithm import get_display + +# Utility functions for handling Arabic text +def reshape_arabic_text(text): + """Reshape Arabic text for proper display""" + if text and isinstance(text, str): + try: + reshaped_text = arabic_reshaper.reshape(text) + return get_display(reshaped_text) + except Exception as e: + st.error(f"Error reshaping text: {e}") + return text + return text -# دمج وحدة التحقق من الصور مباشرة لتجنب مشاكل الاستيراد -def validate_image(image_path, max_size_mb=10): +def validate_image(file_content, max_size_mb=10, min_width=100, min_height=100): """ - التحقق من صحة الصورة وتحسينها قبل إرسالها إلى API + Validates and preprocesses an image before sending to AI API. - المعلمات: - image_path: مسار الصورة - max_size_mb: الحجم الأقصى المسموح به بالميجابايت + Args: + file_content: The binary content of the image file + max_size_mb: Maximum file size in MB + min_width: Minimum image width + min_height: Minimum image height - العوائد: - tuple: (صورة محسنة، نوع الملف، حالة الصلاحية، رسالة الخطأ) + Returns: + Tuple of (is_valid, message, processed_content) """ try: - # التحقق من وجود الملف - if not os.path.exists(image_path): - return None, None, False, f"الملف غير موجود: {image_path}" + # Check if file content exists + if not file_content: + return False, "ملف فارغ", None - # التحقق من حجم الملف - file_size_mb = os.path.getsize(image_path) / (1024 * 1024) + # Check file size + file_size_mb = len(file_content) / (1024 * 1024) if file_size_mb > max_size_mb: - return None, None, False, f"حجم الملف كبير جداً: {file_size_mb:.2f} ميجابايت (الحد الأقصى: {max_size_mb} ميجابايت)" - - # تحديد نوع الملف من امتداده - _, 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: - return None, None, False, f"نوع الملف غير مدعوم: {ext}" - - # فتح الصورة باستخدام PIL للتحقق من صحتها - try: - with Image.open(image_path) as img: - # التحقق من أبعاد الصورة - width, height = img.size - if width > 4000 or height > 4000: - # تغيير حجم الصورة إذا كانت كبيرة جداً - img = img.resize((min(width, 4000), min(height, 4000)), Image.LANCZOS) - - # تحويل الصورة إلى تنسيق مناسب - if ext == '.png' and img.mode == 'RGBA': - # تحويل الصور الشفافة إلى RGB - background = Image.new('RGB', img.size, (255, 255, 255)) - background.paste(img, mask=img.split()[3]) - img = background - - # حفظ الصورة في ذاكرة مؤقتة - buffer = io.BytesIO() - img.save(buffer, format=file_type.split('/')[1].upper()) - buffer.seek(0) - - # تحويل إلى base64 - file_content = buffer.getvalue() - file_base64 = base64.b64encode(file_content).decode('utf-8') - - return file_base64, file_type, True, "" - - except Exception as e: - return None, None, False, f"خطأ في معالجة الصورة: {str(e)}" + return False, f"حجم الملف كبير جدًا. الحد الأ��صى هو {max_size_mb} ميجابايت", None - except Exception as e: - return None, None, False, f"خطأ في التحقق من الصورة: {str(e)}" - -# محاولة استيراد المحللات المختلفة -try: - from document_analyzer import TextExtractor, ItemExtractor - DOCUMENT_ANALYZER_AVAILABLE = True -except ImportError: - DOCUMENT_ANALYZER_AVAILABLE = False - -try: - from contract_analyzer import ContractAnalyzer - CONTRACT_ANALYZER_AVAILABLE = True -except ImportError: - CONTRACT_ANALYZER_AVAILABLE = False - -try: - from bim_analyzer import BIMAnalyzer - BIM_ANALYZER_AVAILABLE = True -except ImportError: - BIM_ANALYZER_AVAILABLE = False - -try: - from cad_bim_analyzer import CADBIMAnalyzer - CAD_BIM_ANALYZER_AVAILABLE = True -except ImportError: - CAD_BIM_ANALYZER_AVAILABLE = False - -try: - from engineering_drawing_analyzer import EngineeringDrawingAnalyzer - ENGINEERING_DRAWING_ANALYZER_AVAILABLE = True -except ImportError: - ENGINEERING_DRAWING_ANALYZER_AVAILABLE = False - -try: - from data_integration import DataAIIntegration - DATA_INTEGRATION_AVAILABLE = True -except ImportError: - DATA_INTEGRATION_AVAILABLE = False - -# تكوين التسجيل -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') -logger = logging.getLogger(__name__) - -class AIAssistantApp: - """ - تطبيق المساعد الذكي المتكامل - """ - def __init__(self): - """تهيئة تطبيق المساعد الذكي""" - self.claude_service = ClaudeAIService() - self.initialize_analyzers() - self.setup_session_state() - - def initialize_analyzers(self): - """تهيئة المحللات المختلفة""" - # تهيئة محلل المستندات - if DOCUMENT_ANALYZER_AVAILABLE: - self.text_extractor = TextExtractor() - self.item_extractor = ItemExtractor() - - # تهيئة محلل العقود - if CONTRACT_ANALYZER_AVAILABLE: - self.contract_analyzer = ContractAnalyzer(api_key_source="security_section") - - # تهيئة محلل BIM - if BIM_ANALYZER_AVAILABLE: - self.bim_analyzer = BIMAnalyzer() - - # تهيئة محلل CAD/BIM - if CAD_BIM_ANALYZER_AVAILABLE: - self.cad_bim_analyzer = CADBIMAnalyzer(claude_client=self.claude_service) - - # تهيئة محلل الرسومات الهندسية - if ENGINEERING_DRAWING_ANALYZER_AVAILABLE: - self.engineering_drawing_analyzer = EngineeringDrawingAnalyzer(claude_client=self.claude_service) - - # تهيئة وحدة تكامل البيانات - if DATA_INTEGRATION_AVAILABLE: - self.data_integration = DataAIIntegration() - - def setup_session_state(self): - """إعداد حالة الجلسة""" - if 'messages' not in st.session_state: - st.session_state.messages = [] - - if 'selected_model' not in st.session_state: - st.session_state.selected_model = "claude-3-7-sonnet" - - if 'analysis_results' not in st.session_state: - st.session_state.analysis_results = {} - - if 'uploaded_files' not in st.session_state: - st.session_state.uploaded_files = {} - - def render(self): - """عرض واجهة المستخدم الرئيسية""" - st.title("وحبي - المساعد الذكي للمناقصات والعقود") - - # عرض التبويبات - tabs = st.tabs([ - "المساعد الذكي", - "تحليل المستندات", - "تحليل العقود", - "تحليل المخاطر", - "تحليل التكاليف", - "تحليل المحتوى المحلي", - "الأسئلة الشائعة" - ]) - - # تبويب المساعد الذكي - with tabs[0]: - self._render_ai_assistant_tab() - - # تبويب تحليل المستندات - with tabs[1]: - self._render_document_analysis_tab() - - # تبويب تحليل العقود - with tabs[2]: - self._render_contract_analysis_tab() + # Open image using PIL + image = Image.open(BytesIO(file_content)) - # تبويب تحليل المخاطر - with tabs[3]: - self._render_risk_analysis_tab() - - # تبويب تحليل التكاليف - with tabs[4]: - self._render_cost_prediction_tab() + # Check image dimensions + width, height = image.size + if width < min_width or height < min_height: + return False, f"أبعاد الصورة صغيرة جدًا. الحد الأدنى هو {min_width}x{min_height}", None + + # Convert transparent images to white background + if image.mode in ('RGBA', 'LA') or (image.mode == 'P' and 'transparency' in image.info): + # Create a white background image + bg = Image.new('RGB', image.size, (255, 255, 255)) + if image.mode == 'P': + image = image.convert('RGBA') + # Paste the image on the background + bg.paste(image, mask=image.split()[3] if image.mode == 'RGBA' else None) + image = bg + + # Resize if image is too large + max_dimension = 2000 + if width > max_dimension or height > max_dimension: + if width > height: + new_width = max_dimension + new_height = int(height * (max_dimension / width)) + else: + new_height = max_dimension + new_width = int(width * (max_dimension / height)) + image = image.resize((new_width, new_height), Image.LANCZOS) + + # Convert to RGB if not already + if image.mode != 'RGB': + image = image.convert('RGB') + + # Save to bytes + img_byte_arr = BytesIO() + image.save(img_byte_arr, format='JPEG', quality=85) + processed_content = img_byte_arr.getvalue() - # تبويب تحليل المحتوى المحلي - with tabs[5]: - self._render_local_content_tab() + return True, "تم التحقق من الصورة بنجاح", processed_content - # تبويب الأسئلة الشائعة - with tabs[6]: - self._render_faq_tab() + except Exception as e: + return False, f"خطأ في معالجة الصورة: {str(e)}", None + +def _get_huggingface_secret(secret_name): + """Get a secret from Hugging Face environment variables""" + env_var_name = f"HF_SECRET_{secret_name.upper()}" + secret_value = os.environ.get(env_var_name) + if not secret_value: + st.warning(f"لم يتم العثور على المفتاح السري: {secret_name}. يرجى التأكد من إضافته في إعدادات Hugging Face.") + return secret_value class ClaudeAIService: - """ - فئة خدمة Claude AI للتحليل الذكي - """ - def __init__(self): - """تهيئة خدمة Claude AI""" - self.api_url = "https://api.anthropic.com/v1/messages" - - def _get_huggingface_secret(self, secret_name): - """ - الحصول على قيمة السر من متغيرات البيئة في Hugging Face - - المعلمات: - secret_name: اسم السر - - العوائد: - str: قيمة السر - """ - # في بيئة Hugging Face، الأسرار متاحة كمتغيرات بيئة بتنسيق HF_SECRET_ - env_var_name = f"HF_SECRET_{secret_name.upper()}" - secret_value = os.environ.get(env_var_name) - - # إذا لم يتم العثور على السر في تنسيق Hugging Face، نحاول البحث عنه مباشرة - if not secret_value: - secret_value = os.environ.get(secret_name) + """Service for interacting with Claude AI API""" + + def __init__(self, api_key=None): + self.api_key = api_key or _get_huggingface_secret("anthropic") + if self.api_key: + self.client = anthropic.Anthropic(api_key=self.api_key) + else: + self.client = None - return secret_value + def is_available(self): + return self.client is not None - def get_api_key(self): - """الحصول على مفتاح API من متغيرات البيئة""" - # محاولة الحصول على المفتاح من أسرار Hugging Face أولاً - api_key = self._get_huggingface_secret("anthropic") - - if not api_key: - # محاولة الحصول على المفتاح من متغيرات البيئة العادية - 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 - أسرع نموذج للمهام اليومية", - "claude-3-opus": "Claude 3 Opus - أقوى نموذج للمهام المعقدة" - } - - def get_model_full_name(self, short_name): - """ - تحويل الاسم المختصر للنموذج إلى الاسم الكامل + """Return available Claude AI models""" + return [ + "claude-3-5-sonnet-20240620", + "claude-3-opus-20240229", + "claude-3-sonnet-20240229", + "claude-3-haiku-20240307" + ] - المعلمات: - short_name: الاسم المختصر للنموذج - - العوائد: - str: الاسم الكامل للنموذج - """ - valid_models = { - "claude-3-7-sonnet": "claude-3-7-sonnet-20250219", - "claude-3-5-haiku": "claude-3-5-haiku-20240307", - "claude-3-opus": "claude-3-opus-20240229" + def chat(self, messages, model="claude-3-5-sonnet-20240620", temperature=0.7, max_tokens=4000): + """Send a chat request to Claude AI""" + if not self.is_available(): + return "لم يتم تكوين مفتاح API للذكاء الاصطناعي. يرجى إضافة المفتاح في إعدادات Hugging Face." + + try: + response = self.client.messages.create( + model=model, + max_tokens=max_tokens, + temperature=temperature, + messages=messages + ) + return response.content[0].text + except Exception as e: + return f"حدث خطأ أثناء الاتصال بـ Claude AI: {str(e)}" + + def analyze_document(self, document_text, analysis_type, model="claude-3-5-sonnet-20240620"): + """Analyze document text using Claude AI""" + if not self.is_available(): + return "لم يتم تكوين مفتاح API للذكاء الاصطناعي. يرجى إضافة المفتاح في إعدادات Hugging Face." + + prompt_map = { + "items_extraction": "قم بتحليل المستند التالي واستخراج جميع البنود والمواصفات الفنية. قم بتنظيمها في قائمة منسقة مع الأرقام والتفاصيل.", + "contract_terms": "قم بتحليل العقد التالي واستخراج جميع الشروط التعاقدية الهامة. حدد الالتزامات والحقوق والمواعيد النهائية والغرامات.", + "quantities": "قم بتحليل المستند التالي واستخراج جدول الكميات. قم بتنظيم المعلومات في جدول يتضمن البند والكمية والوحدة والسعر إن وجد.", + "legal_requirements": "قم بتحليل المستند التالي وتحديد جميع المتطلبات القانونية والتنظيمية. اشرح الالتزامات القانونية والمخاطر المحتملة." } - 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 المراد استخدامه + prompt = prompt_map.get(analysis_type, "قم بتحليل المستند التالي وتلخيص النقاط الرئيسية.") - العوائد: - dict: نتائج التحليل - """ try: - # الحصول على مفتاح API - api_key = self.get_api_key() + messages = [ + { + "role": "user", + "content": f"{prompt}\n\nالمستند:\n{document_text}" + } + ] - # التحقق من صحة الصورة وتحسينها - file_base64, file_type, is_valid, error_message = validate_image(image_path) + response = self.client.messages.create( + model=model, + max_tokens=4000, + temperature=0.3, + messages=messages + ) - if not is_valid: - return {"error": f"عذراً، حدث خطأ أثناء تحليل الملف: {error_message}"} + return response.content[0].text + except Exception as e: + return f"حدث خطأ أثناء تحليل المستند: {str(e)}" - # التحقق من اسم النموذج وتصحيحه إذا لزم الأمر - model_name = self.get_model_full_name(model_name) + def analyze_image(self, image_content, prompt, model="claude-3-5-sonnet-20240620"): + """Analyze image using Claude AI""" + if not self.is_available(): + return "لم يتم تكوين مفتاح API للذكاء الاصطناعي. يرجى إضافة المفتاح في إعدادات Hugging Face." - # إعداد البيانات للطلب - headers = { - "Content-Type": "application/json", - "x-api-key": api_key, - "anthropic-version": "2023-06-01" - } + try: + # Validate and preprocess the image + is_valid, message, processed_image = validate_image(image_content) - 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 - } + if not is_valid: + return f"خطأ في الصورة: {message}" + + # Convert image to base64 + file_base64 = base64.b64encode(processed_image).decode('utf-8') + + # Create message with image + messages = [ + { + "role": "user", + "content": [ + { + "type": "image", + "source": { + "type": "base64", + "media_type": "image/jpeg", + "data": file_base64 } - ] - } - ] - } + }, + { + "type": "text", + "text": prompt + } + ] + } + ] - # إرسال الطلب إلى API - response = requests.post( - self.api_url, - headers=headers, - json=payload, - timeout=60 + # Send request to Claude + response = self.client.messages.create( + model=model, + max_tokens=4000, + temperature=0.3, + messages=messages ) - # التحقق من نجاح الطلب - 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 response.content[0].text + except Exception as e: + return f"حدث خطأ أثناء تحليل الصورة: {str(e)}" + + def analyze_engineering_drawing(self, file_content, file_type, model="claude-3-5-sonnet-20240620"): + """Analyze engineering drawing using Claude AI""" + if not self.is_available(): + return "لم يتم تكوين مفتاح API للذكاء الاصطناعي. يرجى إضافة المفتاح في إعدادات Hugging Face." + + try: + # For image-based drawings, use image analysis + if file_type.lower() in ['png', 'jpg', 'jpeg']: + prompt = "هذه رسمة هندسية. قم بتحليلها وتحديد العناصر الرئيسية والأبعاد والمواصفات الفنية. قدم وصفًا تفصيليًا للرسم الهندسي." + return self.analyze_image(file_content, prompt, model) - return {"error": error_message} + # For DWG/DXF files, we need to extract text and data + # This is a placeholder - actual implementation would require specialized libraries + return "تحليل ملفات DWG/DXF يتطلب مكتبات متخصصة. يرجى تحويل الملف إلى صورة للتحليل." - # معالجة الاستجابة - result = response.json() + except Exception as e: + return f"حدث خطأ أثناء تحليل الرسم الهندسي: {str(e)}" - return { - "success": True, - "content": result["content"][0]["text"], - "model": result["model"], - "usage": result.get("usage", {}) - } + def analyze_project_file(self, file_content, file_type, analysis_type, model="claude-3-5-sonnet-20240620"): + """Analyze project management file using Claude AI""" + if not self.is_available(): + return "لم يتم تكوين مفتاح API للذكاء الاصطناعي. يرجى إضافة المفتاح في إعدادات Hugging Face." + + try: + # This is a placeholder - actual implementation would require specialized libraries + prompt = "" + if analysis_type == "schedule": + prompt = "قم بتحليل الجدول الزمني للمشروع وتحديد المراحل الرئيسية والمواعيد النهائية والمسار الحرج." + elif analysis_type == "quantities": + prompt = "قم بتحليل جدول الكميات وتحديد البنود الرئيسية والكميات والتكاليف." + + # For image-based files, use image analysis + if file_type.lower() in ['png', 'jpg', 'jpeg']: + return self.analyze_image(file_content, prompt, model) + + # For other file types, this is a placeholder + return "تحليل ملفات إدارة المشاريع يتطلب مكتبات متخصصة. يرجى تحويل الملف إلى صورة للتحليل." 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 المراد استخدامه + return f"حدث خطأ أثناء تحليل ملف المشروع: {str(e)}" + + def analyze_risk(self, project_data, model="claude-3-5-sonnet-20240620"): + """Analyze project risks using Claude AI""" + if not self.is_available(): + return "لم يتم تكوين مفتاح API للذكاء الاصطناعي. يرجى إضافة المفتاح في إعدادات Hugging Face." - العوائد: - dict: نتائج الإكمال - """ try: - # الحصول على مفتاح API - api_key = self.get_api_key() - - # تحويل رسائل streamlit إلى تنسيق Claude API - 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" - } + prompt = "قم بتحليل بيانات المشروع التالية وتحديد المخاطر المحتملة وتصنيفها حسب الاحتمالية والتأثير. قدم توصيات للتخفيف من هذه المخاطر." - payload = { - "model": model_name, - "max_tokens": 4096, - "messages": claude_messages - } + messages = [ + { + "role": "user", + "content": f"{prompt}\n\nبيانات المشروع:\n{json.dumps(project_data, ensure_ascii=False)}" + } + ] - # إرسال الطلب إلى API - response = requests.post( - self.api_url, - headers=headers, - json=payload, - timeout=60 + response = self.client.messages.create( + model=model, + max_tokens=4000, + temperature=0.3, + messages=messages ) - # التحقق من نجاح الطلب - 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} + return response.content[0].text + except Exception as e: + return f"حدث خطأ أثناء تحليل المخاطر: {str(e)}" - # معالجة الاستجابة - result = response.json() + def analyze_cost(self, cost_data, model="claude-3-5-sonnet-20240620"): + """Analyze project costs using Claude AI""" + if not self.is_available(): + return "لم يتم تكوين مفتاح API للذكاء الاصطناعي. يرجى إضافة المفتاح في إعدادات Hugging Face." - return { - "success": True, - "content": result["content"][0]["text"], - "model": result["model"], - "usage": result.get("usage", {}) - } + try: + prompt = "قم بتحليل بيانات التكلفة التالية وتحديد الاتجاهات والانحرافات والفرص المحتملة لتوفير التكاليف. قدم توصيات لتحسين إدارة التكلفة." + + messages = [ + { + "role": "user", + "content": f"{prompt}\n\nبيانات التكلفة:\n{json.dumps(cost_data, ensure_ascii=False)}" + } + ] + response = self.client.messages.create( + model=model, + max_tokens=4000, + temperature=0.3, + messages=messages + ) + + return response.content[0].text except Exception as e: - logging.error(f"خطأ أثناء إكمال المحادثة: {str(e)}") - return {"error": f"فشل في إكمال المحادثة: {str(e)}"} - - def analyze_document(self, file_path, analysis_type="comprehensive", model_name="claude-3-7-sonnet"): - """ - تحليل مستند باستخدام نموذج Claude AI - - المعلمات: - file_path: مسار المستند المراد تحليله - analysis_type: نوع التحليل المطلوب - model_name: اسم نموذج Claude المراد استخدامه - - العوائد: - dict: نتائج التحليل - """ + return f"حدث خطأ أثناء تحليل التكلفة: {str(e)}" + + def analyze_local_content(self, local_content_data, model="claude-3-5-sonnet-20240620"): + """Analyze local content using Claude AI""" + if not self.is_available(): + return "لم يتم تكوين مفتاح API للذكاء الاصطناعي. يرجى إضافة المفتاح في إعدادات Hugging Face." + try: - # التحقق من نوع الملف - _, ext = os.path.splitext(file_path) - ext = ext.lower() - - # تحديد نوع التحليل المناسب - if ext in ['.jpg', '.jpeg', '.png', '.gif', '.webp']: - # تحليل الصورة - prompt = self._get_analysis_prompt(analysis_type, ext) - return self.analyze_image(file_path, prompt, model_name) - elif ext in ['.pdf', '.doc', '.docx', '.txt']: - # تحليل المستند النصي (محاكاة) - return { - "success": True, - "content": f"تم تحليل المستند {file_path} بنجاح. نوع التحليل: {analysis_type}", - "model": model_name + prompt = "قم بتحليل بيانات المحتوى المحلي التالية وحساب نسبة المحتوى المحلي. حدد الفرص لزيادة المحتوى المحلي وتقديم توصيات للتحسين." + + messages = [ + { + "role": "user", + "content": f"{prompt}\n\nبيانات المحتوى المحلي:\n{json.dumps(local_content_data, ensure_ascii=False)}" } - else: - return {"error": f"نوع الملف غير مدعوم: {ext}"} - + ] + + response = self.client.messages.create( + model=model, + max_tokens=4000, + temperature=0.3, + messages=messages + ) + + return response.content[0].text except Exception as e: - logging.error(f"خطأ أثناء تحليل المستند: {str(e)}") - return {"error": f"فشل في تحليل المستند: {str(e)}"} - - def _get_analysis_prompt(self, analysis_type, file_ext): - """ - الحصول على التوجيه المناسب لنوع التحليل - - المعلمات: - analysis_type: نوع التحليل المطلوب - file_ext: امتداد الملف - - العوائد: - str: التوجيه المناسب - """ - if analysis_type == "items_extraction": - return """ - قم بتحليل هذه الصورة من مستند المناقصة واستخراج جميع البنود والمواصفات الفنية. - قدم النتائج بتنسيق منظم مع ترقيم البنود وتصنيفها حسب الأقسام. - """ - elif analysis_type == "contract_terms": - return """ - قم بتحليل هذه الصورة من العقد واستخراج جميع الشروط التعاقدية المهمة. - حدد الالتزامات والحقوق لكل طرف، والمواعيد النهائية، وشروط الدفع، والضمانات. - """ - elif analysis_type == "quantities": - return """ - قم بتحليل هذه الصورة من جدول الكميات واستخراج جميع البنود والكميات والأسعار. - قدم ملخصاً للتكاليف الإجمالية وتحليلاً للبنود الرئيسية. - """ - elif analysis_type == "legal_requirements": - return """ - قم بتحليل هذه الصورة واستخراج جميع المتطلبات القانونية والتنظيمية. - حدد الشروط القانونية الهامة، والمتطلبات التنظيمية، والالتزامات القانونية. - """ - else: # comprehensive - return """ - قم بتحليل هذه الصورة من مستند المناقصة وتقديم تحليل شامل للمحتوى. - استخرج المعلومات الرئيسية، والبنود المهمة، والمواصفات الفنية، والشروط التعاقدية. - """ + return f"حدث خطأ أثناء تحليل المحتوى المحلي: {str(e)}" -# إضافة دوال تبويبات واجهة المستخدم -def _render_ai_assistant_tab(self): - """عرض تبويب المساعد الذكي""" - st.header("المساعد الذكي") - - # اختيار نموذج الذكاء الاصطناعي - models = self.claude_service.get_available_models() - model_options = list(models.keys()) - model_descriptions = list(models.values()) - - col1, col2 = st.columns([1, 3]) - with col1: - selected_index = st.selectbox( - "اختر نموذج التحليل", - range(len(model_options)), - format_func=lambda i: model_descriptions[i] - ) - st.session_state.selected_model = model_options[selected_index] - - # عرض المحادثة السابقة - for message in st.session_state.messages: - with st.chat_message(message["role"]): - st.markdown(message["content"]) +class AIAssistantApp: + """Main application class for AI Assistant""" - # إدخال المستخدم - if prompt := st.chat_input("اكتب سؤالك هنا..."): - # إضافة رسالة المستخدم إلى المحادثة - st.session_state.messages.append({"role": "user", "content": prompt}) - with st.chat_message("user"): - st.markdown(prompt) + def __init__(self): + self.claude_service = ClaudeAIService() + self.chat_history = [] + self.selected_model = "claude-3-5-sonnet-20240620" + self.temperature = 0.7 - # معالجة الرسالة وإضافة رد المساعد - with st.chat_message("assistant"): - with st.spinner("جاري التفكير..."): - # إرسال المحادثة إلى Claude - response = self.claude_service.chat_completion( - st.session_state.messages, - model_name=st.session_state.selected_model - ) - - if "error" in response: - st.error(response["error"]) - content = "عذراً، حدث خطأ أثناء معالجة طلبك. يرجى المحاولة مرة أخرى." - else: - content = response["content"] + # Try to import specialized modules + try: + import ezdxf + self.has_ezdxf = True + except ImportError: + self.has_ezdxf = False + + try: + import ifcopenshell + self.has_ifcopenshell = True + except ImportError: + self.has_ifcopenshell = False + + def _render_sidebar(self): + """Render the sidebar with AI model selection""" + with st.sidebar: + st.title("إعدادات الذكاء الاصطناعي") + + # Model selection + available_models = self.claude_service.get_available_models() + self.selected_model = st.selectbox( + "اختر نموذج الذكاء الاصطناعي", + available_models, + index=0 + ) + + # Temperature slider + self.temperature = st.slider( + "درجة الإبداعية", + min_value=0.0, + max_value=1.0, + value=0.7, + step=0.1, + help="قيمة أقل تعني إجابات أكثر تحديدًا، قيمة أعلى تعني إجابات أكثر إبداعًا" + ) + + # API key status + if self.claude_service.is_available(): + st.success("✅ تم تكوين مفتاح API بنجاح") + else: + st.error("❌ لم يتم تكوين مفتاح API") + st.info("أضف مفتاح API في إعدادات Hugging Face باسم 'anthropic'") - st.markdown(content) - - # إضافة رد المساعد إلى المحادثة - st.session_state.messages.append({"role": "assistant", "content": content}) - - # إعادة تحميل الصفحة لتحديث المحادثة - st.rerun() - -def _render_document_analysis_tab(self): - """عرض تبويب تحليل المستندات""" - st.header("تحليل المستندات") - - # اختيار نوع التحليل - analysis_type = st.selectbox( - "اختر نوع التحليل", - [ - "تحليل شامل", - "استخراج البنود والمواصفات", - "استخراج الشروط التعاقدية", - "تحليل الكميات", - "تحليل المتطلبات القانونية" - ] - ) - - # تحويل اختيار المستخدم إلى قيمة برمجية - analysis_type_map = { - "تحليل شامل": "comprehensive", - "استخراج البنود والمواصفات": "items_extraction", - "استخراج الشروط التعاقدية": "contract_terms", - "تحليل الكميات": "quantities", - "تحليل المتطلبات القانونية": "legal_requirements" - } - - selected_analysis = analysis_type_map.get(analysis_type, "comprehensive") - - # رفع الملف - uploaded_file = st.file_uploader( - "ارفع ملف المستند (PDF, Word, صورة)", - type=["pdf", "docx", "doc", "jpg", "jpeg", "png"] - ) - - if uploaded_file is not None: - # حفظ الملف مؤقتاً - with tempfile.NamedTemporaryFile(delete=False, suffix=f".{uploaded_file.name.split('.')[-1]}") as tmp_file: - tmp_file.write(uploaded_file.getvalue()) - tmp_path = tmp_file.name + def _extract_text_from_pdf(self, pdf_file): + """Extract text from PDF file""" + text = "" + try: + pdf_reader = PyPDF2.PdfReader(pdf_file) + for page_num in range(len(pdf_reader.pages)): + text += pdf_reader.pages[page_num].extract_text() + "\n" + except Exception as e: + st.error(f"خطأ في استخراج النص من ملف PDF: {e}") + return text - # عرض معلومات الملف - file_details = { - "اسم الملف": uploaded_file.name, - "نوع الملف": uploaded_file.type, - "حجم الملف": f"{uploaded_file.size / 1024:.2f} كيلوبايت" - } + def _extract_text_from_docx(self, docx_file): + """Extract text from DOCX file""" + text = "" + try: + doc = docx.Document(docx_file) + for para in doc.paragraphs: + text += para.text + "\n" + except Exception as e: + st.error(f"خطأ في استخراج النص من ملف DOCX: {e}") + return text - st.json(file_details) + def _render_ai_assistant_tab(self): + """Render the AI Assistant tab""" + st.header("المساعد الذكي") - # زر التحليل - if st.button("تحليل المستند"): - with st.spinner("جاري تحليل المستند..."): - # تحليل المستند باستخدام Claude - result = self.claude_service.analyze_document( - tmp_path, - analysis_type=selected_analysis, - model_name=st.session_state.selected_model - ) + # Check if Claude AI is available + if not self.claude_service.is_available(): + st.warning("لم يتم تكوين مفتاح API للذكاء الاصطناعي. يرجى إضافة المفتاح في إعدادات Hugging Face.") + return + + # Initialize chat history + if "messages" not in st.session_state: + st.session_state.messages = [] + + # Display chat history + for message in st.session_state.messages: + with st.chat_message(message["role"]): + st.markdown(message["content"]) - if "error" in result: - st.error(result["error"]) - else: - st.success("تم تحليل المستند بنجاح!") - st.subheader("نتائج التحليل") - st.markdown(result["content"]) + # Chat input + if prompt := st.chat_input("اكتب رسالتك هنا..."): + # Add user message to chat history + st.session_state.messages.append({"role": "user", "content": prompt}) + + # Display user message + with st.chat_message("user"): + st.markdown(prompt) + + # Get AI response + with st.chat_message("assistant"): + with st.spinner("جاري التفكير..."): + messages = [ + {"role": m["role"], "content": m["content"]} + for m in st.session_state.messages + ] + + response = self.claude_service.chat( + messages=messages, + model=self.selected_model, + temperature=self.temperature + ) - # حفظ النتائج في حالة الجلسة - if "document_analysis" not in st.session_state.analysis_results: - st.session_state.analysis_results["document_analysis"] = [] + st.markdown(response) - st.session_state.analysis_results["document_analysis"].append({ - "file_name": uploaded_file.name, - "analysis_type": analysis_type, - "result": result, - "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") - }) + # Add assistant response to chat history + st.session_state.messages.append({"role": "assistant", "content": response}) + + # Clear chat button + if st.button("مسح المحادثة"): + st.session_state.messages = [] + st.rerun() + + def _render_document_analysis_tab(self): + """Render the Document Analysis tab""" + st.header("تحليل المستندات") - # حذف الملف المؤقت بعد الانتهاء - try: - os.unlink(tmp_path) - except: - pass - -def _render_contract_analysis_tab(self): - """عرض تبويب تحليل العقود""" - st.header("تحليل العقود") - - if not CONTRACT_ANALYZER_AVAILABLE: - st.warning("وحدة تحليل العقود غير متاحة حالياً.") - return - - # اختيار نوع التحليل - analysis_type = st.selectbox( - "اختر نوع التحليل", - [ - "تحليل شامل للعقد", - "تحليل الشروط المالية", - "تحليل الشروط القانونية", - "تحليل المخاطر التعاقدية", - "تحليل الجدول الزمني" - ], - key="contract_analysis_type" - ) - - # تحويل اختيار المستخدم إلى قيمة برمجية - analysis_type_map = { - "تحليل شامل للعقد": "comprehensive", - "تحليل الشروط المالية": "financial", - "تحليل الشروط القانونية": "legal", - "تحليل المخاطر التعاقدية": "risk", - "تحليل الجدول الزمني": "timeline" - } - - selected_analysis = analysis_type_map.get(analysis_type, "comprehensive") - - # رفع الملف - uploaded_file = st.file_uploader( - "ارفع ملف العقد (PDF, Word)", - type=["pdf", "docx", "doc"], - key="contract_file_uploader" - ) - - if uploaded_file is not None: - # حفظ الملف مؤقتاً - with tempfile.NamedTemporaryFile(delete=False, suffix=f".{uploaded_file.name.split('.')[-1]}") as tmp_file: - tmp_file.write(uploaded_file.getvalue()) - tmp_path = tmp_file.name + # Check if Claude AI is available + if not self.claude_service.is_available(): + st.warning("لم يتم تكوين مفتاح API للذكاء الاصطناعي. يرجى إضافة المفتاح في إعدادات Hugging Face.") + return + + # Analysis type selection + analysis_type = st.selectbox( + "اختر نوع التحليل", + options=[ + "استخراج البنود والمواصفات", + "استخراج الشروط التعاقدية", + "تحليل الكميات", + "تحليل المتطلبات القانونية" + ] + ) - # عرض معلومات الملف - file_details = { - "اسم الملف": uploaded_file.name, - "نوع الملف": uploaded_file.type, - "حجم الملف": f"{uploaded_file.size / 1024:.2f} كيلوبايت" + # Map UI selection to API parameter + analysis_type_map = { + "استخراج البنود والمواصفات": "items_extraction", + "استخراج الشروط التعاقدية": "contract_terms", + "تحليل الكميات": "quantities", + "تحليل المتطلبات القانونية": "legal_requirements" } - st.json(file_details) + # File upload + uploaded_file = st.file_uploader( + "ارفع ملف المستند (PDF, Word, صورة)", + type=["pdf", "docx", "txt", "png", "jpg", "jpeg"] + ) - # زر التحليل - if st.button("تحليل العقد"): - with st.spinner("جاري تحليل العقد..."): - # تحليل العقد باستخدام محلل العقود - result = self.contract_analyzer.analyze_contract( - tmp_path, - analysis_type=selected_analysis - ) + if uploaded_file is not None: + # Display file info + file_details = { + "اسم الملف": uploaded_file.name, + "نوع الملف": uploaded_file.type, + "حجم الملف": f"{uploaded_file.size / 1024:.2f} كيلوبايت" + } + + st.json(file_details) + + # Extract text based on file type + document_text = "" + file_content = uploaded_file.getvalue() + + if uploaded_file.type == "application/pdf": + with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as temp_file: + temp_file.write(file_content) + temp_file_path = temp_file.name - if isinstance(result, dict) and "error" in result: - st.error(result["error"]) - else: - st.success("تم تحليل العقد بنجاح!") - - # عرض نتائج التحليل - st.subheader("ملخص التحليل") - st.markdown(result.get("summary", "لا يوجد ملخص متاح")) - - st.subheader("البنود الرئيسية") - for i, clause in enumerate(result.get("key_clauses", [])): - st.markdown(f"**{i+1}. {clause['title']}**") - st.markdown(clause["description"]) - - st.subheader("المخاطر المحتملة") - risk_data = [] - for risk in result.get("risks", []): - risk_data.append({ - "المخاطرة": risk["description"], - "المستوى": risk["level"], - "التأثير": risk["impact"] - }) - - if risk_data: - st.dataframe(pd.DataFrame(risk_data)) + with open(temp_file_path, "rb") as pdf_file: + document_text = self._extract_text_from_pdf(pdf_file) + + os.unlink(temp_file_path) + + elif uploaded_file.type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document": + with tempfile.NamedTemporaryFile(delete=False, suffix=".docx") as temp_file: + temp_file.write(file_content) + temp_file_path = temp_file.name + + with open(temp_file_path, "rb") as docx_file: + document_text = self._extract_text_from_docx(docx_file) + + os.unlink(temp_file_path) + + elif uploaded_file.type == "text/plain": + document_text = file_content.decode("utf-8") + + elif uploaded_file.type in ["image/png", "image/jpeg", "image/jpg"]: + # For images, we'll use image analysis + if st.button("تحليل المستند"): + with st.spinner("جاري تحليل المستند..."): + prompt = "" + if analysis_type == "استخراج البنود والمواصفات": + prompt = "هذا مستند يحتوي على بنود ومواصفات. قم بتحليله واستخراج جميع البنود والمواصفات الفنية. قم بتنظيمها في قائمة منسقة مع الأرقام والتفاصيل." + elif analysis_type == "استخراج الشروط التعاقدية": + prompt = "هذا عقد. قم بتحليله واستخراج جميع الشروط التعاقدية الهامة. حدد الالتزامات والحقوق والمواعيد النهائية والغرامات." + elif analysis_type == "تحليل الكميات": + prompt = "هذا مستند يحتوي على جدول كميات. قم بتحليله واستخراج جدول الكميات. قم بتنظيم المعلومات في جدول يتضم�� البند والكمية والوحدة والسعر إن وجد." + elif analysis_type == "تحليل المتطلبات القانونية": + prompt = "هذا مستند يحتوي على متطلبات قانونية. قم بتحليله وتحديد جميع المتطلبات القانونية والتنظيمية. اشرح الالتزامات القانونية والمخاطر المحتملة." + + analysis_result = self.claude_service.analyze_image( + file_content, + prompt, + model=self.selected_model + ) + + st.subheader("نتائج التحليل") + st.write(analysis_result) + + return + + # Analyze text document + if document_text and st.button("تحليل المستند"): + with st.spinner("جاري تحليل المستند..."): + analysis_result = self.claude_service.analyze_document( + document_text, + analysis_type_map[analysis_type], + model=self.selected_model + ) - # حفظ النتائج في حالة الجلسة - if "contract_analysis" not in st.session_state.analysis_results: - st.session_state.analysis_results["contract_analysis"] = [] + st.subheader("نتائج التحليل") + st.write(analysis_result) - st.session_state.analysis_results["contract_analysis"].append({ - "file_name": uploaded_file.name, - "analysis_type": analysis_type, - "result": result, - "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") - }) + def _render_contract_analysis_tab(self): + """Render the Contract Analysis tab""" + st.header("تحليل العقود") - # حذف الملف المؤقت بعد الانتهاء - try: - os.unlink(tmp_path) - except: - pass - -def _render_risk_analysis_tab(self): - """عرض تبويب تحليل المخاطر""" - st.header("تحليل المخاطر") - - # إدخال بيانات المشروع - st.subheader("بيانات المشروع") - - col1, col2 = st.columns(2) - with col1: - project_name = st.text_input("اسم المشروع") - project_type = st.selectbox( - "نوع المشروع", - ["إنشائي", "طرق", "جسور", "مباني", "بنية تحتية", "أخرى"] + # Check if Claude AI is available + if not self.claude_service.is_available(): + st.warning("لم يتم تكوين مفتاح API للذكاء الاصطناعي. يرجى إضافة المفتاح في إعدادات Hugging Face.") + return + + # File upload + uploaded_file = st.file_uploader( + "ارفع ملف العقد (PDF, Word, صورة)", + type=["pdf", "docx", "txt", "png", "jpg", "jpeg"] ) - - with col2: - project_value = st.number_input("قيمة المشروع (ريال)", min_value=0, value=1000000) - project_duration = st.number_input("مدة المشروع (شهر)", min_value=1, value=12) - - # تحليل المخاطر - if st.button("تحليل المخاطر"): - with st.spinner("جاري تحليل المخاطر..."): - # محاكاة تحليل المخاطر باستخدام الذكاء الاصطناعي - time.sleep(1) # محاكاة وقت المعالجة - - # إنشاء مصفوفة المخاطر - st.subheader("مصفوفة المخاطر") - - # إنشاء بيانات مصفوفة المخاطر - risk_matrix = np.array([ - [1, 2, 3], - [2, 4, 6], - [3, 6, 9] - ]) - - # إنشاء مصفوفة المخاطر باستخدام plotly - fig = px.imshow( - risk_matrix, - labels=dict(x="احتمال الحدوث", y="التأثير", color="درجة الخطورة"), - x=["منخفض", "متوسط", "مرتفع"], - y=["منخفض", "متوسط", "مرتفع"], - color_continuous_scale=["green", "yellow", "red"], - text_auto=True - ) - - # تحسين مظهر المصفوفة - fig.update_layout( - width=600, - height=500, - title="مصفوفة المخاطر", - font=dict(size=14), - coloraxis_colorbar=dict( - title=dict( - text="درجة الخطورة", - side="right" - ) - ) - ) - - st.plotly_chart(fig) + + if uploaded_file is not None: + # Display file info + file_details = { + "اسم الملف": uploaded_file.name, + "نوع الملف": uploaded_file.type, + "حجم الملف": f"{uploaded_file.size / 1024:.2f} كيلوبايت" + } - # عرض جدول المخاطر - st.subheader("تحليل المخاطر المحتملة") + st.json(file_details) - # إنشاء بيانات المخاطر - risks_data = [ - {"المخاطرة": "تأخر توريد المواد", "الاحتمال": "مرتفع", "التأثير": "متوسط", "الدرجة": 6, "الإجراء": "التعاقد مع موردين بدلاء"}, - {"المخاطرة": "تغير أسعار المواد", "الاحتمال": "متوسط", "التأثير": "مرتفع", "الدرجة": 6, "الإجراء": "تضمين بند تعديل الأسعار في العقد"}, - {"المخاطرة": "ظروف جوية قاسية", "الاحتمال": "منخفض", "التأثير": "مرتفع", "الدرجة": 3, "الإجراء": "وضع خطة طوارئ للعمل"}, - {"المخاطرة": "نقص العمالة", "الاحتمال": "متوسط", "التأثير": "متوسط", "الدرجة": 4, "الإجراء": "التعاقد مع شركات توريد عمالة"}, - {"المخاطرة": "مشاكل فنية", "الاحتمال": "متوسط", "التأثير": "مرتفع", "الدرجة": 6, "الإجراء": "توفير استشاريين فنيين"} - ] + # Extract text based on file type + contract_text = "" + file_content = uploaded_file.getvalue() - # عرض جدول المخاطر - st.dataframe(pd.DataFrame(risks_data)) + if uploaded_file.type == "application/pdf": + with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as temp_file: + temp_file.write(file_content) + temp_file_path = temp_file.name + + with open(temp_file_path, "rb") as pdf_file: + contract_text = self._extract_text_from_pdf(pdf_file) + + os.unlink(temp_file_path) + + elif uploaded_file.type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document": + with tempfile.NamedTemporaryFile(delete=False, suffix=".docx") as temp_file: + temp_file.write(file_content) + temp_file_path = temp_file.name + + with open(temp_file_path, "rb") as docx_file: + contract_text = self._extract_text_from_docx(docx_file) + + os.unlink(temp_file_path) + + elif uploaded_file.type == "text/plain": + contract_text = file_content.decode("utf-8") + + elif uploaded_file.type in ["image/png", "image/jpeg", "image/jpg"]: + # For images, we'll use image analysis + if st.button("تحليل العقد"): + with st.spinner("جاري تحليل العقد..."): + prompt = "هذا عقد. قم بتحليله وتحديد الأطراف المتعاقدة، والالتزامات الرئيسية، والشروط الهامة، والمواعيد النهائية، والغرامات، وآليات فض النزاعات. قدم ملخصًا شاملاً للعقد." + + analysis_result = self.claude_service.analyze_image( + file_content, + prompt, + model=self.selected_model + ) + + st.subheader("نتائج تحليل العقد") + st.write(analysis_result) + + return + + # Analyze text document + if contract_text and st.button("تحليل العقد"): + with st.spinner("جاري تحليل العقد..."): + analysis_result = self.claude_service.analyze_document( + contract_text, + "contract_terms", + model=self.selected_model + ) + + st.subheader("نتائج تحليل العقد") + st.write(analysis_result) + + def _render_risk_analysis_tab(self): + """Render the Risk Analysis tab""" + st.header("تحليل المخاطر") + + # Check if Claude AI is available + if not self.claude_service.is_available(): + st.warning("لم يتم تكوين مفتاح API للذكاء الاصطناعي. يرجى إضافة المفتاح في إعدادات Hugging Face.") + return - # تحليل ذكي للمخاطر باستخدام Claude - st.subheader("تحليل ذكي للمخاطر") + # Project information + st.subheader("معلومات المشروع") + + col1, col2 = st.columns(2) + with col1: + project_name = st.text_input("اسم المشروع") + project_duration = st.number_input("مدة المشروع (بالأشهر)", min_value=1, value=12) - # إنشاء توجيه للنموذج - prompt = f""" - قم بتحليل المخاطر المحتملة لمشروع بالمواصفات التالية: - - اسم المشروع: {project_name} - - نوع المشروع: {project_type} - - قيمة المشروع: {project_value} ريال - - مدة المشروع: {project_duration} شهر + with col2: + project_budget = st.number_input("ميزانية المشروع (بالريال)", min_value=1000, value=1000000) + project_type = st.selectbox( + "نوع المشروع", + options=["إنشائي", "تطوير برمجيات", "استشارات", "توريدات", "أخرى"] + ) - قدم تحليلاً شاملاً للمخاطر المحتملة وتوصيات للتخفيف منها. - """ + # Risk factors + st.subheader("عوامل المخاطر") + + risk_factors = {} + + col1, col2 = st.columns(2) + with col1: + risk_factors["technical"] = st.slider("المخاطر التقنية", 1, 10, 5) + risk_factors["schedule"] = st.slider("مخاطر الجدول الزمني", 1, 10, 5) + risk_factors["resource"] = st.slider("مخاطر الموارد", 1, 10, 5) - # استدعاء النموذج - response = self.claude_service.chat_completion( - [{"role": "user", "content": prompt}], - model_name=st.session_state.selected_model - ) + with col2: + risk_factors["financial"] = st.slider("المخاطر المالية", 1, 10, 5) + risk_factors["legal"] = st.slider("المخاطر القانونية", 1, 10, 5) + risk_factors["external"] = st.slider("المخاطر الخارجية", 1, 10, 5) - if "error" in response: - st.error(response["error"]) - else: - st.markdown(response["content"]) - - # حفظ النتائج في حالة الجلسة - if "risk_analysis" not in st.session_state.analysis_results: - st.session_state.analysis_results["risk_analysis"] = [] - - st.session_state.analysis_results["risk_analysis"].append({ + # Risk matrix visualization + st.subheader("مصفوفة المخاطر") + + # Create risk matrix data + impact = [1, 2, 3] + probability = [1, 2, 3] + risk_matrix = np.array([ + [1, 2, 3], + [2, 4, 6], + [3, 6, 9] + ]) + + # Create heatmap using Plotly + fig = go.Figure(data=go.Heatmap( + z=risk_matrix, + x=[reshape_arabic_text("منخفض"), reshape_arabic_text("متوسط"), reshape_arabic_text("مرتفع")], + y=[reshape_arabic_text("منخفض"), reshape_arabic_text("متوسط"), reshape_arabic_text("مرتفع")], + colorscale=[ + [0, 'green'], + [0.33, 'lightgreen'], + [0.66, 'orange'], + [1, 'red'] + ], + showscale=True, + colorbar=dict( + title=dict( + text=reshape_arabic_text("درجة الخطورة"), + side="right" + ) + ), + text=risk_matrix, + texttemplate="%{text}", + textfont={"size": 20} + )) + + # Update layout + fig.update_layout( + title=reshape_arabic_text("مصفوفة المخاطر"), + xaxis_title=reshape_arabic_text("التأثير"), + yaxis_title=reshape_arabic_text("الاحتمالية"), + height=400, + width=500, + font=dict(size=14) + ) + + st.plotly_chart(fig) + + # Analyze risks + if st.button("تحليل المخاطر"): + with st.spinner("جاري تحليل المخاطر..."): + # Prepare project data + project_data = { "project_name": project_name, - "project_type": project_type, - "project_value": project_value, "project_duration": project_duration, - "result": response["content"], - "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") - }) - -def _render_cost_prediction_tab(self): - """عرض تبويب التنبؤ بالتكاليف""" - st.header("تحليل وتنبؤ التكاليف") - - # إدخال بيانات المشروع - st.subheader("بيانات المشروع") - - col1, col2 = st.columns(2) - with col1: - project_name = st.text_input("اسم المشروع", key="cost_project_name") - project_type = st.selectbox( - "نوع المشروع", - ["إنشائي", "طرق", "جسور", "مباني", "بنية تحتية", "أخرى"], - key="cost_project_type" - ) - project_area = st.number_input("المساحة (متر مربع)", min_value=0, value=1000) - - with col2: - project_location = st.selectbox( - "الموقع", - ["الرياض", "جدة", "الدمام", "مكة", "المدينة", "أخرى"] - ) - project_quality = st.selectbox( - "مستوى الجودة", - ["اقتصادي", "متوسط", "فاخر", "ممتاز"] - ) - has_basement = st.checkbox("يتضمن بدروم") - - # أقسام التكاليف - st.subheader("أقسام التكاليف") - - col1, col2, col3 = st.columns(3) - with col1: - structural_percent = st.slider("الهيكل الإنشائي (%)", 0, 100, 35) - with col2: - finishing_percent = st.slider("التشطيبات (%)", 0, 100, 40) - with col3: - mep_percent = st.slider("الكهروميكانيك (%)", 0, 100, 25) - - # التنبؤ بالتكاليف - if st.button("تحليل التكاليف"): - with st.spinner("جاري تحليل التكاليف..."): - # محاكاة تحليل التكاليف باستخدام الذكاء الاصطناعي - time.sleep(1) # محاكاة وقت المعالجة - - # حساب التكلفة التقديرية بناءً على المدخلات - base_cost = 0 - if project_type == "إنشائي": - base_cost = 2000 - elif project_type == "طرق": - base_cost = 1500 - elif project_type == "جسور": - base_cost = 3000 - elif project_type == "مباني": - base_cost = 2500 - elif project_type == "بنية تحتية": - base_cost = 1800 - else: - base_cost = 2200 - - # تعديل التكلفة بناءً على الموقع - location_factor = 1.0 - if project_location == "الرياض": - location_factor = 1.1 - elif project_location == "جدة": - location_factor = 1.15 - elif project_location == "الدمام": - location_factor = 1.05 - elif project_location == "مكة": - location_factor = 1.2 - elif project_location == "المدينة": - location_factor = 1.1 - - # تعديل التكلفة بناءً على مستوى الجودة - quality_factor = 1.0 - if project_quality == "اقتصادي": - quality_factor = 0.8 - elif project_quality == "متوسط": - quality_factor = 1.0 - elif project_quality == "فاخر": - quality_factor = 1.3 - elif project_quality == "ممتاز": - quality_factor = 1.5 - - # تعديل التكلفة إذا كان يتضمن بدروم - basement_factor = 1.2 if has_basement else 1.0 - - # حساب التكلفة الإجمالية - total_cost = base_cost * project_area * location_factor * quality_factor * basement_factor - - # حساب تكلفة كل قسم - structural_cost = total_cost * (structural_percent / 100) - finishing_cost = total_cost * (finishing_percent / 100) - mep_cost = total_cost * (mep_percent / 100) - - # عرض النتائج - st.subheader("نتائج تحليل التكاليف") - - col1, col2 = st.columns(2) - with col1: - st.metric("التكلفة الإجمالية التقديرية", f"{total_cost:,.2f} ريال") - st.metric("تكلفة المتر المربع", f"{total_cost/project_area:,.2f} ريال/م²") + "project_budget": project_budget, + "project_type": project_type, + "risk_factors": risk_factors + } + + # Get AI analysis + analysis_result = self.claude_service.analyze_risk( + project_data, + model=self.selected_model + ) + + st.subheader("نتائج تحليل المخاطر") + st.write(analysis_result) + + def _render_cost_prediction_tab(self): + """Render the Cost Prediction tab""" + st.header("تحليل التكاليف") + + # Check if Claude AI is available + if not self.claude_service.is_available(): + st.warning("لم يتم تكوين مفتاح API للذكاء الاصطناعي. يرجى إضافة المفتاح في إعدادات Hugging Face.") + return - with col2: - st.metric("تكلفة الهيكل الإنشائي", f"{structural_cost:,.2f} ريال") - st.metric("تكلفة التشطيبات", f"{finishing_cost:,.2f} ريال") - st.metric("تكلفة الكهروميكانيك", f"{mep_cost:,.2f} ريال") - - # عرض الرسم البياني للتكاليف - cost_data = pd.DataFrame({ - "القسم": ["الهيكل الإنشائي", "التشطيبات", "الكهروميكانيك"], - "التكلفة": [structural_cost, finishing_cost, mep_cost] - }) - - fig = px.pie( - cost_data, - values="التكلفة", - names="القسم", - title="توزيع التكاليف", - color_discrete_sequence=px.colors.qualitative.Set2 - ) + # Project information + st.subheader("معلومات المشروع") + + col1, col2 = st.columns(2) + with col1: + project_name = st.text_input("اسم المشروع", key="cost_project_name") + project_duration = st.number_input("مدة المشروع (بالأشهر)", min_value=1, value=12, key="cost_project_duration") - fig.update_layout( - font=dict(size=14), - legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1) + with col2: + project_budget = st.number_input("ميزانية المشروع (بالريال)", min_value=1000, value=1000000, key="cost_project_budget") + project_type = st.selectbox( + "نوع المشروع", + options=["إنشائي", "تطوير برمجيات", "استشارات", "توريدات", "أخرى"], + key="cost_project_type" ) - st.plotly_chart(fig) + # Cost categories + st.subheader("فئات التكاليف") + + cost_categories = {} + + col1, col2 = st.columns(2) + with col1: + cost_categories["labor"] = st.number_input("تكاليف العمالة", min_value=0, value=400000) + cost_categories["materials"] = st.number_input("تكاليف المواد", min_value=0, value=300000) + cost_categories["equipment"] = st.number_input("تكاليف المعدات", min_value=0, value=150000) - # تحليل ذكي للتكاليف باستخدام Claude - st.subheader("تحليل ذكي للتكاليف") - - # إنشاء توجيه للنموذج - prompt = f""" - قم بتحليل تكاليف مشروع بالمواصفات التالية: - - اسم المشروع: {project_name} - - نوع المشروع: {project_type} - - المساحة: {project_area} متر مربع - - الموقع: {project_location} - - مستوى الجودة: {project_quality} - - يتضمن بدروم: {"نعم" if has_basement else "لا"} - - التكلفة الإجمالية التقديرية: {total_cost:,.2f} ريال - تكلفة المتر المربع: {total_cost/project_area:,.2f} ريال/م² - - توزيع التكاليف: - - الهيكل الإنشائي: {structural_cost:,.2f} ريال ({structural_percent}%) - - التشطيبات: {finishing_cost:,.2f} ريال ({finishing_percent}%) - - الكهروميكانيك: {mep_cost:,.2f} ريال ({mep_percent}%) - - قدم تحليلاً شاملاً للتكاليف وتوصيات لتحسين الكفاءة وتقليل التكاليف. - """ - - # استدعاء النموذج - response = self.claude_service.chat_completion( - [{"role": "user", "content": prompt}], - model_name=st.session_state.selected_model - ) + with col2: + cost_categories["subcontractors"] = st.number_input("تكاليف المقاولي�� من الباطن", min_value=0, value=100000) + cost_categories["overhead"] = st.number_input("التكاليف العامة", min_value=0, value=50000) + cost_categories["contingency"] = st.number_input("احتياطي الطوارئ", min_value=0, value=0) - if "error" in response: - st.error(response["error"]) - else: - st.markdown(response["content"]) - - # حفظ النتائج في حالة الجلسة - if "cost_analysis" not in st.session_state.analysis_results: - st.session_state.analysis_results["cost_analysis"] = [] - - st.session_state.analysis_results["cost_analysis"].append({ - "project_name": project_name, - "project_type": project_type, - "project_area": project_area, - "project_location": project_location, - "project_quality": project_quality, - "has_basement": has_basement, - "total_cost": total_cost, - "result": response["content"], - "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") - }) - -def _render_local_content_tab(self): - """عرض تبويب تحليل المحتوى المحلي""" - st.header("تحليل المحتوى المحلي") - - # إدخال بيانات المشروع - st.subheader("بيانات المشروع") - - col1, col2 = st.columns(2) - with col1: - project_name = st.text_input("اسم المشروع", key="local_project_name") - project_type = st.selectbox( - "نوع المشروع", - ["إنشائي", "طرق", "جسور", "مباني", "بنية تحتية", "أخرى"], - key="local_project_type" - ) - - with col2: - project_value = st.number_input("قيمة المشروع (ريال)", min_value=0, value=1000000, key="local_project_value") - project_duration = st.number_input("مدة المشروع (شهر)", min_value=1, value=12, key="local_project_duration") - - # إدخال بيانات المحتوى المحلي - st.subheader("بيانات المحتوى المحلي") - - # إضافة مكونات المشروع - components = [] - - st.write("أضف مكونات المشروع:") - - # نموذج إضافة مكون جديد - with st.form("add_component_form"): - col1, col2, col3 = st.columns(3) + # Calculate total cost + total_cost = sum(cost_categories.values()) - with col1: - component_name = st.text_input("اسم المكون") + # Cost visualization + st.subheader("توزيع التكاليف") - with col2: - component_value = st.number_input("القيمة (ريال)", min_value=0) + # Create pie chart using Plotly + labels = [ + reshape_arabic_text("العمالة"), + reshape_arabic_text("المواد"), + reshape_arabic_text("المعدات"), + reshape_arabic_text("المقاولين من الباطن"), + reshape_arabic_text("التكاليف العامة"), + reshape_arabic_text("احتياطي الطوارئ") + ] - with col3: - local_percentage = st.slider("نسبة المحتوى المحلي (%)", 0, 100, 50) + values = list(cost_categories.values()) - submitted = st.form_submit_button("إضافة المكون") + fig = go.Figure(data=[go.Pie( + labels=labels, + values=values, + textinfo='label+percent', + insidetextorientation='radial', + hole=0.3 + )]) - if submitted and component_name and component_value > 0: - # إضافة المكون إلى القائمة - if "local_content_components" not in st.session_state: - st.session_state.local_content_components = [] - - st.session_state.local_content_components.append({ - "name": component_name, - "value": component_value, - "local_percentage": local_percentage - }) - - # عرض المكونات المضافة - if "local_content_components" in st.session_state and st.session_state.local_content_components: - st.subheader("المكونات المضافة") + fig.update_layout( + title=reshape_arabic_text("توزيع التكاليف"), + height=400, + width=600 + ) - components_data = [] - for i, component in enumerate(st.session_state.local_content_components): - components_data.append({ - "#": i + 1, - "المكون": component["name"], - "القيمة (ريال)": component["value"], - "نسبة المحت��ى المحلي (%)": component["local_percentage"], - "قيمة المحتوى المحلي (ريال)": component["value"] * (component["local_percentage"] / 100) - }) + st.plotly_chart(fig) - st.dataframe(pd.DataFrame(components_data)) + # Display total cost + st.subheader("إجمالي التكاليف") + st.write(f"{total_cost:,} ريال") - # زر حذف جميع المكونات - if st.button("حذف جميع المكونات"): - st.session_state.local_content_components = [] - st.rerun() - - # تحليل المحتوى المحلي - if st.button("تحليل المحتوى المحلي"): - if "local_content_components" not in st.session_state or not st.session_state.local_content_components: - st.warning("يرجى إضافة مكونات المشروع أولاً") + # Cost variance + st.subheader("تباين التكاليف") + + cost_variance = total_cost - project_budget + variance_percentage = (cost_variance / project_budget) * 100 if project_budget > 0 else 0 + + if cost_variance > 0: + st.error(f"تجاوز الميزانية بمقدار {cost_variance:,} ريال ({variance_percentage:.2f}%)") + elif cost_variance < 0: + st.success(f"أقل من الميزانية بمقدار {abs(cost_variance):,} ريال ({abs(variance_percentage):.2f}%)") else: - with st.spinner("جاري تحليل المحتوى المحلي..."): - # حساب إجمالي قيمة المشروع من المكونات - total_value = sum(component["value"] for component in st.session_state.local_content_components) + st.info("التكاليف مطابقة للميزانية") + + # Analyze costs + if st.button("تحليل التكاليف"): + with st.spinner("جاري تحليل التكاليف..."): + # Prepare cost data + cost_data = { + "project_name": project_name, + "project_duration": project_duration, + "project_budget": project_budget, + "project_type": project_type, + "cost_categories": cost_categories, + "total_cost": total_cost, + "cost_variance": cost_variance, + "variance_percentage": variance_percentage + } - # حساب قيمة المحتوى المحلي - local_content_value = sum( - component["value"] * (component["local_percentage"] / 100) - for component in st.session_state.local_content_components + # Get AI analysis + analysis_result = self.claude_service.analyze_cost( + cost_data, + model=self.selected_model ) - # حساب نسبة المحتوى المحلي الإجمالية - overall_local_percentage = (local_content_value / total_value) * 100 if total_value > 0 else 0 - - # عرض النتائج - st.subheader("نتائج تحليل المحتوى المحلي") + st.subheader("نتائج تحليل التكاليف") + st.write(analysis_result) - col1, col2 = st.columns(2) - with col1: - st.metric("إجمالي قيمة المشروع", f"{total_value:,.2f} ريال") - st.metric("قيمة المحتوى المحلي", f"{local_content_value:,.2f} ريال") + def _render_local_content_tab(self): + """Render the Local Content tab""" + st.header("تحليل المحتوى المحلي") + + # Check if Claude AI is available + if not self.claude_service.is_available(): + st.warning("لم يتم تكوين مفتاح API للذكاء الاصطناعي. يرجى إضافة المفتاح في إعدادات Hugging Face.") + return + + # Initialize local content components if not in session state + if "local_content_components" not in st.session_state: + st.session_state.local_content_components = [] + + # Project information + st.subheader("معلومات المشروع") + + col1, col2 = st.columns(2) + with col1: + project_name = st.text_input("اسم المشروع", key="lc_project_name") + project_budget = st.number_input("ميزانية المشروع (بالريال)", min_value=1000, value=1000000, key="lc_project_budget") + + with col2: + project_type = st.selectbox( + "نوع المشروع", + options=["إنشائي", "تطوير برمجيات", "استشارات", "توريدات", "أخرى"], + key="lc_project_type" + ) + target_lc_percentage = st.number_input("نسبة المحتوى المحلي المستهدفة (%)", min_value=0, max_value=100, value=40) + + # Add new component + st.subheader("إضافة مكون جديد") + + col1, col2, col3 = st.columns(3) + with col1: + new_component_name = st.text_input("اسم المكون") + + with col2: + new_component_cost = st.number_input("التكلفة (بالريال)", min_value=0, value=0) + + with col3: + new_component_local_percentage = st.number_input("نسبة المحتوى المحلي (%)", min_value=0, max_value=100, value=0) + + # Add component button + if st.button("إضافة المكون"): + if new_component_name and new_component_cost > 0: + new_component = { + "name": new_component_name, + "cost": new_component_cost, + "local_percentage": new_component_local_percentage, + "local_content_value": (new_component_cost * new_component_local_percentage) / 100 + } - with col2: - st.metric("نسبة المحتوى المحلي", f"{overall_local_percentage:.2f}%") - - # تقييم مستوى المحتوى المحلي - if overall_local_percentage >= 70: - st.success("مستوى ممتاز من المحتوى المحلي") - elif overall_local_percentage >= 50: - st.info("مستوى جيد من المحتوى المحلي") - elif overall_local_percentage >= 30: - st.warning("مستوى متوسط من المحتوى المحلي") - else: - st.error("مستوى منخفض من المحتوى المحلي") + st.session_state.local_content_components.append(new_component) + st.success(f"تمت إضافة المكون: {new_component_name}") - # عرض الرسم البياني للمحتوى المحلي - st.subheader("توزيع المحتوى المحلي") + # Clear input fields + st.rerun() - # إعداد بيانات الرسم البياني - chart_data = [] - for component in st.session_state.local_content_components: - local_value = component["value"] * (component["local_percentage"] / 100) - non_local_value = component["value"] - local_value - - chart_data.append({ - "المكون": component["name"], - "محتوى محلي": local_value, - "محتوى غير محلي": non_local_value - }) + # Display components + if st.session_state.local_content_components: + st.subheader("المكونات المضافة") + + # Create DataFrame + components_df = pd.DataFrame(st.session_state.local_content_components) + components_df.columns = ["اسم المكون", "التكلفة (ريال)", "نسبة المحتوى المحلي (%)", "قيمة المحتوى المحلي (ريال)"] + + # Display table + st.dataframe(components_df) + + # Calculate total cost and local content + total_cost = sum(component["cost"] for component in st.session_state.local_content_components) + total_local_content = sum(component["local_content_value"] for component in st.session_state.local_content_components) + + overall_lc_percentage = (total_local_content / total_cost) * 100 if total_cost > 0 else 0 + + # Display summary + st.subheader("ملخص المحتوى المحلي") + + col1, col2, col3 = st.columns(3) + with col1: + st.metric("إجمالي التكلفة", f"{total_cost:,.2f} ريال") - chart_df = pd.DataFrame(chart_data) + with col2: + st.metric("قيمة المحتوى المحلي", f"{total_local_content:,.2f} ريال") - # رسم بياني شريطي مكدس - fig = px.bar( - chart_df, - x="المكون", - y=["محتوى محلي", "محتوى غير محلي"], - title="توزيع المحتوى المحلي حسب المكونات", - color_discrete_sequence=["#2ca02c", "#d62728"] - ) + with col3: + st.metric("نسبة المحتوى المحلي", f"{overall_lc_percentage:.2f}%") - fig.update_layout( - font=dict(size=14), - legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1) + # Local content visualization + st.subheader("توزيع المحتوى المحلي") + + # Create bar chart using Plotly + component_names = [reshape_arabic_text(component["name"]) for component in st.session_state.local_content_components] + local_percentages = [component["local_percentage"] for component in st.session_state.local_content_components] + + fig = go.Figure(data=[ + go.Bar( + x=component_names, + y=local_percentages, + marker_color='royalblue' ) + ]) + + fig.update_layout( + title=reshape_arabic_text("نسبة المحتوى المحلي حسب المكون"), + xaxis_title=reshape_arabic_text("المكون"), + yaxis_title=reshape_arabic_text("نسبة المحتوى المحلي (%)"), + height=400, + width=600 + ) + + st.plotly_chart(fig) + + # Target comparison + st.subheader("مقارنة مع النسبة المستهدفة") + + target_gap = overall_lc_percentage - target_lc_percentage + + if target_gap >= 0: + st.success(f"تم تحقيق النسبة المستهدفة بزيادة {target_gap:.2f}%") + else: + st.warning(f"لم يتم تحقيق النسبة المستهدفة بفارق {abs(target_gap):.2f}%") - st.plotly_chart(fig) - - # تحليل ذكي للمحتوى المحلي باستخدام Claude - st.subheader("تحليل ذكي للمحتوى المحلي") - - # إنشاء توجيه للنموذج - components_text = "\n".join([ - f"- {component['name']}: {component['value']:,.2f} ريال (نسبة المحتوى المحلي: {component['local_percentage']}%)" - for component in st.session_state.local_content_components - ]) - - prompt = f""" - قم بتحليل المحتوى المحلي لمشروع بالمواصفات التالية: - - اسم المشروع: {project_name} - - نوع المشروع: {project_type} - - قيمة المشروع: {total_value:,.2f} ريال - - مدة المشروع: {project_duration} شهر - - مكونات المشروع: - {components_text} - - نتائج التحليل: - - إجمالي قيمة المشروع: {total_value:,.2f} ريال - - قيمة المحتوى المحلي: {local_content_value:,.2f} ريال - - نسبة المحتوى المحلي: {overall_local_percentage:.2f}% - - قدم تحليلاً شاملاً للمحتوى المحلي وتوصيات لتحسين نسبة المحتوى المحلي في المشروع. - اشرح كيف يمكن زيادة المحتوى المحلي مع الحفاظ على جودة المشروع وتكلفته التنافسية. - قدم أمثلة على موردين محليين يمكن الاستعانة بهم لزيادة المحتوى المحلي. - """ - - # استدعاء النموذج - response = self.claude_service.chat_completion( - [{"role": "user", "content": prompt}], - model_name=st.session_state.selected_model - ) + # Clear components button + if st.button("مسح جميع المكونات"): + st.session_state.local_content_components = [] + st.rerun() - if "error" in response: - st.error(response["error"]) + # Analyze local content + if st.button("تحليل المحتوى المحلي"): + with st.spinner("جاري تحليل المحتوى المحلي..."): + # Prepare local content data + local_content_data = { + "project_name": project_name, + "project_budget": project_budget, + "project_type": project_type, + "target_lc_percentage": target_lc_percentage, + "components": st.session_state.local_content_components, + "total_cost": total_cost, + "total_local_content": total_local_content, + "overall_lc_percentage": overall_lc_percentage, + "target_gap": target_gap + } + + # Get AI analysis + analysis_result = self.claude_service.analyze_local_content( + local_content_data, + model=self.selected_model + ) + + st.subheader("نتائج تحليل المحتوى المحلي") + st.write(analysis_result) + + def _render_engineering_drawing_tab(self): + """Render the Engineering Drawing Analysis tab""" + st.header("تحليل الرسومات الهندسية") + + # Check if Claude AI is available + if not self.claude_service.is_available(): + st.warning("لم يتم تكوين مفتاح API للذكاء الاصطناعي. يرجى إضافة المفتاح في إعدادات Hugging Face.") + return + + # File upload + uploaded_file = st.file_uploader( + "ارفع ملف الرسم الهندسي (DWG, DXF, صورة)", + type=["dwg", "dxf", "png", "jpg", "jpeg"] + ) + + if uploaded_file is not None: + # Display file info + file_details = { + "اسم الملف": uploaded_file.name, + "نوع الملف": uploaded_file.type, + "حجم الملف": f"{uploaded_file.size / 1024:.2f} كيلوبايت" + } + + st.json(file_details) + + # Get file extension + file_extension = uploaded_file.name.split(".")[-1].lower() + file_content = uploaded_file.getvalue() + + # Check if we can process this file type + can_process = False + + if file_extension in ["png", "jpg", "jpeg"]: + can_process = True + elif file_extension in ["dwg", "dxf"]: + if file_extension == "dxf" and self.has_ezdxf: + can_process = True + elif file_extension == "dwg": + st.warning("تحليل ملفات DWG يتطلب تحويلها إلى صورة أو DXF أولاً.") else: - st.markdown(response["content"]) + st.warning("المكتبات المطلوبة لتحليل هذا النوع من الملفات غير متوفرة.") + + # Analyze drawing + if can_process and st.button("تحليل الرسم الهندسي"): + with st.spinner("جاري تحليل الرسم الهندسي..."): + analysis_result = self.claude_service.analyze_engineering_drawing( + file_content, + file_extension, + model=self.selected_model + ) - # حفظ النتائج في حالة الجلسة - if "local_content_analysis" not in st.session_state.analysis_results: - st.session_state.analysis_results["local_content_analysis"] = [] + st.subheader("نتائج تحليل الرسم الهندسي") + st.write(analysis_result) - st.session_state.analysis_results["local_content_analysis"].append({ - "project_name": project_name, - "project_type": project_type, - "components": st.session_state.local_content_components.copy(), - "total_value": total_value, - "local_content_value": local_content_value, - "overall_local_percentage": overall_local_percentage, - "result": response["content"], - "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") - }) - -def _render_faq_tab(self): - """عرض تبويب الأسئلة الشائعة""" - st.header("الأسئلة الشائعة") - - # قائمة الأسئلة الشائعة - faqs = [ - { - "question": "ما هو وحبي - المساعد الذكي للمناقصات والعقود؟", - "answer": """ - وحبي هو نظام ذكاء اصطناعي متكامل مصمم خصيصاً لتحليل المناقصات والعقود في قطاع البناء والإنشاءات. - يوفر النظام مجموعة من الأدوات المتقدمة لتحليل المستندات، وتقييم المخاطر، وتحليل التكاليف، وتحليل المحتوى المحلي، - بالإضافة إلى مساعد ذكي يمكنه الإجابة على الاستفسارات المتعلقة بالمناقصات والعقود. - """ - }, - { - "question": "ما هي أنو��ع الملفات المدعومة للتحليل؟", - "answer": """ - يدعم النظام مجموعة واسعة من أنواع الملفات، بما في ذلك: - - ملفات PDF - - مستندات Word (DOCX, DOC) - - ملفات Excel (XLSX, XLS) - - الصور (JPG, PNG) - - رسومات CAD (DWG, DXF) - - نماذج BIM (IFC, RVT) - """ - }, - { - "question": "كيف يمكنني تحليل مستند مناقصة؟", - "answer": """ - لتحليل مستند مناقصة، اتبع الخطوات التالية: - 1. انتقل إلى تبويب "تحليل المستندات" - 2. اختر نوع التحليل المناسب (تحليل شامل، استخراج البنود، إلخ) - 3. ارفع ملف المستند باستخدام زر رفع الملفات - 4. انقر على زر "تحليل المستند" - 5. انتظر حتى يكتمل التحليل وعرض النتائج - """ - }, - { - "question": "كيف يمكنني تحليل المخاطر المحتملة في مشروع؟", - "answer": """ - لتحليل المخاطر المحتملة في مشروع، اتبع الخطوات التالية: - 1. انتقل إلى تبويب "تحليل المخاطر" - 2. أدخل بيانات المشروع (الاسم، النوع، القيمة، المدة) - 3. انقر على زر "تحليل المخاطر" - 4. سيعرض النظام مصفوفة المخاطر وجدول المخاطر المحتملة - 5. سيقدم النظام أيضاً تحليلاً ذكياً للمخاطر وتوصيات للتخفيف منها - """ - }, - { - "question": "كيف يمكنني حساب نسبة المحتوى المحلي في مشروع؟", - "answer": """ - لحساب نسبة المحتوى المحلي في مشروع، اتبع الخطوات التالية: - 1. انتقل إلى تبويب "تحليل المحتوى المحلي" - 2. أدخل بيانات المشروع (الاسم، النوع، القيمة، المدة) - 3. أضف مكونات المشروع مع تحديد قيمة كل مكون ونسبة المحتوى المحلي فيه - 4. انقر على زر "تحليل المحتوى المحلي" - 5. سيعرض النظام نتائج التحليل ورسوماً بيانية توضح توزيع المحتوى المحلي - 6. سيقدم النظام أيضاً تحليلاً ذكياً وتوصيات لتحسين نسبة المحتوى المحلي - """ - }, - { - "question": "هل يمكنني استخدام المساعد الذكي للإجابة على أسئلة محددة؟", - "answer": """ - نعم، يمكنك استخدام المساعد الذكي للإجابة على أسئلة محددة حول المناقصات والعقود. - انتقل إلى تبويب "المساعد الذكي" واكتب سؤالك في مربع الدردشة. - يمكنك اختيار نموذج الذكاء الاصطناعي المناسب لاحتياجاتك من القائمة المنسدلة. - """ - }, - { - "question": "هل يمكنني تحليل رسومات CAD وملفات BIM؟", - "answer": """ - نعم، يدعم النظام تحليل رسومات CAD وملفات BIM. يمكنك رفع ملفات DWG أو DXF أو IFC أو RVT، - وسيقوم النظام بتحليلها واستخراج المعلومات المهمة منها، مثل العناصر والطبقات والأبعاد. - يمكن أيضاً تحليل التكاليف والمخاطر المرتبطة بالرسومات والنماذج. - """ - }, - { - "question": "كيف يمكنني الحصول على المساعدة؟", - "answer": """ - للحصول على المساعدة، يمكنك: - 1. استخدام المساعد الذكي في تبويب "المساعد الذكي" لطرح أسئلتك - 2. الاطلاع على الأسئلة الشائعة في هذا التبويب - 3. التواصل مع فريق الدعم الفني عبر البريد الإلكتروني: tgoahry@sajco.com.sa - 4. زيارة موقع الدعم الفني: https://sajco.com - """ + def _render_project_management_tab(self): + """Render the Project Management Files Analysis tab""" + st.header("تحليل ملفات إدارة المشاريع") + + # Check if Claude AI is available + if not self.claude_service.is_available(): + st.warning("لم ��تم تكوين مفتاح API للذكاء الاصطناعي. يرجى إضافة المفتاح في إعدادات Hugging Face.") + return + + # Analysis type selection + analysis_type = st.selectbox( + "اختر نوع التحليل", + options=[ + "تحليل الجدول الزمني", + "تحليل جدول الكميات" + ] + ) + + # Map UI selection to API parameter + analysis_type_map = { + "تحليل الجدول الزمني": "schedule", + "تحليل جدول الكميات": "quantities" } - ] - - # عرض الأسئلة الشائعة - for i, faq in enumerate(faqs): - with st.expander(faq["question"]): - st.markdown(faq["answer"]) - -# إضافة الدوال إلى فئة AIAssistantApp -AIAssistantApp._render_ai_assistant_tab = _render_ai_assistant_tab -AIAssistantApp._render_document_analysis_tab = _render_document_analysis_tab -AIAssistantApp._render_contract_analysis_tab = _render_contract_analysis_tab -AIAssistantApp._render_risk_analysis_tab = _render_risk_analysis_tab -AIAssistantApp._render_cost_prediction_tab = _render_cost_prediction_tab -AIAssistantApp._render_local_content_tab = _render_local_content_tab -AIAssistantApp._render_faq_tab = _render_faq_tab + + # File upload + uploaded_file = st.file_uploader( + "ارفع ملف المشروع (Primavera, MS Project, Power BI, صورة)", + type=["xer", "mpp", "pbix", "xlsx", "png", "jpg", "jpeg"] + ) + + if uploaded_file is not None: + # Display file info + file_details = { + "اسم الملف": uploaded_file.name, + "نوع الملف": uploaded_file.type, + "حجم الملف": f"{uploaded_file.size / 1024:.2f} كيلوبايت" + } + + st.json(file_details) + + # Get file extension + file_extension = uploaded_file.name.split(".")[-1].lower() + file_content = uploaded_file.getvalue() + + # Check if we can process this file type + can_process = False + + if file_extension in ["png", "jpg", "jpeg"]: + can_process = True + elif file_extension in ["xer", "mpp", "pbix", "xlsx"]: + st.warning("تحليل هذا النوع من الملفات يتطلب مكتبات متخصصة. سيتم استخدام الذكاء الاصطناعي للتحليل العام.") + can_process = True + + # Analyze project file + if can_process and st.button("تحليل ملف المشروع"): + with st.spinner("جاري تحليل ملف المشروع..."): + analysis_result = self.claude_service.analyze_project_file( + file_content, + file_extension, + analysis_type_map[analysis_type], + model=self.selected_model + ) + + st.subheader("نتائج تحليل ملف المشروع") + st.write(analysis_result) + + def _render_faq_tab(self): + """Render the FAQ tab""" + st.header("الأسئلة الشائعة") + + faqs = [ + { + "question": "ما هو وحبي - المساعد الذكي للمناقصات والعقود؟", + "answer": "وحبي هو نظام ذكاء اصطناعي متكامل مصمم خصيصًا لتحليل المناقصات والعقود في المملكة العربية السعودية. يساعد في تحليل المستندات، واستخراج البنود والشروط، وتحليل المخاطر، وتقدير التكاليف، وحساب المحتوى المحلي، وتحليل الرسومات الهندسية وملفات إدارة المشاريع." + }, + { + "question": "كيف يمكنني تحليل مستند مناقصة؟", + "answer": "انتقل إلى تبويب 'تحليل المستندات'، واختر نوع التحليل المطلوب (استخراج البنود، الشروط التعاقدية، الكميات، المتطلبات القانونية)، ثم ارفع ملف المستند (PDF، Word، صورة) واضغط على زر 'تحليل المستند'." + }, + { + "question": "كيف يمكنني تحليل المخاطر لمشروع؟", + "answer": "انتقل إلى تبويب 'تحليل المخاطر'، وأدخل معلومات المشروع وعوامل المخاطر، ثم اضغط على زر 'تحليل المخاطر' للحصول على تحليل شامل للمخاطر المحتملة وتوصيات للتخفيف منها." + }, + { + "question": "كيف يمكنني حساب نسبة المحتوى المحلي؟", + "answer": "انتقل إلى تبويب 'تحليل المحتوى المحلي'، وأدخل معلومات المشروع، ثم أضف المكونات المختلفة مع تكلفتها ونسبة المحتوى المحلي لكل منها. سيتم حساب النسبة الإجمالية للمحتوى المحلي تلقائيًا ومقارنتها بالنسبة المستهدفة." + }, + { + "question": "هل يمكنني تحليل الرسومات الهندسية؟", + "answer": "نعم، انتقل إلى تبويب 'تحليل الرسومات الهندسية' وارفع ملف الرسم الهندسي (DWG، DXF، صورة). سيقوم النظام بتحليل الرسم واستخراج المعلومات الهامة منه." + }, + { + "question": "هل يمكنني تحليل ملفات إدارة المشاريع؟", + "answer": "نعم، انتقل إلى تبويب 'تحليل ملفات إدارة المشاريع' واختر نوع التحليل (الجدول الزمني، جدول الكميات) ثم ارفع ملف المشروع (Primavera، MS Project، Power BI، صورة). سيقوم النظام بتحليل الملف واستخراج المعلومات الهامة منه." + }, + { + "question": "كيف يمكنني تغيير نموذج الذكاء الاصطناعي المستخدم؟", + "answer": "يمكنك تغيير نموذج الذكاء الاصطناعي من القائمة الجانبية تحت 'إعدادات الذكاء الاصطناعي'. اختر النموذج المناسب من القائمة المنسدلة." + }, + { + "question": "هل يمكنني استخدام النظام بدون مفتاح API؟", + "answer": "لا، يتطلب النظام مفتاح API صالح لـ Claude AI. يجب إضافة المفتاح في إعدادات Hugging Face باسم 'anthropic'." + } + ] + + for i, faq in enumerate(faqs): + with st.expander(faq["question"]): + st.write(faq["answer"]) + + def render(self): + """Render the main application""" + st.title("وحبي - المساعد الذكي للمناقصات والعقود") + + # Render sidebar + self._render_sidebar() + + # Create tabs + tabs = st.tabs([ + "المساعد الذكي", + "تحليل المستندات", + "تحليل العقود", + "تحليل المخاطر", + "تحليل التكاليف", + "تحليل المحتوى المحلي", + "تحليل الرسومات الهندسية", + "تحليل ملفات المشاريع", + "الأسئلة الشائعة" + ]) + + # Render tabs + with tabs[0]: + self._render_ai_assistant_tab() + + with tabs[1]: + self._render_document_analysis_tab() + + with tabs[2]: + self._render_contract_analysis_tab() + + with tabs[3]: + self._render_risk_analysis_tab() + + with tabs[4]: + self._render_cost_prediction_tab() + + with tabs[5]: + self._render_local_content_tab() + + with tabs[6]: + self._render_engineering_drawing_tab() + + with tabs[7]: + self._render_project_management_tab() + + with tabs[8]: + self._render_faq_tab() -# نقطة الدخول الرئيسية للتطبيق +# Main function to run the app def main(): - # تهيئة تطبيق المساعد الذكي - ai_app = AIAssistantApp() + st.set_page_config( + page_title="وحبي - المساعد الذكي للمناقصات والعقود", + page_icon="📊", + layout="wide", + initial_sidebar_state="expanded" + ) + + # Set Streamlit theme to make Arabic text display better + st.markdown(""" + + """, unsafe_allow_html=True) - # عرض واجهة المستخدم - ai_app.render() + # Run the app + app = AIAssistantApp() + app.render() if __name__ == "__main__": main()