azdxit commited on
Commit
3e31bd5
·
1 Parent(s): 3cdc13c

Add image generation endpoints using Hugging Face API

Browse files

Integrates new image generation endpoints into `app_local.py` using the Hugging Face Inference API, including synchronous, asynchronous, and base64 options. Updates health check status and version number.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: a662ebb5-fd71-4dd7-ad81-6f1890051700
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: b03946f7-415b-473e-96d1-92cc1cee45b5
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/d9f57912-08a1-48b9-ad13-f36ce06579fd/a662ebb5-fd71-4dd7-ad81-6f1890051700/fWRWvD9

Files changed (1) hide show
  1. app_local.py +127 -2
app_local.py CHANGED
@@ -4,12 +4,100 @@ This version uses simple image processing instead of AI models.
4
  For full AI processing, deploy to Hugging Face Spaces.
5
  """
6
  import io
 
7
  import uuid
 
 
8
  from pathlib import Path
9
  from http.server import HTTPServer, SimpleHTTPRequestHandler
10
  import json
11
  import urllib.parse
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  UPLOAD_DIR = Path("uploads")
14
  OUTPUT_DIR = Path("outputs")
15
  UPLOAD_DIR.mkdir(exist_ok=True)
@@ -27,8 +115,8 @@ class APIHandler(SimpleHTTPRequestHandler):
27
  elif path == "/health":
28
  self.send_json({
29
  "status": "healthy",
30
- "version": "2.0.0 (preview)",
31
- "features": ["enhance", "remove-background", "denoise"]
32
  })
33
  elif path == "/model-info":
34
  self.send_json({
@@ -46,6 +134,13 @@ class APIHandler(SimpleHTTPRequestHandler):
46
  "noise_reduction": {
47
  "name": "Non-Local Means Denoising",
48
  "description": "Advanced noise reduction algorithm"
 
 
 
 
 
 
 
49
  }
50
  },
51
  "supported_formats": ["png", "jpg", "jpeg", "webp", "bmp"]
@@ -54,6 +149,32 @@ class APIHandler(SimpleHTTPRequestHandler):
54
  self.serve_openapi()
55
  elif path.startswith("/outputs/"):
56
  self.serve_file(path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  else:
58
  super().do_GET()
59
 
@@ -68,6 +189,10 @@ class APIHandler(SimpleHTTPRequestHandler):
68
  self.handle_remove_background(path, query)
69
  elif path == "/denoise" or path == "/denoise/base64":
70
  self.handle_denoise(path, query)
 
 
 
 
71
  else:
72
  self.send_error(404, "Not Found")
73
 
 
4
  For full AI processing, deploy to Hugging Face Spaces.
5
  """
6
  import io
7
+ import os
8
  import uuid
9
+ import threading
10
+ import base64
11
  from pathlib import Path
12
  from http.server import HTTPServer, SimpleHTTPRequestHandler
13
  import json
14
  import urllib.parse
15
 
