import os import sys import json import time import logging import asyncio import inspect from datetime import datetime from functools import wraps from typing import Optional, Dict, List, Union from flask import Flask, request, jsonify, Response, send_from_directory, stream_with_context from flask_cors import CORS # ====================================== # g4f imports مع استيراد تلقائي لكل شيء # ====================================== G4F_AVAILABLE = False g4f = None g4f_provider_module = None try: import g4f import g4f.Provider as g4f_provider_module from g4f.client import Client G4F_AVAILABLE = True print("✅ g4f مُثبّتة بنجاح") except ImportError as e: print(f"⚠️ g4f غير مثبّتة: {e}") # ====================================== # إعداد التطبيق # ====================================== app = Flask(__name__, static_folder='.', static_url_path='', template_folder='.') CORS(app, resources={r"/api/*": {"origins": "*"}, r"/chat*": {"origins": "*"}}) logging.basicConfig( level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s', handlers=[logging.StreamHandler(sys.stdout)] ) logger = logging.getLogger('g4fpro') SERVER_CONFIG = { 'host': os.getenv('HOST', '0.0.0.0'), 'port': int(os.getenv('PORT', 7860)), 'debug': os.getenv('DEBUG', 'false').lower() == 'true', 'max_message_length': int(os.getenv('MAX_MSG', 8000)), 'rate_limit': int(os.getenv('RATE_LIMIT', 20)), } # ====================================== # الاستيراد التلقائي لجميع المزودين والنماذج # ====================================== def get_all_providers_dynamic(): """ يستورد تلقائياً جميع المزودين من مكتبة g4f بدون أي قائمة ثابتة - كل شيء ديناميكي """ if not G4F_AVAILABLE or g4f_provider_module is None: return get_fallback_providers() providers_data = {} # قائمة المزودين التي تحتاج مصادقة (نستثنيها أو نعلمها) auth_required = { 'OpenaiChat', 'Gemini', 'GeminiPro', 'BingCreateImages', 'HuggingFace', 'HuggingFaceAPI' } # نحصل على جميع المزودين من g4f.Provider try: all_providers = [] # استخدام __providers__ إذا متوفر if hasattr(g4f_provider_module, '__providers__'): all_providers = list(g4f_provider_module.__providers__) else: # استخراج كل الـ classes من الـ module for name in dir(g4f_provider_module): obj = getattr(g4f_provider_module, name, None) if obj is None: continue if inspect.isclass(obj): # نتحقق أنه مزود حقيقي if hasattr(obj, 'create_completion') or hasattr(obj, 'create_async_generator'): all_providers.append(obj) logger.info(f"🔍 وجدنا {len(all_providers)} مزود في g4f") # إضافة Auto أولاً providers_data["Auto"] = { "name": "⚡ Auto (تلقائي)", "description": "اختيار تلقائي لأفضل مزود متاح", "icon": "fa-bolt", "color": "#f39c12", "models": get_auto_models(), "type": "text", "needs_auth": False, "working": True, "provider_class": None } # معالجة كل مزود for provider_class in all_providers: try: pname = getattr(provider_class, '__name__', None) if not pname or pname.startswith('_') or pname == 'BaseProvider': continue # استخراج النماذج المدعومة models = [] if hasattr(provider_class, 'model') and provider_class.model: m = provider_class.model if isinstance(m, str): models = [m] elif isinstance(m, list): models = list(m) if hasattr(provider_class, 'models') and provider_class.models: m = provider_class.models if isinstance(m, (list, tuple)): models = list(set(models + [str(x) for x in m if x])) if hasattr(provider_class, 'default_model') and provider_class.default_model: dm = provider_class.default_model if dm and dm not in models: models.insert(0, dm) # إذا لم يكن هناك نماذج، نضيف الافتراضية if not models: models = ["gpt-4o", "gpt-4o-mini"] # تنظيف النماذج models = [str(m).strip() for m in models if m and str(m).strip()] models = list(dict.fromkeys(models)) # إزالة المكرر مع الحفاظ على الترتيب # تحديد نوع المزود (نص أم صورة) provider_type = "text" image_keywords = ['image', 'img', 'flux', 'dall', 'stable', 'midjourney', 'pollination'] if any(k in pname.lower() for k in image_keywords): provider_type = "image" if hasattr(provider_class, 'image_models') and provider_class.image_models: provider_type = "image" # تحديد الحالة is_working = getattr(provider_class, 'working', True) needs_auth = pname in auth_required or getattr(provider_class, 'needs_auth', False) # اختيار الأيقونة واللون icon, color = get_provider_style(pname) providers_data[pname] = { "name": format_provider_name(pname), "description": f"مزود {pname} عبر g4f", "icon": icon, "color": color, "models": models[:30], # حد أقصى 30 نموذج لكل مزود "type": provider_type, "needs_auth": needs_auth, "working": is_working, "provider_class": pname } except Exception as e: logger.debug(f"خطأ في معالجة المزود {provider_class}: {e}") continue logger.info(f"✅ تم تحميل {len(providers_data)} مزود بنجاح") return providers_data except Exception as e: logger.error(f"❌ خطأ في الاستيراد التلقائي: {e}") return get_fallback_providers() def get_auto_models(): """نماذج للوضع التلقائي""" base = [ "gpt-4o", "gpt-4o-mini", "gpt-4", "gpt-4-turbo", "claude-3-5-sonnet", "claude-3-opus", "claude-3-haiku", "gemini-2.0-flash", "gemini-1.5-pro", "gemini-1.5-flash", "llama-3.1-70b", "llama-3.1-8b", "llama-3.3-70b", "deepseek-v3", "deepseek-r1", "deepseek-chat", "mixtral-8x7b", "qwen-2.5-72b", "qwen-2.5-coder-32b", "grok-2", "o1", "o1-mini", "o3-mini" ] # محاولة إضافة النماذج من g4f إذا متاحة if G4F_AVAILABLE: try: import g4f.models as g4f_models for name in dir(g4f_models): obj = getattr(g4f_models, name, None) if isinstance(obj, str) and obj and obj not in base: base.append(obj) except: pass return list(dict.fromkeys(base)) def get_provider_style(name): """اختيار أيقونة ولون بناءً على اسم المزود""" name_lower = name.lower() styles = { 'openai': ('fa-brands fa-openai', '#10a37f'), 'google': ('fa-brands fa-google', '#4285f4'), 'gemini': ('fa-brands fa-google', '#4285f4'), 'meta': ('fa-brands fa-meta', '#0668E1'), 'llama': ('fa-brands fa-meta', '#0668E1'), 'anthropic': ('fa-robot', '#d4a96a'), 'claude': ('fa-robot', '#d4a96a'), 'deepseek': ('fa-brain', '#3b82f6'), 'mistral': ('fa-wind', '#ff7043'), 'hugging': ('fa-smile', '#FFD21E'), 'perplexity': ('fa-search', '#22c55e'), 'cloudflare': ('fa-cloud', '#f38020'), 'grok': ('fa-robot', '#1da1f2'), 'together': ('fa-layer-group', '#ef4444'), 'pollination': ('fa-image', '#ec4899'), 'image': ('fa-image', '#ec4899'), 'blackbox': ('fa-code', '#6366f1'), 'you': ('fa-globe', '#0066ff'), 'bing': ('fa-microsoft', '#0078d4'), 'copilot': ('fa-microsoft', '#0078d4'), 'deepinfra': ('fa-server', '#7c3aed'), 'cohere': ('fa-chart-line', '#39c5bb'), 'nvidia': ('fa-microchip', '#76b900'), 'qwen': ('fa-dragon', '#ff6b35'), 'yi': ('fa-infinity', '#8b5cf6'), 'groq': ('fa-bolt', '#f59e0b'), } for key, style in styles.items(): if key in name_lower: return style # لون عشوائي للبقية colors = ['#6c5ce7', '#00cec9', '#fd79a8', '#74b9ff', '#a29bfe', '#55efc4', '#fdcb6e'] color = colors[hash(name) % len(colors)] return ('fa-robot', color) def format_provider_name(name): """تنسيق اسم المزود للعرض""" # إضافة مسافات قبل الأحرف الكبيرة import re result = re.sub(r'([A-Z])', r' \1', name).strip() return result def get_fallback_providers(): """قائمة احتياطية إذا فشل الاستيراد التلقائي""" return { "Auto": { "name": "⚡ Auto (تلقائي)", "description": "اختيار تلقائي لأفضل مزود متاح", "icon": "fa-bolt", "color": "#f39c12", "models": ["gpt-4o", "gpt-4o-mini", "gpt-4", "claude-3-5-sonnet", "gemini-2.0-flash"], "type": "text", "needs_auth": False, "working": True, "provider_class": None } } # تحميل المزودين عند بدء التشغيل PROVIDERS_DATABASE = {} def load_providers(): global PROVIDERS_DATABASE logger.info("🔄 جاري تحميل المزودين...") PROVIDERS_DATABASE = get_all_providers_dynamic() logger.info(f"✅ تم تحميل {len(PROVIDERS_DATABASE)} مزود") load_providers() # ====================================== # Rate Limiting # ====================================== rate_limit_store: Dict[str, List[float]] = {} def rate_limit(func): @wraps(func) def wrapper(*args, **kwargs): ip = request.headers.get('X-Forwarded-For', request.remote_addr) now = time.time() if ip in rate_limit_store: rate_limit_store[ip] = [t for t in rate_limit_store[ip] if now - t < 60] else: rate_limit_store[ip] = [] if len(rate_limit_store[ip]) >= SERVER_CONFIG['rate_limit']: return jsonify({"status": "error", "message": "تجاوزت حد الطلبات. انتظر دقيقة.", "retry_after": 60}), 429 rate_limit_store[ip].append(now) return func(*args, **kwargs) return wrapper # ====================================== # G4F Client Wrapper # ====================================== class G4FClientWrapper: def __init__(self): self.client = Client() if G4F_AVAILABLE else None self.retry_attempts = 2 def get_provider_class(self, provider_name: str): if not G4F_AVAILABLE or not provider_name or provider_name == "Auto": return None try: return getattr(g4f_provider_module, provider_name, None) except: return None async def generate_response(self, message, model, provider_name="Auto", stream=False, system_prompt="", conversation_history=None): if not G4F_AVAILABLE: yield "❌ مكتبة g4f غير مثبّتة. الرجاء تنفيذ: pip install g4f" return messages = [] if system_prompt: messages.append({"role": "system", "content": system_prompt}) if conversation_history: for msg in conversation_history[-15:]: messages.append({"role": msg.get("role", "user"), "content": msg.get("content", "")}) messages.append({"role": "user", "content": message}) provider = self.get_provider_class(provider_name) last_error = None for attempt in range(self.retry_attempts): try: kwargs = {"model": model, "messages": messages, "stream": stream} if provider: kwargs["provider"] = provider if stream: response = self.client.chat.completions.create(**kwargs) for chunk in response: if hasattr(chunk, 'choices') and chunk.choices: delta = chunk.choices[0].delta content = getattr(delta, 'content', '') or '' if content: yield content return else: response = self.client.chat.completions.create(**kwargs) if hasattr(response, 'choices') and response.choices: yield response.choices[0].message.content elif isinstance(response, str): yield response else: yield str(response) return except Exception as e: last_error = e logger.warning(f"محاولة {attempt+1} فشلت ({provider_name}/{model}): {str(e)[:100]}") if "rate limit" in str(e).lower(): await asyncio.sleep(2 * (attempt + 1)) elif attempt < self.retry_attempts - 1: await asyncio.sleep(1) yield f"❌ فشل المزود {provider_name}: {str(last_error)[:200]}" g4f_wrapper = G4FClientWrapper() # ====================================== # API Endpoints # ====================================== @app.route('/') def index(): return send_from_directory('.', 'index.html') @app.route('/') def static_files(filename): return send_from_directory('.', filename) @app.route('/api/health', methods=['GET']) def health_check(): return jsonify({ "status": "healthy", "timestamp": datetime.now().isoformat(), "g4f_available": G4F_AVAILABLE, "g4f_version": getattr(g4f, '__version__', 'unknown') if G4F_AVAILABLE else 'not installed', "providers_count": len(PROVIDERS_DATABASE), "version": "2.0.0" }) @app.route('/api/providers', methods=['GET']) def get_providers(): """إرجاع جميع المزودين - يُحدَّث تلقائياً من g4f""" try: # إعادة تحميل المزودين في كل مرة للحصول على أحدث نسخة providers_response = {} for key, data in PROVIDERS_DATABASE.items(): providers_response[key] = { "name": data["name"], "description": data["description"], "icon": data["icon"], "color": data["color"], "models": data["models"], "type": data.get("type", "text"), "needs_auth": data.get("needs_auth", False), "working": data.get("working", True), "available": G4F_AVAILABLE } return jsonify({ "status": "success", "providers": providers_response, "total_providers": len(providers_response), "g4f_installed": G4F_AVAILABLE }) except Exception as e: logger.error(f"خطأ في /api/providers: {e}") return jsonify({"status": "error", "message": str(e)}), 500 @app.route('/api/models/', methods=['GET']) def get_models_for_provider(provider: str): """إرجاع نماذج مزود معين - مع محاولة الاستيراد المباشر من g4f""" if provider not in PROVIDERS_DATABASE: # محاولة الحصول على المزود مباشرة من g4f if G4F_AVAILABLE: try: pclass = getattr(g4f_provider_module, provider, None) if pclass: models = [] for attr in ['models', 'model', 'default_model']: val = getattr(pclass, attr, None) if val: if isinstance(val, (list, tuple)): models.extend([str(m) for m in val]) elif isinstance(val, str): models.append(val) if models: return jsonify({ "status": "success", "provider": provider, "models": list(dict.fromkeys(models)), "count": len(models) }) except: pass return jsonify({"status": "error", "message": f"المزود '{provider}' غير موجود"}), 404 data = PROVIDERS_DATABASE[provider] return jsonify({ "status": "success", "provider": provider, "provider_name": data["name"], "models": data["models"], "count": len(data["models"]), "type": data.get("type", "text") }) @app.route('/api/reload-providers', methods=['POST']) def reload_providers(): """إعادة تحميل المزودين من g4f""" try: load_providers() return jsonify({ "status": "success", "message": f"تم إعادة تحميل {len(PROVIDERS_DATABASE)} مزود", "providers_count": len(PROVIDERS_DATABASE) }) except Exception as e: return jsonify({"status": "error", "message": str(e)}), 500 @app.route('/chat', methods=['POST']) @rate_limit def chat(): try: data = request.get_json(force=True, silent=True) or {} message = data.get('message', '').strip() if not message: return jsonify({"status": "error", "message": "الرسالة مطلوبة"}), 400 if len(message) > SERVER_CONFIG['max_message_length']: return jsonify({"status": "error", "message": f"الرسالة طويلة جداً (الحد: {SERVER_CONFIG['max_message_length']})"}), 400 model = data.get('model', 'gpt-4o') provider = data.get('provider', 'Auto') stream = data.get('stream', False) logger.info(f"Chat: {provider}/{model} - {len(message)} حرف") if stream: request._chat_data = data return chat_streaming_internal(data) loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) result_parts = [] async def collect(): async for chunk in g4f_wrapper.generate_response( message=message, model=model, provider_name=provider, stream=False, system_prompt=data.get('system_prompt', ''), conversation_history=data.get('conversation_history', []) ): result_parts.append(chunk) loop.run_until_complete(collect()) loop.close() response_text = ''.join(result_parts) return jsonify({ "status": "success" if not response_text.startswith("❌") else "error", "reply": response_text, "model": model, "provider": provider, "provider_name": PROVIDERS_DATABASE.get(provider, {}).get("name", provider), "timestamp": datetime.now().isoformat() }) except Exception as e: logger.exception("خطأ في /chat") return jsonify({"status": "error", "message": str(e)}), 500 def chat_streaming_internal(data): message = data.get('message', '') model = data.get('model', 'gpt-4o') provider = data.get('provider', 'Auto') def generate(): try: yield f"data: {json.dumps({'type': 'start', 'model': model, 'provider': provider})}\n\n" loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) chunks = [] async def collect_stream(): async for chunk in g4f_wrapper.generate_response( message=message, model=model, provider_name=provider, stream=True, system_prompt=data.get('system_prompt', ''), conversation_history=data.get('conversation_history', []) ): chunks.append(chunk) loop.run_until_complete(collect_stream()) loop.close() for chunk in chunks: if chunk: yield f"data: {json.dumps({'type': 'chunk', 'content': chunk})}\n\n" yield f"data: {json.dumps({'type': 'end', 'timestamp': datetime.now().isoformat()})}\n\n" except Exception as e: yield f"data: {json.dumps({'type': 'error', 'message': str(e)})}\n\n" finally: yield f"data: {json.dumps({'type': 'close'})}\n\n" return Response( stream_with_context(generate()), mimetype='text/event-stream', headers={'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no', 'Connection': 'keep-alive'} ) @app.route('/chat/stream', methods=['POST']) @rate_limit def chat_stream(): data = request.get_json(force=True, silent=True) or {} return chat_streaming_internal(data) @app.route('/api/image', methods=['POST']) @rate_limit def generate_image(): try: data = request.get_json() or {} prompt = data.get('prompt', '').strip() if not prompt: return jsonify({"status": "error", "message": "وصف الصورة مطلوب"}), 400 provider = data.get('provider', 'PollinationsAI') model = data.get('model', 'flux') import urllib.parse encoded = urllib.parse.quote(prompt) image_url = f"https://image.pollinations.ai/prompt/{encoded}?model={model}&width={data.get('width',1024)}&height={data.get('height',1024)}&nologo=true" return jsonify({ "status": "success", "image_url": image_url, "provider": provider, "model": model, "prompt": prompt }) except Exception as e: return jsonify({"status": "error", "message": str(e)}), 500 @app.route('/api/version', methods=['GET']) def get_version(): return jsonify({ "application": "g4fpro", "version": "2.0.0", "g4f_version": getattr(g4f, '__version__', 'unknown') if G4F_AVAILABLE else 'not installed', "python_version": sys.version, "providers_loaded": len(PROVIDERS_DATABASE) }) @app.errorhandler(404) def not_found(e): if request.path.startswith('/api/'): return jsonify({"status": "error", "message": "النقطة غير موجودة"}), 404 return send_from_directory('.', 'index.html') @app.errorhandler(500) def internal_error(e): return jsonify({"status": "error", "message": "خطأ داخلي في الخادم"}), 500 @app.errorhandler(429) def rate_limit_error(e): return jsonify({"status": "error", "message": "تجاوزت حد الطلبات"}), 429 if __name__ == '__main__': print("\n" + "="*60) print("🚀 g4fpro v2.0 - بدء التشغيل") print("="*60) print(f"🌐 العنوان: http://{SERVER_CONFIG['host']}:{SERVER_CONFIG['port']}") print(f"📦 g4f: {'✅ مثبّتة' if G4F_AVAILABLE else '❌ غير مثبّتة'}") if G4F_AVAILABLE: print(f" الإصدار: {getattr(g4f, '__version__', 'غير معروف')}") print(f"🔌 عدد المزودين: {len(PROVIDERS_DATABASE)}") print("="*60 + "\n") app.run( host=SERVER_CONFIG['host'], port=SERVER_CONFIG['port'], debug=SERVER_CONFIG['debug'], threaded=True )