diff --git "a/modules/ai_finetuning/model_finetuning.py" "b/modules/ai_finetuning/model_finetuning.py" new file mode 100644--- /dev/null +++ "b/modules/ai_finetuning/model_finetuning.py" @@ -0,0 +1,2081 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +وحدة تخصيص وضبط نماذج الذكاء الاصطناعي للمصطلحات التعاقدية المتخصصة +تتيح هذه الوحدة إمكانية تدريب نماذج الذكاء الاصطناعي على المصطلحات المتخصصة في مجال العقود والمناقصات +""" + +import os +import sys +import streamlit as st +import pandas as pd +import numpy as np +import json +import time +import datetime +from typing import List, Dict, Any, Optional, Tuple +import openai +import matplotlib.pyplot as plt +import tempfile +import csv +import re +import random +from pathlib import Path + +# إضافة مسار النظام للوصول للملفات المشتركة +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) + +# استيراد مكونات واجهة المستخدم +from utils.components.header import render_header +from utils.components.credits import render_credits +from utils.helpers import format_number, format_currency, styled_button + + +class ModelFinetuning: + """فئة تخصيص وضبط نماذج الذكاء الاصطناعي""" + + def __init__(self): + """تهيئة وحدة تخصيص وضبط نماذج الذكاء الاصطناعي""" + # تهيئة مجلدات حفظ البيانات + self.data_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../data/finetuning")) + os.makedirs(self.data_dir, exist_ok=True) + + # تهيئة الملفات والمجلدات الفرعية + self.training_data_dir = os.path.join(self.data_dir, "training_data") + os.makedirs(self.training_data_dir, exist_ok=True) + + self.models_dir = os.path.join(self.data_dir, "models") + os.makedirs(self.models_dir, exist_ok=True) + + self.terminology_file = os.path.join(self.data_dir, "terminology.json") + + # تهيئة حالة الجلسة + if 'terminology_data' not in st.session_state: + if os.path.exists(self.terminology_file): + with open(self.terminology_file, 'r', encoding='utf-8') as f: + st.session_state.terminology_data = json.load(f) + else: + st.session_state.terminology_data = { + "terms": [], + "training_examples": [], + "models": [] + } + + if 'active_training_job' not in st.session_state: + st.session_state.active_training_job = None + + if 'training_results' not in st.session_state: + st.session_state.training_results = [] + + # ضبط API مفاتيح الذكاء الاصطناعي + self.api_key = os.environ.get("OPENAI_API_KEY") + self.anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY") + + def render(self): + """عرض واجهة وحدة تخصيص وضبط نماذج الذكاء الاصطناعي""" + # عرض الشعار والعنوان الرئيسي + render_header("تخصيص وضبط نماذج الذكاء الاصطناعي للمصطلحات التعاقدية المتخصصة") + + # تبويبات الوحدة + tabs = st.tabs([ + "قاموس المصطلحات المتخصصة", + "إعداد بيانات التدريب", + "تدريب النموذج", + "اختبار النموذج", + "المساعد المتخصص" + ]) + + # تبويب قاموس المصطلحات المتخصصة + with tabs[0]: + self._render_terminology_dictionary() + + # تبويب إعداد بيانات التدريب + with tabs[1]: + self._render_training_data_setup() + + # تبويب تدريب النموذج + with tabs[2]: + self._render_model_training() + + # تبويب اختبار النموذج + with tabs[3]: + self._render_model_testing() + + # تبويب المساعد المتخصص + with tabs[4]: + self._render_specialized_assistant() + + # عرض حقوق النشر + render_credits() + + def _render_terminology_dictionary(self): + """عرض قاموس المصطلحات المتخصصة""" + st.markdown(""" +
+

📚 قاموس المصطلحات المتخصصة

+

أضف وحرر المصطلحات الفنية المتخصصة في مجال العقود والمناقصات باللغة العربية.

+

هذه المصطلحات ستستخدم لتدريب وضبط نماذج الذكاء الاصطناعي للتعرف عليها بدقة عالية.

