CORVO-AI commited on
Commit
121e4b0
·
verified ·
1 Parent(s): 07307b3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +159 -445
app.py CHANGED
@@ -1,471 +1,185 @@
1
- from flask import Flask, request, jsonify
 
2
  import requests
3
- import random
4
- import string
5
- import time
6
- import json
7
 
8
  app = Flask(__name__)
9
 
10
- # Global variables to store workspace and bot IDs
11
- GLOBAL_WORKSPACE_ID = None
12
- GLOBAL_BOT_ID = None
13
-
14
-
15
- # Authorization value used in requests (should be updated with a valid Authorization),
16
- TOKEN = "Bearer bp_pat_vTuxol25N0ymBpYaWqtWpFfGPKt260IfT784"
17
- # -------------------------------------------------------------------
18
- # Helper functions for random bot/workspace names
19
- # -------------------------------------------------------------------
20
- def generate_random_name(length=5):
21
- """Generate a random name for workspace or bot"""
22
- return ''.join(random.choices(string.ascii_letters, k=length))
23
-
24
- # -------------------------------------------------------------------
25
- # Functions to create/delete workspaces and bots
26
- # -------------------------------------------------------------------
27
- def create_workspace():
28
- """Create a new workspace and return its ID"""
29
- ws_url = "https://api.botpress.cloud/v1/admin/workspaces"
30
- headers = {
31
- "User-Agent": "Mozilla/5.0",
32
- "Authorization": TOKEN
33
- }
34
- payload = {"name": generate_random_name()}
35
-
36
- try:
37
- response = requests.post(ws_url, headers=headers, json=payload)
38
- if response.status_code == 200:
39
- response_json = response.json()
40
- workspace_id = response_json.get('id')
41
- print(f"Successfully created workspace: {workspace_id}")
42
- return workspace_id
43
- else:
44
- print(f"Workspace creation failed with: {response.status_code}, {response.text}")
45
- return None
46
- except Exception as e:
47
- print(f"Error creating workspace: {str(e)}")
48
- return None
49
-
50
-
51
- def create_bot(workspace_id):
52
- """Create a new bot in the specified workspace and return its ID"""
53
- if not workspace_id:
54
- print("Cannot create bot: No workspace ID provided")
55
- return None
56
-
57
- bot_url = "https://api.botpress.cloud/v1/admin/bots"
58
- headers = {
59
- "User-Agent": "Mozilla/5.0",
60
- "x-workspace-id": workspace_id,
61
- "Authorization": TOKEN,
62
- "Content-Type": "application/json"
63
- }
64
- payload = {"name": generate_random_name()}
65
-
66
- try:
67
- response = requests.post(bot_url, headers=headers, json=payload)
68
- if response.status_code == 200:
69
- response_json = response.json()
70
- bot_id = response_json.get("bot", {}).get("id")
71
- if not bot_id:
72
- print("Bot ID not found in the response.")
73
- return None
74
-
75
- print(f"Successfully created bot: {bot_id} in workspace: {workspace_id}")
76
-
77
- # Install integration for the new bot
78
- integration_success = install_bot_integration(bot_id, workspace_id)
79
- if integration_success:
80
- print(f"Successfully installed integration for bot {bot_id}")
81
- return bot_id
82
- else:
83
- print(f"Failed to install integration for bot {bot_id}")
84
- return bot_id # Still return the bot ID even if integration fails
85
- else:
86
- print(f"Bot creation failed with: {response.status_code}, {response.text}")
87
- return None
88
- except Exception as e:
89
- print(f"Error creating bot: {str(e)}")
90
- return None
91
-
92
-
93
- def install_bot_integration(bot_id, workspace_id):
94
- """Install required integration for the bot to function properly"""
95
- if not bot_id or not workspace_id:
96
- print("Cannot install integration: Missing bot ID or workspace ID")
97
- return False
98
-
99
- url = f"https://api.botpress.cloud/v1/admin/bots/{bot_id}"
100
- headers = {
101
- "User-Agent": "Mozilla/5.0",
102
- "Authorization": TOKEN,
103
- "Content-Type": "application/json",
104
- "x-bot-id": bot_id,
105
- "x-workspace-id": workspace_id
106
- }
107
- # Integration payload
108
- payload = {
109
- "integrations": {
110
- "intver_01K235NKSVEGFZAJ7E0MT2N647": {
111
- "enabled": True
112
- }
113
  }
114
- }
115
-
116
- try:
117
- response = requests.put(url, headers=headers, json=payload)
118
- if response.status_code == 200:
119
- print(f"Successfully installed integration for bot {bot_id}")
120
- return True
121
- else:
122
- print(f"Failed to install integration: {response.status_code}, {response.text}")
123
- return False
124
- except Exception as e:
125
- print(f"Error installing integration: {str(e)}")
126
- return False
127
-
128
-
129
- def try_delete_bot(bot_id, workspace_id):
130
- """Attempt to delete a bot from the specified workspace but continue if it fails"""
131
- if not bot_id or not workspace_id:
132
- print("Cannot delete bot: Missing bot ID or workspace ID")
133
- return False
134
 
