Spaces:
Sleeping
Sleeping
| from flask import Flask, request, Response, jsonify, stream_with_context | |
| from flask_cors import CORS | |
| import os, json, httpx | |
| import config | |
| import base64 | |
| from io import BytesIO | |
| import io | |
| from PIL import Image | |
| import requests | |
| app = Flask(__name__) | |
| CORS(app) | |
| OPENROUTER_API_KEY = os.getenv("API_KEYS") | |
| def health(): | |
| return jsonify({"status": "ready"}), 200 | |
| def _headers(): | |
| return { | |
| "Authorization": f"Bearer {OPENROUTER_API_KEY}", | |
| "Content-Type": "application/json", | |
| "HTTP-Referer": "https://YOURSPACE.hf.space", | |
| "X-Title": "Minimal Streaming Test", | |
| } | |
| def upload_image_to_imgbb(image_bytes, filename): | |
| """Upload image to imgbb with 5 minute expiration and return public URL""" | |
| IMGBB_API_KEY = "22f355dd0069ef8742dc94f0c80e4c4d" | |
| try: | |
| # Convert bytes to base64 for imgbb upload | |
| base64_image = base64.b64encode(image_bytes).decode('utf-8') | |
| response = requests.post( | |
| "https://api.imgbb.com/1/upload", | |
| data={ | |
| "key": IMGBB_API_KEY, | |
| "image": base64_image, | |
| "name": filename, | |
| "expiration": 300 # 300 seconds = 5 minutes | |
| }, | |
| timeout=10 | |
| ) | |
| if response.status_code == 200: | |
| return response.json()['data']['url'] | |
| else: | |
| print(f"ImgBB error: {response.text}") | |
| return None | |
| except Exception as e: | |
| print(f"Upload error: {e}") | |
| return None | |
| # Fixed _payload: For images, use data[1] directly as the public URL (no prefixing with app URL, since it's now hosted on imgbb). | |
| def _payload(q: str): | |
| data=q.split("%") | |
| content="" | |
| system="" | |
| if data[0]=="Text": | |
| content=data[1] | |
| else: | |
| # For images, content must be an array with text + image | |
| content=[ | |
| { | |
| "type": "text", | |
| "text": "Analyze image and get question" | |
| }, | |
| { | |
| "type": "image_url", | |
| "image_url": { | |
| "url": data[1] # Direct public URL from imgbb | |
| } | |
| } | |
| ] | |
| info=config.config | |
| system=info[data[2]][data[3]] | |
| return { | |
| "model": "meta-llama/llama-4-maverick-17b-128e-instruct", | |
| "stream": True, | |
| "messages": [{"role":"system","content":system},{"role": "user", "content": content}], | |
| "temperature": 0.7, | |
| "top_p":0.9, | |
| "max_tokens": 999, | |
| } | |
| def stream_openrouter(q: str): | |
| if not OPENROUTER_API_KEY: | |
| yield "data: Error: Missing API key\n\n" | |
| return | |
| try: | |
| with httpx.Client(timeout=30.0) as client: | |
| with client.stream("POST", | |
| "https://api.groq.com/openai/v1/chat/completions", | |
| headers=_headers(), | |
| json=_payload(q)) as response: | |
| response.raise_for_status() | |
| for line in response.iter_lines(): | |
| if line.startswith('data: '): | |
| data = line[6:] # Remove 'data: ' prefix | |
| if data == '[DONE]': | |
| break | |
| try: | |
| json_data = json.loads(data) | |
| if 'choices' in json_data: | |
| content = json_data['choices'][0].get('delta', {}).get('content') | |
| print(content) | |
| if content: | |
| yield f"data: {json.dumps(content)}\n\n" | |
| except json.JSONDecodeError: | |
| continue | |
| yield "data: [DONE]\n\n" | |
| except httpx.HTTPError as e: | |
| yield f"data: Error: {str(e)}\n\n" | |
| except Exception as e: | |
| yield f"data: Unexpected error: {str(e)}\n\n" | |
| def ask(): | |
| payload = request.get_json(silent=True) or {} | |
| q = payload.get("question", "").strip() | |
| lang = payload.get("selectedLanguage", "").strip() | |
| mode = payload.get("selectedMode", "").strip() | |
| prompt="" | |
| q2lang="" | |
| if not q: | |
| q = "Say hello in one short sentence." | |
| prompt="Text"+"%"+q+"%"+mode+"%"+lang | |
| print(prompt) | |
| return Response( | |
| stream_with_context(stream_openrouter(prompt)), | |
| mimetype="text/event-stream", | |
| headers={ | |
| "Cache-Control": "no-cache", | |
| "X-Accel-Buffering": "no" | |
| } | |
| ) | |
| # Fixed /askimage endpoint: | |
| # - Use upload_image_to_imgbb to get a public URL instead of saving to local disk (which isn't served statically). | |
| # - Removed invalid 'if not image_url:' check (path is always truthy). | |
| # - Removed local file save. | |
| # - Updated prompt construction to use the public URL directly. | |
| def askimage(): | |
| # Get form data | |
| image_file = request.files.get('image') | |
| lang = request.form.get('selectedLanguage', '').strip() | |
| mode = request.form.get('selectedMode', '').strip() | |
| if not image_file: | |
| return jsonify({"error": "No image provided"}), 400 | |
| try: | |
| # Read image bytes | |
| image_bytes = image_file.read() | |
| # Load image from bytes | |
| img = Image.open(io.BytesIO(image_bytes)) | |
| # Resize if larger than 1024x1024 to reduce size (adjust as needed) | |
| max_size = 1024 | |
| if img.size[0] > max_size or img.size[1] > max_size: | |
| img.thumbnail((max_size, max_size), Image.Resampling.LANCZOS) | |
| # Save compressed version (quality 85 for balance of speed/size) | |
| output = io.BytesIO() | |
| img.save(output, format='JPEG', quality=85, optimize=True) | |
| compressed_bytes = output.getvalue() | |
| # Use compressed bytes for upload | |
| image_url = upload_image_to_imgbb(compressed_bytes, image_file.filename) | |
| if not image_url: | |
| return jsonify({"error": "Failed to upload image to host"}), 500 | |
| # Build prompt with public URL | |
| prompt = "Image"+"%"+image_url+"%"+mode+"%"+lang | |
| print(f"Image URL: {image_url}") | |
| return Response( | |
| stream_with_context(stream_openrouter(prompt)), | |
| mimetype="text/event-stream", | |
| headers={ | |
| "Cache-Control": "no-cache", | |
| "X-Accel-Buffering": "no" | |
| } | |
| ) | |
| except Exception as e: | |
| return jsonify({"error": str(e)}), 500 | |
| if __name__ == "__main__": | |
| app.run(host="0.0.0.0", port=7860, debug=False) |