Bl4ckSpaces's picture
Update app.py
58c3956 verified
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 ---
@app.route('/', methods=['GET'])
def home():
return jsonify({"status": "Online", "mode": "Gradio 4 Native"})
# Handle OPTIONS request manual untuk mencegah Failed to Fetch
@app.route('/generate', methods=['OPTIONS'])
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
@app.route('/generate', methods=['POST'])
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)