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

Add image generation capabilities to the application

Browse files

Introduce new API endpoints for synchronous and asynchronous image generation, integrate image generation options into the frontend UI, and update OpenAPI specifications.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: a662ebb5-fd71-4dd7-ad81-6f1890051700
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: efbf4b19-3deb-44d5-b4b1-00474abad1e9
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 (3) hide show
  1. .replit +0 -4
  2. app_local.py +148 -2
  3. templates/index.html +93 -11
.replit CHANGED
@@ -34,10 +34,6 @@ outputType = "webview"
34
  localPort = 5000
35
  externalPort = 80
36
 
37
- [[ports]]
38
- localPort = 38543
39
- externalPort = 3003
40
-
41
  [[ports]]
42
  localPort = 38887
43
  externalPort = 3000
 
34
  localPort = 5000
35
  externalPort = 80
36
 
 
 
 
 
37
  [[ports]]
38
  localPort = 38887
39
  externalPort = 3000
app_local.py CHANGED
@@ -382,6 +382,99 @@ class APIHandler(SimpleHTTPRequestHandler):
382
  except Exception as e:
383
  self.send_error(500, f"Error processing image: {str(e)}")
384
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  def serve_html(self):
386
  html_path = Path("templates/index.html")
387
  if html_path.exists():
@@ -430,8 +523,8 @@ class APIHandler(SimpleHTTPRequestHandler):
430
  "openapi": "3.1.0",
431
  "info": {
432
  "title": "AI Image Processing API",
433
- "description": "Comprehensive AI-powered image processing API.\n\n**Features:**\n- Image enhancement and upscaling (Real-ESRGAN)\n- Background removal (BiRefNet)\n- Noise reduction (OpenCV NLM)\n\n**Note:** This is a preview deployment. Deploy to Hugging Face Spaces for full AI processing.",
434
- "version": "2.0.0"
435
  },
436
  "servers": [{"url": "/", "description": "Current server"}],
437
  "paths": {
@@ -462,6 +555,59 @@ class APIHandler(SimpleHTTPRequestHandler):
462
  "responses": {"200": {"description": "Denoised image"}}
463
  }
464
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465
  "/health": {"get": {"summary": "Health check", "responses": {"200": {"description": "API status"}}}},
466
  "/model-info": {"get": {"summary": "Model information", "responses": {"200": {"description": "Model details"}}}}
467
  }
 
382
  except Exception as e:
383
  self.send_error(500, f"Error processing image: {str(e)}")
384
 
