Spaces:
Paused
Paused
| 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): | |
| 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 | |
| # ====================================== | |
| def index(): | |
| return send_from_directory('.', 'index.html') | |
| def static_files(filename): | |
| return send_from_directory('.', filename) | |
| 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" | |
| }) | |
| 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 | |
| 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") | |
| }) | |
| 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 | |
| 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'} | |
| ) | |
| def chat_stream(): | |
| data = request.get_json(force=True, silent=True) or {} | |
| return chat_streaming_internal(data) | |
| 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 | |
| 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) | |
| }) | |
| def not_found(e): | |
| if request.path.startswith('/api/'): | |
| return jsonify({"status": "error", "message": "النقطة غير موجودة"}), 404 | |
| return send_from_directory('.', 'index.html') | |
| def internal_error(e): | |
| return jsonify({"status": "error", "message": "خطأ داخلي في الخادم"}), 500 | |
| 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 | |
| ) |