+
+ """, unsafe_allow_html=True) + + # إضا��ة مصطلح جديد + st.markdown("### إضافة مصطلح جديد") + + col1, col2 = st.columns(2) + + with col1: + term = st.text_input("المصطلح", key="new_term") + category = st.selectbox( + "الفئة", + options=[ + "شروط تعاقدية", "مواصفات فنية", "مستندات مناقصة", + "بنود مالية", "جداول كميات", "ضمانات", "مصطلحات قانونية", + "محتوى محلي", "أخرى" + ], + key="new_term_category" + ) + + with col2: + english_term = st.text_input("المصطلح بالإنجليزية (اختياري)", key="new_term_english") + importance = st.slider("مستوى الأهمية", 1, 5, 3, key="new_term_importance") + + definition = st.text_area("التعريف", key="new_term_definition") + examples = st.text_area("أمثلة على استخدام المصطلح (فصل بين الأمثلة بسطر جديد)", key="new_term_examples") + + # زر إضافة المصطلح + if styled_button("إضافة المصطلح", key="add_term", type="primary", icon="➕"): + if not term or not definition: + st.error("يرجى تعبئة المصطلح والتعريف على الأقل.") + else: + # إنشاء كائن المصطلح + new_term = { + "term": term, + "definition": definition, + "category": category, + "english_term": english_term, + "importance": importance, + "examples": [ex.strip() for ex in examples.split("\n") if ex.strip()], + "added_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + + # إضافة المصطلح للقائمة + st.session_state.terminology_data["terms"].append(new_term) + + # حفظ البيانات + self._save_terminology_data() + + st.success(f"تمت إضافة المصطلح '{term}' بنجاح!") + st.rerun() + + # عرض المصطلحات الموجودة + st.markdown("### المصطلحات الموجودة") + + terms = st.session_state.terminology_data.get("terms", []) + + if not terms: + st.info("لا توجد مصطلحات مضافة. يرجى إضافة مصطلحات جديدة.") + else: + # تصفية المصطلحات + filter_col1, filter_col2 = st.columns(2) + + with filter_col1: + filter_category = st.selectbox( + "تصفية حسب الفئة", + options=["الكل"] + list(set(t.get("category") for t in terms)), + key="filter_term_category" + ) + + with filter_col2: + search_query = st.text_input("بحث", key="search_term") + + # تطبيق التصفية + filtered_terms = terms + if filter_category != "الكل": + filtered_terms = [t for t in filtered_terms if t.get("category") == filter_category] + + if search_query: + filtered_terms = [ + t for t in filtered_terms + if search_query.lower() in t.get("term", "").lower() or + search_query.lower() in t.get("definition", "").lower() or + search_query.lower() in t.get("english_term", "").lower() + ] + + # عرض المصطلحات المصفاة + if not filtered_terms: + st.warning("لا توجد مصطلحات تطابق معايير التصفية.") + else: + # إعداد بيانات للعرض + for i, term in enumerate(filtered_terms): + with st.expander(f"{term.get('term')} ({term.get('english_term', '')})", expanded=i==0 and len(filtered_terms)<5): + term_col1, term_col2 = st.columns([3, 1]) + + with term_col1: + st.markdown(f"**التعريف:** {term.get('definition')}") + st.markdown(f"**الفئة:** {term.get('category')}") + st.markdown(f"**المصطلح بالإنجليزية:** {term.get('english_term', '-')}") + + if "examples" in term and term["examples"]: + st.markdown("**أمثلة:**") + for ex in term["examples"]: + st.markdown(f"- {ex}") + + with term_col2: + st.markdown(f"**مستوى الأهمية:** {'⭐' * term.get('importance', 3)}") + st.markdown(f"**تاريخ الإضافة:** {term.get('added_at', '-')}") + + # أزرار التحرير والحذف + if styled_button("تحرير", key=f"edit_term_{i}", type="secondary", icon="✏️"): + st.session_state.term_to_edit = i + + if styled_button("حذف", key=f"delete_term_{i}", type="danger", icon="🗑️"): + st.session_state.term_to_delete = i + + # معالجة تحرير أو حذف المصطلح + if "term_to_edit" in st.session_state: + self._render_edit_term_form(st.session_state.term_to_edit, filtered_terms) + + if "term_to_delete" in st.session_state: + if st.warning(f"هل أنت متأكد من حذف المصطلح '{filtered_terms[st.session_state.term_to_delete].get('term')}'؟"): + if styled_button("تأكيد الحذف", key="confirm_delete", type="danger", icon="🗑️"): + # حذف المصطلح + term_index = terms.index(filtered_terms[st.session_state.term_to_delete]) + del st.session_state.terminology_data["terms"][term_index] + + # حفظ البيانات + self._save_terminology_data() + + # إعادة ضبط حالة الحذف + del st.session_state.term_to_delete + + st.success("تم حذف المصطلح بنجاح!") + st.rerun() + + if styled_button("إلغاء", key="cancel_delete", type="secondary", icon="❌"): + del st.session_state.term_to_delete + st.rerun() + + # تصدير المصطلحات + st.markdown("### تصدير وتوريد المصطلحات") + + col1, col2 = st.columns(2) + + with col1: + if styled_button("تصدير المصطلحات إلى CSV", key="export_terms", type="primary", icon="📤"): + self._export_terms_to_csv() + + with col2: + uploaded_file = st.file_uploader("استيراد المصطلحات من ملف CSV", type=["csv"], key="import_terms_file") + + if uploaded_file is not None: + if styled_button("استيراد المصطلحات", key="import_terms", type="success", icon="📥"): + self._import_terms_from_csv(uploaded_file) + + def _render_edit_term_form(self, term_index, terms_list): + """عرض نموذج تحرير المصطلح""" + term = terms_list[term_index] + + st.markdown("### تحرير المصطلح") + + col1, col2 = st.columns(2) + + with col1: + edited_term = st.text_input("المصطلح", value=term.get("term", ""), key="edit_term_name") + edited_category = st.selectbox( + "الفئة", + options=[ + "شروط تعاقدية", "مواصفات فنية", "مستندات مناقصة", + "بنود مالية", "جداول كميات", "ضمانات", "مصطلحات قانونية", + "محتوى محلي", "أخرى" + ], + index=["شروط تعاقدية", "مواصفات فنية", "مستندات مناقصة", "بنود مالية", "جداول كميات", "ضمانات", "مصطلحات قانونية", "محتوى محلي", "أخرى"].index(term.get("category", "أخرى")), + key="edit_term_category" + ) + + with col2: + edited_english_term = st.text_input("المصطلح بالإنجليزية (اختياري)", value=term.get("english_term", ""), key="edit_term_english") + edited_importance = st.slider("مستوى الأهمية", 1, 5, term.get("importance", 3), key="edit_term_importance") + + edited_definition = st.text_area("التعريف", value=term.get("definition", ""), key="edit_term_definition") + edited_examples = st.text_area("أمثلة على استخدام المصطلح (فصل بين الأمثلة بسطر جديد)", value="\n".join(term.get("examples", [])), key="edit_term_examples") + + col1, col2 = st.columns(2) + + with col1: + if styled_button("حفظ التغييرات", key="save_edited_term", type="primary", icon="💾"): + if not edited_term or not edited_definition: + st.error("يرجى تعبئة المصطلح والتعريف على الأقل.") + else: + # تحديث المصطلح + updated_term = { + "term": edited_term, + "definition": edited_definition, + "category": edited_category, + "english_term": edited_english_term, + "importance": edited_importance, + "examples": [ex.strip() for ex in edited_examples.split("\n") if ex.strip()], + "added_at": term.get("added_at", datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")), + "updated_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + + # الحصول على المؤشر الفعلي في القائمة الكاملة + all_terms = st.session_state.terminology_data["terms"] + actual_index = all_terms.index(term) + + # تحديث المصطلح + st.session_state.terminology_data["terms"][actual_index] = updated_term + + # حفظ البيانات + self._save_terminology_data() + + # إعادة ضبط حالة التحرير + del st.session_state.term_to_edit + + st.success(f"تم تحديث المصطلح '{edited_term}' بنجاح!") + st.rerun() + + with col2: + if styled_button("إلغاء", key="cancel_edit_term", type="secondary", icon="❌"): + del st.session_state.term_to_edit + st.rerun() + + def _export_terms_to_csv(self): + """تصدير المصطلحات إلى ملف CSV""" + terms = st.session_state.terminology_data.get("terms", []) + + if not terms: + st.error("لا توجد مصطلحات للتصدير.") + return + + # إنشاء ملف CSV مؤقت + with tempfile.NamedTemporaryFile(mode='w+', suffix='.csv', newline='', encoding='utf-8', delete=False) as f: + writer = csv.writer(f) + + # كتابة الترويسة + writer.writerow([ + 'المصطلح', 'التعريف', 'الفئة', 'المصطلح بالإنجليزية', + 'مستوى الأهمية', 'الأمثلة', 'تاريخ الإضافة' + ]) + + # كتابة المصطلحات + for term in terms: + writer.writerow([ + term.get('term', ''), + term.get('definition', ''), + term.get('category', ''), + term.get('english_term', ''), + term.get('importance', 3), + '|'.join(term.get('examples', [])), + term.get('added_at', '') + ]) + + # الحصول على مسار الملف + csv_path = f.name + + # قراءة الملف وتقديمه للتنزيل + with open(csv_path, 'r', encoding='utf-8') as f: + csv_data = f.read() + + # تقديم الملف للتنزيل + st.download_button( + label="تنزيل ملف CSV", + data=csv_data, + file_name="terminology_dictionary.csv", + mime="text/csv" + ) + + # حذف الملف المؤقت + os.unlink(csv_path) + + def _import_terms_from_csv(self, uploaded_file): + """استيراد المصطلحات من ملف CSV""" + try: + # قراءة الملف + df = pd.read_csv(uploaded_file, encoding='utf-8') + + # التحقق من وجود الأعمدة المطلوبة + required_columns = ['المصطلح', 'التعريف'] + missing_columns = [col for col in required_columns if col not in df.columns] + + if missing_columns: + st.error(f"الملف لا يحتوي على الأعمدة التالية: {', '.join(missing_columns)}") + return + + # إضافة المصطلحات + terms_added = 0 + terms_updated = 0 + + for _, row in df.iterrows(): + term = row['المصطلح'] + + # البحث عن المصطلح الموجود + existing_term = next((t for t in st.session_state.terminology_data["terms"] if t.get("term") == term), None) + + # تحضير كائن المصطلح + term_obj = { + "term": term, + "definition": row.get('التعريف', ''), + "category": row.get('الفئة', 'أخرى'), + "english_term": row.get('المصطلح بالإنجليزية', ''), + "importance": int(row.get('مستوى الأهمية', 3)), + "examples": row.get('الأمثلة', '').split('|') if 'الأمثلة' in row else [], + "added_at": row.get('تاريخ الإضافة', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + } + + if existing_term: + # تحديث المصطلح الموجود + index = st.session_state.terminology_data["terms"].index(existing_term) + st.session_state.terminology_data["terms"][index] = term_obj + terms_updated += 1 + else: + # إضافة مصطلح جديد + st.session_state.terminology_data["terms"].append(term_obj) + terms_added += 1 + + # حفظ البيانات + self._save_terminology_data() + + st.success(f"تم استيراد المصطلحات بنجاح! (تمت إضافة {terms_added} مصطلح جديد، وتحديث {terms_updated} مصطلح موجود)") + st.rerun() + + except Exception as e: + st.error(f"حدث خطأ أثناء استيراد المصطلحات: {str(e)}") + + def _render_training_data_setup(self): + """عرض إعداد بيانات التدريب""" + st.markdown(""" +
+

🔬 إعداد بيانات التدريب

+

قم بإنشاء وتحرير أمثلة التدريب لضبط نماذج الذكاء الاصطناعي على المصطلحات المتخصصة.

+

يمكنك إنشاء أمثلة يدوياً أو استيرادها من ملف أو توليدها تلقائياً باستخدام نماذج الذكاء الاصطناعي الحالية.

