|
|
|
|
|
|
|
""" |
|
وحدة المساعدة المركزية للنظام |
|
تحتوي على دوال مساعدة مشتركة تستخدم في جميع أنحاء التطبيق |
|
""" |
|
|
|
import os |
|
import sys |
|
import streamlit as st |
|
import pandas as pd |
|
import numpy as np |
|
import json |
|
import re |
|
import time |
|
import tempfile |
|
from datetime import datetime, timedelta |
|
import random |
|
import secrets |
|
import shutil |
|
import base64 |
|
import logging |
|
from pathlib import Path |
|
|
|
|
|
logging.basicConfig( |
|
level=logging.INFO, |
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' |
|
) |
|
logger = logging.getLogger("wahbi-ai") |
|
|
|
|
|
def create_directory_if_not_exists(directory_path): |
|
"""إنشاء مسار إذا لم يكن موجوداً""" |
|
try: |
|
if not os.path.exists(directory_path): |
|
os.makedirs(directory_path) |
|
logger.info(f"تم إنشاء المجلد: {directory_path}") |
|
return True |
|
except Exception as e: |
|
logger.error(f"خطأ في إنشاء المجلد {directory_path}: {e}") |
|
return False |
|
|
|
|
|
def get_data_folder(): |
|
"""الحصول على مسار مجلد البيانات الرئيسي""" |
|
|
|
data_folder = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data") |
|
create_directory_if_not_exists(data_folder) |
|
return data_folder |
|
|
|
|
|
def load_config(): |
|
"""تحميل إعدادات التكوين من ملف config.json""" |
|
config_path = os.path.join(get_data_folder(), "config.json") |
|
|
|
|
|
if not os.path.exists(config_path): |
|
default_config = { |
|
"system": { |
|
"version": "1.0.0", |
|
"release_date": "2025-03-30", |
|
"company_name": "شركة شبه الجزيرة للمقاولات", |
|
"company_logo": "", |
|
"language": "ar", |
|
"theme": "light", |
|
"debug_mode": False |
|
}, |
|
"ai_models": { |
|
"default_model": "claude-3-7-sonnet-20250219", |
|
"openai_model": "gpt-4o", |
|
"huggingface_model": "mistralai/Mistral-7B-Instruct-v0.2" |
|
}, |
|
"notifications": { |
|
"enable_email": False, |
|
"enable_browser": True, |
|
"check_interval": 60 |
|
} |
|
} |
|
|
|
with open(config_path, 'w', encoding='utf-8') as f: |
|
json.dump(default_config, f, ensure_ascii=False, indent=2) |
|
|
|
return default_config |
|
|
|
|
|
try: |
|
with open(config_path, 'r', encoding='utf-8') as f: |
|
return json.load(f) |
|
except Exception as e: |
|
logger.error(f"خطأ في تحميل ملف التكوين: {e}") |
|
return {} |
|
|
|
|
|
def save_config(config): |
|
"""حفظ إعدادات التكوين إلى ملف config.json""" |
|
config_path = os.path.join(get_data_folder(), "config.json") |
|
|
|
try: |
|
with open(config_path, 'w', encoding='utf-8') as f: |
|
json.dump(config, f, ensure_ascii=False, indent=2) |
|
return True |
|
except Exception as e: |
|
logger.error(f"خطأ في حفظ ملف التكوين: {e}") |
|
return False |
|
|
|
|
|
def format_time(timestamp=None, format_str="%Y-%m-%d %H:%M:%S"): |
|
"""تنسيق الوقت إلى صيغة معينة""" |
|
if timestamp is None: |
|
timestamp = datetime.now() |
|
elif isinstance(timestamp, (int, float)): |
|
timestamp = datetime.fromtimestamp(timestamp) |
|
|
|
return timestamp.strftime(format_str) |
|
|
|
|
|
def get_user_info(): |
|
"""الحصول على معلومات المستخدم الحالي""" |
|
|
|
if "user_info" in st.session_state: |
|
return st.session_state.user_info |
|
|
|
|
|
return { |
|
"id": 1, |
|
"username": "admin", |
|
"full_name": "مدير النظام", |
|
"email": "[email protected]", |
|
"role": "مدير", |
|
"department": "الإدارة", |
|
"last_login": format_time() |
|
} |
|
|
|
|
|
def get_current_project(): |
|
"""الحصول على معلومات المشروع الحالي""" |
|
if "current_project" in st.session_state: |
|
return st.session_state.current_project |
|
|
|
|
|
return None |
|
|
|
|
|
def load_icons(): |
|
"""تحميل الأيقونات المستخدمة في النظام""" |
|
icons_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets", "icons") |
|
icons = {} |
|
|
|
|
|
if not os.path.exists(icons_path): |
|
create_directory_if_not_exists(icons_path) |
|
return icons |
|
|
|
|
|
for icon_file in os.listdir(icons_path): |
|
if icon_file.endswith(('.png', '.svg', '.jpg')): |
|
icon_name = os.path.splitext(icon_file)[0] |
|
icon_path = os.path.join(icons_path, icon_file) |
|
|
|
try: |
|
with open(icon_path, "rb") as f: |
|
icons[icon_name] = base64.b64encode(f.read()).decode() |
|
except Exception as e: |
|
logger.error(f"خطأ في تحميل الأيقونة {icon_name}: {e}") |
|
|
|
return icons |
|
|
|
|
|
def get_random_id(length=8): |
|
"""إنشاء معرف عشوائي بطول محدد""" |
|
return secrets.token_hex(length // 2) |
|
|
|
|
|
def compress_text(text, max_length=10000): |
|
"""اختصار النص إلى حد أقصى محدد مع الحفاظ على المعنى""" |
|
if not text or len(text) <= max_length: |
|
return text |
|
|
|
|
|
sentences = re.split(r'(?<=[.!?])\s+', text) |
|
|
|
|
|
avg_sentence_length = len(text) / len(sentences) |
|
|
|
|
|
num_sentences_to_keep = int(max_length / avg_sentence_length) |
|
|
|
|
|
keep_first = num_sentences_to_keep // 2 |
|
keep_last = num_sentences_to_keep - keep_first |
|
|
|
|
|
compressed_text = ' '.join(sentences[:keep_first] + sentences[-keep_last:]) |
|
|
|
|
|
if len(compressed_text) < len(text): |
|
compressed_text += " [...المزيد من النص تم اختصاره...]" |
|
|
|
return compressed_text |
|
|
|
|
|
def str_to_bool(text): |
|
"""تحويل النص إلى قيمة منطقية""" |
|
return text.lower() in ('yes', 'true', 'y', 't', '1', 'نعم', 'صحيح') |
|
|
|
|
|
def handle_arabic_text(text): |
|
"""معالجة النص العربي للعرض بشكل صحيح""" |
|
if not text: |
|
return "" |
|
|
|
|
|
return f"<div dir='rtl'>{text}</div>" |
|
|
|
|
|
def render_credits(): |
|
"""عرض معلومات النظام وحقوق الملكية""" |
|
st.markdown("---") |
|
|
|
config = load_config() |
|
system_info = config.get("system", {}) |
|
|
|
col1, col2, col3 = st.columns([1, 2, 1]) |
|
|
|
with col2: |
|
st.markdown( |
|
f""" |
|
<div style="text-align: center; color: #666;"> |
|
<p>{system_info.get('company_name', 'شركة شبه الجزيرة للمقاولات')}</p> |
|
<p>الإصدار: {system_info.get('version', '1.0.0')}</p> |
|
<p>© جميع الحقوق محفوظة 2025</p> |
|
</div> |
|
""", |
|
unsafe_allow_html=True |
|
) |
|
|
|
|
|
|
|
def get_connection(): |
|
""" |
|
دالة للحصول على اتصال بقاعدة البيانات |
|
|
|
ملاحظة: ينبغي أن تكون مستبدلة بالدالة من db_connector.py في البيئة الإنتاجية |
|
""" |
|
try: |
|
|
|
from database.db_connector import get_connection as get_db_connection |
|
return get_db_connection() |
|
except ImportError: |
|
|
|
logger.warning("لم يتم العثور على وحدة اتصال قاعدة البيانات. استخدام مخزن بيانات مؤقت.") |
|
|
|
return None |
|
|
|
|
|
def load_css(file_name=None): |
|
"""تحميل ملف CSS مخصص""" |
|
try: |
|
if file_name: |
|
css_file = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets", "css", file_name) |
|
|
|
if os.path.exists(css_file): |
|
with open(css_file, "r", encoding="utf-8") as f: |
|
css_content = f.read() |
|
else: |
|
logger.warning(f"ملف CSS غير موجود: {css_file}") |
|
return |
|
else: |
|
|
|
css_content = """ |
|
.sidebar .sidebar-content { |
|
direction: rtl; |
|
text-align: right; |
|
} |
|
div[data-testid="stForm"] { |
|
border: 1px solid #ddd; |
|
padding: 10px; |
|
border-radius: 10px; |
|
} |
|
.module-title { |
|
color: #1E88E5; |
|
text-align: center; |
|
font-size: 1.8rem; |
|
margin-bottom: 1rem; |
|
} |
|
.instructions { |
|
background-color: #f8f9fa; |
|
border-right: 3px solid #4CAF50; |
|
padding: 10px; |
|
margin-bottom: 15px; |
|
} |
|
.results-container { |
|
background-color: #f5f5f5; |
|
padding: 15px; |
|
border-radius: 5px; |
|
margin-top: 20px; |
|
} |
|
.risk-high { |
|
color: #d32f2f; |
|
font-weight: bold; |
|
} |
|
.risk-medium { |
|
color: #f57c00; |
|
font-weight: bold; |
|
} |
|
.risk-low { |
|
color: #388e3c; |
|
font-weight: bold; |
|
} |
|
.form-container { |
|
background-color: #f9f9f9; |
|
padding: 20px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
} |
|
.section-header { |
|
color: #2196F3; |
|
font-size: 1.2rem; |
|
font-weight: bold; |
|
margin-top: 20px; |
|
margin-bottom: 10px; |
|
border-bottom: 1px solid #eee; |
|
padding-bottom: 5px; |
|
} |
|
""" |
|
|
|
st.markdown(f"<style>{css_content}</style>", unsafe_allow_html=True) |
|
|
|
except Exception as e: |
|
logger.error(f"خطأ في تحميل ملف CSS: {e}") |