Bl4ckSpaces commited on
Commit
58c3956
·
verified ·
1 Parent(s): dc71255

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -76
app.py CHANGED
@@ -4,17 +4,18 @@ import time
4
  import threading
5
  import requests
6
  import json
7
- from flask import Flask, request, jsonify, send_file
8
  from flask_cors import CORS
9
- from concurrent.futures import ThreadPoolExecutor
10
 
11
  app = Flask(__name__)
12
- CORS(app)
 
13
 
14
- # --- KONFIGURASI TARGET ---
15
- TARGET_SPACE_URL = "https://black-forest-labs-flux-2-dev.hf.space"
 
16
 
17
- # --- TOKEN LIST (Tetap ditanam) ---
18
  RAW_TOKENS = [
19
  "hf_+PiRCDDtPcPFMLWkTkVaZmzoleHOunXnLIA",
20
  "hf_+BHvZXGICstaktSwycmwNmzHGrTNmKxnlRZ",
@@ -30,7 +31,6 @@ RAW_TOKENS = [
30
  "hf_+ivSBboJYQVcifWkCNcOTOnxUQrZOtOglnU"
31
  ]
32
 
33
- # --- TOKEN MANAGER ---
34
  class TokenManager:
35
  def __init__(self, raw_tokens):
36
  self.tokens = [t.replace("+", "").strip() for t in raw_tokens if t.strip()]
@@ -39,42 +39,36 @@ class TokenManager:
39
 
40
  def get_token(self):
41
  with self.lock:
42
- if not self.tokens: raise Exception("List token kosong!")
43
  token = self.tokens[self.current_index]
44
  self.current_index = (self.current_index + 1) % len(self.tokens)
45
  return token
46
 
47
  token_manager = TokenManager(RAW_TOKENS)
48
 
49
- # --- FUNGSI REQUEST MANUAL (CARA LAIN) ---
50
- def request_flux_manual(prompt, width, height, guidance_scale, seed):
51
  """
52
- Menggunakan requests murni, tanpa gradio_client.
53
- Menembak endpoint /api/predict milik Space.
54
  """
55
  max_retries = 5
56
  attempt = 0
57
  last_error = ""
58
-
59
- # Endpoint API Gradio standar (Legacy & V4 compatible)
60
- # Kita coba dua kemungkinan endpoint: /api/predict (Gradio 3/Lite) atau /run/predict (Gradio 4)
61
- # Biasanya untuk space publik, /api/predict adalah yang paling umum terekspos.
62
- api_url = f"{TARGET_SPACE_URL}/api/predict"
63
 
64
  while attempt < max_retries:
65
- current_token = token_manager.get_token()
66
- print(f"[LOG] Attempt {attempt+1} - Token: ...{current_token[-5:]}")
67
-
68
  headers = {
69
- "Authorization": f"Bearer {current_token}",
70
  "Content-Type": "application/json"
71
  }
72
 
73
- # Payload JSON standar untuk Flux Dev Space
74
- # Urutan 'data' sangat penting! Biasanya:
75
- # [prompt, seed, randomize_seed, width, height, guidance, num_inference_steps]
76
  payload = {
77
- "fn_index": 0, # Index fungsi default (biasanya 0 untuk generate)
78
  "data": [
79
  prompt,
80
  int(seed) if seed is not None else 0,
@@ -82,83 +76,94 @@ def request_flux_manual(prompt, width, height, guidance_scale, seed):
82
  int(width),
83
  int(height),
84
  float(guidance_scale),
85
- 28 # num_inference_steps (default flux)
86
  ]
87
  }
88
 
89
  try:
90
- # 1. Kirim Request Generate
91
- response = requests.post(api_url, headers=headers, json=payload, timeout=120)
92
 
93
  if response.status_code != 200:
94
- raise Exception(f"Status {response.status_code}: {response.text[:100]}")
95
-
96
  resp_json = response.json()
 
 
 
 
 
 
 
 
97
 
98
- # 2. Parsing Hasil
99
- # Gradio API biasanya mengembalikan: {"data": ["/tmp/..."], ...}
100
- if "data" in resp_json and len(resp_json["data"]) > 0:
101
- file_info = resp_json["data"][0]
 
102
 
103
- # Cek apakah response berupa URL atau Path
104
- # Flux space sering mengembalikan object seperti {"path": "...", "url": "..."} atau string path langsung
105
- download_url = ""
106
 
107
- if isinstance(file_info, dict) and "url" in file_info:
108
- download_url = file_info["url"]
109
- elif isinstance(file_info, str):
110
- # Jika string path lokal (/tmp/...), kita harus fetch dari endpoint file=
111
- # Kecuali kalau dia sudah full URL
112
- if file_info.startswith("http"):
113
- download_url = file_info
114
- else:
115
- download_url = f"{TARGET_SPACE_URL}/file={file_info}"
116
- else:
117
- raise Exception("Format response data tidak dikenali.")
118
-
119
- # 3. Download Gambar Hasil ke Server Proxy (Memory)
120
- print(f"[LOG] Downloading result from: {download_url}")
121
- img_response = requests.get(download_url, timeout=60)
122
-
123
- # Simpan sementara di /tmp server kita untuk dikirim ke user
124
- temp_filename = f"/tmp/flux_{int(time.time())}_{random.randint(0,1000)}.webp"
125
- with open(temp_filename, 'wb') as f:
126
- f.write(img_response.content)
127
-
128
- return temp_filename
129
-
130
- else:
131
- raise Exception(f"Response data kosong/salah format: {resp_json}")
132
 
133
  except Exception as e:
134
  err_msg = str(e)
135
  print(f"[ERROR] {err_msg}")
136
-
137
  if "429" in err_msg or "quota" in err_msg.lower():
138
- print("[INFO] Rate limit/Quota, lanjut token berikutnya.")
139
  else:
140
  last_error = err_msg
141
-
142
  attempt += 1
143
  time.sleep(1)
144
 
145
- raise Exception(f"Gagal total setelah {max_retries}x coba. Error: {last_error}")
146
-
147
 
148
  # --- ROUTES ---
149
- @app.route('/')
 
150
  def home():
151
- return jsonify({
152
- "status": "Online (Requests Mode)",
153
- "target": TARGET_SPACE_URL,
154
- "mode": "No-Gradio-Client"
155
- })
 
 
 
 
 
156
 
157
  @app.route('/generate', methods=['POST'])
158
  def generate():
159
  data = request.json
160
  if not data or 'prompt' not in data:
161
- return jsonify({"error": "Prompt wajib diisi"}), 400
162
 
163
  prompt = data.get('prompt')
164
  width = data.get('width', 1024)
@@ -167,10 +172,13 @@ def generate():
167
  seed = data.get('seed', None)
168
 
169
  try:
170
- image_path = request_flux_manual(prompt, width, height, guidance, seed)
171
  return send_file(image_path, mimetype='image/webp')
172
  except Exception as e:
173
- return jsonify({"error": str(e)}), 500
 
 
 
174
 
175
  if __name__ == '__main__':
176
  app.run(host='0.0.0.0', port=7860)
 
4
  import threading
5
  import requests
6
  import json
7
+ from flask import Flask, request, jsonify, send_file, make_response
8
  from flask_cors import CORS
 
9
 
10
  app = Flask(__name__)
11
+ # Enable CORS secara explicit dan permissive
12
+ CORS(app, resources={r"/*": {"origins": "*"}})
13
 
14
+ # --- TARGET CONFIG ---
15
+ # URL Space Flux kamu
16
+ BASE_URL = "https://black-forest-labs-flux-2-dev.hf.space"
17
 
18
+ # --- TOKEN LIST (HARDCODED) ---
19
  RAW_TOKENS = [
20
  "hf_+PiRCDDtPcPFMLWkTkVaZmzoleHOunXnLIA",
21
  "hf_+BHvZXGICstaktSwycmwNmzHGrTNmKxnlRZ",
 
31
  "hf_+ivSBboJYQVcifWkCNcOTOnxUQrZOtOglnU"
32
  ]
33
 
 
34
  class TokenManager:
35
  def __init__(self, raw_tokens):
36
  self.tokens = [t.replace("+", "").strip() for t in raw_tokens if t.strip()]
 
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 token
46
 
47
  token_manager = TokenManager(RAW_TOKENS)
48
 
49
+ def request_flux_gradio4(prompt, width, height, guidance_scale, seed):
 
50
  """
51
+ Menangani request khusus untuk Gradio 4 (Flux Standard)
52
+ Endpoint: /gradio_api/call/infer
53
  """
54
  max_retries = 5
55
  attempt = 0
56
  last_error = ""
57
+
58
+ # Endpoint Gradio 4 yang Benar
59
+ 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,
 
76
  int(width),
77
  int(height),
78
  float(guidance_scale),
79
+ 28 # num_inference_steps
80
  ]
