Spaces:
Sleeping
Sleeping
| import os | |
| import random | |
| import time | |
| import threading | |
| import requests | |
| import json | |
| from flask import Flask, request, jsonify, send_file, make_response | |
| from flask_cors import CORS | |
| app = Flask(__name__) | |
| # Enable CORS secara explicit dan permissive | |
| CORS(app, resources={r"/*": {"origins": "*"}}) | |
| # --- TARGET CONFIG --- | |
| # URL Space Flux kamu | |
| BASE_URL = "https://black-forest-labs-flux-2-dev.hf.space" | |
| # --- TOKEN LIST (HARDCODED) --- | |
| RAW_TOKENS = [ | |
| "hf_+PiRCDDtPcPFMLWkTkVaZmzoleHOunXnLIA", | |
| "hf_+BHvZXGICstaktSwycmwNmzHGrTNmKxnlRZ", | |
| "hf_+ZdgawyTPzXIpwhnRYIteUKSMsWnEDtGKtM", | |
| "hf_+nMiFYAFsINxAJWPwiCQlaunmdgmrcxKoaT", | |
| "hf_+PccpUIbTckCiafwErDLkRlsvqhgtfZaBHL", | |
| "hf_+faGyXBPfBkaHXDMUSJtxEggonhhZbomFIz", | |
| "hf_+SndsPaRWsevDXCgZcSjTUlBYUJqOkSfFmn", | |
| "hf_+CqobFdUpeVCeuhUaiuXwvdczBUmoUHXRGa", | |
| "hf_+JKCQYUhhHPPkpucegqkNSyureLdXpmeXRF", | |
| "hf_+tBYfslUwHNiNMufzwAYIlrDVovEWmOQulC", | |
| "hf_+LKLdrdUxyUyKODSUthmqHXqDMfHrQueera", | |
| "hf_+ivSBboJYQVcifWkCNcOTOnxUQrZOtOglnU" | |
| ] | |
| class TokenManager: | |
| def __init__(self, raw_tokens): | |
| self.tokens = [t.replace("+", "").strip() for t in raw_tokens if t.strip()] | |
| self.current_index = 0 | |
| self.lock = threading.Lock() | |
| def get_token(self): | |
| with self.lock: | |
| if not self.tokens: raise Exception("No tokens available") | |
| token = self.tokens[self.current_index] | |
| self.current_index = (self.current_index + 1) % len(self.tokens) | |
| return token | |
| token_manager = TokenManager(RAW_TOKENS) | |
| def request_flux_gradio4(prompt, width, height, guidance_scale, seed): | |
| """ | |
| Menangani request khusus untuk Gradio 4 (Flux Standard) | |
| Endpoint: /gradio_api/call/infer | |
| """ | |
| max_retries = 5 | |
| attempt = 0 | |
| last_error = "" | |
| # Endpoint Gradio 4 yang Benar | |
| api_url = f"{BASE_URL}/gradio_api/call/infer" | |
| while attempt < max_retries: | |
| token = token_manager.get_token() | |
| print(f"[LOG] Attempt {attempt+1} - Token: ...{token[-5:]}") | |
| headers = { | |
| "Authorization": f"Bearer {token}", | |
| "Content-Type": "application/json" | |
| } | |
| # Payload Gradio 4 (Tanpa fn_index, langsung data array) | |
| payload = { | |
| "data": [ | |
| prompt, | |
| int(seed) if seed is not None else 0, | |
| True if seed is None else False, # randomize_seed | |
| int(width), | |
| int(height), | |
| float(guidance_scale), | |
| 28 # num_inference_steps | |
| ] | |
| } | |
| try: | |
| # 1. Kirim Job ID Request | |
| response = requests.post(api_url, headers=headers, json=payload, timeout=30) | |
| if response.status_code != 200: | |
| raise Exception(f"HTTP Error {response.status_code}: {response.text}") | |
| resp_json = response.json() | |
| # Gradio 4 mengembalikan { "event_id": "..." } | |
| event_id = resp_json.get("event_id") | |
| if not event_id: | |
| raise Exception(f"No event_id returned: {resp_json}") | |
| # 2. Polling Hasil (Gradio 4 butuh polling status request) | |
| # Kita tembak endpoint result untuk event_id tersebut | |
| result_url = f"{BASE_URL}/gradio_api/call/infer/{event_id}" | |
| # Tunggu proses selesai (polling loop) | |
| # Karena flux cepat, kita poll tiap 1 detik | |
| for _ in range(60): # Max tunggu 60 detik | |
| time.sleep(1) | |
| poll_resp = requests.get(result_url, headers=headers, stream=True) # Stream true untuk jaga2 | |
| # Gradio 4 streaming response berbentuk text event stream | |
| # Kita cari baris yang mengandung "complete" atau data url | |
| content = poll_resp.text | |
| if "process_completed" in content and "url" in content: | |
| # Parsing manual dari format event-stream | |
| # Biasanya format: event: complete \n data: [...] | |
| lines = content.split('\n') | |
| for line in lines: | |
| if line.startswith('data:'): | |
| try: | |
| data_block = json.loads(line[5:]) | |
| # Cari URL output | |
| if isinstance(data_block, list) and len(data_block) > 0: | |
| file_info = data_block[0] | |
| final_url = file_info.get("url") | |
| if final_url: | |
| # Download Gambar Akhir | |
| print(f"[LOG] Downloading: {final_url}") | |
| img_res = requests.get(final_url, timeout=60) | |
| temp_filename = f"/tmp/flux_{int(time.time())}_{random.randint(0,999)}.webp" | |
| with open(temp_filename, 'wb') as f: | |
| f.write(img_res.content) | |
| return temp_filename | |
| except: | |
| continue | |
| raise Exception("Timeout waiting for Gradio 4 result") | |
| except Exception as e: | |
| err_msg = str(e) | |
| print(f"[ERROR] {err_msg}") | |
| if "429" in err_msg or "quota" in err_msg.lower(): | |
| print("Rate limit detected.") | |
| else: | |
| last_error = err_msg | |
| attempt += 1 | |
| time.sleep(1) | |
| raise Exception(f"Gagal Total: {last_error}") | |
| # --- ROUTES --- | |
| def home(): | |
| return jsonify({"status": "Online", "mode": "Gradio 4 Native"}) | |
| # Handle OPTIONS request manual untuk mencegah Failed to Fetch | |
| def options_generate(): | |
| response = make_response() | |
| response.headers.add("Access-Control-Allow-Origin", "*") | |
| response.headers.add("Access-Control-Allow-Headers", "*") | |
| response.headers.add("Access-Control-Allow-Methods", "POST, OPTIONS") | |
| return response | |
| def generate(): | |
| data = request.json | |
| if not data or 'prompt' not in data: | |
| return jsonify({"error": "Prompt required"}), 400 | |
| prompt = data.get('prompt') | |
| width = data.get('width', 1024) | |
| height = data.get('height', 1024) | |
| guidance = data.get('guidance', 3.5) | |
| seed = data.get('seed', None) | |
| try: | |
| image_path = request_flux_gradio4(prompt, width, height, guidance, seed) | |
| return send_file(image_path, mimetype='image/webp') | |
| except Exception as e: | |
| # Return JSON error tapi dengan CORS header agar terbaca di frontend | |
| resp = jsonify({"error": str(e)}) | |
| resp.headers.add("Access-Control-Allow-Origin", "*") | |
| return resp, 500 | |
| if __name__ == '__main__': | |
| app.run(host='0.0.0.0', port=7860) | |