shashwatIDR commited on
Commit
4584969
·
verified ·
1 Parent(s): e144790

Rename server.js to app.py

Browse files
Files changed (2) hide show
  1. app.py +264 -0
  2. server.js +0 -429
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
- });