Spaces:
Running
Running
Rename server.js to app.py
Browse files
app.py
ADDED
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import random
|
2 |
+
import string
|
3 |
+
import requests
|
4 |
+
from flask import Flask, request, Response, stream_with_context, send_file
|
5 |
+
from io import BytesIO
|
6 |
+
|
7 |
+
app = Flask(__name__)
|
8 |
+
|
9 |
+
# --- Utility functions ---
|
10 |
+
def random_user_agent():
|
11 |
+
agents = [
|
12 |
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
13 |
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
|
14 |
+
'Mozilla/5.0 (Linux; Android 13; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36',
|
15 |
+
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
|
16 |
+
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
|
17 |
+
]
|
18 |
+
return random.choice(agents)
|
19 |
+
|
20 |
+
def random_ip():
|
21 |
+
return '.'.join(str(random.randint(0, 255)) for _ in range(4))
|
22 |
+
|
23 |
+
def random_session_hash():
|
24 |
+
return ''.join(random.choices(string.ascii_lowercase + string.digits, k=12))
|
25 |
+
|
26 |
+
# --- SSE Streaming Helper ---
|
27 |
+
def sse_stream(generator):
|
28 |
+
return Response(stream_with_context(generator), mimetype='text/event-stream')
|
29 |
+
|
30 |
+
# --- UNO-FLUX Endpoint ---
|
31 |
+
@app.route('/api/generate/flux', methods=['POST'])
|
32 |
+
def generate_flux():
|
33 |
+
data = request.json or {}
|
34 |
+
prompt = data.get('prompt', '')
|
35 |
+
width = int(data.get('width', 1280))
|
36 |
+
height = int(data.get('height', 720))
|
37 |
+
guidance = data.get('guidance', 4)
|
38 |
+
num_steps = data.get('num_steps', 25)
|
39 |
+
seed = data.get('seed', -1)
|
40 |
+
image_prompt1 = data.get('image_prompt1')
|
41 |
+
image_prompt2 = data.get('image_prompt2')
|
42 |
+
image_prompt3 = data.get('image_prompt3')
|
43 |
+
image_prompt4 = data.get('image_prompt4')
|
44 |
+
aspect = data.get('aspect', '16:9')
|
45 |
+
if not prompt:
|
46 |
+
return sse_stream(lambda: iter([f"data: {{\"type\":\"error\",\"message\":\"Prompt is required\"}}\n\n"]))
|
47 |
+
# Aspect ratio logic
|
48 |
+
if aspect == '9:16':
|
49 |
+
width, height = 720, 1280
|
50 |
+
elif aspect == '1:1':
|
51 |
+
width, height = 1024, 1024
|
52 |
+
session_hash = random_session_hash()
|
53 |
+
join_payload = {
|
54 |
+
"data": [
|
55 |
+
prompt,
|
56 |
+
width,
|
57 |
+
height,
|
58 |
+
guidance,
|
59 |
+
num_steps,
|
60 |
+
seed,
|
61 |
+
image_prompt1,
|
62 |
+
image_prompt2,
|
63 |
+
image_prompt3,
|
64 |
+
image_prompt4
|
65 |
+
],
|
66 |
+
"event_data": None,
|
67 |
+
"fn_index": 0,
|
68 |
+
"trigger_id": 25,
|
69 |
+
"session_hash": session_hash
|
70 |
+
}
|
71 |
+
def event_gen():
|
72 |
+
# Join queue
|
73 |
+
try:
|
74 |
+
join_resp = requests.post(
|
75 |
+
'https://bytedance-research-uno-flux.hf.space/gradio_api/queue/join?__theme=system',
|
76 |
+
json=join_payload,
|
77 |
+
headers={
|
78 |
+
'Content-Type': 'application/json',
|
79 |
+
'Referer': 'https://bytedance-research-uno-flux.hf.space/',
|
80 |
+
'Origin': 'https://bytedance-research-uno-flux.hf.space',
|
81 |
+
'User-Agent': random_user_agent(),
|
82 |
+
'X-Forwarded-For': random_ip()
|
83 |
+
},
|
84 |
+
timeout=30
|
85 |
+
)
|
86 |
+
if join_resp.status_code != 200:
|
87 |
+
yield f"data: {{\"type\":\"error\",\"message\":\"Failed to join UNO-FLUX queue\",\"status\":{join_resp.status_code}}}\n\n"
|
88 |
+
return
|
89 |
+
except Exception as e:
|
90 |
+
yield f"data: {{\"type\":\"error\",\"message\":\"Failed to join UNO-FLUX queue\",\"details\":\"{str(e)}\"}}\n\n"
|
91 |
+
return
|
92 |
+
# Poll for results
|
93 |
+
max_attempts = 90
|
94 |
+
for attempt in range(max_attempts):
|
95 |
+
try:
|
96 |
+
poll_resp = requests.get(
|
97 |
+
f'https://bytedance-research-uno-flux.hf.space/gradio_api/queue/data?session_hash={session_hash}',
|
98 |
+
headers={
|
99 |
+
'Accept': 'text/event-stream',
|
100 |
+
'Referer': 'https://bytedance-research-uno-flux.hf.space/',
|
101 |
+
'Origin': 'https://bytedance-research-uno-flux.hf.space',
|
102 |
+
'User-Agent': random_user_agent(),
|
103 |
+
'X-Forwarded-For': random_ip()
|
104 |
+
},
|
105 |
+
timeout=30
|
106 |
+
)
|
107 |
+
if poll_resp.status_code != 200:
|
108 |
+
yield f"data: {{\"type\":\"error\",\"message\":\"Polling failed\",\"status\":{poll_resp.status_code}}}\n\n"
|
109 |
+
return
|
110 |
+
lines = poll_resp.text.split('\n')
|
111 |
+
event_data = ''
|
112 |
+
for line in lines:
|
113 |
+
if line.startswith('data: '):
|
114 |
+
event_data += line[6:]
|
115 |
+
elif line == '':
|
116 |
+
if event_data:
|
117 |
+
try:
|
118 |
+
json_event = eval(event_data, {}, {}) if event_data.strip().startswith('{') else None
|
119 |
+
except Exception:
|
120 |
+
json_event = None
|
121 |
+
if json_event:
|
122 |
+
if json_event.get('msg') == 'estimation':
|
123 |
+
yield f"data: {{\"type\":\"estimation\",\"queueSize\":{json_event.get('queue_size')},\"eta\":{json_event.get('rank_eta')}}}\n\n"
|
124 |
+
elif json_event.get('msg') == 'process_starts':
|
125 |
+
yield f"data: {{\"type\":\"processing\"}}\n\n"
|
126 |
+
elif json_event.get('msg') == 'process_generating':
|
127 |
+
yield f"data: {{\"type\":\"generating\",\"output\":{json_event.get('output')}}}\n\n"
|
128 |
+
elif json_event.get('msg') == 'process_failed':
|
129 |
+
yield f"data: {{\"type\":\"error\",\"message\":\"Generation failed on server\"}}\n\n"
|
130 |
+
return
|
131 |
+
elif json_event.get('msg') == 'process_completed' and json_event.get('output', {}).get('data', [{}])[0].get('url'):
|
132 |
+
image_url = json_event['output']['data'][0]['url']
|
133 |
+
proxies = f"/api/proxy-image?url={image_url}"
|
134 |
+
yield f"data: {{\"type\":\"success\",\"imageUrl\":\"{image_url}\",\"proxies\":\"{proxies}\"}}\n\n"
|
135 |
+
return
|
136 |
+
elif json_event.get('msg') == 'process_completed' and json_event.get('output', {}).get('error'):
|
137 |
+
yield f"data: {{\"type\":\"error\",\"message\":\"{json_event['output']['error']}\"}}\n\n"
|
138 |
+
return
|
139 |
+
event_data = ''
|
140 |
+
elif line.startswith(':'):
|
141 |
+
continue
|
142 |
+
elif line != '':
|
143 |
+
event_data += line
|
144 |
+
except Exception as e:
|
145 |
+
yield f"data: {{\"type\":\"error\",\"message\":\"Polling error\",\"details\":\"{str(e)}\"}}\n\n"
|
146 |
+
return
|
147 |
+
yield f"data: {{\"type\":\"error\",\"message\":\"Generation timed out\"}}\n\n"
|
148 |
+
return sse_stream(event_gen())
|
149 |
+
|
150 |
+
# --- Heartsync Endpoint ---
|
151 |
+
@app.route('/api/generate', methods=['POST'])
|
152 |
+
def generate_heartsync():
|
153 |
+
data = request.json or {}
|
154 |
+
prompt = data.get('prompt', '')
|
155 |
+
if not prompt:
|
156 |
+
return sse_stream(lambda: iter([f"data: {{\"type\":\"error\",\"message\":\"Prompt is required\"}}\n\n"]))
|
157 |
+
session_hash = random_session_hash()
|
158 |
+
join_payload = {
|
159 |
+
"data": [
|
160 |
+
prompt,
|
161 |
+
"text, talk bubble, low quality, watermark, signature",
|
162 |
+
0,
|
163 |
+
True,
|
164 |
+
1024,
|
165 |
+
1024,
|
166 |
+
7,
|
167 |
+
28
|
168 |
+
],
|
169 |
+
"event_data": None,
|
170 |
+
"fn_index": 2,
|
171 |
+
"trigger_id": 14,
|
172 |
+
"session_hash": session_hash
|
173 |
+
}
|
174 |
+
def event_gen():
|
175 |
+
try:
|
176 |
+
join_resp = requests.post(
|
177 |
+
'https://heartsync-nsfw-uncensored.hf.space/gradio_api/queue/join',
|
178 |
+
json=join_payload,
|
179 |
+
headers={
|
180 |
+
'Content-Type': 'application/json',
|
181 |
+
'Referer': 'https://heartsync-nsfw-uncensored.hf.space/',
|
182 |
+
'User-Agent': random_user_agent(),
|
183 |
+
'X-Forwarded-For': random_ip()
|
184 |
+
},
|
185 |
+
timeout=30
|
186 |
+
)
|
187 |
+
if join_resp.status_code != 200:
|
188 |
+
yield f"data: {{\"type\":\"error\",\"message\":\"Failed to join Heartsync queue\",\"status\":{join_resp.status_code}}}\n\n"
|
189 |
+
return
|
190 |
+
except Exception as e:
|
191 |
+
yield f"data: {{\"type\":\"error\",\"message\":\"Failed to join Heartsync queue\",\"details\":\"{str(e)}\"}}\n\n"
|
192 |
+
return
|
193 |
+
max_attempts = 60
|
194 |
+
for attempt in range(max_attempts):
|
195 |
+
try:
|
196 |
+
poll_resp = requests.get(
|
197 |
+
f'https://heartsync-nsfw-uncensored.hf.space/gradio_api/queue/data?session_hash={session_hash}',
|
198 |
+
headers={
|
199 |
+
'Accept': 'text/event-stream',
|
200 |
+
'Referer': 'https://heartsync-nsfw-uncensored.hf.space/',
|
201 |
+
'User-Agent': random_user_agent(),
|
202 |
+
'X-Forwarded-For': random_ip()
|
203 |
+
},
|
204 |
+
timeout=30
|
205 |
+
)
|
206 |
+
if poll_resp.status_code != 200:
|
207 |
+
yield f"data: {{\"type\":\"error\",\"message\":\"Polling failed\",\"status\":{poll_resp.status_code}}}\n\n"
|
208 |
+
return
|
209 |
+
lines = poll_resp.text.split('\n')
|
210 |
+
event_data = ''
|
211 |
+
for line in lines:
|
212 |
+
if line.startswith('data: '):
|
213 |
+
event_data += line[6:]
|
214 |
+
elif line == '':
|
215 |
+
if event_data:
|
216 |
+
try:
|
217 |
+
json_event = eval(event_data, {}, {}) if event_data.strip().startswith('{') else None
|
218 |
+
except Exception:
|
219 |
+
json_event = None
|
220 |
+
if json_event:
|
221 |
+
if json_event.get('msg') == 'estimation':
|
222 |
+
yield f"data: {{\"type\":\"estimation\",\"queueSize\":{json_event.get('queue_size')},\"eta\":{json_event.get('rank_eta')}}}\n\n"
|
223 |
+
elif json_event.get('msg') == 'process_starts':
|
224 |
+
yield f"data: {{\"type\":\"processing\"}}\n\n"
|
225 |
+
elif json_event.get('msg') == 'process_generating':
|
226 |
+
yield f"data: {{\"type\":\"generating\",\"output\":{json_event.get('output')}}}\n\n"
|
227 |
+
elif json_event.get('msg') == 'process_failed':
|
228 |
+
yield f"data: {{\"type\":\"error\",\"message\":\"Generation failed on server\"}}\n\n"
|
229 |
+
return
|
230 |
+
elif json_event.get('msg') == 'process_completed' and json_event.get('output', {}).get('data', [{}])[0].get('url'):
|
231 |
+
image_url = json_event['output']['data'][0]['url']
|
232 |
+
proxies = f"/api/proxy-image?url={image_url}"
|
233 |
+
yield f"data: {{\"type\":\"success\",\"originalUrl\":\"{image_url}\"}}\n\n"
|
234 |
+
return
|
235 |
+
elif json_event.get('msg') == 'process_completed' and json_event.get('output', {}).get('error'):
|
236 |
+
yield f"data: {{\"type\":\"error\",\"message\":\"{json_event['output']['error']}\"}}\n\n"
|
237 |
+
return
|
238 |
+
event_data = ''
|
239 |
+
elif line.startswith(':'):
|
240 |
+
continue
|
241 |
+
elif line != '':
|
242 |
+
event_data += line
|
243 |
+
except Exception as e:
|
244 |
+
yield f"data: {{\"type\":\"error\",\"message\":\"Polling error\",\"details\":\"{str(e)}\"}}\n\n"
|
245 |
+
return
|
246 |
+
yield f"data: {{\"type\":\"error\",\"message\":\"Generation timed out\"}}\n\n"
|
247 |
+
return sse_stream(event_gen())
|
248 |
+
|
249 |
+
# --- Proxy Image Endpoint ---
|
250 |
+
@app.route('/api/proxy-image')
|
251 |
+
def proxy_image():
|
252 |
+
url = request.args.get('url')
|
253 |
+
if not url:
|
254 |
+
return 'Missing url parameter', 400
|
255 |
+
try:
|
256 |
+
resp = requests.get(url, stream=True, timeout=30)
|
257 |
+
if resp.status_code != 200:
|
258 |
+
return 'Failed to fetch image', resp.status_code
|
259 |
+
return Response(resp.raw, content_type=resp.headers.get('content-type', 'image/png'))
|
260 |
+
except Exception as e:
|
261 |
+
return f'Proxy error: {str(e)}', 500
|
262 |
+
|
263 |
+
if __name__ == '__main__':
|
264 |
+
app.run(host='0.0.0.0', port=7860, debug=True)
|
server.js
DELETED
@@ -1,429 +0,0 @@
|
|
1 |
-
import express from 'express';
|
2 |
-
import axios from 'axios';
|
3 |
-
import { Client } from '@gradio/client';
|
4 |
-
import dotenv from 'dotenv';
|
5 |
-
import https from 'https';
|
6 |
-
import http from 'http';
|
7 |
-
|
8 |
-
dotenv.config();
|
9 |
-
|
10 |
-
// --- Hugging Face Space Auto-Restarter ---
|
11 |
-
const HF_TOKEN = process.env.HF_TOKEN;
|
12 |
-
const SPACE_ID = process.env.HF_SPACE_ID || 'shashwatIDR/vision';
|
13 |
-
const HF_API_BASE = 'https://huggingface.co/api';
|
14 |
-
|
15 |
-
async function restartSpace() {
|
16 |
-
console.log(`[🔄 Attempting restart] ${SPACE_ID}`);
|
17 |
-
try {
|
18 |
-
const response = await axios.post(`${HF_API_BASE}/spaces/${SPACE_ID}/restart`, {}, {
|
19 |
-
headers: {
|
20 |
-
Authorization: `Bearer ${HF_TOKEN}`,
|
21 |
-
'Content-Type': 'application/json'
|
22 |
-
},
|
23 |
-
timeout: 30000
|
24 |
-
});
|
25 |
-
console.log(`[✅ Restarted] ${SPACE_ID} at ${new Date().toLocaleString()}`);
|
26 |
-
if (response.data) {
|
27 |
-
console.log('[📊 Response]', response.data);
|
28 |
-
}
|
29 |
-
} catch (err) {
|
30 |
-
if (err.code === 'ENOTFOUND') {
|
31 |
-
console.error('[❌ DNS Error] Cannot resolve huggingface.co - check your internet connection');
|
32 |
-
} else if (err.code === 'ETIMEDOUT') {
|
33 |
-
console.error('[❌ Timeout] Request timed out - try again later');
|
34 |
-
} else if (err.response) {
|
35 |
-
console.error(`[❌ HTTP ${err.response.status}]`, err.response.data || err.response.statusText);
|
36 |
-
if (err.response.status === 401) {
|
37 |
-
console.error('[🔑 Auth Error] Check your Hugging Face token');
|
38 |
-
} else if (err.response.status === 404) {
|
39 |
-
console.error('[🔍 Not Found] Space may not exist or token lacks permissions');
|
40 |
-
}
|
41 |
-
} else {
|
42 |
-
console.error('[❌ Failed to Restart]', err.message);
|
43 |
-
}
|
44 |
-
}
|
45 |
-
}
|
46 |
-
|
47 |
-
async function testHFAPI() {
|
48 |
-
try {
|
49 |
-
console.log('[🔍 Testing Hugging Face API endpoint...]');
|
50 |
-
const response = await axios.get(`${HF_API_BASE}/spaces`, {
|
51 |
-
headers: { Authorization: `Bearer ${HF_TOKEN}` },
|
52 |
-
timeout: 10000,
|
53 |
-
params: { limit: 1 }
|
54 |
-
});
|
55 |
-
console.log('[✅ API Test] Endpoint is working');
|
56 |
-
return true;
|
57 |
-
} catch (err) {
|
58 |
-
console.error('[❌ API Test Failed]', err.response?.status || err.code || err.message);
|
59 |
-
return false;
|
60 |
-
}
|
61 |
-
}
|
62 |
-
|
63 |
-
// On server startup, test API and schedule restarts
|
64 |
-
(async () => {
|
65 |
-
if (!HF_TOKEN) {
|
66 |
-
console.error('[❌ No HF_TOKEN] Hugging Face token not set. Skipping auto-restart scheduling.');
|
67 |
-
return;
|
68 |
-
}
|
69 |
-
console.log('[🚀 Starting] HuggingFace Space Auto-Restarter');
|
70 |
-
const apiWorking = await testHFAPI();
|
71 |
-
if (!apiWorking) {
|
72 |
-
console.log('[⚠️ Warning] API test failed, but proceeding anyway...');
|
73 |
-
}
|
74 |
-
// Only schedule periodic restarts (no immediate restart on startup)
|
75 |
-
setInterval(restartSpace, 5 * 60 * 1000);
|
76 |
-
console.log('[⏰ Scheduled] Space will restart every 5 minutes');
|
77 |
-
})();
|
78 |
-
|
79 |
-
const app = express();
|
80 |
-
const port = process.env.PORT || 3000;
|
81 |
-
app.use(express.json());
|
82 |
-
|
83 |
-
// Serve static files from public
|
84 |
-
app.use(express.static('public'));
|
85 |
-
|
86 |
-
// Serve index.html at root
|
87 |
-
app.get('/', (req, res) => {
|
88 |
-
res.sendFile(__dirname + '/public/index.html');
|
89 |
-
});
|
90 |
-
|
91 |
-
// Image Generation endpoint (Heartsync)
|
92 |
-
app.post('/api/generate', async (req, res) => {
|
93 |
-
res.setHeader('Content-Type', 'text/event-stream');
|
94 |
-
res.setHeader('Cache-Control', 'no-cache');
|
95 |
-
res.setHeader('Connection', 'keep-alive');
|
96 |
-
|
97 |
-
try {
|
98 |
-
const { prompt } = req.body;
|
99 |
-
const sessionHash = Math.random().toString(36).substring(2, 15);
|
100 |
-
|
101 |
-
// First, join the queue
|
102 |
-
const joinResponse = await axios.post('https://api.allorigins.win/raw?url=https://heartsync-nsfw-uncensored.hf.space/gradio_api/queue/join', {
|
103 |
-
data: [
|
104 |
-
prompt,
|
105 |
-
"text, talk bubble, low quality, watermark, signature",
|
106 |
-
0,
|
107 |
-
true,
|
108 |
-
1024,
|
109 |
-
1024,
|
110 |
-
7,
|
111 |
-
28
|
112 |
-
],
|
113 |
-
event_data: null,
|
114 |
-
fn_index: 2,
|
115 |
-
trigger_id: 14,
|
116 |
-
session_hash: sessionHash
|
117 |
-
}, {
|
118 |
-
headers: {
|
119 |
-
'Content-Type': 'application/json',
|
120 |
-
'Referer': 'https://heartsync-nsfw-uncensored.hf.space/'
|
121 |
-
}
|
122 |
-
});
|
123 |
-
|
124 |
-
// Poll for results
|
125 |
-
let attempts = 0;
|
126 |
-
const maxAttempts = 60; // Increased attempts for potentially longer queues
|
127 |
-
|
128 |
-
while (attempts < maxAttempts) {
|
129 |
-
const dataResponse = await axios.get(
|
130 |
-
`https://api.allorigins.win/raw?url=https://heartsync-nsfw-uncensored.hf.space/gradio_api/queue/data?session_hash=${encodeURIComponent(sessionHash)}`,
|
131 |
-
{
|
132 |
-
headers: {
|
133 |
-
'Accept': 'text/event-stream',
|
134 |
-
'Referer': 'https://heartsync-nsfw-uncensored.hf.space/'
|
135 |
-
}
|
136 |
-
}
|
137 |
-
);
|
138 |
-
|
139 |
-
const data = dataResponse.data;
|
140 |
-
|
141 |
-
// Manually parse the event stream data
|
142 |
-
const lines = data.split('\n');
|
143 |
-
let eventData = '';
|
144 |
-
let foundEvent = false;
|
145 |
-
for (const line of lines) {
|
146 |
-
if (line.startsWith('data: ')) {
|
147 |
-
eventData += line.substring(6);
|
148 |
-
} else if (line === '') {
|
149 |
-
// Process eventData when an empty line is encountered
|
150 |
-
if (eventData) {
|
151 |
-
try {
|
152 |
-
const json = JSON.parse(eventData);
|
153 |
-
console.log(`[Image] Parsed event: ${json.msg}`);
|
154 |
-
|
155 |
-
if (json.msg === 'process_completed' && json.output?.data?.[0]?.url) {
|
156 |
-
const imageUrl = json.output.data[0].url;
|
157 |
-
|
158 |
-
// Send the original image URL back to the client as an SSE
|
159 |
-
res.write(`data: ${JSON.stringify({
|
160 |
-
type: 'success',
|
161 |
-
originalUrl: imageUrl
|
162 |
-
})}\n\n`);
|
163 |
-
res.end(); // End the connection after sending the final event
|
164 |
-
return; // Exit the function
|
165 |
-
}
|
166 |
-
|
167 |
-
if (json.msg === 'estimation') {
|
168 |
-
res.write(`data: ${JSON.stringify({ type: 'estimation', queueSize: json.queue_size, eta: json.rank_eta })}\n\n`);
|
169 |
-
foundEvent = true;
|
170 |
-
} else if (json.msg === 'process_starts') {
|
171 |
-
res.write(`data: ${JSON.stringify({ type: 'processing' })}\n\n`);
|
172 |
-
foundEvent = true;
|
173 |
-
} else if (json.msg === 'process_failed') {
|
174 |
-
console.log(`[Image] Process failed:`, json);
|
175 |
-
res.write(`data: ${JSON.stringify({ type: 'error', message: 'Generation failed on server' })}\n\n`);
|
176 |
-
res.end();
|
177 |
-
return;
|
178 |
-
}
|
179 |
-
|
180 |
-
} catch (error) {
|
181 |
-
console.error('[Image] Error parsing event data:', error, 'Raw data:', eventData);
|
182 |
-
}
|
183 |
-
eventData = ''; // Reset for the next event
|
184 |
-
}
|
185 |
-
} else if (line.startsWith(':')) {
|
186 |
-
// Ignore comment lines
|
187 |
-
continue;
|
188 |
-
} else if (line !== '') {
|
189 |
-
// If line is not data or comment, it might be part of a multi-line data chunk, append to eventData
|
190 |
-
eventData += line;
|
191 |
-
}
|
192 |
-
}
|
193 |
-
|
194 |
-
// If loop finishes without 'process_completed', wait and try again
|
195 |
-
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait longer before next poll attempt
|
196 |
-
attempts++;
|
197 |
-
}
|
198 |
-
|
199 |
-
// If the loop times out without success
|
200 |
-
if (!foundEvent) {
|
201 |
-
console.log(`[Image] No events found in response, raw data:`, data.substring(0, 500));
|
202 |
-
}
|
203 |
-
res.status(500).write(`data: ${JSON.stringify({ type: 'error', message: 'Generation timed out' })}\n\n`);
|
204 |
-
res.end();
|
205 |
-
|
206 |
-
} catch (error) {
|
207 |
-
console.error('Generation endpoint error:', error);
|
208 |
-
res.status(500).write(`data: ${JSON.stringify({ type: 'error', message: error.message || 'Failed to generate image' })}\n\n`);
|
209 |
-
res.end();
|
210 |
-
}
|
211 |
-
});
|
212 |
-
|
213 |
-
// Proxy image endpoint
|
214 |
-
app.get('/api/proxy-image', async (req, res) => {
|
215 |
-
const { url } = req.query;
|
216 |
-
if (!url) {
|
217 |
-
return res.status(400).send('Missing url parameter');
|
218 |
-
}
|
219 |
-
try {
|
220 |
-
// Support both http and https
|
221 |
-
const client = url.startsWith('https') ? https : http;
|
222 |
-
client.get(url, (imageRes) => {
|
223 |
-
if (imageRes.statusCode !== 200) {
|
224 |
-
res.status(imageRes.statusCode).send('Failed to fetch image');
|
225 |
-
return;
|
226 |
-
}
|
227 |
-
res.setHeader('Content-Type', imageRes.headers['content-type'] || 'image/png');
|
228 |
-
imageRes.pipe(res);
|
229 |
-
}).on('error', (err) => {
|
230 |
-
res.status(500).send('Proxy error: ' + err.message);
|
231 |
-
});
|
232 |
-
} catch (err) {
|
233 |
-
res.status(500).send('Proxy error: ' + err.message);
|
234 |
-
}
|
235 |
-
});
|
236 |
-
|
237 |
-
// Flux model generation endpoint using UNO-FLUX (bytedance-research-uno-flux.hf.space)
|
238 |
-
app.post('/api/generate/flux', async (req, res) => {
|
239 |
-
res.setHeader('Content-Type', 'text/event-stream');
|
240 |
-
res.setHeader('Cache-Control', 'no-cache');
|
241 |
-
res.setHeader('Connection', 'keep-alive');
|
242 |
-
// No data is stored on the server for this endpoint
|
243 |
-
function randomUserAgent() {
|
244 |
-
// Simple pool of common user agents
|
245 |
-
const agents = [
|
246 |
-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
247 |
-
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
|
248 |
-
'Mozilla/5.0 (Linux; Android 13; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36',
|
249 |
-
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
|
250 |
-
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
|
251 |
-
];
|
252 |
-
return agents[Math.floor(Math.random() * agents.length)];
|
253 |
-
}
|
254 |
-
function randomIP() {
|
255 |
-
return Array(4).fill(0).map(() => Math.floor(Math.random() * 256)).join('.');
|
256 |
-
}
|
257 |
-
try {
|
258 |
-
const {
|
259 |
-
prompt = '',
|
260 |
-
width = 1280,
|
261 |
-
height = 720,
|
262 |
-
guidance = 4,
|
263 |
-
num_steps = 25,
|
264 |
-
seed = -1,
|
265 |
-
image_prompt1 = null,
|
266 |
-
image_prompt2 = null,
|
267 |
-
image_prompt3 = null,
|
268 |
-
image_prompt4 = null,
|
269 |
-
aspect = '16:9'
|
270 |
-
} = req.body;
|
271 |
-
|
272 |
-
if (!prompt) {
|
273 |
-
res.write(`data: ${JSON.stringify({ type: 'error', message: 'Prompt is required' })}\n\n`);
|
274 |
-
res.end();
|
275 |
-
return;
|
276 |
-
}
|
277 |
-
|
278 |
-
// Aspect ratio logic
|
279 |
-
let w = width, h = height;
|
280 |
-
if (aspect === '9:16') {
|
281 |
-
w = 720; h = 1280;
|
282 |
-
} else if (aspect === '1:1') {
|
283 |
-
w = 1024; h = 1024;
|
284 |
-
}
|
285 |
-
|
286 |
-
const sessionHash = Math.random().toString(36).substring(2, 15);
|
287 |
-
const joinPayload = {
|
288 |
-
data: [
|
289 |
-
prompt,
|
290 |
-
w,
|
291 |
-
h,
|
292 |
-
guidance,
|
293 |
-
num_steps,
|
294 |
-
seed,
|
295 |
-
image_prompt1,
|
296 |
-
image_prompt2,
|
297 |
-
image_prompt3,
|
298 |
-
image_prompt4
|
299 |
-
],
|
300 |
-
event_data: null,
|
301 |
-
fn_index: 0,
|
302 |
-
trigger_id: 25,
|
303 |
-
session_hash: sessionHash
|
304 |
-
};
|
305 |
-
|
306 |
-
// Step 1: Join the queue
|
307 |
-
let joinResponse;
|
308 |
-
try {
|
309 |
-
joinResponse = await axios.post(
|
310 |
-
'https://bytedance-research-uno-flux.hf.space/gradio_api/queue/join?__theme=system',
|
311 |
-
joinPayload,
|
312 |
-
{
|
313 |
-
headers: {
|
314 |
-
'Content-Type': 'application/json',
|
315 |
-
'Referer': 'https://bytedance-research-uno-flux.hf.space/',
|
316 |
-
'Origin': 'https://bytedance-research-uno-flux.hf.space',
|
317 |
-
'User-Agent': randomUserAgent(),
|
318 |
-
'X-Forwarded-For': randomIP()
|
319 |
-
},
|
320 |
-
validateStatus: () => true // Accept all status codes
|
321 |
-
}
|
322 |
-
);
|
323 |
-
} catch (err) {
|
324 |
-
res.write(`data: ${JSON.stringify({ type: 'error', message: 'Failed to join UNO-FLUX queue', details: err.message })}\n\n`);
|
325 |
-
res.end();
|
326 |
-
return;
|
327 |
-
}
|
328 |
-
if (!joinResponse || joinResponse.status !== 200) {
|
329 |
-
res.write(`data: ${JSON.stringify({ type: 'error', message: 'Failed to join UNO-FLUX queue', status: joinResponse?.status, data: joinResponse?.data })}\n\n`);
|
330 |
-
res.end();
|
331 |
-
return;
|
332 |
-
}
|
333 |
-
|
334 |
-
// Step 2: Poll for results (Axios does not support true SSE streaming, so we poll every 2s)
|
335 |
-
let attempts = 0;
|
336 |
-
const maxAttempts = 90; // Increased for slow generations
|
337 |
-
let foundEvent = false;
|
338 |
-
while (attempts < maxAttempts) {
|
339 |
-
let dataResponse;
|
340 |
-
try {
|
341 |
-
dataResponse = await axios.get(
|
342 |
-
`https://bytedance-research-uno-flux.hf.space/gradio_api/queue/data?session_hash=${encodeURIComponent(sessionHash)}`,
|
343 |
-
{
|
344 |
-
headers: {
|
345 |
-
'Accept': 'text/event-stream',
|
346 |
-
'Referer': 'https://bytedance-research-uno-flux.hf.space/',
|
347 |
-
'Origin': 'https://bytedance-research-uno-flux.hf.space',
|
348 |
-
'User-Agent': randomUserAgent(),
|
349 |
-
'X-Forwarded-For': randomIP()
|
350 |
-
},
|
351 |
-
validateStatus: () => true // Accept all status codes
|
352 |
-
}
|
353 |
-
);
|
354 |
-
} catch (pollErr) {
|
355 |
-
res.write(`data: ${JSON.stringify({ type: 'error', message: 'Polling error', details: pollErr.message })}\n\n`);
|
356 |
-
res.end();
|
357 |
-
return;
|
358 |
-
}
|
359 |
-
if (!dataResponse || dataResponse.status !== 200) {
|
360 |
-
res.write(`data: ${JSON.stringify({ type: 'error', message: 'Polling failed', status: dataResponse?.status, data: dataResponse?.data })}\n\n`);
|
361 |
-
res.end();
|
362 |
-
return;
|
363 |
-
}
|
364 |
-
const data = dataResponse.data;
|
365 |
-
// Manually parse the event stream data
|
366 |
-
const lines = data.split('\n');
|
367 |
-
let eventData = '';
|
368 |
-
for (const line of lines) {
|
369 |
-
if (line.startsWith('data: ')) {
|
370 |
-
eventData += line.substring(6);
|
371 |
-
} else if (line === '') {
|
372 |
-
if (eventData) {
|
373 |
-
try {
|
374 |
-
const json = JSON.parse(eventData);
|
375 |
-
console.log('[UNO-FLUX Event]', json); // Log every event
|
376 |
-
if (json.msg === 'estimation') {
|
377 |
-
res.write(`data: ${JSON.stringify({ type: 'estimation', queueSize: json.queue_size, eta: json.rank_eta })}\n\n`);
|
378 |
-
foundEvent = true;
|
379 |
-
} else if (json.msg === 'process_starts') {
|
380 |
-
res.write(`data: ${JSON.stringify({ type: 'processing' })}\n\n`);
|
381 |
-
foundEvent = true;
|
382 |
-
} else if (json.msg === 'process_generating') {
|
383 |
-
// Forward process_generating events to client for progress feedback
|
384 |
-
res.write(`data: ${JSON.stringify({ type: 'generating', output: json.output })}\n\n`);
|
385 |
-
foundEvent = true;
|
386 |
-
} else if (json.msg === 'process_failed') {
|
387 |
-
res.write(`data: ${JSON.stringify({ type: 'error', message: 'Generation failed on server' })}\n\n`);
|
388 |
-
res.end();
|
389 |
-
return;
|
390 |
-
} else if (json.msg === 'process_completed' && json.output?.data?.[0]?.url) {
|
391 |
-
const imageUrl = json.output.data[0].url;
|
392 |
-
// Build proxy URL
|
393 |
-
const base = req.protocol + '://' + req.get('host');
|
394 |
-
const proxies = base + '/api/proxy-image?url=' + encodeURIComponent(imageUrl);
|
395 |
-
res.write(`data: ${JSON.stringify({ type: 'success', imageUrl, proxies })}\n\n`);
|
396 |
-
res.end();
|
397 |
-
return;
|
398 |
-
} else if (json.msg === 'process_completed' && json.output?.error) {
|
399 |
-
res.write(`data: ${JSON.stringify({ type: 'error', message: json.output.error })}\n\n`);
|
400 |
-
res.end();
|
401 |
-
return;
|
402 |
-
}
|
403 |
-
} catch (error) {
|
404 |
-
console.error('[UNO-FLUX] Error parsing event data:', error, 'Raw data:', eventData);
|
405 |
-
}
|
406 |
-
eventData = '';
|
407 |
-
}
|
408 |
-
} else if (line.startsWith(':')) {
|
409 |
-
continue;
|
410 |
-
} else if (line !== '') {
|
411 |
-
eventData += line;
|
412 |
-
}
|
413 |
-
}
|
414 |
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
415 |
-
attempts++;
|
416 |
-
}
|
417 |
-
if (!foundEvent) {
|
418 |
-
res.write(`data: ${JSON.stringify({ type: 'error', message: 'Generation timed out' })}\n\n`);
|
419 |
-
}
|
420 |
-
res.end();
|
421 |
-
} catch (err) {
|
422 |
-
res.write(`data: ${JSON.stringify({ type: 'error', message: err.message })}\n\n`);
|
423 |
-
res.end();
|
424 |
-
}
|
425 |
-
});
|
426 |
-
|
427 |
-
app.listen(port, () => {
|
428 |
-
console.log(`Server running on port ${port}`);
|
429 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|