16
+ try:
17
+ import httpx
18
+ HAS_HTTPX = True
19
+ except ImportError:
20
+ import urllib.request
21
+ HAS_HTTPX = False
22
+
23
+ HF_API_URL = "https://api-inference.huggingface.co/models/black-forest-labs/FLUX.1-schnell"
24
+
25
+ jobs = {}
26
+
27
+ def get_hf_token():
28
+ """Get Hugging Face API token from environment."""
29
+ token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_TOKEN")
30
+ if not token:
31
+ return None
32
+ return token
33
+
34
+ def generate_image_from_hf(prompt: str, width: int = 1024, height: int = 1024) -> bytes:
35
+ """Generate image using Hugging Face Inference API with FLUX.1-schnell model."""
36
+ token = get_hf_token()
37
+ if not token:
38
+ raise Exception("Hugging Face API token not configured. Please set HF_TOKEN secret.")
39
+
40
+ headers = {
41
+ "Authorization": f"Bearer {token}",
42
+ "Content-Type": "application/json"
43
+ }
44
+ payload = {
45
+ "inputs": prompt,
46
+ "parameters": {
47
+ "width": width,
48
+ "height": height,
49
+ "num_inference_steps": 4
50
+ }
51
+ }
52
+
53
+ if HAS_HTTPX:
54
+ with httpx.Client(timeout=120.0) as client:
55
+ response = client.post(HF_API_URL, headers=headers, json=payload)
56
+
57
+ if response.status_code == 503:
58
+ error_data = response.json()
59
+ if "estimated_time" in error_data:
60
+ raise Exception(f"Model is loading. Estimated time: {error_data['estimated_time']:.0f}s. Please retry shortly.")
61
+
62
+ if response.status_code != 200:
63
+ try:
64
+ error_detail = response.json()
65
+ except:
66
+ error_detail = response.text
67
+ raise Exception(f"Hugging Face API error: {error_detail}")
68
+
69
+ return response.content
70
+ else:
71
+ req = urllib.request.Request(HF_API_URL, data=json.dumps(payload).encode(), headers=headers, method='POST')
72
+ try:
73
+ with urllib.request.urlopen(req, timeout=120) as response:
74
+ return response.read()
75
+ except urllib.error.HTTPError as e:
76
+ raise Exception(f"Hugging Face API error: {e.read().decode()}")
77
+
78
+ def process_generate_image_job(job_id: str, prompt: str, width: int, height: int, output_path: Path):
79
+ """Background task for image generation."""
80
+ try:
81
+ jobs[job_id] = {"status": "processing", "progress": 20.0, "message": "Sending prompt to FLUX.1-schnell..."}
82
+
83
+ image_bytes = generate_image_from_hf(prompt, width, height)
84
+
85
+ jobs[job_id] = {"status": "processing", "progress": 80.0, "message": "Saving image..."}
86
+
87
+ from PIL import Image
88
+ generated_image = Image.open(io.BytesIO(image_bytes))
89
+ generated_image.save(output_path, "PNG")
90
+
91
+ jobs[job_id] = {
92
+ "status": "completed",
93
+ "progress": 100.0,
94
+ "message": f"Image generated: {generated_image.width}x{generated_image.height}",
95
+ "result": str(output_path)
96
+ }
97
+
98
+ except Exception as e:
99
+ jobs[job_id] = {"status": "failed", "progress": 0, "error": str(e)}
100
+
101
  UPLOAD_DIR = Path("uploads")
102
  OUTPUT_DIR = Path("outputs")
103
  UPLOAD_DIR.mkdir(exist_ok=True)
 
115
  elif path == "/health":
116
  self.send_json({
117
  "status": "healthy",
118
+ "version": "2.2.0 (preview)",
119
+ "features": ["enhance", "remove-background", "denoise", "generate-image"]
120
  })
121
  elif path == "/model-info":
122
  self.send_json({
 
134
  "noise_reduction": {
135
  "name": "Non-Local Means Denoising",
136
  "description": "Advanced noise reduction algorithm"
137
+ },
138
+ "image_generation": {
139
+ "name": "FLUX.1-schnell",
140
+ "description": "Fast, high-quality text-to-image generation by Black Forest Labs",
141
+ "max_resolution": "1440x1440",
142
+ "default_resolution": "1024x1024",
143
+ "source": "https://huggingface.co/black-forest-labs/FLUX.1-schnell"
144
  }
145
  },
146
  "supported_formats": ["png", "jpg", "jpeg", "webp", "bmp"]
 
149
  self.serve_openapi()
150
  elif path.startswith("/outputs/"):
151
  self.serve_file(path)
152
+ elif path.startswith("/progress/"):
153
+ job_id = path.split("/progress/")[1]
154
+ if job_id in jobs:
155
+ self.send_json(jobs[job_id])
156
+ else:
157
+ self.send_error(404, "Job not found")
158
+ elif path.startswith("/result/"):
159
+ job_id = path.split("/result/")[1]
160
+ if job_id in jobs:
161
+ job = jobs[job_id]
162
+ if job.get("status") == "completed" and job.get("result"):
163
+ result_path = Path(job["result"])
164
+ if result_path.exists():
165
+ self.send_response(200)
166
+ self.send_header('Content-Type', 'image/png')
167
+ self.send_header('Content-Disposition', f'attachment; filename="{result_path.name}"')
168
+ self.end_headers()
169
+ self.wfile.write(result_path.read_bytes())
170
+ else:
171
+ self.send_error(404, "Result file not found")
172
+ elif job.get("status") == "failed":
173
+ self.send_error(500, job.get("error", "Job failed"))
174
+ else:
175
+ self.send_json({"status": job.get("status"), "progress": job.get("progress"), "message": "Job still processing"})
176
+ else:
177
+ self.send_error(404, "Job not found")
178
  else:
179
  super().do_GET()
180
 
 
189
  self.handle_remove_background(path, query)
190
  elif path == "/denoise" or path == "/denoise/base64":
191
  self.handle_denoise(path, query)
192
+ elif path == "/generate-image" or path == "/generate-image/base64":
193
+ self.handle_generate_image(path, query)
194
+ elif path == "/generate-image/async":
195
+ self.handle_generate_image_async(query)
196
  else:
197
  self.send_error(404, "Not Found")
198