+
+ """, unsafe_allow_html=True) + + # تبويبات إعداد البيانات + training_tabs = st.tabs(["أمثلة التدريب الحالية", "إنشاء أمثلة يدوياً", "توليد أمثلة تلقائياً", "استيراد وتصدير البيانات"]) + + # عرض أمثلة التدريب الحالية + with training_tabs[0]: + self._render_existing_training_examples() + + # إنشاء أمثلة يدوياً + with training_tabs[1]: + self._render_manual_example_creation() + + # توليد أمثلة تلقائياً + with training_tabs[2]: + self._render_automatic_example_generation() + + # استيراد وتصدير البيانات + with training_tabs[3]: + self._render_import_export_training_data() + + def _render_existing_training_examples(self): + """عرض أمثلة التدريب الحالية""" + st.markdown("### أمثلة التدريب الحالية") + + examples = st.session_state.terminology_data.get("training_examples", []) + + if not examples: + st.info("لا توجد أمثلة تدريب. يرجى إنشاء أمثلة جديدة.") + return + + # عرض إحصائيات البيانات + st.markdown("#### إحصائيات البيانات") + + total_examples = len(examples) + categories = {} + terms_used = set() + + for ex in examples: + cat = ex.get("category", "غير مصنف") + categories[cat] = categories.get(cat, 0) + 1 + + for term in ex.get("terms", []): + terms_used.add(term) + + col1, col2, col3 = st.columns(3) + + with col1: + st.metric("إجمالي الأمثلة", total_examples) + + with col2: + st.metric("عدد المصطلحات المستخدمة", len(terms_used)) + + with col3: + st.metric("عدد الفئات", len(categories)) + + # عرض توزيع الفئات + st.markdown("#### توزيع الأمثلة حسب الفئة") + + categories_df = pd.DataFrame({ + "الفئة": list(categories.keys()), + "عدد الأمثلة": list(categories.values()) + }) + + fig, ax = plt.subplots(figsize=(10, 6)) + ax.bar(categories_df["الفئة"], categories_df["عدد الأمثلة"]) + ax.set_title("توزيع أمثلة التدريب حسب الفئة") + ax.set_xlabel("الفئة") + ax.set_ylabel("عدد الأمثلة") + + # تدوير أسماء الفئات لتسهيل القراءة + plt.xticks(rotation=45, ha='right') + plt.tight_layout() + + st.pyplot(fig) + + # تصفية الأمثلة + st.markdown("#### تصفية الأمثلة") + + filter_col1, filter_col2 = st.columns(2) + + with filter_col1: + filter_category = st.selectbox( + "تصفية حسب الفئة", + options=["الكل"] + list(categories.keys()), + key="filter_example_category" + ) + + with filter_col2: + search_query = st.text_input("بحث في النص", key="search_example") + + # تطبيق التصفية + filtered_examples = examples + if filter_category != "الكل": + filtered_examples = [ex for ex in filtered_examples if ex.get("category") == filter_category] + + if search_query: + filtered_examples = [ + ex for ex in filtered_examples + if search_query.lower() in ex.get("input", "").lower() or + search_query.lower() in ex.get("output", "").lower() + ] + + # عرض الأمثلة المصفاة + if not filtered_examples: + st.warning("لا توجد أمثلة تطابق معايير التصفية.") + else: + # عرض عدد محدود من الأمثلة في كل صفحة + examples_per_page = 10 + total_pages = (len(filtered_examples) - 1) // examples_per_page + 1 + + # التنقل بين الصفحات + col1, col2, col3 = st.columns([1, 3, 1]) + + with col2: + page = st.slider("الصفحة", 1, max(1, total_pages), 1, key="examples_page") + + start_idx = (page - 1) * examples_per_page + end_idx = min(start_idx + examples_per_page, len(filtered_examples)) + + page_examples = filtered_examples[start_idx:end_idx] + + # عرض الأمثلة + for i, example in enumerate(page_examples): + example_idx = start_idx + i + with st.expander(f"مثال #{example_idx+1} - {example.get('category', 'غير مصنف')}", expanded=i==0 and len(page_examples)<5): + ex_col1, ex_col2 = st.columns([3, 1]) + + with ex_col1: + st.markdown("**النص المدخل:**") + st.markdown(f"```\n{example.get('input', '')}\n```") + + st.markdown("**النص المتوقع:**") + st.markdown(f"```\n{example.get('output', '')}\n```") + + with ex_col2: + st.markdown("**الفئة:** " + example.get('category', 'غير مصنف')) + st.markdown("**المصطلحات المستخدمة:**") + for term in example.get("terms", []): + st.markdown(f"- {term}") + + # تاريخ الإنشاء + if "created_at" in example: + st.markdown(f"**تاريخ الإنشاء:** {example['created_at']}") + + # أزرار التحرير والحذف + if styled_button("تحرير", key=f"edit_example_{example_idx}", type="secondary", icon="✏️"): + st.session_state.example_to_edit = example_idx + + if styled_button("حذف", key=f"delete_example_{example_idx}", type="danger", icon="🗑️"): + st.session_state.example_to_delete = example_idx + + # معالجة تحرير أو حذف مثال + if "example_to_edit" in st.session_state: + self._render_edit_example_form(st.session_state.example_to_edit, filtered_examples) + + if "example_to_delete" in st.session_state: + if st.warning(f"هل أنت متأكد من حذف المثال #{st.session_state.example_to_delete+1}؟"): + if styled_button("تأكيد الحذف", key="confirm_delete_example", type="danger", icon="🗑️"): + # حذف المثال + example_index = examples.index(filtered_examples[st.session_state.example_to_delete]) + del st.session_state.terminology_data["training_examples"][example_index] + + # حفظ البيانات + self._save_terminology_data() + + # إعادة ضبط حالة الحذف + del st.session_state.example_to_delete + + st.success("تم حذف المثال بنجاح!") + st.rerun() + + if styled_button("إلغاء", key="cancel_delete_example", type="secondary", icon="❌"): + del st.session_state.example_to_delete + st.rerun() + + def _render_edit_example_form(self, example_index, examples_list): + """عرض نموذج تحرير مثال التدريب""" + example = examples_list[example_index] + + st.markdown("### تحرير مثال التدريب") + + # اختيار المصطلحات المستخدمة + all_terms = [term.get("term") for term in st.session_state.terminology_data.get("terms", [])] + selected_terms = st.multiselect( + "المصطلحات المستخدمة في المثال", + options=all_terms, + default=example.get("terms", []), + key="edit_example_terms" + ) + + # إدخال النص المدخل والمتوقع + input_text = st.text_area("النص المدخل", value=example.get("input", ""), key="edit_example_input", height=150) + output_text = st.text_area("النص المتوقع", value=example.get("output", ""), key="edit_example_output", height=150) + + # اختيار الفئة + category = st.selectbox( + "الفئة", + options=["شروط تعاقدية", "مواصفات فنية", "مستندات مناقصة", "بنود مالية", "جداول كميات", "ضمانات", "مصطلحات قانونية", "محتوى محلي", "أخرى"], + index=["شروط تعاقدية", "مواصفات فنية", "مستندات مناقصة", "بنود مالية", "جداول كميات", "ضمانات", "مصطلحات قانونية", "محتوى محلي", "أخرى"].index(example.get("category", "أخرى")), + key="edit_example_category" + ) + + col1, col2 = st.columns(2) + + with col1: + if styled_button("حفظ التغييرات", key="save_edited_example", type="primary", icon="💾"): + if not input_text or not output_text: + st.error("يرجى تعبئة النص المدخل والنص المتوقع.") + else: + # تحديث المثال + updated_example = { + "input": input_text, + "output": output_text, + "category": category, + "terms": selected_terms, + "created_at": example.get("created_at", datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")), + "updated_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + + # الحصول على المؤشر الفعلي في القائمة الكاملة + all_examples = st.session_state.terminology_data["training_examples"] + actual_index = all_examples.index(example) + + # تحديث المثال + st.session_state.terminology_data["training_examples"][actual_index] = updated_example + + # حفظ البيانات + self._save_terminology_data() + + # إعادة ضبط حالة التحرير + del st.session_state.example_to_edit + + st.success("تم تحديث مثال التدريب بنجاح!") + st.rerun() + + with col2: + if styled_button("إلغاء", key="cancel_edit_example", type="secondary", icon="❌"): + del st.session_state.example_to_edit + st.rerun() + + def _render_manual_example_creation(self): + """عرض نموذج إنشاء أمثلة يدوياً""" + st.markdown("### إنشاء مثال تدريب جديد") + + # اختيار المصطلحات المستخدمة + all_terms = [term.get("term") for term in st.session_state.terminology_data.get("terms", [])] + selected_terms = st.multiselect( + "المصطلحات المستخدمة في المثال", + options=all_terms, + key="new_example_terms" + ) + + # اختيار الفئة + category = st.selectbox( + "الفئة", + options=["شروط تعاقدية", "مواصفات فنية", "مستندات مناقصة", "بنود مالية", "جداول كميات", "ضمانات", "مصطلحات قانونية", "محتوى محلي", "أخرى"], + key="new_example_category" + ) + + # إدخال النص المدخل والمتوقع + st.markdown("**النص المدخل** (نص السؤال أو الطلب)") + input_text = st.text_area("", key="new_example_input", height=150, placeholder="مثال: قم بشرح معنى مصطلح 'محتوى محلي' وكيفية حسابه في المشاريع الحكومية.") + + st.markdown("**النص المتوقع** (الإجابة المثالية التي يجب أن يقدمها النموذج)") + output_text = st.text_area("", key="new_example_output", height=150, placeholder="مثال: المحتوى المحلي (Local Content) هو نسبة القيمة المحلية المضافة في المنتجات والخدمات المقدمة في المشروع...") + + # عرض تعريفات المصطلحات المختارة للمساعدة + if selected_terms: + with st.expander("تعريفات المصطلحات المختارة", expanded=True): + for term_name in selected_terms: + term = next((t for t in st.session_state.terminology_data.get("terms", []) if t.get("term") == term_name), None) + if term: + st.markdown(f"**{term_name}**: {term.get('definition', '')}") + + # زر إضافة المثال + if styled_button("إضافة مثال التدريب", key="add_example", type="primary", icon="➕"): + if not input_text or not output_text: + st.error("يرجى تعبئة النص المدخل والنص المتوقع.") + else: + # إنشاء كائن المثال + new_example = { + "input": input_text, + "output": output_text, + "category": category, + "terms": selected_terms, + "created_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + + # إضافة المثال للقائمة + if "training_examples" not in st.session_state.terminology_data: + st.session_state.terminology_data["training_examples"] = [] + + st.session_state.terminology_data["training_examples"].append(new_example) + + # حفظ البيانات + self._save_terminology_data() + + st.success("تم إضافة مثال التدريب بنجاح!") + st.rerun() + + def _render_automatic_example_generation(self): + """عرض واجهة توليد أمثلة تلقائياً""" + st.markdown("### توليد أمثلة تدريب تلقائياً") + + # التحقق من وجود مفاتيح API + if not self.api_key and not self.anthropic_api_key: + st.warning("لم يتم العثور على مفاتيح API للذكاء الاصطناعي. يرجى إضافة OPENAI_API_KEY أو ANTHROPIC_API_KEY إلى المتغيرات البيئية.") + return + + # اختيار نموذج الذكاء الاصطناعي + ai_models = [] + + if self.api_key: + ai_models.extend(["gpt-4o", "gpt-3.5-turbo"]) + + if self.anthropic_api_key: + ai_models.extend(["claude-3-7-sonnet-20250219"]) + + selected_model = st.selectbox( + "اختر نموذج الذكاء الاصطناعي", + options=ai_models, + key="auto_gen_model" + ) + + # اختيار المصطلحات لتوليد أمثلة حولها + all_terms = [term.get("term") for term in st.session_state.terminology_data.get("terms", [])] + selected_terms = st.multiselect( + "اختر المصطلحات لتوليد أمثلة حولها", + options=all_terms, + key="auto_gen_terms" + ) + + # اختيار عدد الأمثلة المراد توليدها + num_examples = st.slider("عدد الأمثلة لكل مصطلح", 1, 5, 2, key="auto_gen_count") + + # اختيار الفئات المرغوبة + selected_categories = st.multiselect( + "اختر الفئات المرغوبة للأمثلة", + options=["شروط تعاقدية", "مواصفات فنية", "مستندات مناقصة", "بنود مالية", "جداول كميات", "ضمانات", "مصطلحات قانونية", "محتوى محلي", "أخرى"], + default=["شروط تعاقدية", "مستندات مناقصة", "مصطلحات قانونية"], + key="auto_gen_categories" + ) + + # زر توليد الأمثلة + if styled_button("توليد الأمثلة", key="generate_examples", type="primary", icon="✨"): + if not selected_terms: + st.error("يرجى اختيار مصطلح واحد على الأقل.") + elif not selected_categories: + st.error("يرجى اختيار فئة واحدة على الأقل.") + else: + # عرض شريط التقدم + progress_bar = st.progress(0) + status_text = st.empty() + + # تجهيز المصطلحات وتعريفاتها + terms_with_definitions = {} + for term_name in selected_terms: + term = next((t for t in st.session_state.terminology_data.get("terms", []) if t.get("term") == term_name), None) + if term: + terms_with_definitions[term_name] = term.get('definition', '') + + # توليد الأمثلة + generated_examples = [] + total_iterations = len(selected_terms) * len(selected_categories) + current_iteration = 0 + + for term_name, definition in terms_with_definitions.items(): + for category in selected_categories: + current_iteration += 1 + progress = current_iteration / total_iterations + progress_bar.progress(progress) + status_text.text(f"جاري توليد أمثلة للمصطلح '{term_name}' في الفئة '{category}'...") + + # توليد أمثلة لهذا المصطلح والفئة + examples = self._generate_examples_with_ai( + term_name, + definition, + category, + num_examples, + selected_model + ) + + generated_examples.extend(examples) + + # إضافة الأمثلة المولدة إلى البيانات + if "training_examples" not in st.session_state.terminology_data: + st.session_state.terminology_data["training_examples"] = [] + + st.session_state.terminology_data["training_examples"].extend(generated_examples) + + # حفظ البيانات + self._save_terminology_data() + + # إكمال شريط التقدم + progress_bar.progress(1.0) + status_text.text(f"تم توليد {len(generated_examples)} مثال بنجاح!") + + st.success(f"تم توليد {len(generated_examples)} مثال بنجاح!") + st.rerun() + + def _generate_examples_with_ai(self, term_name, definition, category, num_examples, model): + """توليد أمثلة باستخدام الذكاء الاصطناعي""" + # تحضير الرسالة + prompt = f""" + أنت خبير في توليد أمثلة تدريب لضبط نماذج الذكاء الاصطناعي على المصطلحات التعاقدية والهندسية المتخصصة باللغة العربية. + + أريد منك توليد {num_examples} مثال تدريب للمصطلح التالي: + + المصطلح: {term_name} + التعريف: {definition} + الفئة: {category} + + لكل مثال، قم بتوليد: + 1. نص المدخل (سؤال أو طلب حول المصطلح) + 2. نص المخرج المتوقع (الإجابة المثالية التي يجب أن يقدمها النموذج) + + تأكد من: + - جعل الأمثلة متنوعة وواقعية + - تضمين سياقات مختلفة لاستخدام المصطلح + - استخدام أسلوب مناسب لوثائق المناقصات والعقود + - تضمين تفاصيل تقنية دقيقة عند الحاجة + + قم بإرجاع النتائج بتنسيق JSON كما يلي: + + ```json + [ + { + "input": "نص المدخل للمثال الأول", + "output": "نص المخرج المتوقع للمثال الأول" + }, + { + "input": "نص المدخل للمثال الثاني", + "output": "نص المخرج المتوقع للمثال الثاني" + }, + ... + ] + ``` + + أرجع البيانات بتنسيق JSON فقط. + """ + + try: + # استدعاء API المناسب حسب النموذج المختار + if "gpt" in model and self.api_key: + # استخدام OpenAI API + openai.api_key = self.api_key + + response = openai.chat.completions.create( + model=model, + messages=[ + {"role": "system", "content": "أنت مساعد محترف متخصص في توليد بيانات تدريب لضبط نماذج الذكاء الاصطناعي."}, + {"role": "user", "content": prompt} + ], + temperature=0.7, + response_format={"type": "json_object"} + ) + + # استخراج النتيجة + result_text = response.choices[0].message.content + + # تنظيف النص واستخراج JSON + json_match = re.search(r'```json\s*(.*?)\s*```', result_text, re.DOTALL) + if json_match: + result_json = json_match.group(1) + else: + result_json = result_text + + # تحليل JSON + examples_data = json.loads(result_json) + + # إذا كان الناتج كائن JSON بخاصية examples + if isinstance(examples_data, dict) and "examples" in examples_data: + examples_data = examples_data["examples"] + + elif "claude" in model and self.anthropic_api_key: + # استخدام Anthropic API + from anthropic import Anthropic + + anthropic_client = Anthropic(api_key=self.anthropic_api_key) + + response = anthropic_client.messages.create( + model=model, + max_tokens=4000, + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.7 + ) + + # استخراج النتيجة + result_text = response.content[0].text + + # تنظيف النص واستخراج JSON + json_match = re.search(r'```json\s*(.*?)\s*```', result_text, re.DOTALL) + if json_match: + result_json = json_match.group(1) + else: + result_json = result_text + + # تحليل JSON + examples_data = json.loads(result_json) + + # إذا كان الناتج كائن JSON بخاصية examples + if isinstance(examples_data, dict) and "examples" in examples_data: + examples_data = examples_data["examples"] + + else: + # في حالة عدم توفر النموذج المطلوب + return [] + + # تحويل البيانات إلى الصيغة المطلوبة للأمثلة + formatted_examples = [] + + for example in examples_data: + formatted_examples.append({ + "input": example["input"], + "output": example["output"], + "category": category, + "terms": [term_name], + "created_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "generated_by": model + }) + + return formatted_examples + + except Exception as e: + st.error(f"حدث خطأ أثناء توليد الأمثلة: {str(e)}") + return [] + + def _render_import_export_training_data(self): + """عرض واجهة استيراد وتصدير بيانات التدريب""" + st.markdown("### استيراد وتصدير بيانات التدريب") + + col1, col2 = st.columns(2) + + with col1: + st.markdown("#### تصدير بيانات التدريب") + + export_format = st.selectbox( + "صيغة التصدير", + options=["JSON", "JSONL", "CSV"], + key="export_format" + ) + + if styled_button("تصدير البيانات", key="export_data", type="primary", icon="📤"): + self._export_training_data(export_format) + + with col2: + st.markdown("#### استيراد بيانات التدريب") + + import_format = st.selectbox( + "صيغة الاستيراد", + options=["JSON", "JSONL", "CSV"], + key="import_format" + ) + + uploaded_file = st.file_uploader("استيراد بيانات التدريب", type=["json", "jsonl", "csv"], key="import_data_file") + + if uploaded_file is not None: + if styled_button("استيراد البيانات", key="import_data", type="success", icon="📥"): + self._import_training_data(uploaded_file, import_format) + + def _export_training_data(self, format): + """تصدير بيانات التدريب إلى ملف""" + examples = st.session_state.terminology_data.get("training_examples", []) + + if not examples: + st.error("لا توجد بيانات تدريب للتصدير.") + return + + try: + if format == "JSON": + # تصدير إلى ملف JSON + with tempfile.NamedTemporaryFile(mode='w+', suffix='.json', encoding='utf-8', delete=False) as f: + json.dump(examples, f, ensure_ascii=False, indent=2) + json_path = f.name + + # قراءة الملف وتقديمه للتنزيل + with open(json_path, 'r', encoding='utf-8') as f: + json_data = f.read() + + st.download_button( + label="تنزيل ملف JSON", + data=json_data, + file_name="training_data.json", + mime="application/json" + ) + + # حذف الملف المؤقت + os.unlink(json_path) + + elif format == "JSONL": + # تصدير إلى ملف JSONL + jsonl_content = "" + for example in examples: + jsonl_content += json.dumps(example, ensure_ascii=False) + "\n" + + st.download_button( + label="تنزيل ملف JSONL", + data=jsonl_content, + file_name="training_data.jsonl", + mime="application/jsonl" + ) + + elif format == "CSV": + # تصدير إلى ملف CSV + with tempfile.NamedTemporaryFile(mode='w+', suffix='.csv', newline='', encoding='utf-8', delete=False) as f: + writer = csv.writer(f) + + # كتابة الترويسة + writer.writerow([ + 'النص المدخل', 'النص المتوقع', 'الفئة', 'المصطلحات', 'تاريخ الإنشاء', 'تم التوليد بواسطة' + ]) + + # كتابة البيانات + for example in examples: + writer.writerow([ + example.get('input', ''), + example.get('output', ''), + example.get('category', ''), + '|'.join(example.get('terms', [])), + example.get('created_at', ''), + example.get('generated_by', 'يدوي') + ]) + + # الحصول على مسار الملف + csv_path = f.name + + # قراءة الملف وتقديمه للتنزيل + with open(csv_path, 'r', encoding='utf-8') as f: + csv_data = f.read() + + st.download_button( + label="تنزيل ملف CSV", + data=csv_data, + file_name="training_data.csv", + mime="text/csv" + ) + + # حذف الملف المؤقت + os.unlink(csv_path) + + except Exception as e: + st.error(f"حدث خطأ أثناء تصدير البيانات: {str(e)}") + + def _import_training_data(self, uploaded_file, format): + """استيراد بيانات التدريب من ملف""" + try: + examples = [] + + if format == "JSON": + # استيراد من ملف JSON + content = uploaded_file.read().decode('utf-8') + examples = json.loads(content) + + elif format == "JSONL": + # استيراد من ملف JSONL + content = uploaded_file.read().decode('utf-8') + + for line in content.strip().split('\n'): + if line.strip(): + examples.append(json.loads(line)) + + elif format == "CSV": + # استيراد من ملف CSV + df = pd.read_csv(uploaded_file, encoding='utf-8') + + for _, row in df.iterrows(): + example = { + "input": row.get('النص المدخل', ''), + "output": row.get('النص المتوقع', ''), + "category": row.get('الفئة', 'أخرى'), + "terms": row.get('المصطلحات', '').split('|') if pd.notna(row.get('المصطلحات', '')) else [], + "created_at": row.get('تاريخ الإنشاء', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")), + "generated_by": row.get('تم التوليد بواسطة', 'مستورد') + } + + examples.append(example) + + # إضافة الأمثلة المستوردة + if "training_examples" not in st.session_state.terminology_data: + st.session_state.terminology_data["training_examples"] = [] + + # فلترة الأمثلة الصحيحة + valid_examples = [] + for ex in examples: + if "input" in ex and "output" in ex: + valid_examples.append(ex) + + # إضافة الأمثلة وحفظ البيانات + if valid_examples: + st.session_state.terminology_data["training_examples"].extend(valid_examples) + self._save_terminology_data() + + st.success(f"تم استيراد {len(valid_examples)} مثال بنجاح!") + st.rerun() + else: + st.error("لم يتم العثور على أمثلة صالحة في الملف.") + + except Exception as e: + st.error(f"حدث خطأ أثناء استيراد البيانات: {str(e)}") + + def _render_model_training(self): + """عرض واجهة تدريب النموذج""" + st.markdown(""" +
+