385
+ def handle_generate_image(self, path, query):
386
+ """Handle synchronous image generation."""
387
+ try:
388
+ prompt = query.get('prompt', [''])[0]
389
+ if not prompt:
390
+ self.send_error(400, "Missing 'prompt' parameter")
391
+ return
392
+
393
+ width = int(query.get('width', [1024])[0])
394
+ height = int(query.get('height', [1024])[0])
395
+ width = max(256, min(1440, width))
396
+ height = max(256, min(1440, height))
397
+ async_mode = query.get('async_mode', ['false'])[0].lower() == 'true'
398
+
399
+ if async_mode:
400
+ self.handle_generate_image_async(query)
401
+ return
402
+
403
+ from PIL import Image
404
+ image_bytes = generate_image_from_hf(prompt, width, height)
405
+ generated_image = Image.open(io.BytesIO(image_bytes))
406
+
407
+ file_id = str(uuid.uuid4())
408
+ output_path = OUTPUT_DIR / f"{file_id}_generated.png"
409
+ generated_image.save(output_path, "PNG")
410
+
411
+ if "/base64" in path:
412
+ buffer = io.BytesIO()
413
+ generated_image.save(buffer, format="PNG")
414
+ buffer.seek(0)
415
+ img_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
416
+
417
+ self.send_json({
418
+ "success": True,
419
+ "image_base64": img_base64,
420
+ "size": {"width": generated_image.width, "height": generated_image.height},
421
+ "model": "FLUX.1-schnell",
422
+ "prompt": prompt
423
+ })
424
+ else:
425
+ self.send_response(200)
426
+ self.send_header('Content-Type', 'image/png')
427
+ self.send_header('Content-Disposition', f'attachment; filename="generated_{file_id[:8]}.png"')
428
+ self.end_headers()
429
+ with open(output_path, 'rb') as f:
430
+ self.wfile.write(f.read())
431
+
432
+ except Exception as e:
433
+ self.send_error(500, f"Error generating image: {str(e)}")
434
+
435
+ def handle_generate_image_async(self, query):
436
+ """Handle async image generation with progress tracking."""
437
+ try:
438
+ prompt = query.get('prompt', [''])[0]
439
+ if not prompt:
440
+ self.send_error(400, "Missing 'prompt' parameter")
441
+ return
442
+
443
+ width = int(query.get('width', [1024])[0])
444
+ height = int(query.get('height', [1024])[0])
445
+ width = max(256, min(1440, width))
446
+ height = max(256, min(1440, height))
447
+
448
+ token = get_hf_token()
449
+ if not token:
450
+ self.send_error(500, "Hugging Face API token not configured. Please set HF_TOKEN secret.")
451
+ return
452
+
453
+ job_id = str(uuid.uuid4())
454
+ file_id = str(uuid.uuid4())
455
+ output_path = OUTPUT_DIR / f"{file_id}_generated.png"
456
+
457
+ jobs[job_id] = {"status": "pending", "progress": 0, "message": "Starting image generation..."}
458
+
459
+ thread = threading.Thread(
460
+ target=process_generate_image_job,
461
+ args=(job_id, prompt, width, height, output_path)
462
+ )
463
+ thread.start()
464
+
465
+ self.send_json({
466
+ "job_id": job_id,
467
+ "status": "processing",
468
+ "message": "Image generation started. Poll /progress/{job_id} for updates.",
469
+ "progress_url": f"/progress/{job_id}",
470
+ "result_url": f"/result/{job_id}",
471
+ "model": "FLUX.1-schnell",
472
+ "prompt": prompt
473
+ })
474
+
475
+ except Exception as e:
476
+ self.send_error(500, f"Error starting image generation: {str(e)}")
477
+
478
  def serve_html(self):
479
  html_path = Path("templates/index.html")
480
  if html_path.exists():
 
523
  "openapi": "3.1.0",
524
  "info": {
525
  "title": "AI Image Processing API",
526
+ "description": "Comprehensive AI-powered image processing API.\n\n**Features:**\n- Image enhancement and upscaling (Real-ESRGAN)\n- Background removal (BiRefNet)\n- Noise reduction (OpenCV NLM)\n- Image generation from text (FLUX.1-schnell)\n\n**Note:** This is a preview deployment. Deploy to Hugging Face Spaces for full AI processing.",
527
+ "version": "2.2.0"
528
  },
529
  "servers": [{"url": "/", "description": "Current server"}],
530
  "paths": {
 
555
  "responses": {"200": {"description": "Denoised image"}}
556
  }
557
  },
558
+ "/generate-image": {
559
+ "post": {
560
+ "summary": "Generate image from text",
561
+ "description": "Generate an image from a text prompt using FLUX.1-schnell AI model.",
562
+ "parameters": [
563
+ {"name": "prompt", "in": "query", "required": True, "schema": {"type": "string"}, "description": "Text prompt describing the image to generate"},
564
+ {"name": "width", "in": "query", "schema": {"type": "integer", "default": 1024, "minimum": 256, "maximum": 1440}},
565
+ {"name": "height", "in": "query", "schema": {"type": "integer", "default": 1024, "minimum": 256, "maximum": 1440}},
566
+ {"name": "async_mode", "in": "query", "schema": {"type": "boolean", "default": False}, "description": "Use async mode with progress tracking"}
567
+ ],
568
+ "responses": {"200": {"description": "Generated image"}}
569
+ }
570
+ },
571
+ "/generate-image/async": {
572
+ "post": {
573
+ "summary": "Generate image (async)",
574
+ "description": "Start async image generation with progress tracking using FLUX.1-schnell.",
575
+ "parameters": [
576
+ {"name": "prompt", "in": "query", "required": True, "schema": {"type": "string"}, "description": "Text prompt describing the image to generate"},
577
+ {"name": "width", "in": "query", "schema": {"type": "integer", "default": 1024, "minimum": 256, "maximum": 1440}},
578
+ {"name": "height", "in": "query", "schema": {"type": "integer", "default": 1024, "minimum": 256, "maximum": 1440}}
579
+ ],
580
+ "responses": {"200": {"description": "Job ID for tracking progress"}}
581
+ }
582
+ },
583
+ "/generate-image/base64": {
584
+ "post": {
585
+ "summary": "Generate image (base64)",
586
+ "description": "Generate an image and return as base64-encoded string.",
587
+ "parameters": [
588
+ {"name": "prompt", "in": "query", "required": True, "schema": {"type": "string"}, "description": "Text prompt describing the image to generate"},
589
+ {"name": "width", "in": "query", "schema": {"type": "integer", "default": 1024, "minimum": 256, "maximum": 1440}},
590
+ {"name": "height", "in": "query", "schema": {"type": "integer", "default": 1024, "minimum": 256, "maximum": 1440}}
591
+ ],
592
+ "responses": {"200": {"description": "Base64 encoded image"}}
593
+ }
594
+ },
595
+ "/progress/{job_id}": {
596
+ "get": {
597
+ "summary": "Get job progress",
598
+ "description": "Get the progress of an async image processing job.",
599
+ "parameters": [{"name": "job_id", "in": "path", "required": True, "schema": {"type": "string"}}],
600
+ "responses": {"200": {"description": "Job progress"}}
601
+ }
602
+ },
603
+ "/result/{job_id}": {
604
+ "get": {
605
+ "summary": "Get job result",
606
+ "description": "Get the result of a completed async job.",
607
+ "parameters": [{"name": "job_id", "in": "path", "required": True, "schema": {"type": "string"}}],
608
+ "responses": {"200": {"description": "Processed image"}}
609
+ }
610
+ },
611
  "/health": {"get": {"summary": "Health check", "responses": {"200": {"description": "API status"}}}},