135
- url = f"https://api.botpress.cloud/v1/admin/bots/{bot_id}"
136
- headers = {
137
- "User-Agent": "Mozilla/5.0",
138
- "x-workspace-id": workspace_id,
139
- "Authorization": TOKEN
 
 
 
 
 
 
 
 
140
  }
141
 
142
- try:
143
- response = requests.delete(url, headers=headers)
144
- if response.status_code in [200, 204]:
145
- print(f"Successfully deleted bot: {bot_id}")
146
- return True
147
- else:
148
- print(f"Failed to delete bot: {response.status_code}, {response.text}")
149
- return False
150
- except Exception as e:
151
- print(f"Error deleting bot: {str(e)}")
152
- return False
153
-
154
-
155
- def try_delete_workspace(workspace_id):
156
- """Attempt to delete a workspace but continue if it fails"""
157
- if not workspace_id:
158
- print("Cannot delete workspace: No workspace ID provided")
159
- return False
160
-
161
- url = f"https://api.botpress.cloud/v1/admin/workspaces/{workspace_id}"
162
- headers = {
163
- "User-Agent": "Mozilla/5.0",
164
- "Authorization": TOKEN
165
  }
166
-
167
- try:
168
- response = requests.delete(url, headers=headers)
169
- if response.status_code in [200, 204]:
170
- print(f"Successfully deleted workspace: {workspace_id}")
171
- return True
172
- else:
173
- print(f"Failed to delete workspace: {response.status_code}, {response.text}")
174
- return False
175
- except Exception as e:
176
- print(f"Error deleting workspace: {str(e)}")
177
- return False
178
-
179
-
180
- # -------------------------------------------------------------------
181
- # Main function that calls the Botpress API endpoint
182
- # -------------------------------------------------------------------
183
- def chat_with_assistant(user_input, chat_history, bot_id, workspace_id, temperature=0.9, top_p=0.95, max_tokens=None):
184
  """
185
- Sends the user input and chat history to the Botpress API endpoint,
186
- returns the assistant's response and (possibly updated) bot/workspace IDs.
187
- """
188
- # Prepare the headers
189
- headers = {
190
- "User-Agent": "Mozilla/5.0",
191
- "x-bot-id": bot_id,
192
- "Content-Type": "application/json",
193
- "Authorization": TOKEN
 
 
 
 
 
 
 
 
 
194
  }
 
 
195
 
196
- # Process chat history into the format expected by the API
197
- messages = []
198
- system_prompt = ""
199
-
200
- for msg in chat_history:
201
- if msg["role"] == "system":
202
- system_prompt = msg["content"]
203
- elif msg["role"] in ["user", "assistant"]:
204
- # Pass multipart messages directly without modifying their structure
205
- if "type" in msg and msg["type"] == "multipart" and "content" in msg:
206
- messages.append(msg) # Keep the original multipart structure
207
- # Handle regular text messages
208
- else:
209
- messages.append({
210
- "role": msg["role"],
211
- "content": msg["content"]
212
- })
213
 
214
- # Add the latest user input if not already in chat history
215
- if user_input and isinstance(user_input, str) and (not messages or messages[-1]["role"] != "user" or messages[-1]["content"] != user_input):
216
- messages.append({
217
- "role": "user",
218
- "content": user_input
219
- })
220
 