🧠 تدريب النموذج

+

قم بتدريب نموذج الذكاء الاصطناعي على المصطلحات المتخصصة باستخدام أمثلة التدريب.

+

يمكنك اختيار النموذج الأساسي والإعدادات المناسبة لعملية التدريب.

+
+ """, unsafe_allow_html=True) + + # التحقق من وجود بيانات تدريب كافية + examples = st.session_state.terminology_data.get("training_examples", []) + if len(examples) < 10: + st.warning(f"عدد أمثلة التدريب الحالية ({len(examples)}) غير كافٍ للتدريب. يُنصح بوجود 10 أمثلة على الأقل.") + + # تبويبات تدريب النموذج + training_tabs = st.tabs(["إعداد التدريب", "نماذج سابقة", "وظائف التدريب النشطة"]) + + # تبويب إعداد التدريب + with training_tabs[0]: + self._render_training_setup() + + # تبويب النماذج السابقة + with training_tabs[1]: + self._render_previous_models() + + # تبويب وظائف التدريب النشطة + with training_tabs[2]: + self._render_active_training_jobs() + + def _render_training_setup(self): + """عرض إعدادات تدريب النموذج""" + st.markdown("### إعداد عملية التدريب") + + # التحقق من وجود مفاتيح API + if not self.api_key and not self.anthropic_api_key: + st.warning("لم يتم العثور على مفاتيح API للذكاء الاصطناعي. يرجى إضافة OPENAI_API_KEY أو ANTHROPIC_API_KEY إلى المتغيرات البيئية.") + return + + # اختيار نموذج الذكاء الاصطناعي الأساسي + provider_options = [] + if self.api_key: + provider_options.append("OpenAI") + if self.anthropic_api_key: + provider_options.append("Anthropic") + + provider = st.selectbox( + "مزود الذكاء الاصطناعي", + options=provider_options, + key="training_provider" + ) + + # الإعدادات حسب المزود + if provider == "OpenAI": + # نماذج OpenAI المتاحة للضبط + base_model = st.selectbox( + "النموذج الأساسي", + options=["gpt-3.5-turbo-0125", "gpt-4o-mini"], + key="openai_base_model" + ) + + # إعدادات التدريب + col1, col2 = st.columns(2) + + with col1: + n_epochs = st.slider("عدد الحقب (Epochs)", 1, 4, 2, key="openai_epochs") + batch_size = st.selectbox("حجم الدفعة (Batch Size)", options=[1, 2, 4, 8], index=1, key="openai_batch_size") + + with col2: + learning_rate_multiplier = st.slider("مضاعف معدل التعلم", 0.1, 2.0, 1.0, 0.1, key="openai_lr") + suffix = st.text_input("لاحقة اسم النموذج", value="arabic-contracts-expert", key="openai_suffix") + + # زر بدء التدريب + if styled_button("بدء التدريب", key="start_openai_training", type="primary", icon="🚀"): + # التحقق من وجود بيانات كافية + examples = st.session_state.terminology_data.get("training_examples", []) + if len(examples) < 10: + st.error("عدد أ��ثلة التدريب الحالية قليل جداً. يُفضل وجود على الأقل 10 أمثلة للتدريب.") + else: + # التأكيد على بدء التدريب + confirm = st.warning(f"سيتم بدء عملية تدريب نموذج {base_model} باستخدام {len(examples)} مثال. هل أنت متأكد؟") + if styled_button("تأكيد بدء التدريب", key="confirm_openai_training", type="success", icon="✅"): + # توجيه بيانات التدريب لصيغة OpenAI + formatted_data = self._format_training_data_for_openai(examples) + + # بدء التدريب + self._start_openai_training( + base_model=base_model, + training_data=formatted_data, + n_epochs=n_epochs, + batch_size=batch_size, + learning_rate_multiplier=learning_rate_multiplier, + suffix=suffix + ) + + elif provider == "Anthropic": + st.info("ضبط نماذج Anthropic غير متاح حالياً في واجهة البرمجة العامة. يمكنك استخدام أمثلة التدريب مع المساعد المتخصص.") + + def _format_training_data_for_openai(self, examples): + """تنسيق بيانات التدريب لواجهة برمجة OpenAI""" + formatted_examples = [] + + for example in examples: + formatted_examples.append({ + "messages": [ + {"role": "user", "content": example.get("input", "")}, + {"role": "assistant", "content": example.get("output", "")} + ] + }) + + return formatted_examples + + def _start_openai_training(self, base_model, training_data, n_epochs, batch_size, learning_rate_multiplier, suffix): + """بدء عملية تدريب نموذج OpenAI""" + try: + # تهيئة واجهة برمجة OpenAI + openai.api_key = self.api_key + + # إنشاء ملف تدريب + training_file_path = os.path.join(self.training_data_dir, f"training_data_{int(time.time())}.jsonl") + + with open(training_file_path, 'w', encoding='utf-8') as f: + for example in training_data: + f.write(json.dumps(example, ensure_ascii=False) + "\n") + + # رفع ملف التدريب إلى OpenAI + with open(training_file_path, 'rb') as f: + response = openai.files.create( + file=f, + purpose="fine-tune" + ) + + file_id = response.id + + # بدء وظيفة التدريب + response = openai.fine_tuning.jobs.create( + training_file=file_id, + model=base_model, + hyperparameters={ + "n_epochs": n_epochs, + "batch_size": batch_size, + "learning_rate_multiplier": learning_rate_multiplier + }, + suffix=suffix + ) + + job_id = response.id + + # تخزين معلومات وظيفة التدريب + training_job = { + "job_id": job_id, + "provider": "OpenAI", + "base_model": base_model, + "n_epochs": n_epochs, + "batch_size": batch_size, + "learning_rate_multiplier": learning_rate_multiplier, + "suffix": suffix, + "status": "running", + "file_id": file_id, + "file_path": training_file_path, + "examples_count": len(training_data), + "started_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "finished_at": None, + "fine_tuned_model": None + } + + # إضافة الوظيفة إلى حالة الجلسة + st.session_state.active_training_job = training_job + + # إضافة الوظيفة إلى قائمة وظائف التدريب + if "training_jobs" not in st.session_state.terminology_data: + st.session_state.terminology_data["training_jobs"] = [] + + st.session_state.terminology_data["training_jobs"].append(training_job) + + # حفظ البيانات + self._save_terminology_data() + + st.success(f"تم بدء وظيفة التدريب بنجاح! معرف الوظيفة: {job_id}") + st.info("يمكنك متابعة حالة التدريب من تبويب 'وظائف التدريب النشطة'.") + + except Exception as e: + st.error(f"حدث خطأ أثناء بدء عملية التدريب: {str(e)}") + + def _render_previous_models(self): + """عرض النماذج المدربة سابقاً""" + st.markdown("### النماذج المدربة سابقاً") + + # الحصول على النماذج المدربة + models = st.session_state.terminology_data.get("models", []) + + if not models: + st.info("لا توجد نماذج مدربة سابقاً.") + return + + # عرض النماذج + for i, model in enumerate(models): + with st.expander(f"{model.get('model_id')} - {model.get('base_model')}", expanded=i==0): + col1, col2 = st.columns([3, 1]) + + with col1: + st.markdown(f"**معرف النموذج:** {model.get('model_id')}") + st.markdown(f"**النموذج الأساسي:** {model.get('base_model')}") + st.markdown(f"**عدد أمثلة التدريب:** {model.get('examples_count')}") + st.markdown(f"**تاريخ الإنشاء:** {model.get('created_at')}") + + # عرض مؤشرات الأداء إن وجدت + if "metrics" in model: + st.markdown("#### مؤشرات الأداء") + + metrics = model.get("metrics", {}) + for metric_name, metric_value in metrics.items(): + st.markdown(f"**{metric_name}:** {metric_value}") + + with col2: + # أزرار الاستخدام والحذف + if styled_button("استخدام النموذج", key=f"use_model_{i}", type="primary", icon="✅"): + st.session_state.selected_model = model.get('model_id') + st.success(f"تم اختيار النموذج {model.get('model_id')} للاستخدام.") + + if styled_button("حذف النموذج", key=f"delete_model_{i}", type="danger", icon="🗑️"): + st.session_state.model_to_delete = i + + # عرض الوصف والملاحظات + st.markdown(f"**الوصف:** {model.get('description', 'لا يوجد وصف.')}") + + # عرض النماذج المستخدمة في التدريب + if "examples_preview" in model and model["examples_preview"]: + with st.expander("عينة من أمثلة التدريب"): + for j, example in enumerate(model["examples_preview"]): + st.markdown(f"**مثال #{j+1}**") + st.markdown(f"**المدخل:** {example.get('input')}") + st.markdown(f"**المخرج:** {example.get('output')}") + st.markdown("---") + + # معالجة حذف النموذج + if "model_to_delete" in st.session_state: + if st.warning(f"هل أنت متأكد من حذف النموذج '{models[st.session_state.model_to_delete].get('model_id')}'؟"): + if styled_button("تأكيد الحذف", key="confirm_delete_model", type="danger", icon="🗑️"): + # حذف النموذج + del st.session_state.terminology_data["models"][st.session_state.model_to_delete] + + # حفظ البيانات + self._save_terminology_data() + + # إعادة ضبط حالة الحذف + del st.session_state.model_to_delete + + st.success("تم حذف النموذج بنجاح!") + st.rerun() + + if styled_button("إلغاء", key="cancel_delete_model", type="secondary", icon="❌"): + del st.session_state.model_to_delete + st.rerun() + + def _render_active_training_jobs(self): + """عرض وظائف التدريب النشطة""" + st.markdown("### وظائف التدريب النشطة") + + # الحصول على وظائف التدريب + jobs = st.session_state.terminology_data.get("training_jobs", []) + + # فرز الوظائف حسب الحالة + active_jobs = [job for job in jobs if job.get("status") in ["running", "validating_files", "queued"]] + completed_jobs = [job for job in jobs if job.get("status") == "succeeded"] + failed_jobs = [job for job in jobs if job.get("status") in ["failed", "cancelled"]] + + # زر تحديث حالة الوظائف + if styled_button("تحديث حالة الوظائف", key="refresh_jobs", type="primary", icon="🔄"): + self._refresh_training_jobs_status() + + # عرض الوظائف النشطة + if active_jobs: + st.markdown("#### الوظائف النشطة") + + for i, job in enumerate(active_jobs): + with st.expander(f"{job.get('job_id')} - {job.get('base_model')}", expanded=True): + col1, col2 = st.columns([3, 1]) + + with col1: + st.markdown(f"**معرف الوظيفة:** {job.get('job_id')}") + st.markdown(f"**النموذج الأساسي:** {job.get('base_model')}") + st.markdown(f"**الحالة:** {job.get('status')}") + st.markdown(f"**تاريخ البدء:** {job.get('started_at')}") + + # عرض تقدم التدريب إن وجد + if "progress" in job: + progress = job.get("progress", 0) + st.progress(progress) + st.markdown(f"**التقدم:** {progress*100:.1f}%") + + with col2: + # زر إلغاء الوظيفة + if styled_button("إلغاء الوظيفة", key=f"cancel_job_{i}", type="danger", icon="⛔"): + st.session_state.job_to_cancel = i + + # عرض معلومات إضافية + st.markdown(f"**عدد الحقب:** {job.get('n_epochs')}") + st.markdown(f"**حجم الدفعة:** {job.get('batch_size')}") + st.markdown(f"**مضاعف معدل التعلم:** {job.get('learning_rate_multiplier')}") + else: + st.info("لا توجد وظائف تدريب نشطة حالياً.") + + # عرض الوظائف المكتملة + if completed_jobs: + st.markdown("#### الوظائف المكتملة") + + for i, job in enumerate(completed_jobs): + with st.expander(f"{job.get('job_id')} - {job.get('base_model')}", expanded=False): + col1, col2 = st.columns([3, 1]) + + with col1: + st.markdown(f"**معرف الوظيفة:** {job.get('job_id')}") + st.markdown(f"**النموذج الأساسي:** {job.get('base_model')}") + st.markdown(f"**تاريخ البدء:** {job.get('started_at')}") + st.markdown(f"**تاريخ الانتهاء:** {job.get('finished_at')}") + st.markdown(f"**النموذج المدرب:** {job.get('fine_tuned_model')}") + + with col2: + # زر استخدام النموذج المدرب + if styled_button("استخدام النموذج", key=f"use_trained_model_{i}", type="primary", icon="✅"): + st.session_state.selected_model = job.get('fine_tuned_model') + st.success(f"تم اختيار النموذج {job.get('fine_tuned_model')} للاستخدام.") + + # زر حذف الوظيفة + if styled_button("حذف الوظيفة", key=f"delete_completed_job_{i}", type="danger", icon="🗑️"): + st.session_state.completed_job_to_delete = len(active_jobs) + i + + # عرض الوظائف الفاشلة + if failed_jobs: + st.markdown("#### الوظائف الفاشلة") + + for i, job in enumerate(failed_jobs): + with st.expander(f"{job.get('job_id')} - {job.get('base_model')}", expanded=False): + col1, col2 = st.columns([3, 1]) + + with col1: + st.markdown(f"**معرف الوظيفة:** {job.get('job_id')}") + st.markdown(f"**النموذج الأساسي:** {job.get('base_model')}") + st.markdown(f"**الحالة:** {job.get('status')}") + st.markdown(f"**تاريخ البدء:** {job.get('started_at')}") + + # عرض سبب الفشل إن وجد + if "error" in job: + st.error(f"سبب الفشل: {job.get('error')}") + + with col2: + # زر حذف الوظيفة + if styled_button("حذف الوظيفة", key=f"delete_failed_job_{i}", type="danger", icon="🗑️"): + st.session_state.failed_job_to_delete = len(active_jobs) + len(completed_jobs) + i + + # معالجة إلغاء الوظيفة + if "job_to_cancel" in st.session_state: + if st.warning(f"هل أنت م��أكد من إلغاء وظيفة التدريب '{active_jobs[st.session_state.job_to_cancel].get('job_id')}'؟"): + if styled_button("تأكيد الإلغاء", key="confirm_cancel_job", type="danger", icon="🗑️"): + # إلغاء الوظيفة + self._cancel_training_job(active_jobs[st.session_state.job_to_cancel]) + + # إعادة ضبط حالة الإلغاء + del st.session_state.job_to_cancel + + st.success("تم إلغاء وظيفة التدريب بنجاح!") + st.rerun() + + if styled_button("إلغاء", key="cancel_job_cancellation", type="secondary", icon="❌"): + del st.session_state.job_to_cancel + st.rerun() + + # معالجة حذف الوظائف المكتملة + if "completed_job_to_delete" in st.session_state: + idx = st.session_state.completed_job_to_delete + if 0 <= idx < len(jobs): + if st.warning(f"هل أنت متأكد من حذف وظيفة التدريب '{jobs[idx].get('job_id')}'؟"): + if styled_button("تأكيد الحذف", key="confirm_delete_completed_job", type="danger", icon="🗑️"): + # حذف الوظيفة + del st.session_state.terminology_data["training_jobs"][idx] + + # حفظ البيانات + self._save_terminology_data() + + # إعادة ضبط حالة الحذف + del st.session_state.completed_job_to_delete + + st.success("تم حذف وظيفة التدريب بنجاح!") + st.rerun() + + if styled_button("إلغاء", key="cancel_completed_job_deletion", type="secondary", icon="❌"): + del st.session_state.completed_job_to_delete + st.rerun() + + # معالجة حذف الوظائف الفاشلة + if "failed_job_to_delete" in st.session_state: + idx = st.session_state.failed_job_to_delete + if 0 <= idx < len(jobs): + if st.warning(f"هل أنت متأكد من حذف وظيفة التدريب '{jobs[idx].get('job_id')}'؟"): + if styled_button("تأكيد الحذف", key="confirm_delete_failed_job", type="danger", icon="🗑️"): + # حذف الوظيفة + del st.session_state.terminology_data["training_jobs"][idx] + + # حفظ البيانات + self._save_terminology_data() + + # إعادة ضبط حالة الحذف + del st.session_state.failed_job_to_delete + + st.success("تم حذف وظيفة التدريب بنجاح!") + st.rerun() + + if styled_button("إلغاء", key="cancel_failed_job_deletion", type="secondary", icon="❌"): + del st.session_state.failed_job_to_delete + st.rerun() + + def _refresh_training_jobs_status(self): + """تحديث حالة وظائف التدريب""" + jobs = st.session_state.terminology_data.get("training_jobs", []) + + # فلترة الوظائف النشطة + active_jobs = [job for job in jobs if job.get("status") in ["running", "validating_files", "queued"]] + + if not active_jobs: + st.info("لا توجد وظائف تدريب نشطة للتحديث.") + return + + try: + # تحديث حالة كل وظيفة نشطة + for job in active_jobs: + if job.get("provider") == "OpenAI" and self.api_key: + # تحديث حالة وظيفة OpenAI + job_id = job.get("job_id") + + # استعلام عن حالة الوظيفة + openai.api_key = self.api_key + response = openai.fine_tuning.jobs.retrieve(job_id) + + # تحديث حالة الوظيفة + job["status"] = response.status + + # تحديث التقدم إن وجد + if hasattr(response, "progress") and response.progress is not None: + job["progress"] = response.progress + + # إذا اكتملت الوظيفة، تحديث معلومات النموذج المدرب + if response.status == "succeeded": + job["finished_at"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + job["fine_tuned_model"] = response.fine_tuned_model + + # إضافة النموذج المدرب إلى قائمة النماذج + self._add_trained_model(job, response) + + # إذا فشلت الوظيفة، تسجيل سبب الفشل + elif response.status == "failed" and hasattr(response, "error"): + job["error"] = response.error + + # حفظ البيانات + self._save_terminology_data() + + st.success("تم تحديث حالة وظائف التدريب بنجاح!") + + except Exception as e: + st.error(f"حدث خطأ أثناء تحديث حالة وظائف التدريب: {str(e)}") + + def _cancel_training_job(self, job): + """إلغاء وظيفة تدريب""" + try: + if job.get("provider") == "OpenAI" and self.api_key: + # إلغاء وظيفة OpenAI + job_id = job.get("job_id") + + # استدعاء واجهة برمجة OpenAI + openai.api_key = self.api_key + openai.fine_tuning.jobs.cancel(job_id) + + # تحديث حالة الوظيفة + job["status"] = "cancelled" + job["finished_at"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # حفظ البيانات + self._save_terminology_data() + + return True + + return False + + except Exception as e: + st.error(f"حدث خطأ أثناء إلغاء وظيفة التدريب: {str(e)}") + return False + + def _add_trained_model(self, job, response): + """إضافة النموذج المدرب إلى قائمة النماذج""" + # إنشاء كائن النموذج + model = { + "model_id": response.fine_tuned_model, + "base_model": job.get("base_model"), + "provider": job.get("provider"), + "training_job_id": job.get("job_id"), + "description": f"النموذج المدرب على المصطلحات المتخصصة في {job.get('suffix')}", + "examples_count": job.get("examples_count"), + "created_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "metrics": {} + } + + # إضافة مؤشرات الأداء إن وجدت + if hasattr(response, "result_files") and response.result_files: + # تنزيل ملف النتائج وقراءة مؤشرات الأداء + pass + + # إضافة عينة من أمثلة التدريب + examples = st.session_state.terminology_data.get("training_examples", []) + if examples: + # أخذ 5 أمثلة كعينة + sample_examples = random.sample(examples, min(5, len(examples))) + model["examples_preview"] = sample_examples + + # إضافة النموذج إلى قائمة النماذج + if "models" not in st.session_state.terminology_data: + st.session_state.terminology_data["models"] = [] + + st.session_state.terminology_data["models"].append(model) + + def _render_model_testing(self): + """عرض واجهة اختبار النموذج""" + st.markdown(""" +
+