612
  "/model-info": {"get": {"summary": "Model information", "responses": {"200": {"description": "Model details"}}}}
613
  }
templates/index.html CHANGED
@@ -361,7 +361,7 @@
361
  <div class="container">
362
  <header>
363
  <h1>AI Image Processing</h1>
364
- <p class="subtitle">Enhance, remove backgrounds, denoise, and scan documents with AI</p>
365
  <div class="api-link">
366
  <a href="/docs" target="_blank">View API Documentation</a>
367
  </div>
@@ -373,6 +373,7 @@
373
  <button class="feature-tab" data-feature="remove-bg">Remove Background</button>
374
  <button class="feature-tab" data-feature="denoise">Denoise</button>
375
  <button class="feature-tab" data-feature="docscan">Doc Scan</button>
 
376
  </div>
377
 
378
  <div class="drop-zone" id="dropZone">
@@ -451,6 +452,40 @@
451
  </p>
452
  </div>
453
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
454
  <button class="process-btn" id="processBtn" disabled>Process Image</button>
455
 
456
  <div class="error" id="error"></div>
@@ -502,6 +537,10 @@
502
  <h4>Document Scanning</h4>
503
  <p>Auto-crop, align, and enhance documents with AI</p>
504
  </div>
 
 
 
 
505
  <div class="info-item">
506
  <h4>API Access</h4>
507
  <p>Full RESTful API with Swagger documentation at /docs</p>
@@ -544,12 +583,20 @@
544
 
545
  if (currentFeature === 'enhance') {
546
  document.getElementById('enhanceOptions').classList.add('active');
 
547
  } else if (currentFeature === 'remove-bg') {
548
  document.getElementById('removeBgOptions').classList.add('active');
 
549
  } else if (currentFeature === 'denoise') {
550
  document.getElementById('denoiseOptions').classList.add('active');
 
551
  } else if (currentFeature === 'docscan') {
552
  document.getElementById('docscanOptions').classList.add('active');
 
 
 
 
 
553
  }
554
 
555
  updateButtonText();
@@ -565,7 +612,8 @@
565
  'enhance': 'Enhance Image',
566
  'remove-bg': 'Remove Background',
567
  'denoise': 'Denoise Image',
568
- 'docscan': 'Scan Document'
 
569
  };
570
  processBtn.textContent = texts[currentFeature] || 'Process Image';
571
  }
@@ -688,10 +736,22 @@
688
  }
689
 