221
- # Prepare the payload for the API
222
  payload = {
223
- "type": "openai:generateContent",
224
- "input": {
225
- "model": {
226
- "id": "gpt-4.1-2025-04-14"
227
- },
228
- "systemPrompt": system_prompt,
229
- "messages": messages,
230
- "temperature": temperature,
231
- "debug": False,
232
- }
233
  }
234
 
235
- # Add maxTokens to the payload if provided
236
- if max_tokens is not None:
237
- payload["input"]["maxTokens"] = max_tokens
238
-
239
- botpress_url = "https://api.botpress.cloud/v1/chat/actions"
240
- max_retries = 3
241
- timeout = 120 # Increased timeout for long messages
242
-
243
- # For debugging
244
- print("Payload being sent to Botpress:")
245
- print(json.dumps(payload, indent=2))
246
-
247
- # Attempt to send the request
248
- for attempt in range(max_retries):
249
- try:
250
- print(f"Attempt {attempt+1}: Sending request to Botpress API with bot_id={bot_id}, workspace_id={workspace_id}")
251
- response = requests.post(botpress_url, json=payload, headers=headers, timeout=timeout)
252
-
253
- # If successful (200)
254
- if response.status_code == 200:
255
- data = response.json()
256
- assistant_content = data.get('output', {}).get('choices', [{}])[0].get('content', '')
257
- print(f"Successfully received response from Botpress API")
258
- return assistant_content, bot_id, workspace_id
259
-
260
- # Check for authentication or permission errors (401, 403)
261
- elif response.status_code in [401, 403]:
262
- error_message = "Authentication error"
263
- try:
264
- error_data = response.json()
265
- error_message = error_data.get('message', 'Authentication error')
266
- except:
267
- pass
268
-
269
- print(f"Authentication error detected: {error_message}")
270
-
271
- # We need to create new resources immediately
272
- print("Creating new workspace and bot...")
273
- new_workspace_id = create_workspace()
274
- if not new_workspace_id:
275
- print("Failed to create a new workspace")
276
- if attempt < max_retries - 1:
277
- time.sleep(3)
278
- continue
279
- else:
280
- return "Unable to create new resources. Please try again later.", bot_id, workspace_id
281
-
282
- new_bot_id = create_bot(new_workspace_id)
283
- if not new_bot_id:
284
- print("Failed to create a new bot")
285
- if attempt < max_retries - 1:
286
- time.sleep(3)
287
- continue
288
- else:
289
- return "Unable to create new bot. Please try again later.", new_workspace_id, workspace_id
290
-
291
- print(f"Created new workspace: {new_workspace_id} and bot: {new_bot_id}")
292
-
293
- # Try again with new IDs
294
- headers["x-bot-id"] = new_bot_id
295
- try:
296
- print(f"Retrying with new bot_id={new_bot_id}")
297
- retry_response = requests.post(botpress_url, json=payload, headers=headers, timeout=timeout)
298
-
299
- if retry_response.status_code == 200:
300
- data = retry_response.json()
301
- assistant_content = data.get('output', {}).get('choices', [{}])[0].get('content', '')
302
- print(f"Successfully received response with new IDs")
303
-
304
- # Try to clean up old resources in the background, but don't wait for result
305
- if bot_id and workspace_id:
306
- print(f"Attempting to clean up old resources in the background")
307
- try_delete_bot(bot_id, workspace_id)
308
- try_delete_workspace(workspace_id)
309
-
310
- return assistant_content, new_bot_id, new_workspace_id
311
- else:
312
- print(f"Failed with new IDs: {retry_response.status_code}")
313
- if attempt < max_retries - 1:
314
- time.sleep(2)
315
- continue
316
- else:
317
- return f"Unable to get a response even with new credentials.", new_bot_id, new_workspace_id
318
-
319
- except Exception as e:
320
- print(f"Error with new IDs: {str(e)}")
321
- if attempt < max_retries - 1:
322
- time.sleep(2)
323
- continue
324
- else:
325
- return f"Error with new credentials: {str(e)}", new_bot_id, new_workspace_id
326
-
327
- # Handle network errors or timeouts (just retry)
328
- elif response.status_code in [404, 408, 502, 503, 504]:
329
- print(f"Received error {response.status_code}. Retrying...")
330
- time.sleep(3) # Wait before retrying
331
- continue
332
-
333
- # Any other error status code
334
- else:
335
- print(f"Received unexpected error: {response.status_code}, {response.text}")
336
- if attempt < max_retries - 1:
337
- time.sleep(2)
338
- continue
339
- else:
340
- return f"Unable to get a response from the assistant (Error {response.status_code}).", bot_id, workspace_id
341
-
342
- except requests.exceptions.Timeout:
343
- print(f"Request timed out. Retrying...")
344
- if attempt < max_retries - 1:
345
- time.sleep(2)
346
- continue
347
- else:
348
- return "The assistant is taking too long to respond. Please try again with a shorter message.", bot_id, workspace_id
349
-
350
- except Exception as e:
351
- print(f"Error during request: {str(e)}")
352
- if attempt < max_retries - 1:
353
- time.sleep(2)
354
- continue
355
- else:
356
- return f"Unable to get a response from the assistant: {str(e)}", bot_id, workspace_id
357
-
358
- # Should not reach here due to the handling in the loop
359
- return "Unable to get a response from the assistant.", bot_id, workspace_id
360
-
361
-
362
- # -------------------------------------------------------------------
363
- # Flask Endpoint
364
- # -------------------------------------------------------------------
365
- @app.route("/chat", methods=["POST"])
366
- def chat_endpoint():
367
- """
368
- Expects JSON with:
369
- {
370
- "user_input": "string", // Can be null if multipart message is in chat_history
371
- "chat_history": [
372
- {"role": "system", "content": "..."},
373
- {"role": "user", "content": "..."},
374
- // Or for images:
375
- {"role": "user", "type": "multipart", "content": [
376
- {"type": "image", "url": "https://example.com/image.jpg"},
377
- {"type": "text", "text": "What's in this image?"}
378
- ]},
379
- ...
380
- ],
381
- "temperature": 0.9, // Optional, defaults to 0.9
382
- "top_p": 0.95, // Optional, defaults to 0.95
383
- "max_tokens": 1000 // Optional, defaults to null (no limit)
384
- }
385
- Returns JSON with:
386
- {
387
- "assistant_response": "string"
388
- }
389
- """
390
- global GLOBAL_WORKSPACE_ID, GLOBAL_BOT_ID
391
-
392
- # Parse JSON from request
393
- data = request.get_json(force=True)
394
- user_input = data.get("user_input", "")
395
- chat_history = data.get("chat_history", [])
396
-
397
- # Get temperature, top_p, and max_tokens from request, or use defaults
398
- temperature = data.get("temperature", 0.9)
399
- top_p = data.get("top_p", 0.95)
400
- max_tokens = data.get("max_tokens", None)
401
-
402
- # Validate temperature and top_p values
403
- try:
404
- temperature = float(temperature)
405
- if not 0 <= temperature <= 2:
406
- temperature = 0.9
407
- print(f"Invalid temperature value. Using default: {temperature}")
408
- except (ValueError, TypeError):
409
- temperature = 0.9
410
- print(f"Invalid temperature format. Using default: {temperature}")
411
-
412
  try:
413
- top_p = float(top_p)
414
- if not 0 <= top_p <= 1:
415
- top_p = 0.95
416
- print(f"Invalid top_p value. Using default: {top_p}")
417
- except (ValueError, TypeError):
418
- top_p = 0.95
419
- print(f"Invalid top_p format. Using default: {top_p}")
420
 
421
- # Validate max_tokens if provided
422
- if max_tokens is not None:
423
- try:
424
- max_tokens = int(max_tokens)
425
- if max_tokens <= 0:
426
- print("Invalid max_tokens value (must be positive). Not using max_tokens.")
427
- max_tokens = None
428
- except (ValueError, TypeError):
429
- print("Invalid max_tokens format. Not using max_tokens.")
430
- max_tokens = None
431
 
432
- # If we don't yet have a workspace or bot, create them
433
- if not GLOBAL_WORKSPACE_ID or not GLOBAL_BOT_ID:
434
- print("No existing IDs found. Creating new workspace and bot...")
435
- GLOBAL_WORKSPACE_ID = create_workspace()
436
- if GLOBAL_WORKSPACE_ID:
437
- GLOBAL_BOT_ID = create_bot(GLOBAL_WORKSPACE_ID)
438
-
439
- # If creation failed
440
- if not GLOBAL_WORKSPACE_ID or not GLOBAL_BOT_ID:
441
- return jsonify({"assistant_response": "I'm currently unavailable. Please try again later."}), 500
442
-
443
- # Call our function that interacts with Botpress API
444
- print(f"Sending chat request with existing bot_id={GLOBAL_BOT_ID}, workspace_id={GLOBAL_WORKSPACE_ID}")
445
- print(f"Using temperature={temperature}, top_p={top_p}, max_tokens={max_tokens}")
446
-
447
- assistant_response, updated_bot_id, updated_workspace_id = chat_with_assistant(
448
- user_input,
449
- chat_history,
450
- GLOBAL_BOT_ID,
451
- GLOBAL_WORKSPACE_ID,
452
- temperature,
453
- top_p,
454
- max_tokens
455
- )
456
 
