from __future__ import annotations import json import os from pathlib import Path from typing import Any, Dict, List import httpx from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse, JSONResponse from fastapi.staticfiles import StaticFiles from pydantic import BaseModel, Field BASE_DIR = Path(__file__).resolve().parent PROMPT_BUNDLE = BASE_DIR / "prompts" / "prompt_bundle.json" INDEX_FILE = BASE_DIR / "index.html" app = FastAPI(title="THE Z AI/AGENT", version="1.0.0") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.mount("/static", StaticFiles(directory=str(BASE_DIR)), name="static") class FilePayload(BaseModel): name: str content: str class ChatRequest(BaseModel): messages: List[Dict[str, str]] tier: str = Field(default="NORMAL") mode: str = Field(default="chat") attachments: List[FilePayload] = Field(default_factory=list) lang: str = Field(default="ar") def load_prompts() -> Dict[str, Any]: if not PROMPT_BUNDLE.exists(): raise RuntimeError("prompt_bundle.json not found") return json.loads(PROMPT_BUNDLE.read_text(encoding="utf-8")) def provider_config() -> Dict[str, str]: # List of Groq API keys to try in order groq_keys = [ os.getenv("GROQ_API_KEY", "").strip(), "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", "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", "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", ] openrouter_key = os.getenv("OPENROUTER_API_KEY", "").strip() # Try each Groq key until one works for key in groq_keys: if key: return { "provider": "groq", "api_key": key, "base_url": os.getenv("GROQ_BASE_URL", "https://api.groq.com/openai/v1/chat/completions"), "model": os.getenv("GROQ_MODEL", "llama-3.3-70b-versatile"), } if openrouter_key: return { "provider": "openrouter", "api_key": openrouter_key, "base_url": os.getenv("OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1/chat/completions"), "model": os.getenv("OPENROUTER_MODEL", "meta-llama/llama-3.1-70b-instruct:free"), } raise RuntimeError("Set GROQ_API_KEY or OPENROUTER_API_KEY") def build_system_prompt(payload: ChatRequest) -> str: prompts = load_prompts() tier = str(payload.tier or "NORMAL").upper() mode = str(payload.mode or "chat").lower() tier_prompt = prompts.get("tiers", {}).get(tier, {}).get("prompt", "") system = prompts.get("system", "") coder = prompts.get("coder", "") if mode in {"coder", "agent"} else "" parts = [system] if tier_prompt: parts.append(f"[TIER {tier}]\n{tier_prompt}") if coder: parts.append(coder) return "\n\n".join(parts).strip() def attachments_context(attachments: List[FilePayload]) -> str: if not attachments: return "" parts = ["[ATTACHED FILES]\nRead these files carefully before answering."] for idx, item in enumerate(attachments, start=1): parts.append(f"File {idx}: {item.name}\n{item.content}") return "\n\n".join(parts) @app.get("/") def home(): return FileResponse(INDEX_FILE) @app.get("/api/health") def health(): return {"ok": True} @app.post("/api/chat") async def chat(req: ChatRequest): try: cfg = provider_config() except RuntimeError as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc system_prompt = build_system_prompt(req) extra = attachments_context(req.attachments) messages = list(req.messages) if extra: messages.append({"role": "system", "content": extra}) body = { "model": cfg["model"], "messages": [{"role": "system", "content": system_prompt}] + messages, "temperature": 0.7, "max_tokens": 3500, } headers = { "Authorization": f"Bearer {cfg['api_key']}", "Content-Type": "application/json", } if cfg["provider"] == "openrouter": headers["HTTP-Referer"] = os.getenv("OPENROUTER_SITE_URL", "https://example.com") headers["X-Title"] = os.getenv("OPENROUTER_SITE_NAME", "THE Z AI/AGENT") async with httpx.AsyncClient(timeout=120) as client: try: response = await client.post(cfg["base_url"], headers=headers, json=body) except httpx.HTTPError as exc: raise HTTPException(status_code=502, detail=f"Upstream request failed: {exc}") from exc if response.status_code >= 400: raise HTTPException(status_code=502, detail=f"Upstream error {response.status_code}: {response.text[:500]}") data = response.json() try: reply = data["choices"][0]["message"]["content"] except Exception: reply = data.get("output_text") or data.get("response") or json.dumps(data, ensure_ascii=False) return JSONResponse({"reply": reply, "raw": data}) if __name__ == "__main__": import uvicorn uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", "7860")), reload=False)