690
  processBtn.addEventListener('click', async () => {
691
- if (!selectedFile) return;
 
 
 
 
 
 
 
 
 
 
692
 
693
  const formData = new FormData();
694
- formData.append('file', selectedFile);
 
 
695
 
696
  let endpoint = '/enhance/async';
697
  let params = new URLSearchParams();
@@ -726,12 +786,27 @@
726
  params.append('enhance_hd', enhanceHd);
727
  loadingText.textContent = 'Scanning and enhancing document...';
728
  resultLabel.textContent = 'Scanned Document';
 
 
 
 
 
 
 
 
 
 
 
729
  }
730
 
731
  if (currentFeature !== 'remove-bg') {
732
  resultBox.classList.remove('checkerboard');
733
  }
734
 
 
 
 
 
735
  loading.classList.add('show');
736
  results.classList.remove('show');
737
  processBtn.disabled = true;
@@ -739,10 +814,15 @@
739
  resetProgress();
740
 
741
  try {
742
- const response = await fetch(`${endpoint}?${params.toString()}`, {
743
- method: 'POST',
744
- body: formData
745
- });
 
 
 
 
 
746
 
747
  if (!response.ok) {
748
  const errorData = await response.json();
@@ -773,10 +853,12 @@
773
  'enhance': 'enhanced',
774
  'remove-bg': 'nobg',
775
  'denoise': 'denoised',
776
- 'docscan': 'scanned'
 
777
  };
778
  const filename = filenames[currentFeature] || 'processed';
779
- downloadBtn.download = `${filename}_${selectedFile.name.split('.')[0]}.png`;
 
780
 
781
  loading.classList.remove('show');
782
  results.classList.add('show');
@@ -786,7 +868,7 @@
786
  loading.classList.remove('show');
787
  }
788
 
789
- processBtn.disabled = false;
790
  });
791
 
792
  function showError(message) {
 
361
  <div class="container">
362
  <header>
363
  <h1>AI Image Processing</h1>
364
+ <p class="subtitle">Enhance, remove backgrounds, denoise, scan documents, and generate images with AI</p>
365
  <div class="api-link">
366
  <a href="/docs" target="_blank">View API Documentation</a>
367
  </div>
 
373
  <button class="feature-tab" data-feature="remove-bg">Remove Background</button>
374
  <button class="feature-tab" data-feature="denoise">Denoise</button>
375
  <button class="feature-tab" data-feature="docscan">Doc Scan</button>
376
+ <button class="feature-tab" data-feature="generate">Generate Image</button>
377
  </div>
378
 
379
  <div class="drop-zone" id="dropZone">
 
452
  </p>
453
  </div>
454
 
455
+ <div id="generateOptions" class="feature-options">
456
+ <div class="options">
457
+ <div class="option-group" style="flex: 2;">
458
+ <label for="prompt">Image Prompt</label>
459
+ <input type="text" id="prompt" placeholder="A beautiful sunset over mountains, detailed, 8k quality">
460
+ </div>
461
+ </div>
462
+ <div class="options" style="margin-top: 10px;">
463
+ <div class="option-group">
464
+ <label for="genWidth">Width</label>
465
+ <select id="genWidth">
466
+ <option value="512">512px</option>
467
+ <option value="768">768px</option>
468
+ <option value="1024" selected>1024px</option>
469
+ <option value="1280">1280px</option>
470
+ <option value="1440">1440px</option>
471
+ </select>
472
+ </div>
473
+ <div class="option-group">
474
+ <label for="genHeight">Height</label>
475
+ <select id="genHeight">
476
+ <option value="512">512px</option>
477
+ <option value="768">768px</option>
478
+ <option value="1024" selected>1024px</option>
479
+ <option value="1280">1280px</option>
480
+ <option value="1440">1440px</option>
481
+ </select>
482
+ </div>
483
+ </div>
484
+ <p style="color: #888; font-size: 0.85rem; margin-top: 10px;">
485
+ Generate images from text using FLUX.1-schnell by Black Forest Labs. No image upload needed.
486
+ </p>
487
+ </div>
488
+
489
  <button class="process-btn" id="processBtn" disabled>Process Image</button>
490
 
491
  <div class="error" id="error"></div>
 
537
  <h4>Document Scanning</h4>
538
  <p>Auto-crop, align, and enhance documents with AI</p>
539
  </div>
540
+ <div class="info-item">
541
+ <h4>Image Generation</h4>
542
+ <p>Generate images from text prompts using FLUX.1-schnell</p>
543
+ </div>
544
  <div class="info-item">
545
  <h4>API Access</h4>
546
  <p>Full RESTful API with Swagger documentation at /docs</p>
 
583
 
584
  if (currentFeature === 'enhance') {
585
  document.getElementById('enhanceOptions').classList.add('active');
586
+ dropZone.style.display = 'block';
587
  } else if (currentFeature === 'remove-bg') {
588
  document.getElementById('removeBgOptions').classList.add('active');
589
+ dropZone.style.display = 'block';
590
  } else if (currentFeature === 'denoise') {
591
  document.getElementById('denoiseOptions').classList.add('active');
592
+ dropZone.style.display = 'block';
593
  } else if (currentFeature === 'docscan') {
594
  document.getElementById('docscanOptions').classList.add('active');
595
+ dropZone.style.display = 'block';
596
+ } else if (currentFeature === 'generate') {
597
+ document.getElementById('generateOptions').classList.add('active');
598
+ dropZone.style.display = 'none';
599
+ processBtn.disabled = false;
600
  }
601
 
602
  updateButtonText();
 
612
  'enhance': 'Enhance Image',
613
  'remove-bg': 'Remove Background',
614
  'denoise': 'Denoise Image',
615
+ 'docscan': 'Scan Document',
616
+ 'generate': 'Generate Image'
617
  };