🧪 اختبار النموذج

+

اختبر نموذج الذكاء الاصطناعي المدرب على المصطلحات المتخصصة.

+

يمكنك تجريب أسئلة مختلفة ومقارنة النتائج مع النماذج الأخرى.

+
+ """, unsafe_allow_html=True) + + # التحقق من وجود مفاتيح API + if not self.api_key and not self.anthropic_api_key: + st.warning("لم يتم العثور على مفاتيح API للذكاء الاصطناعي. يرجى إضافة OPENAI_API_KEY أو ANTHROPIC_API_KEY إلى المتغيرات البيئية.") + return + + # الحصول على قائمة النماذج المتاحة + available_models = [] + + # OpenAI + if self.api_key: + available_models.extend(["gpt-4o", "gpt-3.5-turbo"]) + + # إضافة النماذج المدربة إن وجدت + for model in st.session_state.terminology_data.get("models", []): + if model.get("provider") == "OpenAI": + available_models.append(model.get("model_id")) + + # Anthropic + if self.anthropic_api_key: + available_models.extend(["claude-3-7-sonnet-20250219"]) + + # اختيار النموذج + selected_model = st.selectbox( + "اختر النموذج", + options=available_models, + key="test_model" + ) + + # إدخال النص للاختبار + test_input = st.text_area( + "أدخل نص الاختبار", + value="ما هو مفهوم المحتوى المحلي في المشاريع الحكومية وكيف يتم حسابه؟", + height=150, + key="test_input" + ) + + # خيارات متقدمة + with st.expander("خيارات متقدمة"): + temperature = st.slider("درجة الإبداعية (Temperature)", 0.0, 1.0, 0.7, 0.1, key="test_temperature") + max_tokens = st.slider("الحد الأقصى للرموز (Max Tokens)", 100, 2000, 500, 100, key="test_max_tokens") + + # تحميل المصطلحات والتعريفات + terms_with_definitions = {} + for term in st.session_state.terminology_data.get("terms", []): + terms_with_definitions[term.get("term")] = term.get("definition") + + # زر إجراء الاختبار + if styled_button("إجراء الاختبار", key="run_test", type="primary", icon="🧪"): + if not test_input: + st.error("يرجى إدخال نص للاختبار.") + else: + # عرض شريط التقدم + with st.spinner("جاري معالجة النص..."): + # إجراء الاختبار + response = self._test_model( + model=selected_model, + input_text=test_input, + temperature=temperature, + max_tokens=max_tokens, + terms_with_definitions=terms_with_definitions + ) + + # عرض النتيجة + st.markdown("### نتيجة الاختبار") + st.markdown(response) + + # تحليل الاستجابة لاكتشاف المصطلحات المستخدمة + used_terms = [] + for term in terms_with_definitions: + if term in response: + used_terms.append(term) + + if used_terms: + st.markdown("### المصطلحات المكتشفة في الاستجابة") + for term in used_terms: + st.markdown(f"- **{term}**: {terms_with_definitions[term]}") + + def _test_model(self, model, input_text, temperature, max_tokens, terms_with_definitions): + """اختبار النموذج""" + try: + # تجهيز المحتوى النظامي + system_prompt = "أنت مساعد متخصص في عقود المقاولات والمناقصات باللغة العربية. قم بالإجابة بدقة على الأسئلة والطلبات مع مراعاة المصطلحات الفنية المتخصصة." + + # إضافة المصطلحات إلى المحتوى النظامي + if terms_with_definitions: + system_prompt += "\n\nفيما يلي قائمة بالمصطلحات المتخصصة وتعريفاتها:\n\n" + for term, definition in terms_with_definitions.items(): + system_prompt += f"- {term}: {definition}\n" + + # OpenAI + if "gpt" in model or any(model_data.get("model_id") == model for model_data in st.session_state.terminology_data.get("models", [])): + # استخدام OpenAI API + openai.api_key = self.api_key + + response = openai.chat.completions.create( + model=model, + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": input_text} + ], + temperature=temperature, + max_tokens=max_tokens + ) + + return response.choices[0].message.content + + # Anthropic + elif "claude" in model and self.anthropic_api_key: + # استخدام Anthropic API + from anthropic import Anthropic + + anthropic_client = Anthropic(api_key=self.anthropic_api_key) + + response = anthropic_client.messages.create( + model=model, + max_tokens=max_tokens, + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": input_text} + ], + temperature=temperature + ) + + return response.content[0].text + + else: + return "النموذج المختار غير مدعوم حالياً." + + except Exception as e: + return f"حدث خطأ أثناء اختبار النموذج: {str(e)}" + + def _render_specialized_assistant(self): + """عرض واجهة المساعد المتخصص""" + st.markdown(""" +
+

