Bl4ckSpaces/Flux.2-dev-API-URL
0
1import os2import random3import time4import threading5import requests6import json7from flask import Flask, request, jsonify, send_file, make_response8from flask_cors import CORS9 10app = Flask(__name__)11# Enable CORS secara explicit dan permissive12CORS(app, resources={r"/*": {"origins": "*"}})13 14# --- TARGET CONFIG ---15# URL Space Flux kamu16BASE_URL = "https://black-forest-labs-flux-2-dev.hf.space"17 18# --- TOKEN LIST (HARDCODED) ---19RAW_TOKENS = [20 "hf_+PiRCDDtPcPFMLWkTkVaZmzoleHOunXnLIA",21 "hf_+BHvZXGICstaktSwycmwNmzHGrTNmKxnlRZ",22 "hf_+ZdgawyTPzXIpwhnRYIteUKSMsWnEDtGKtM",23 "hf_+nMiFYAFsINxAJWPwiCQlaunmdgmrcxKoaT",24 "hf_+PccpUIbTckCiafwErDLkRlsvqhgtfZaBHL",25 "hf_+faGyXBPfBkaHXDMUSJtxEggonhhZbomFIz",26 "hf_+SndsPaRWsevDXCgZcSjTUlBYUJqOkSfFmn",27 "hf_+CqobFdUpeVCeuhUaiuXwvdczBUmoUHXRGa",28 "hf_+JKCQYUhhHPPkpucegqkNSyureLdXpmeXRF",29 "hf_+tBYfslUwHNiNMufzwAYIlrDVovEWmOQulC",30 "hf_+LKLdrdUxyUyKODSUthmqHXqDMfHrQueera",31 "hf_+ivSBboJYQVcifWkCNcOTOnxUQrZOtOglnU"32]33 34class TokenManager:35 def __init__(self, raw_tokens):36 self.tokens = [t.replace("+", "").strip() for t in raw_tokens if t.strip()]37 self.current_index = 038 self.lock = threading.Lock()39 40 def get_token(self):41 with self.lock:42 if not self.tokens: raise Exception("No tokens available")43 token = self.tokens[self.current_index]44 self.current_index = (self.current_index + 1) % len(self.tokens)45 return token46 47token_manager = TokenManager(RAW_TOKENS)48 49def request_flux_gradio4(prompt, width, height, guidance_scale, seed):50 """51 Menangani request khusus untuk Gradio 4 (Flux Standard)52 Endpoint: /gradio_api/call/infer53 """54 max_retries = 555 attempt = 056 last_error = ""57 58 # Endpoint Gradio 4 yang Benar59 api_url = f"{BASE_URL}/gradio_api/call/infer"60 61 while attempt < max_retries:62 token = token_manager.get_token()63 print(f"[LOG] Attempt {attempt+1} - Token: ...{token[-5:]}")64 65 headers = {66 "Authorization": f"Bearer {token}",67 "Content-Type": "application/json"68 }69 70 # Payload Gradio 4 (Tanpa fn_index, langsung data array)71 payload = {72 "data": [73 prompt,74 int(seed) if seed is not None else 0,75 True if seed is None else False, # randomize_seed76 int(width),77 int(height),78 float(guidance_scale),79 28 # num_inference_steps80 ]81 }82 83 try:84 # 1. Kirim Job ID Request85 response = requests.post(api_url, headers=headers, json=payload, timeout=30)86 87 if response.status_code != 200:88 raise Exception(f"HTTP Error {response.status_code}: {response.text}")89 90 resp_json = response.json()91 # Gradio 4 mengembalikan { "event_id": "..." }92 event_id = resp_json.get("event_id")93 if not event_id:94 raise Exception(f"No event_id returned: {resp_json}")95 96 # 2. Polling Hasil (Gradio 4 butuh polling status request)97 # Kita tembak endpoint result untuk event_id tersebut98 result_url = f"{BASE_URL}/gradio_api/call/infer/{event_id}"99 100 # Tunggu proses selesai (polling loop)101 # Karena flux cepat, kita poll tiap 1 detik102 for _ in range(60): # Max tunggu 60 detik103 time.sleep(1) 104 poll_resp = requests.get(result_url, headers=headers, stream=True) # Stream true untuk jaga2105 106 # Gradio 4 streaming response berbentuk text event stream107 # Kita cari baris yang mengandung "complete" atau data url108 content = poll_resp.text109 110 if "process_completed" in content and "url" in content:111 # Parsing manual dari format event-stream112 # Biasanya format: event: complete \n data: [...]113 lines = content.split('\n')114 for line in lines:115 if line.startswith('data:'):116 try:117 data_block = json.loads(line[5:])118 # Cari URL output119 if isinstance(data_block, list) and len(data_block) > 0:120 file_info = data_block[0]121 final_url = file_info.get("url")122 if final_url:123 # Download Gambar Akhir124 print(f"[LOG] Downloading: {final_url}")125 img_res = requests.get(final_url, timeout=60)126 temp_filename = f"/tmp/flux_{int(time.time())}_{random.randint(0,999)}.webp"127 with open(temp_filename, 'wb') as f:128 f.write(img_res.content)129 return temp_filename130 except:131 continue132 133 raise Exception("Timeout waiting for Gradio 4 result")134 135 except Exception as e:136 err_msg = str(e)137 print(f"[ERROR] {err_msg}")138 if "429" in err_msg or "quota" in err_msg.lower():139 print("Rate limit detected.")140 else:141 last_error = err_msg142 attempt += 1143 time.sleep(1)144 145 raise Exception(f"Gagal Total: {last_error}")146 147# --- ROUTES ---148 149@app.route('/', methods=['GET'])150def home():151 return jsonify({"status": "Online", "mode": "Gradio 4 Native"})152 153# Handle OPTIONS request manual untuk mencegah Failed to Fetch154@app.route('/generate', methods=['OPTIONS'])155def options_generate():156 response = make_response()157 response.headers.add("Access-Control-Allow-Origin", "*")158 response.headers.add("Access-Control-Allow-Headers", "*")159 response.headers.add("Access-Control-Allow-Methods", "POST, OPTIONS")160 return response161 162@app.route('/generate', methods=['POST'])163def generate():164 data = request.json165 if not data or 'prompt' not in data:166 return jsonify({"error": "Prompt required"}), 400167 168 prompt = data.get('prompt')169 width = data.get('width', 1024)170 height = data.get('height', 1024)171 guidance = data.get('guidance', 3.5)172 seed = data.get('seed', None)173 174 try:175 image_path = request_flux_gradio4(prompt, width, height, guidance, seed)176 return send_file(image_path, mimetype='image/webp')177 except Exception as e:178 # Return JSON error tapi dengan CORS header agar terbaca di frontend179 resp = jsonify({"error": str(e)})180 resp.headers.add("Access-Control-Allow-Origin", "*")181 return resp, 500182 183if __name__ == '__main__':184 app.run(host='0.0.0.0', port=7860)185 