618
  processBtn.textContent = texts[currentFeature] || 'Process Image';
619
  }
 
736
  }
737
 
738
  processBtn.addEventListener('click', async () => {
739
+ const isGenerate = currentFeature === 'generate';
740
+
741
+ if (!isGenerate && !selectedFile) return;
742
+
743
+ if (isGenerate) {
744
+ const prompt = document.getElementById('prompt').value.trim();
745
+ if (!prompt) {
746
+ showError('Please enter a prompt to generate an image');
747
+ return;
748
+ }
749
+ }
750
 
751
  const formData = new FormData();
752
+ if (!isGenerate) {
753
+ formData.append('file', selectedFile);
754
+ }
755
 
756
  let endpoint = '/enhance/async';
757
  let params = new URLSearchParams();
 
786
  params.append('enhance_hd', enhanceHd);
787
  loadingText.textContent = 'Scanning and enhancing document...';
788
  resultLabel.textContent = 'Scanned Document';
789
+ } else if (currentFeature === 'generate') {
790
+ endpoint = '/generate-image/async';
791
+ const prompt = document.getElementById('prompt').value.trim();
792
+ const width = document.getElementById('genWidth').value;
793
+ const height = document.getElementById('genHeight').value;
794
+ params.append('prompt', prompt);
795
+ params.append('width', width);
796
+ params.append('height', height);
797
+ loadingText.textContent = 'Generating image with FLUX.1-schnell...';
798
+ resultLabel.textContent = 'Generated';
799
+ document.querySelector('.image-box:first-child').style.display = 'none';
800
  }
801
 
802
  if (currentFeature !== 'remove-bg') {
803
  resultBox.classList.remove('checkerboard');
804
  }
805
 
806
+ if (currentFeature !== 'generate') {
807
+ document.querySelector('.image-box:first-child').style.display = 'block';
808
+ }
809
+
810
  loading.classList.add('show');
811
  results.classList.remove('show');
812
  processBtn.disabled = true;
 
814
  resetProgress();
815
 
816
  try {
817
+ const fetchOptions = {
818
+ method: 'POST'
819
+ };
820
+
821
+ if (!isGenerate) {
822
+ fetchOptions.body = formData;
823
+ }
824
+
825
+ const response = await fetch(`${endpoint}?${params.toString()}`, fetchOptions);
826
 
827
  if (!response.ok) {
828
  const errorData = await response.json();
 
853
  'enhance': 'enhanced',
854
  'remove-bg': 'nobg',
855
  'denoise': 'denoised',
856
+ 'docscan': 'scanned',
857
+ 'generate': 'generated'
858
  };
859
  const filename = filenames[currentFeature] || 'processed';
860
+ const baseName = isGenerate ? 'ai_image' : selectedFile.name.split('.')[0];
861
+ downloadBtn.download = `${filename}_${baseName}.png`;
862
 
863
  loading.classList.remove('show');
864
  results.classList.add('show');
 
868
  loading.classList.remove('show');
869
  }
870
 
871
+ processBtn.disabled = currentFeature !== 'generate';
872
  });
873
 
874
  function showError(message) {