CoolFace
Apppublic

Bl4ckSpaces/Flux.2-dev-API-URL

sourceHugging Faceupdated 8mo agoView on Hugging Face
0likes
app.py185 linesDownload Raw Back to root
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