457
- # Update global IDs if they changed
458
- if updated_bot_id != GLOBAL_BOT_ID or updated_workspace_id != GLOBAL_WORKSPACE_ID:
459
- print(f"Updating global IDs: bot_id={updated_bot_id}, workspace_id={updated_workspace_id}")
460
- GLOBAL_BOT_ID = updated_bot_id
461
- GLOBAL_WORKSPACE_ID = updated_workspace_id
462
 
463
- return jsonify({"assistant_response": assistant_response})
 
 
 
464
 
 
 
465
 
466
- # -------------------------------------------------------------------
467
- # Run the Flask app
468
- # -------------------------------------------------------------------
 
 
 
469
 
470
  if __name__ == "__main__":
471
- app.run(host="0.0.0.0", port=7860, debug=True)
 
 
 
 
1
+ from flask import Flask, request, jsonify, render_template, url_for
2
+ from urllib.parse import urlencode
3
  import requests
4
+ import os
 
 
 
5
 
6
  app = Flask(__name__)
7
 
8
+ # External screenshot service config
9
+ SCREENSHOT_API = "https://corvo-ai-xx-sc.hf.space/capture"
10
+
11
+ # Allowed intervals and mapping to TradingView "interval" code
12
+ INTERVAL_MAP = {
13
+ # minutes
14
+ "1m": "1",
15
+ "3m": "3",
16
+ "5m": "5",
17
+ "5min": "5",
18
+ "15m": "15",
19
+ "15min": "15",
20
+ "30m": "30",
21
+ "30min": "30",
22
+ # hours
23
+ "1h": "60",
24
+ "2h": "120",
25
+ "4h": "240",
26
+ # days/weeks/months
27
+ "1d": "D",
28
+ "1D": "D",
29
+ "d": "D",
30
+ "D": "D",
31
+ "1w": "W",
32
+ "1W": "W",
33
+ "w": "W",
34
+ "W": "W",
35
+ "1mth": "M",
36
+ "1M": "M",
37
+ "m": "M",
38
+ "M": "M"
39
+ }
40
+
41
+ def normalize_interval(raw_interval: str) -> str:
42
+ if not raw_interval:
43
+ return "D"
44
+ key = raw_interval.strip()
45
+ return INTERVAL_MAP.get(key, key)
46
+
47
+ def parse_indicators_param(indicators_raw):
48
+ # Accept indicators as:
49
+ # - JSON array in body or query (list)
50
+ # - Comma-separated string "PUB;id1,PUB;id2"
51
+ # Returns a list of indicator IDs
52
+ if indicators_raw is None:
53
+ return []
54
+ if isinstance(indicators_raw, list):
55
+ return [str(x).strip() for x in indicators_raw if str(x).strip()]
56
+ if isinstance(indicators_raw, str):
57
+ parts = [p.strip() for p in indicators_raw.split(",") if p.strip()]
58
+ return parts
59
+ return []
60
+
61
+ @app.route("/")
62
+ def index():
63
+ return jsonify({
64
+ "message": "Chart Screenshot API",
65
+ "endpoints": {
66
+ "chart_html": "/chart?symbol=BTCUSDT&exchange=BINANCE&interval=1h&indicators=PUB;abc,PUB;def",
67
+ "screenshot_api": "/api/screenshot"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  }
69
+ })
70
+
71
+ @app.route("/chart")
72
+ def chart():
73
+ # This endpoint renders the TradingView chart using query params:
74
+ # symbol, exchange, interval, indicators (comma separated), theme (optional)
75
+ symbol = request.args.get("symbol", "BTCUSDT")
76
+ exchange = request.args.get("exchange", "BINANCE")
77
+ interval = normalize_interval(request.args.get("interval", "1D"))
78
+ indicators = parse_indicators_param(request.args.get("indicators"))
79
+ theme = request.args.get("theme", "dark")
80
+
81
+ return render_template(
82
+ "chart.html",
83
+ symbol=symbol,
84
+ exchange=exchange,
85
+ interval=interval,
86
+ indicators=indicators,
87
+ theme=theme
88
+ )
89
 
