#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ G4F Chat API Server — FastAPI + g4f + Streaming SSE """ import g4f import asyncio import json from fastapi import FastAPI, Request, HTTPException from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse from fastapi.middleware.cors import CORSMiddleware from pathlib import Path import uvicorn import logging # إعداد السجلات logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) app = FastAPI(title="G4F Chat API", version="2.1") # ✅ تفعيل CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ───────────────────────────────────────────── # ✅ دالة جلب المزودين والنماذج (آمنة) # ───────────────────────────────────────────── def get_providers_and_models(): """جلب قائمة المزودين العاملين مع نماذجهم""" providers_data = {} try: # محاولة جلب المزودين من g4f provider_list = getattr(g4f.Provider, '__providers__', []) for provider in provider_list: try: name = getattr(provider, '__name__', None) if not name or not getattr(provider, 'working', False): continue # جلب النماذج المدعومة models = [] if hasattr(provider, 'models') and provider.models: models = list(provider.models) if isinstance(provider.models, (list, tuple)) else [provider.models] elif hasattr(provider, 'model') and provider.model: models = [provider.model] if isinstance(provider.model, str) else list(provider.model) else: models = ['default'] providers_data[name] = { 'models': models, 'supports_stream': getattr(provider, 'supports_stream', True), 'needs_auth': getattr(provider, 'needs_auth', False), 'url': getattr(provider, 'url', ''), } except Exception as e: logger.warning(f"تخطي المزوّد: {e}") continue except Exception as e: logger.error(f"خطأ في جلب المزودين: {e}") # قائمة احتياطية في حال الفشل return { 'g4f': { 'models': ['gpt-4o', 'gpt-4o-mini', 'claude-3-5-sonnet', 'llama-3.1-70b'], 'supports_stream': True, 'needs_auth': False, 'url': '', } } return providers_data # ───────────────────────────────────────────── # ✅ Endpoint: قائمة المزودين والنماذج # ───────────────────────────────────────────── @app.get("/api/providers") async def list_providers(): """إرجاع المزودين والنماذج المتاحة بصيغة JSON""" try: data = get_providers_and_models() return JSONResponse(content=data) except Exception as e: logger.error(f"Error in /api/providers: {e}") raise HTTPException(status_code=500, detail=str(e)) # ───────────────────────────────────────────── # ✅ Endpoint: الدردشة العادية (Non-streaming) # ───────────────────────────────────────────── @app.post("/api/chat") async def chat(request: Request): """معالجة طلب الدردشة العادي""" try: body = await request.json() messages = body.get("messages", []) model = body.get("model", "gpt-4o-mini") provider_name = body.get("provider", None) web_search = body.get("web_search", False) if not messages: raise HTTPException(status_code=400, detail="messages list is empty") # تحضير معاملات الاستدعاء kwargs = { "model": model, "messages": messages, "stream": False, } # إضافة المزوّد إذا حُدّد if provider_name and provider_name != "Auto": try: provider_cls = getattr(g4f.Provider, provider_name, None) if provider_cls: kwargs["provider"] = provider_cls except AttributeError: pass # إضافة أداة البحث إذا فُعّلت if web_search and hasattr(g4f, 'tools') and hasattr(g4f.tools, 'search'): kwargs["tool_calls"] = [g4f.tools.search] # تنفيذ الطلب في thread منفصل لتجنب حجب الـ event loop loop = asyncio.get_running_loop() response = await loop.run_in_executor( None, lambda: g4f.ChatCompletion.create(**kwargs) ) # معالجة الرد (قد يكون نصاً أو مولّداً) if hasattr(response, '__iter__') and not isinstance(response, (str, bytes)): response = "".join(str(chunk) for chunk in response if chunk) return JSONResponse({ "response": str(response), "model": model, "provider": provider_name or "Auto" }) except HTTPException: raise except Exception as e: logger.error(f"Chat error: {e}", exc_info=True) raise HTTPException(status_code=500, detail=f"Server error: {str(e)[:200]}") # ───────────────────────────────────────────── # ✅ Endpoint: الدردشة المتدفقة (Streaming SSE) # ───────────────────────────────────────────── @app.post("/api/chat/stream") async def chat_stream(request: Request): """معالجة طلب الدردشة مع البث المباشر (Server-Sent Events)""" try: body = await request.json() messages = body.get("messages", []) model = body.get("model", "gpt-4o-mini") provider_name = body.get("provider", None) web_search = body.get("web_search", False) if not messages: raise HTTPException(status_code=400, detail="messages list is empty") # تحضير معاملات الاستدعاء للبث kwargs = { "model": model, "messages": messages, "stream": True, } if provider_name and provider_name != "Auto": try: provider_cls = getattr(g4f.Provider, provider_name, None) if provider_cls: kwargs["provider"] = provider_cls except AttributeError: pass if web_search and hasattr(g4f, 'tools') and hasattr(g4f.tools, 'search'): kwargs["tool_calls"] = [g4f.tools.search] async def event_generator(): """مولّد أحداث SSE""" try: loop = asyncio.get_running_loop() # تنفيذ g4f في thread منفصل response = await loop.run_in_executor( None, lambda: g4f.ChatCompletion.create(**kwargs) ) # معالجة الرد حسب نوعه if isinstance(response, str): # رد نصي مباشر yield f" {json.dumps({'chunk': response, 'type': 'text'})}\n\n" elif hasattr(response, '__iter__'): # رد متدفق (generator) for chunk in response: if chunk: chunk_str = str(chunk).strip() if chunk_str: yield f" {json.dumps({'chunk': chunk_str, 'type': 'chunk'})}\n\n" await asyncio.sleep(0) # Yield control to event loop else: # معالجة غير متوقعة yield f" {json.dumps({'chunk': str(response), 'type': 'text'})}\n\n" except Exception as e: logger.error(f"Stream error: {e}", exc_info=True) yield f" {json.dumps({'error': str(e)[:300], 'type': 'error'})}\n\n" finally: # إشارة نهاية البث yield " [DONE]\n\n" return StreamingResponse( event_generator(), media_type="text/event-stream", headers={ "Cache-Control": "no-cache, no-transform", "X-Accel-Buffering": "no", "Connection": "keep-alive", }, ) except HTTPException: raise except Exception as e: logger.error(f"Stream endpoint error: {e}", exc_info=True) raise HTTPException(status_code=500, detail=f"Streaming error: {str(e)[:200]}") # ───────────────────────────────────────────── # ✅ تقديم الواجهة الأمامية # ───────────────────────────────────────────── @app.get("/", response_class=HTMLResponse) async def root(): """تقديم ملف index.html""" html_path = Path(__file__).parent / "index.html" if html_path.exists(): content = html_path.read_text(encoding="utf-8") return HTMLResponse(content=content) return HTMLResponse( content="

❌ index.html not found

", status_code=404 ) # ───────────────────────────────────────────── # ✅ نقطة الدخول # ───────────────────────────────────────────── if __name__ == "__main__": import sys port = int(sys.argv[1]) if len(sys.argv) > 1 else 7860 host = "0.0.0.0" print(f"\n🚀 G4F Chat Server starting...") print(f"📍 URL: http://{host if host != '0.0.0.0' else 'localhost'}:{port}") print(f"📦 g4f version: {getattr(g4f, '__version__', 'unknown')}") print(f"⚡ Press Ctrl+C to stop\n") uvicorn.run( app, host=host, port=port, log_level="info", access_log=True, )