Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -33,12 +33,78 @@ async def reroute_to_status():
|
|
| 33 |
|
| 34 |
OLLAMA_LIBRARY_URL = "https://ollama.com/library"
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
REASONING_KEYWORDS = [
|
| 44 |
# explicit reasoning requests
|
|
@@ -211,28 +277,106 @@ def extract_user_text(messages: list) -> str:
|
|
| 211 |
if m.get("role") == "user"
|
| 212 |
).lower()
|
| 213 |
|
| 214 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
now = time.time()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
|
| 217 |
-
if ip not in audio_ip_store:
|
| 218 |
-
audio_ip_store[ip] = {
|
| 219 |
-
"count": 0,
|
| 220 |
-
"reset": now + AUDIO_WINDOW_SECONDS
|
| 221 |
-
}
|
| 222 |
|
| 223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
|
|
|
| 228 |
|
| 229 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
raise HTTPException(
|
| 231 |
status_code=429,
|
| 232 |
-
detail="
|
| 233 |
)
|
| 234 |
|
| 235 |
entry["count"] += 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
|
| 237 |
|
| 238 |
def is_complex_reasoning(prompt: str) -> bool:
|
|
@@ -263,35 +407,17 @@ def is_cinematic_image_prompt(prompt: str) -> bool:
|
|
| 263 |
return True
|
| 264 |
return False
|
| 265 |
|
| 266 |
-
def
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
if ip not in ip_store:
|
| 270 |
-
ip_store[ip] = {"count": 0, "reset": now + WINDOW_SECONDS}
|
| 271 |
-
|
| 272 |
-
entry = ip_store[ip]
|
| 273 |
-
|
| 274 |
-
if now > entry["reset"]:
|
| 275 |
-
entry["count"] = 0
|
| 276 |
-
entry["reset"] = now + WINDOW_SECONDS
|
| 277 |
|
| 278 |
-
if entry["count"] >= RATE_LIMIT:
|
| 279 |
-
raise HTTPException(
|
| 280 |
-
status_code=429,
|
| 281 |
-
detail="Daily limit reached: 25 images per IP"
|
| 282 |
-
)
|
| 283 |
|
| 284 |
-
|
|
|
|
| 285 |
|
| 286 |
PKEY = os.getenv("POLLINATIONS_KEY", "")
|
| 287 |
PKEY2 = os.getenv("POLLINATIONS2_KEY", "")
|
| 288 |
PKEY3 = os.getenv("POLLINATIONS3_KEY", "")
|
| 289 |
|
| 290 |
-
CHAT_RATE_LIMIT = 50
|
| 291 |
-
CHAT_WINDOW_SECONDS = 60 * 60
|
| 292 |
-
|
| 293 |
-
chat_ip_store = {}
|
| 294 |
-
|
| 295 |
GROQ_TOOL_MODELS = [
|
| 296 |
"openai/gpt-oss-120b",
|
| 297 |
"openai/gpt-oss-20b",
|
|
@@ -316,29 +442,8 @@ CEREBRAS_MODELS = [
|
|
| 316 |
"zai-glm-4.7",
|
| 317 |
]
|
| 318 |
|
| 319 |
-
def check_chat_rate_limit(
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
if ip not in chat_ip_store:
|
| 323 |
-
chat_ip_store[ip] = {
|
| 324 |
-
"count": 0,
|
| 325 |
-
"reset": now + CHAT_WINDOW_SECONDS
|
| 326 |
-
}
|
| 327 |
-
|
| 328 |
-
entry = chat_ip_store[ip]
|
| 329 |
-
|
| 330 |
-
if now > entry["reset"]:
|
| 331 |
-
entry["count"] = 0
|
| 332 |
-
entry["reset"] = now + CHAT_WINDOW_SECONDS
|
| 333 |
-
|
| 334 |
-
if entry["count"] >= CHAT_RATE_LIMIT:
|
| 335 |
-
raise HTTPException(
|
| 336 |
-
status_code=429,
|
| 337 |
-
detail="Chat rate limit exceeded"
|
| 338 |
-
)
|
| 339 |
-
|
| 340 |
-
entry["count"] += 1
|
| 341 |
-
return entry["count"]
|
| 342 |
|
| 343 |
@app.head("/status/sfx")
|
| 344 |
async def head_sfx():
|
|
@@ -423,9 +528,12 @@ async def get_status():
|
|
| 423 |
|
| 424 |
@app.post("/gen/image")
|
| 425 |
@app.get("/genimg/{prompt}")
|
| 426 |
-
async def generate_image(
|
| 427 |
-
|
| 428 |
-
|
|
|
|
|
|
|
|
|
|
| 429 |
timeout = httpx.Timeout(300.0, read=300.0)
|
| 430 |
if prompt is None:
|
| 431 |
prompt = (await request.json()).get("prompt")
|
|
@@ -490,14 +598,13 @@ async def get_models() -> List[Dict]:
|
|
| 490 |
return models
|
| 491 |
|
| 492 |
@app.post("/gen/chat/completions")
|
| 493 |
-
async def generate_text(request: Request):
|
| 494 |
body = await request.json()
|
| 495 |
messages = body.get("messages", [])
|
| 496 |
if not isinstance(messages, list) or len(messages) == 0:
|
| 497 |
raise HTTPException(400, "messages[] is required")
|
| 498 |
|
| 499 |
-
|
| 500 |
-
msg_count = check_chat_rate_limit(ip)
|
| 501 |
prompt_text = extract_user_text(messages)
|
| 502 |
|
| 503 |
uses_tools = (
|
|
@@ -645,9 +752,12 @@ async def generate_text(request: Request):
|
|
| 645 |
|
| 646 |
@app.get("/gen/sfx/{prompt}")
|
| 647 |
@app.post("/gen/sfx")
|
| 648 |
-
async def gensfx(
|
| 649 |
-
|
| 650 |
-
|
|
|
|
|
|
|
|
|
|
| 651 |
if prompt is None:
|
| 652 |
prompt = (await request.json()).get("prompt")
|
| 653 |
url = f"https://gen.pollinations.ai/audio/{prompt}?model=elevenmusic&key={PKEY}"
|
|
@@ -675,9 +785,12 @@ async def gensfx(request: Request, prompt: str = None):
|
|
| 675 |
|
| 676 |
@app.get("/gen/tts/{prompt}")
|
| 677 |
@app.post("/gen/tts")
|
| 678 |
-
async def gensfx(
|
| 679 |
-
|
| 680 |
-
|
|
|
|
|
|
|
|
|
|
| 681 |
if prompt is None:
|
| 682 |
prompt = (await request.json()).get("prompt")
|
| 683 |
url = f"https://gen.pollinations.ai/audio/{prompt}?key={PKEY3}"
|
|
@@ -706,9 +819,13 @@ async def gensfx(request: Request, prompt: str = None):
|
|
| 706 |
@app.get("/gen/video/{prompt}")
|
| 707 |
@app.post("/gen/video")
|
| 708 |
@app.head("/gen/video")
|
| 709 |
-
async def genvideo(
|
| 710 |
-
|
| 711 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 712 |
return RedirectResponse(url="/gen/video/airforce", status_code=status.HTTP_307_TEMPORARY_REDIRECT)
|
| 713 |
|
| 714 |
if prompt is None:
|
|
@@ -801,7 +918,11 @@ MAX_VIDEO_RETRIES = 6
|
|
| 801 |
@app.get("/gen/video/airforce/{prompt}")
|
| 802 |
@app.post("/gen/video/airforce")
|
| 803 |
@app.head("/gen/video/airforce")
|
| 804 |
-
async def genvideo_airforce(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 805 |
if request.method == "HEAD":
|
| 806 |
return Response(
|
| 807 |
status_code=200,
|
|
@@ -833,6 +954,8 @@ async def genvideo_airforce(request: Request, prompt: str = None):
|
|
| 833 |
}
|
| 834 |
)
|
| 835 |
|
|
|
|
|
|
|
| 836 |
aspectRatio = "3:2"
|
| 837 |
inputMode = "normal"
|
| 838 |
|
|
@@ -932,36 +1055,61 @@ async def get_subscription(authorization: Optional[str] = Header(None)):
|
|
| 932 |
if "error" in result:
|
| 933 |
raise HTTPException(401, result["error"])
|
| 934 |
|
|
|
|
|
|
|
|
|
|
| 935 |
return result
|
| 936 |
|
| 937 |
-
@app.get("/
|
| 938 |
-
async def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 939 |
return JSONResponse(
|
| 940 |
status_code=200,
|
| 941 |
-
content=
|
| 942 |
-
|
| 943 |
-
|
| 944 |
-
|
| 945 |
-
|
| 946 |
-
|
| 947 |
-
|
| 948 |
-
|
| 949 |
-
|
| 950 |
-
|
| 951 |
-
|
| 952 |
-
|
| 953 |
-
|
| 954 |
-
|
| 955 |
-
|
| 956 |
-
|
| 957 |
{
|
| 958 |
-
"
|
| 959 |
-
"
|
| 960 |
-
"
|
|
|
|
|
|
|
| 961 |
}
|
| 962 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 963 |
)
|
| 964 |
|
| 965 |
@app.get("/portal")
|
| 966 |
def a():
|
| 967 |
-
return RedirectResponse(url="https://billing.stripe.com/p/login/test_6oUcN5g665rp7nLgaq8bS00", status_code=status.HTTP_302_FOUND)
|
|
|
|
| 33 |
|
| 34 |
OLLAMA_LIBRARY_URL = "https://ollama.com/library"
|
| 35 |
|
| 36 |
+
PLAN_ORDER = ["free", "light", "pro", "creator", "professional"]
|
| 37 |
+
TIER_CONFIG = {
|
| 38 |
+
"free": {
|
| 39 |
+
"name": "Free Tier",
|
| 40 |
+
"url": "",
|
| 41 |
+
"price": "0.00",
|
| 42 |
+
"limits": {
|
| 43 |
+
"cloudChatDaily": 50,
|
| 44 |
+
"imagesDaily": 10,
|
| 45 |
+
"videosDaily": 3,
|
| 46 |
+
"audioWeekly": 1,
|
| 47 |
+
},
|
| 48 |
+
},
|
| 49 |
+
"light": {
|
| 50 |
+
"name": "InferencePort AI Light",
|
| 51 |
+
"url": "https://buy.stripe.com/test_6oUcN5g665rp7nLgaq8bS00",
|
| 52 |
+
"price": "9.99",
|
| 53 |
+
"limits": {
|
| 54 |
+
"cloudChatDaily": None,
|
| 55 |
+
"imagesDaily": 50,
|
| 56 |
+
"videosDaily": 10,
|
| 57 |
+
"audioWeekly": 5,
|
| 58 |
+
},
|
| 59 |
+
},
|
| 60 |
+
"pro": {
|
| 61 |
+
"name": "InferencePort AI Pro",
|
| 62 |
+
"url": "https://buy.stripe.com/test_bJe9AT2fg6vt23rgaq8bS01",
|
| 63 |
+
"price": "15.99",
|
| 64 |
+
"limits": {
|
| 65 |
+
"cloudChatDaily": None,
|
| 66 |
+
"imagesDaily": 150,
|
| 67 |
+
"videosDaily": None,
|
| 68 |
+
"audioWeekly": 25,
|
| 69 |
+
},
|
| 70 |
+
},
|
| 71 |
+
"creator": {
|
| 72 |
+
"name": "InferencePort AI Creator",
|
| 73 |
+
"url": "https://buy.stripe.com/test_14AaEX9HIdXV8rPf6m8bS02",
|
| 74 |
+
"price": "29.99",
|
| 75 |
+
"limits": {
|
| 76 |
+
"cloudChatDaily": None,
|
| 77 |
+
"imagesDaily": 300,
|
| 78 |
+
"videosDaily": 50,
|
| 79 |
+
"audioWeekly": 45,
|
| 80 |
+
},
|
| 81 |
+
},
|
| 82 |
+
"professional": {
|
| 83 |
+
"name": "InferencePort AI Professional",
|
| 84 |
+
"url": "https://buy.stripe.com/test_5kQ00jf22cTR0ZncYe8bS03",
|
| 85 |
+
"price": "99.99",
|
| 86 |
+
"limits": {
|
| 87 |
+
"cloudChatDaily": None,
|
| 88 |
+
"imagesDaily": None,
|
| 89 |
+
"videosDaily": None,
|
| 90 |
+
"audioWeekly": 75,
|
| 91 |
+
},
|
| 92 |
+
},
|
| 93 |
+
}
|
| 94 |
+
USAGE_PERIODS = {
|
| 95 |
+
"cloudChatDaily": "daily",
|
| 96 |
+
"imagesDaily": "daily",
|
| 97 |
+
"videosDaily": "daily",
|
| 98 |
+
"audioWeekly": "weekly",
|
| 99 |
+
}
|
| 100 |
+
usage_store = {
|
| 101 |
+
"cloudChatDaily": {},
|
| 102 |
+
"imagesDaily": {},
|
| 103 |
+
"videosDaily": {},
|
| 104 |
+
"audioWeekly": {},
|
| 105 |
+
}
|
| 106 |
+
IDENTITY_CACHE_TTL_SECONDS = 60
|
| 107 |
+
identity_cache = {}
|
| 108 |
|
| 109 |
REASONING_KEYWORDS = [
|
| 110 |
# explicit reasoning requests
|
|
|
|
| 277 |
if m.get("role") == "user"
|
| 278 |
).lower()
|
| 279 |
|
| 280 |
+
def normalize_plan_key(plan_name: Optional[str]) -> str:
|
| 281 |
+
if not plan_name:
|
| 282 |
+
return "free"
|
| 283 |
+
normalized = "".join(ch for ch in str(plan_name).lower() if ch.isalpha())
|
| 284 |
+
if "professional" in normalized:
|
| 285 |
+
return "professional"
|
| 286 |
+
if "creator" in normalized:
|
| 287 |
+
return "creator"
|
| 288 |
+
if "pro" in normalized:
|
| 289 |
+
return "pro"
|
| 290 |
+
if "light" in normalized:
|
| 291 |
+
return "light"
|
| 292 |
+
return "free"
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
def get_usage_period_key(metric: str) -> str:
|
| 296 |
+
now = time.gmtime()
|
| 297 |
+
period = USAGE_PERIODS.get(metric, "daily")
|
| 298 |
+
if period == "weekly":
|
| 299 |
+
iso_year, iso_week, _ = time.strftime("%G %V %u", now).split(" ")
|
| 300 |
+
return f"{iso_year}-W{iso_week}"
|
| 301 |
+
return time.strftime("%Y-%m-%d", now)
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
async def resolve_rate_limit_identity(
|
| 305 |
+
request: Request,
|
| 306 |
+
authorization: Optional[str],
|
| 307 |
+
) -> tuple[str, str]:
|
| 308 |
now = time.time()
|
| 309 |
+
default_subject = f"ip:{request.client.host if request.client else 'unknown'}"
|
| 310 |
+
if not authorization or not authorization.startswith("Bearer "):
|
| 311 |
+
return "free", default_subject
|
| 312 |
+
|
| 313 |
+
token = authorization.split(" ", 1)[1].strip()
|
| 314 |
+
if not token:
|
| 315 |
+
return "free", default_subject
|
| 316 |
+
|
| 317 |
+
cached = identity_cache.get(token)
|
| 318 |
+
if cached and cached.get("expires_at", 0) > now:
|
| 319 |
+
return cached.get("plan_key", "free"), cached.get("subject", default_subject)
|
| 320 |
+
|
| 321 |
+
try:
|
| 322 |
+
sub = await fetch_subscription(token)
|
| 323 |
+
except Exception:
|
| 324 |
+
return "free", default_subject
|
| 325 |
+
|
| 326 |
+
if not isinstance(sub, dict) or sub.get("error"):
|
| 327 |
+
return "free", default_subject
|
| 328 |
+
|
| 329 |
+
email = sub.get("email")
|
| 330 |
+
if isinstance(email, str) and email.strip():
|
| 331 |
+
subject = f"user:{email.strip().lower()}"
|
| 332 |
+
else:
|
| 333 |
+
subject = default_subject
|
| 334 |
+
|
| 335 |
+
plan_key = normalize_plan_key(sub.get("plan_key"))
|
| 336 |
+
identity_cache[token] = {
|
| 337 |
+
"plan_key": plan_key,
|
| 338 |
+
"subject": subject,
|
| 339 |
+
"expires_at": now + IDENTITY_CACHE_TTL_SECONDS,
|
| 340 |
+
}
|
| 341 |
+
return plan_key, subject
|
| 342 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 343 |
|
| 344 |
+
async def enforce_rate_limit(
|
| 345 |
+
request: Request,
|
| 346 |
+
authorization: Optional[str],
|
| 347 |
+
metric: str,
|
| 348 |
+
) -> Dict[str, Optional[int | str]]:
|
| 349 |
+
if metric not in usage_store:
|
| 350 |
+
raise HTTPException(status_code=500, detail=f"Unknown limit metric: {metric}")
|
| 351 |
|
| 352 |
+
plan_key, subject = await resolve_rate_limit_identity(request, authorization)
|
| 353 |
+
plan = TIER_CONFIG.get(plan_key) or TIER_CONFIG["free"]
|
| 354 |
+
plan_limits = plan.get("limits", {})
|
| 355 |
+
limit = plan_limits.get(metric)
|
| 356 |
|
| 357 |
+
if limit is None:
|
| 358 |
+
return {"plan_key": plan_key, "remaining": None}
|
| 359 |
+
|
| 360 |
+
window_key = get_usage_period_key(metric)
|
| 361 |
+
bucket = usage_store[metric]
|
| 362 |
+
entry = bucket.get(subject)
|
| 363 |
+
if not entry or entry.get("window") != window_key:
|
| 364 |
+
entry = {"window": window_key, "count": 0}
|
| 365 |
+
bucket[subject] = entry
|
| 366 |
+
|
| 367 |
+
if entry["count"] >= int(limit):
|
| 368 |
raise HTTPException(
|
| 369 |
status_code=429,
|
| 370 |
+
detail=f"{metric} limit reached for {plan.get('name', 'current plan')}",
|
| 371 |
)
|
| 372 |
|
| 373 |
entry["count"] += 1
|
| 374 |
+
remaining = max(0, int(limit) - entry["count"])
|
| 375 |
+
return {"plan_key": plan_key, "remaining": remaining}
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
async def check_audio_rate_limit(request: Request, authorization: Optional[str]):
|
| 379 |
+
await enforce_rate_limit(request, authorization, "audioWeekly")
|
| 380 |
|
| 381 |
|
| 382 |
def is_complex_reasoning(prompt: str) -> bool:
|
|
|
|
| 407 |
return True
|
| 408 |
return False
|
| 409 |
|
| 410 |
+
async def check_image_rate_limit(request: Request, authorization: Optional[str]):
|
| 411 |
+
await enforce_rate_limit(request, authorization, "imagesDaily")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 412 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 413 |
|
| 414 |
+
async def check_video_rate_limit(request: Request, authorization: Optional[str]):
|
| 415 |
+
await enforce_rate_limit(request, authorization, "videosDaily")
|
| 416 |
|
| 417 |
PKEY = os.getenv("POLLINATIONS_KEY", "")
|
| 418 |
PKEY2 = os.getenv("POLLINATIONS2_KEY", "")
|
| 419 |
PKEY3 = os.getenv("POLLINATIONS3_KEY", "")
|
| 420 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 421 |
GROQ_TOOL_MODELS = [
|
| 422 |
"openai/gpt-oss-120b",
|
| 423 |
"openai/gpt-oss-20b",
|
|
|
|
| 442 |
"zai-glm-4.7",
|
| 443 |
]
|
| 444 |
|
| 445 |
+
async def check_chat_rate_limit(request: Request, authorization: Optional[str]):
|
| 446 |
+
return await enforce_rate_limit(request, authorization, "cloudChatDaily")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 447 |
|
| 448 |
@app.head("/status/sfx")
|
| 449 |
async def head_sfx():
|
|
|
|
| 528 |
|
| 529 |
@app.post("/gen/image")
|
| 530 |
@app.get("/genimg/{prompt}")
|
| 531 |
+
async def generate_image(
|
| 532 |
+
request: Request,
|
| 533 |
+
prompt: str = None,
|
| 534 |
+
authorization: Optional[str] = Header(None),
|
| 535 |
+
):
|
| 536 |
+
await check_image_rate_limit(request, authorization)
|
| 537 |
timeout = httpx.Timeout(300.0, read=300.0)
|
| 538 |
if prompt is None:
|
| 539 |
prompt = (await request.json()).get("prompt")
|
|
|
|
| 598 |
return models
|
| 599 |
|
| 600 |
@app.post("/gen/chat/completions")
|
| 601 |
+
async def generate_text(request: Request, authorization: Optional[str] = Header(None)):
|
| 602 |
body = await request.json()
|
| 603 |
messages = body.get("messages", [])
|
| 604 |
if not isinstance(messages, list) or len(messages) == 0:
|
| 605 |
raise HTTPException(400, "messages[] is required")
|
| 606 |
|
| 607 |
+
await check_chat_rate_limit(request, authorization)
|
|
|
|
| 608 |
prompt_text = extract_user_text(messages)
|
| 609 |
|
| 610 |
uses_tools = (
|
|
|
|
| 752 |
|
| 753 |
@app.get("/gen/sfx/{prompt}")
|
| 754 |
@app.post("/gen/sfx")
|
| 755 |
+
async def gensfx(
|
| 756 |
+
request: Request,
|
| 757 |
+
prompt: str = None,
|
| 758 |
+
authorization: Optional[str] = Header(None),
|
| 759 |
+
):
|
| 760 |
+
await check_audio_rate_limit(request, authorization)
|
| 761 |
if prompt is None:
|
| 762 |
prompt = (await request.json()).get("prompt")
|
| 763 |
url = f"https://gen.pollinations.ai/audio/{prompt}?model=elevenmusic&key={PKEY}"
|
|
|
|
| 785 |
|
| 786 |
@app.get("/gen/tts/{prompt}")
|
| 787 |
@app.post("/gen/tts")
|
| 788 |
+
async def gensfx(
|
| 789 |
+
request: Request,
|
| 790 |
+
prompt: str = None,
|
| 791 |
+
authorization: Optional[str] = Header(None),
|
| 792 |
+
):
|
| 793 |
+
await check_audio_rate_limit(request, authorization)
|
| 794 |
if prompt is None:
|
| 795 |
prompt = (await request.json()).get("prompt")
|
| 796 |
url = f"https://gen.pollinations.ai/audio/{prompt}?key={PKEY3}"
|
|
|
|
| 819 |
@app.get("/gen/video/{prompt}")
|
| 820 |
@app.post("/gen/video")
|
| 821 |
@app.head("/gen/video")
|
| 822 |
+
async def genvideo(
|
| 823 |
+
request: Request,
|
| 824 |
+
prompt: str = None,
|
| 825 |
+
authorization: Optional[str] = Header(None),
|
| 826 |
+
):
|
| 827 |
+
if request.method != "HEAD":
|
| 828 |
+
await check_video_rate_limit(request, authorization)
|
| 829 |
return RedirectResponse(url="/gen/video/airforce", status_code=status.HTTP_307_TEMPORARY_REDIRECT)
|
| 830 |
|
| 831 |
if prompt is None:
|
|
|
|
| 918 |
@app.get("/gen/video/airforce/{prompt}")
|
| 919 |
@app.post("/gen/video/airforce")
|
| 920 |
@app.head("/gen/video/airforce")
|
| 921 |
+
async def genvideo_airforce(
|
| 922 |
+
request: Request,
|
| 923 |
+
prompt: str = None,
|
| 924 |
+
authorization: Optional[str] = Header(None),
|
| 925 |
+
):
|
| 926 |
if request.method == "HEAD":
|
| 927 |
return Response(
|
| 928 |
status_code=200,
|
|
|
|
| 954 |
}
|
| 955 |
)
|
| 956 |
|
| 957 |
+
await check_video_rate_limit(request, authorization)
|
| 958 |
+
|
| 959 |
aspectRatio = "3:2"
|
| 960 |
inputMode = "normal"
|
| 961 |
|
|
|
|
| 1055 |
if "error" in result:
|
| 1056 |
raise HTTPException(401, result["error"])
|
| 1057 |
|
| 1058 |
+
plan_key = normalize_plan_key(result.get("plan_key"))
|
| 1059 |
+
result["plan_key"] = plan_key
|
| 1060 |
+
result["plan_name"] = (TIER_CONFIG.get(plan_key) or TIER_CONFIG["free"])["name"]
|
| 1061 |
return result
|
| 1062 |
|
| 1063 |
+
@app.get("/tier-config")
|
| 1064 |
+
async def tier_config():
|
| 1065 |
+
plans = []
|
| 1066 |
+
for idx, key in enumerate(PLAN_ORDER):
|
| 1067 |
+
plan = TIER_CONFIG.get(key)
|
| 1068 |
+
if not plan:
|
| 1069 |
+
continue
|
| 1070 |
+
plans.append(
|
| 1071 |
+
{
|
| 1072 |
+
"key": key,
|
| 1073 |
+
"name": plan["name"],
|
| 1074 |
+
"url": plan["url"],
|
| 1075 |
+
"price": plan["price"],
|
| 1076 |
+
"limits": plan["limits"],
|
| 1077 |
+
"order": idx,
|
| 1078 |
+
}
|
| 1079 |
+
)
|
| 1080 |
+
|
| 1081 |
return JSONResponse(
|
| 1082 |
status_code=200,
|
| 1083 |
+
content={
|
| 1084 |
+
"defaultPlanKey": "free",
|
| 1085 |
+
"plans": plans,
|
| 1086 |
+
},
|
| 1087 |
+
)
|
| 1088 |
+
|
| 1089 |
+
@app.get("/tiers")
|
| 1090 |
+
async def tiers():
|
| 1091 |
+
paid_plans = []
|
| 1092 |
+
for key in PLAN_ORDER:
|
| 1093 |
+
if key == "free":
|
| 1094 |
+
continue
|
| 1095 |
+
plan = TIER_CONFIG.get(key)
|
| 1096 |
+
if not plan:
|
| 1097 |
+
continue
|
| 1098 |
+
paid_plans.append(
|
| 1099 |
{
|
| 1100 |
+
"key": key,
|
| 1101 |
+
"name": plan["name"],
|
| 1102 |
+
"url": plan["url"],
|
| 1103 |
+
"price": plan["price"],
|
| 1104 |
+
"limits": plan["limits"],
|
| 1105 |
}
|
| 1106 |
+
)
|
| 1107 |
+
|
| 1108 |
+
return JSONResponse(
|
| 1109 |
+
status_code=200,
|
| 1110 |
+
content=paid_plans,
|
| 1111 |
)
|
| 1112 |
|
| 1113 |
@app.get("/portal")
|
| 1114 |
def a():
|
| 1115 |
+
return RedirectResponse(url="https://billing.stripe.com/p/login/test_6oUcN5g665rp7nLgaq8bS00", status_code=status.HTTP_302_FOUND)
|