90
+ @app.route("/api/screenshot", methods=["POST"])
91
+ def api_screenshot():
92
+ """
93
+ Request JSON:
94
+ {
95
+ "symbol": "BTCUSDT", // required
96
+ "exchange": "BINANCE", // optional, default BINANCE
97
+ "interval": "1h", // optional, default 1D
98
+ "indicators": ["PUB;id1","PUB;id2"], // optional, or comma-separated string
99
+ "theme": "dark", // optional, default "dark"
100
+ "width": 1080, // optional
101
+ "height": 1920, // optional
102
+ "fullPage": false // optional
103
  }
104
 
105
+ Response JSON:
106
+ {
107
+ "imageUrl": "https://...png",
108
+ "htmlUrl": "https://...html",
109
+ "screenshotMeta": {...}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  """
112
+ data = request.get_json(silent=True) or {}
113
+
114
+ symbol = data.get("symbol", None)
115
+ if not symbol:
116
+ return jsonify({"error": "symbol is required"}), 400
117
+
118
+ exchange = data.get("exchange", "BINANCE")
119
+ interval = normalize_interval(data.get("interval", "1D"))
120
+ indicators = parse_indicators_param(data.get("indicators"))
121
+ theme = data.get("theme", "dark")
122
+
123
+ # Build the chart URL hosted by this same app
124
+ # indicators are passed as comma-separated values
125
+ query = {
126
+ "symbol": symbol,
127
+ "exchange": exchange,
128
+ "interval": interval,
129
+ "theme": theme
130
  }
131
+ if indicators:
132
+ query["indicators"] = ",".join(indicators)
133
 
134
+ # Absolute URL to /chart so the screenshot service can access it
135
+ chart_url = request.url_root.rstrip("/") + url_for("chart") + "?" + urlencode(query)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
+ # Build screenshot payload
138
+ width = data.get("width", 1080)
139
+ height = data.get("height", 1920)
140
+ full_page = data.get("fullPage", False)
 
 
141
 
 
142
  payload = {
143
+ "urls": [chart_url],
144
+ "width": width,
145
+ "height": height,
146
+ "fullPage": bool(full_page)
 
 
 
 
 
 
147
  }
148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  try:
150
+ resp = requests.post(SCREENSHOT_API, json=payload, timeout=60)
151
+ except requests.RequestException as e:
152
+ return jsonify({"error": "Failed to reach screenshot service", "details": str(e)}), 502
 
 
 
 
153
 
154
+ if resp.status_code != 200:
155
+ return jsonify({"error": "Screenshot service error", "status": resp.status_code, "body": resp.text}), 502
 
 
 
 
 
 
 
 
156
 
157
+ body = resp.json()
158
+ errors = body.get("errors") or []
159
+ if errors:
160
+ return jsonify({"error": "Screenshot service reported errors", "details": errors}), 502
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
+ results = body.get("results") or []
163
+ if not results:
164
+ return jsonify({"error": "No results from screenshot service"}), 502
 
 
165
 
166
+ first = results[0]
167
+ output = first.get("output") or {}
168
+ image_url = output.get("imageUrl")
169
+ html_url = output.get("htmlUrl")
170
 
171
+ if not image_url:
172
+ return jsonify({"error": "Screenshot result missing imageUrl", "raw": first}), 502
173
 
174
+ # Return only the URLs (what you asked for), plus meta for debugging
175
+ return jsonify({
176
+ "imageUrl": image_url,
177
+ "htmlUrl": html_url,
178
+ "screenshotMeta": first.get("meta", {})
179
+ })
180
 
181
  if __name__ == "__main__":
182
+ # Host and port can be configured via env
183
+ host = os.getenv("HOST", "0.0.0.0")
184
+ port = int(os.getenv("PORT", "7860"))
185
+ app.run(host=host, port=port, debug=True)