81
  }
82
 
83
  try:
84
+ # 1. Kirim Job ID Request
85
+ 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 tersebut
98
+ 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 detik
102
+ for _ in range(60): # Max tunggu 60 detik
103
+ time.sleep(1)
104
+ poll_resp = requests.get(result_url, headers=headers, stream=True) # Stream true untuk jaga2
105
 
106
+ # Gradio 4 streaming response berbentuk text event stream
107
+ # Kita cari baris yang mengandung "complete" atau data url
108
+ content = poll_resp.text
109
 
110
+ if "process_completed" in content and "url" in content:
111
+ # Parsing manual dari format event-stream
112
+ # 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 output
119
+ 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 Akhir
124
+ 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_filename
130
+ except:
131
+ continue
132
+
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_msg
 
142
  attempt += 1
143
  time.sleep(1)
144
 
145
+ raise Exception(f"Gagal Total: {last_error}")
 
146
 
147
  # --- ROUTES ---
148
+
149
+ @app.route('/', methods=['GET'])
150
  def home():
151
+ return jsonify({"status": "Online", "mode": "Gradio 4 Native"})
152
+
153
+ # Handle OPTIONS request manual untuk mencegah Failed to Fetch
154
+ @app.route('/generate', methods=['OPTIONS'])
155
+ def 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 response
161
 
162
  @app.route('/generate', methods=['POST'])
163
  def generate():
164
  data = request.json
165
  if not data or 'prompt' not in data:
166
+ return jsonify({"error": "Prompt required"}), 400
167
 
168
  prompt = data.get('prompt')
169
  width = data.get('width', 1024)
 
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 frontend
179
+ resp = jsonify({"error": str(e)})
180
+ resp.headers.add("Access-Control-Allow-Origin", "*")
181
+ return resp, 500
182
 
183
  if __name__ == '__main__':
184
  app.run(host='0.0.0.0', port=7860)