Sssddss / server.py
ayb-bh1146's picture
Create server.py
90db157 verified
Raw
History Blame Contribute Delete
24.4 kB
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('/<path:filename>')
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/<provider>', 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
)