import json import logging import os import uuid from datetime import datetime from typing import Any, Dict, List, Optional import httpx from dotenv import load_dotenv from fastapi import FastAPI, HTTPException, Depends from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from pydantic import BaseModel from starlette.middleware.cors import CORSMiddleware from starlette.responses import StreamingResponse, Response # Configure Logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) logger = logging.getLogger(__name__) # Load Environment Variables load_dotenv() APP_SECRET = os.getenv("APP_SECRET", "786") ACCESS_TOKEN = os.getenv("SD_ACCESS_TOKEN", "") # Initialize FastAPI app = FastAPI() # Define Allowed Models ALLOWED_MODELS = [ {"id": "claude-3.5-sonnet", "name": "Claude 3.5 Sonnet"}, {"id": "claude-3-opus", "name": "Claude 3 Opus"}, {"id": "gemini-1.5-pro", "name": "Gemini 1.5 Pro"}, {"id": "gpt-4o", "name": "GPT-4o"}, {"id": "o1-preview", "name": "O1 Preview"}, {"id": "o1-mini", "name": "O1 Mini"}, {"id": "gpt-4o-mini", "name": "GPT-4o Mini"}, ] # Configure CORS Middleware app.add_middleware( CORSMiddleware, allow_origins=["*"], # Replace with your trusted domains allow_credentials=True, allow_methods=["POST", "OPTIONS"], allow_headers=["Content-Type", "Authorization"], ) # Security Scheme security = HTTPBearer() # Pydantic Models class Message(BaseModel): role: str content: str class ChatRequest(BaseModel): model: str messages: List[Message] stream: Optional[bool] = False # Helper Functions def create_chat_completion_data(content: str, model: str, finish_reason: Optional[str] = None) -> Dict[str, Any]: return { "id": f"chatcmpl-{uuid.uuid4()}", "object": "chat.completion.chunk", "created": int(datetime.now().timestamp()), "model": model, "choices": [ { "index": 0, "delta": {"content": content, "role": "assistant"}, "finish_reason": finish_reason, } ], "usage": None, } def verify_app_secret(credentials: HTTPAuthorizationCredentials = Depends(security)): if credentials.credentials != APP_SECRET: raise HTTPException(status_code=403, detail="Invalid APP_SECRET") return credentials.credentials # Routes @app.options("/hf/v1/chat/completions") async def chat_completions_options(): return Response( status_code=200, headers={ "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization", }, ) @app.get("/hf/v1/models") async def list_models(): return {"object": "list", "data": ALLOWED_MODELS} @app.post("/hf/v1/chat/completions") async def chat_completions( request: ChatRequest, app_secret: str = Depends(verify_app_secret) ): logger.info(f"Received chat completion request for model: {request.model}") if request.model not in [model['id'] for model in ALLOWED_MODELS]: allowed = ', '.join(model['id'] for model in ALLOWED_MODELS) raise HTTPException( status_code=400, detail=f"Model '{request.model}' is not allowed. Allowed models are: {allowed}", ) # Generate a UUID uuid_str = str(uuid.uuid4()).replace("-", "") # Prepare Headers (Move sensitive data to environment variables or secure storage) headers = { 'accept': '*/*', 'accept-language': 'en-US,en;q=0.9', 'authorization': f'Bearer {ACCESS_TOKEN}', 'cache-control': 'no-cache', 'origin': 'chrome-extension://difoiogjjojoaoomphldepapgpbgkhkb', 'pragma': 'no-cache', 'priority': 'u=1, i', 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'none', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36', } # Prepare JSON Payload json_data = { 'prompt': "\n".join( [ f"{'User' if msg.role == 'user' else 'Assistant'}: {msg.content}" for msg in request.messages ] ), 'stream': True, 'app_name': 'ChitChat_Edge_Ext', 'app_version': '4.28.0', 'tz_name': 'Asia/Karachi', 'cid': 'C092SEMXM9BJ', 'model': request.model, 'search': False, 'auto_search': False, 'from': 'chat', 'group_id': 'default', 'prompt_template': { 'key': 'artifacts', 'attributes': { 'lang': 'original', }, }, 'tools': { 'auto': ['text_to_image', 'data_analysis'], }, 'extra_info': { 'origin_url': 'chrome-extension://difoiogjjojoaoomphldepapgpbgkhkb/standalone.html?from=sidebar', 'origin_title': 'Sider', }, } async def generate(): async with httpx.AsyncClient() as client: try: async with client.stream( 'POST', 'https://sider.ai/api/v3/completion/text', headers=headers, json=json_data, timeout=120.0 ) as response: response.raise_for_status() async for line in response.aiter_lines(): if line and ("[DONE]" not in line): try: # Adjust the slicing based on the actual response format data = json.loads(line[5:]) if len(line) > 5 else None if data and "data" in data and "text" in data["data"]: content = data["data"].get("text", "") yield f"data: {json.dumps(create_chat_completion_data(content, request.model))}\n\n" else: logger.warning(f"Unexpected data format: {line}") except json.JSONDecodeError as e: logger.error(f"JSON decode error for line: {line} - {e}") except Exception as e: logger.error(f"Error processing line: {line} - {e}") # Indicate the end of the stream yield f"data: {json.dumps(create_chat_completion_data('', request.model, 'stop'))}\n\n" yield "data: [DONE]\n\n" except httpx.HTTPStatusError as e: logger.error(f"HTTP error occurred: {e}") raise HTTPException(status_code=e.response.status_code, detail=str(e)) except httpx.RequestError as e: logger.error(f"An error occurred while requesting: {e}") raise HTTPException(status_code=500, detail=str(e)) if request.stream: logger.info("Streaming response") return StreamingResponse(generate(), media_type="text/event-stream") else: logger.info("Non-streaming response") full_response = "" async for chunk in generate(): if chunk.startswith("data: ") and not chunk[6:].startswith("[DONE]"): try: data = json.loads(chunk[6:]) if data["choices"][0]["delta"].get("content"): full_response += data["choices"][0]["delta"]["content"] except json.JSONDecodeError as e: logger.error(f"JSON decode error in non-streaming response: {chunk} - {e}") except Exception as e: logger.error(f"Error processing chunk in non-streaming response: {chunk} - {e}") return { "id": f"chatcmpl-{uuid.uuid4()}", "object": "chat.completion", "created": int(datetime.now().timestamp()), "model": request.model, "choices": [ { "index": 0, "message": {"role": "assistant", "content": full_response}, "finish_reason": "stop", } ], "usage": None, }