🤖 المساعد المتخصص

+

استخدم المساعد الذكي المتخصص في المصطلحات التعاقدية الهندسية.

+

يمكنك طرح أسئلة حول المصطلحات وتفسيراتها واستخداماتها.

+
+ """, unsafe_allow_html=True) + + # التحقق من وجود مفاتيح API + if not self.api_key and not self.anthropic_api_key: + st.warning("لم يتم العثور على مفاتيح API للذكاء الاصطناعي. يرجى إضافة OPENAI_API_KEY أو ANTHROPIC_API_KEY إلى المتغيرات البيئية.") + return + + # الحصول على قائمة النماذج المتاحة + available_models = [] + + # OpenAI + if self.api_key: + available_models.extend(["gpt-4o", "gpt-3.5-turbo"]) + + # إضافة النماذج المدربة إن وجدت + for model in st.session_state.terminology_data.get("models", []): + if model.get("provider") == "OpenAI": + available_models.append(model.get("model_id")) + + # Anthropic + if self.anthropic_api_key: + available_models.extend(["claude-3-7-sonnet-20250219"]) + + # تهيئة حالة المحادثة + if "chat_history" not in st.session_state: + st.session_state.chat_history = [] + + if "assistant_model" not in st.session_state: + st.session_state.assistant_model = available_models[0] if available_models else "" + + # اختيار النموذج + selected_model = st.selectbox( + "اختر نموذج المساعد", + options=available_models, + index=available_models.index(st.session_state.assistant_model) if st.session_state.assistant_model in available_models else 0, + key="assistant_model_selector" + ) + + # تحديث النموذج المختار + if selected_model != st.session_state.assistant_model: + st.session_state.assistant_model = selected_model + st.rerun() + + # عرض المحادثة + st.markdown("### المحادثة") + + for message in st.session_state.chat_history: + if message["role"] == "user": + st.markdown(f""" +
+ أنت: {message["content"]} +
+ """, unsafe_allow_html=True) + else: + st.markdown(f""" +
+ المساعد: {message["content"]} +
+ """, unsafe_allow_html=True) + + # إدخال رسالة جديدة + user_input = st.text_area("اكتب رسالتك هنا", key="assistant_input", height=100) + + # أزرار التحكم + col1, col2, col3 = st.columns([1, 1, 1]) + + with col1: + if styled_button("إرسال", key="send_message", type="primary", icon="✉️"): + if not user_input: + st.error("يرجى كتابة رسالة للإرسال.") + else: + # إضافة رسالة المستخدم إلى المحادثة + st.session_state.chat_history.append({ + "role": "user", + "content": user_input + }) + + # الحصول على الرد من النموذج + with st.spinner("المساعد يفكر..."): + # تحميل المصطلحات والتعريفات + terms_with_definitions = {} + for term in st.session_state.terminology_data.get("terms", []): + terms_with_definitions[term.get("term")] = term.get("definition") + + # الحصول على رد المساعد + response = self._get_assistant_response( + model=st.session_state.assistant_model, + chat_history=st.session_state.chat_history, + terms_with_definitions=terms_with_definitions + ) + + # إضافة رد المساعد إلى المحادثة + st.session_state.chat_history.append({ + "role": "assistant", + "content": response + }) + + # إعادة تشغيل لتحديث واجهة المستخدم + st.rerun() + + with col2: + if styled_button("مسح المحادثة", key="clear_chat", type="danger", icon="🗑️"): + st.session_state.chat_history = [] + st.rerun() + + with col3: + if styled_button("اقتراح أسئلة", key="suggest_questions", type="secondary", icon="💡"): + # عرض أسئلة مقترحة + st.markdown("### أسئلة مقترحة") + + # الحصول على المصطلحات المتاحة + terms = [term.get("term") for term in st.session_state.terminology_data.get("terms", [])] + + # إنشاء أسئلة مقترحة + suggested_questions = [ + f"ما هو تعريف مصطلح {term}؟" for term in random.sample(terms, min(3, len(terms))) + ] + + suggested_questions.extend([ + "كيف يتم حساب المحتوى المحلي في مشاريع البنية التحتية؟", + "ما هي أهم البنود التي يجب الانتباه لها في العقود الهندسية؟", + "ما الفرق بين الضمان الابتدائي والضمان النهائي؟", + "كيف يمكن تقييم المخاطر في مشاريع البناء؟" + ]) + + # عرض الأسئلة المقترحة + for i, question in enumerate(suggested_questions): + if styled_button(question, key=f"suggested_q_{i}", type="info", icon="❓"): + # إضافة السؤال المقترح إلى المحادثة + st.session_state.chat_history.append({ + "role": "user", + "content": question + }) + + # الحصول على الرد من النموذج + with st.spinner("المساعد يفكر..."): + # تحميل المصطلحات والتعريفات + terms_with_definitions = {} + for term in st.session_state.terminology_data.get("terms", []): + terms_with_definitions[term.get("term")] = term.get("definition") + + # الحصول على رد المساعد + response = self._get_assistant_response( + model=st.session_state.assistant_model, + chat_history=st.session_state.chat_history, + terms_with_definitions=terms_with_definitions + ) + + # إضافة رد المساعد إلى المحادثة + st.session_state.chat_history.append({ + "role": "assistant", + "content": response + }) + + # إعادة تشغيل لتحديث واجهة المستخدم + st.rerun() + + def _get_assistant_response(self, model, chat_history, terms_with_definitions): + """الحصول على رد المساعد المتخصص""" + try: + # تجهيز المحتوى النظامي + system_prompt = """أنت مساعد متخصص في عقود المقاولات والمناقصات باللغة العربية. + مهمتك هي تقديم معلومات دقيقة ومفصلة حول المصطلحات التعاقدية والهندسية المتخصصة. + قدم شرحاً واضحاً ومفهوماً للمصطلحات، مع أمثلة عملية عند الإمكان. + استخدم لغة مهنية ودقيقة، مع مراعاة السياق الهندسي والقانوني للمصطلحات.""" + + # إضافة المصطلحات إلى المحتوى النظامي + if terms_with_definitions: + system_prompt += "\n\nفيما يلي قائمة بالمصطلحات المتخصصة وتعريفاتها التي يجب عليك استخدامها في إجاباتك:\n\n" + for term, definition in terms_with_definitions.items(): + system_prompt += f"- {term}: {definition}\n" + + # تحويل محادثة streamlit إلى صيغة مناسبة للـ API + messages = [{"role": "system", "content": system_prompt}] + + for msg in chat_history: + messages.append({"role": msg["role"], "content": msg["content"]}) + + # OpenAI + if "gpt" in model or any(model_data.get("model_id") == model for model_data in st.session_state.terminology_data.get("models", [])): + # استخدام OpenAI API + openai.api_key = self.api_key + + response = openai.chat.completions.create( + model=model, + messages=messages, + temperature=0.7 + ) + + return response.choices[0].message.content + + # Anthropic + elif "claude" in model and self.anthropic_api_key: + # استخدام Anthropic API + from anthropic import Anthropic + + # تعديل الرسائل لتتناسب مع صيغة Anthropic + anthropic_messages = [] + for msg in messages[1:]: # تخطي رسالة النظام + anthropic_messages.append({"role": msg["role"], "content": msg["content"]}) + + anthropic_client = Anthropic(api_key=self.anthropic_api_key) + + response = anthropic_client.messages.create( + model=model, + max_tokens=2000, + system=system_prompt, + messages=anthropic_messages, + temperature=0.7 + ) + + return response.content[0].text + + else: + return "النموذج المختار غير مدعوم حالياً." + + except Exception as e: + return f"عذراً، حدث خطأ أثناء معالجة طلبك: {str(e)}" + + def _save_terminology_data(self): + """حفظ بيانات المصطلحات""" + try: + # التأكد من وجود المجلد + os.makedirs(os.path.dirname(self.terminology_file), exist_ok=True) + + # حفظ البيانات + with open(self.terminology_file, 'w', encoding='utf-8') as f: + json.dump(st.session_state.terminology_data, f, ensure_ascii=False, indent=2) + + except Exception as e: + st.error(f"حدث خطأ أثناء حفظ البيانات: {str(e)}") + + +# تشغيل النموذج بشكل مستقل +def main(): + """تشغيل وحدة تخصيص وضبط نماذج الذكاء الاصطناعي بشكل مستقل""" + # تهيئة الواجهة + st.set_page_config( + page_title="تخصيص وضبط نماذج الذكاء الاصطناعي | WAHBi AI", + page_icon="🧠", + layout="wide", + initial_sidebar_state="expanded", + menu_items={ + 'Get Help': 'mailto:support@wahbi-ai.com', + 'Report a bug': 'mailto:support@wahbi-ai.com', + 'About': 'وحدة تخصيص وضبط نماذج الذكاء الاصطناعي للمصطلحات التعاقدية المتخصصة - جزء من نظام WAHBi AI لتحليل المناقصات' + } + ) + + # تهيئة وحدة الضبط + model_finetuning = ModelFinetuning() + + # عرض واجهة الوحدة + model_finetuning.render() + +# تشغيل النموذج عند استدعاء الملف مباشرة +if __name__ == "__main__": + main() \ No newline at end of file