yukee1992 commited on
Commit
f48d445
Β·
verified Β·
1 Parent(s): da3ded3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +93 -498
app.py CHANGED
@@ -161,7 +161,7 @@ class MemoryStatusResponse(BaseModel):
161
  gpu_memory_cached_mb: Optional[float] = None
162
  status: str
163
 
164
- # HIGH-QUALITY MODEL SELECTION - ANIME FOCUSED & WORKING
165
  MODEL_CHOICES = {
166
  "dreamshaper-8": "lykon/dreamshaper-8",
167
  "realistic-vision": "SG161222/Realistic_Vision_V5.1",
@@ -182,7 +182,7 @@ current_pipe = None
182
  model_lock = threading.Lock()
183
 
184
  # =============================================
185
- # FIXED: Infographic Layout Generator with Responsive Design
186
  # =============================================
187
  class InfographicLayoutGenerator:
188
  def __init__(self):
@@ -194,15 +194,14 @@ class InfographicLayoutGenerator:
194
 
195
  def get_font(self, size, bold=False):
196
  """Enhanced font loading with extensive Chinese font support"""
197
- # Comprehensive list of fonts that support Chinese
198
  font_paths = [
199
  # Windows fonts
200
- "C:/Windows/Fonts/msyh.ttc", # Microsoft YaHei
201
- "C:/Windows/Fonts/msyhbd.ttc", # Microsoft YaHei Bold
202
- "C:/Windows/Fonts/simhei.ttf", # SimHei
203
- "C:/Windows/Fonts/simsun.ttc", # SimSun
204
- "C:/Windows/Fonts/STSONG.TTF", # STSong
205
- "C:/Windows/Fonts/STKAITI.TTF", # STKaiti
206
  # Linux fonts
207
  "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
208
  "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
@@ -231,7 +230,6 @@ class InfographicLayoutGenerator:
231
  ]
232
  font_paths = bold_fonts + font_paths
233
 
234
- # Try each font
235
  for font_path in font_paths:
236
  try:
237
  if os.path.exists(font_path):
@@ -239,7 +237,6 @@ class InfographicLayoutGenerator:
239
  except:
240
  continue
241
 
242
- # Ultimate fallback - use default font but try to set encoding
243
  try:
244
  return ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", size)
245
  except:
@@ -248,46 +245,45 @@ class InfographicLayoutGenerator:
248
  def smart_remove_background(self, image):
249
  """Intelligently remove background while keeping the main subject"""
250
  try:
251
- # Convert to RGBA
252
  if image.mode != 'RGBA':
253
  image = image.convert('RGBA')
254
 
255
  data = image.getdata()
256
  new_data = []
257
 
258
- # Get image dimensions
259
  width, height = image.size
260
 
261
- # Sample edges to determine background color
262
  edge_samples = []
263
- # Top edge
264
  for x in range(0, width, 10):
265
- edge_samples.append(data[x])
266
- # Bottom edge
267
  for x in range(0, width, 10):
268
- edge_samples.append(data[(height-1) * width + x])
269
- # Left edge
 
270
  for y in range(0, height, 10):
271
- edge_samples.append(data[y * width])
272
- # Right edge
 
273
  for y in range(0, height, 10):
274
- edge_samples.append(data[y * width + (width-1)])
 
 
 
 
 
275
 
276
- # Calculate average background color from edges
277
  avg_r = sum(s[0] for s in edge_samples) // len(edge_samples)
278
  avg_g = sum(s[1] for s in edge_samples) // len(edge_samples)
279
  avg_b = sum(s[2] for s in edge_samples) // len(edge_samples)
280
 
281
- # Threshold for background detection
282
  threshold = 60
283
 
284
  for item in data:
285
- # Calculate color distance from average background
286
  distance = ((item[0] - avg_r) ** 2 +
287
  (item[1] - avg_g) ** 2 +
288
  (item[2] - avg_b) ** 2) ** 0.5
289
 
290
- # If pixel is close to background color, make transparent
291
  if distance < threshold:
292
  new_data.append((255, 255, 255, 0))
293
  else:
@@ -302,33 +298,24 @@ class InfographicLayoutGenerator:
302
 
303
  def create_responsive_layout(self, num_items):
304
  """Calculate responsive layout based on number of items"""
305
- # 9:16 dimensions
306
  width = 900
307
  height = 1600
308
 
309
- # Responsive sizing
310
  if num_items <= 3:
311
- # Few items - larger icons
312
  icon_size = 180
313
  columns = 1
314
  rows = num_items
315
- vertical_spacing = 100
316
  elif num_items <= 6:
317
- # Medium items - medium icons
318
  icon_size = 150
319
  columns = 2
320
  rows = (num_items + 1) // 2
321
- vertical_spacing = 80
322
  else:
323
- # Many items - smaller icons
324
  icon_size = 120
325
  columns = 2
326
  rows = (num_items + 1) // 2
327
- vertical_spacing = 60
328
 
329
- # Calculate positions
330
  positions = []
331
- available_height = height - 300 # Leave space for title and footer
332
  start_y = 200
333
 
334
  for i in range(num_items):
@@ -343,7 +330,6 @@ class InfographicLayoutGenerator:
343
  else:
344
  x = 3 * width // 4 - icon_size // 2
345
 
346
- # Distribute rows evenly
347
  if rows > 1:
348
  y = start_y + (row * (available_height // rows))
349
  else:
@@ -354,59 +340,41 @@ class InfographicLayoutGenerator:
354
  return positions, icon_size, columns
355
 
356
  def create_food_infographic(self, title, items_with_prompts, generated_images, background_image=None):
357
- """
358
- Create a responsive 9:16 infographic with proper Chinese text
359
- """
360
- # 9:16 dimensions
361
  width = 900
362
  height = 1600
363
 
364
- # Create clean background
365
  if background_image:
366
- # Resize background to fit
367
  bg = background_image.resize((width, height), Image.Resampling.LANCZOS)
368
  canvas = bg.convert('RGB')
369
  else:
370
- # Clean white background
371
  canvas = Image.new('RGB', (width, height), (255, 255, 255))
372
-
373
- # Add very subtle gray gradient at edges only
374
- draw = ImageDraw.Draw(canvas)
375
  for y in range(height):
376
  if y < 20 or y > height-20:
377
- opacity = 10
378
- draw.line([(0, y), (width, y)], fill=(240, 240, 240))
379
- else:
380
- canvas = Image.new('RGB', (width, height), (255, 255, 255))
381
 
382
  draw = ImageDraw.Draw(canvas)
383
-
384
  num_items = len(items_with_prompts)
385
-
386
- # Get responsive layout
387
  positions, icon_size, columns = self.create_responsive_layout(num_items)
388
 
389
- # Draw title with Chinese support
390
  title_font = self.get_font(48, bold=True)
391
-
392
- # Title with very light background
393
  title_bbox = draw.textbbox((0, 0), title, font=title_font)
394
  title_width = title_bbox[2] - title_bbox[0]
395
  title_x = (width - title_width) // 2
396
  title_y = 60
397
 
398
- # Very light gray background for title (NO RED)
399
  draw.rounded_rectangle(
400
  [title_x-20, title_y-10, title_x+title_width+20, title_y+60],
401
  radius=20,
402
  fill=(250, 250, 250),
403
- outline=None # NO OUTLINE
404
  )
405
-
406
- # Draw title text
407
  draw.text((title_x, title_y), title, font=title_font, fill=self.title_color)
408
 
409
- # Draw subtitle in both languages
410
  subtitle_font = self.get_font(24)
411
  subtitle = "Healthy Foods Β· ε₯εΊ·ι£Ÿε“"
412
  subtitle_bbox = draw.textbbox((0, 0), subtitle, font=subtitle_font)
@@ -415,84 +383,55 @@ class InfographicLayoutGenerator:
415
  subtitle_y = title_y + 60
416
  draw.text((subtitle_x, subtitle_y), subtitle, font=subtitle_font, fill=(120, 120, 120))
417
 
418
- # Process and place each item
419
  for i, ((food_name, _), img) in enumerate(zip(items_with_prompts, generated_images)):
420
  if i >= len(positions):
421
  break
422
 
423
  x, y, size = positions[i]
424
 
425
- # Create clean icon from image
426
  if img:
427
- # Remove background
428
  icon_img = self.smart_remove_background(img)
429
-
430
- # Resize maintaining aspect ratio
431
  icon_img.thumbnail((size, size), Image.Resampling.LANCZOS)
432
 
433
- # Create circular mask for smooth edges
434
  mask = Image.new('L', (size, size), 0)
435
  mask_draw = ImageDraw.Draw(mask)
436
  mask_draw.ellipse((0, 0, size, size), fill=255)
437
 
438
- # Create final icon with transparency
439
  icon_final = Image.new('RGBA', (size, size), (0, 0, 0, 0))
440
-
441
- # Center the image
442
  paste_x = (size - icon_img.width) // 2
443
  paste_y = (size - icon_img.height) // 2
444
  icon_final.paste(icon_img, (paste_x, paste_y), icon_img)
445
 
446
- # Very subtle circular background (NO RED OUTLINE)
447
  circle_bg = Image.new('RGBA', (size+20, size+20), (0, 0, 0, 0))
448
  circle_draw = ImageDraw.Draw(circle_bg)
449
  circle_draw.ellipse(
450
  [5, 5, size+15, size+15],
451
- fill=(245, 245, 245), # Very light gray
452
- outline=None # NO OUTLINE
453
  )
454
-
455
- # Paste circle background
456
  canvas.paste(circle_bg, (x-10, y-10), circle_bg)
457
-
458
- # Paste icon
459
  canvas.paste(icon_final, (x, y), icon_final)
460
 
461
- # Draw food name with Chinese support
462
  name_font = self.get_font(28 if num_items > 6 else 32, bold=True)
463
 
464
- # Calculate name position based on layout
465
  if columns == 1:
466
- # Single column - name below icon
467
  name_x = x + (size // 2)
468
  name_y = y + size + 15
469
- anchor = "mm" # Center alignment
470
-
471
- # Draw name
472
  draw.text((name_x, name_y), food_name, font=name_font,
473
- fill=self.text_color, anchor=anchor)
474
-
475
- # Draw number below name
476
  number_font = self.get_font(20)
477
- number_text = f"#{i+1:02d}"
478
- draw.text((name_x, name_y + 35), number_text, font=number_font,
479
- fill=(150, 150, 150), anchor=anchor)
480
-
481
  else:
482
- # Two columns - name beside icon
483
  name_x = x + size + 15
484
  name_y = y + (size // 2) - 20
485
-
486
- # Draw name
487
  draw.text((name_x, name_y), food_name, font=name_font, fill=self.text_color)
488
-
489
- # Draw number
490
  number_font = self.get_font(18)
491
- number_text = f"#{i+1:02d}"
492
- draw.text((name_x, name_y + 30), number_text, font=number_font,
493
  fill=(150, 150, 150))
494
 
495
- # Draw simple footer (NO RED)
496
  footer_font = self.get_font(20)
497
  footer_text = "Β· Fresh & Healthy Β·"
498
  footer_bbox = draw.textbbox((0, 0), footer_text, font=footer_font)
@@ -588,7 +527,6 @@ def get_memory_usage():
588
  memory_used_mb = memory_info.rss / (1024 * 1024)
589
  memory_percent = process.memory_percent()
590
 
591
- # GPU memory if available
592
  gpu_memory_allocated_mb = None
593
  gpu_memory_cached_mb = None
594
 
@@ -610,17 +548,13 @@ def clear_memory(clear_models=True, clear_jobs=False, clear_local_images=False,
610
  """Clear memory by unloading models and cleaning up resources"""
611
  results = []
612
 
613
- # Clear model cache
614
  if clear_models:
615
  with model_lock:
616
  models_cleared = len(model_cache)
617
  for model_name, pipe in model_cache.items():
618
  try:
619
- # Move to CPU first if it's on GPU
620
  if hasattr(pipe, 'to'):
621
  pipe.to('cpu')
622
-
623
- # Delete the pipeline
624
  del pipe
625
  results.append(f"Unloaded model: {model_name}")
626
  except Exception as e:
@@ -632,7 +566,6 @@ def clear_memory(clear_models=True, clear_jobs=False, clear_local_images=False,
632
  current_model_name = None
633
  results.append(f"Cleared {models_cleared} models from cache")
634
 
635
- # Clear completed jobs
636
  if clear_jobs:
637
  jobs_to_clear = []
638
  for job_id, job_data in job_storage.items():
@@ -645,7 +578,6 @@ def clear_memory(clear_models=True, clear_jobs=False, clear_local_images=False,
645
 
646
  results.append(f"Cleared {len(jobs_to_clear)} completed/failed jobs")
647
 
648
- # Clear local images
649
  if clear_local_images:
650
  try:
651
  storage_info = get_local_storage_info()
@@ -659,7 +591,6 @@ def clear_memory(clear_models=True, clear_jobs=False, clear_local_images=False,
659
  except Exception as e:
660
  results.append(f"Error clearing local images: {str(e)}")
661
 
662
- # Force garbage collection
663
  if force_gc:
664
  gc.collect()
665
  if torch.cuda.is_available():
@@ -668,7 +599,6 @@ def clear_memory(clear_models=True, clear_jobs=False, clear_local_images=False,
668
  results.append("GPU cache cleared")
669
  results.append("Garbage collection forced")
670
 
671
- # Get memory status after cleanup
672
  memory_status = get_memory_usage()
673
 
674
  return {
@@ -678,7 +608,7 @@ def clear_memory(clear_models=True, clear_jobs=False, clear_local_images=False,
678
  }
679
 
680
  def load_model(model_name="dreamshaper-8"):
681
- """Thread-safe model loading with HIGH-QUALITY settings and better error handling"""
682
  global model_cache, current_model_name, current_pipe
683
 
684
  with model_lock:
@@ -687,10 +617,9 @@ def load_model(model_name="dreamshaper-8"):
687
  current_model_name = model_name
688
  return current_pipe
689
 
690
- print(f"πŸ”„ Loading HIGH-QUALITY model: {model_name}")
691
  try:
692
  model_id = MODEL_CHOICES.get(model_name, "lykon/dreamshaper-8")
693
-
694
  print(f"πŸ”§ Attempting to load: {model_id}")
695
 
696
  pipe = StableDiffusionPipeline.from_pretrained(
@@ -709,7 +638,7 @@ def load_model(model_name="dreamshaper-8"):
709
  current_pipe = pipe
710
  current_model_name = model_name
711
 
712
- print(f"βœ… HIGH-QUALITY Model loaded: {model_name}")
713
  return pipe
714
 
715
  except Exception as e:
@@ -728,7 +657,7 @@ def load_model(model_name="dreamshaper-8"):
728
  current_pipe = pipe
729
  current_model_name = "sd-1.5"
730
 
731
- print(f"βœ… Fallback model loaded: stable-diffusion-v1-5")
732
  return pipe
733
 
734
  except Exception as fallback_error:
@@ -740,12 +669,10 @@ print("πŸš€ Initializing Storybook & Infographic Generator API...")
740
  load_model("dreamshaper-8")
741
  print("βœ… Model loaded and ready!")
742
 
743
- # SIMPLE PROMPT ENGINEERING - USE PURE PROMPTS ONLY
744
  def enhance_prompt_simple(scene_visual, style="childrens_book"):
745
- """Simple prompt enhancement - uses only the provided visual prompt with style"""
746
-
747
  style_templates = {
748
- "childrens_book": "children's book illustration, watercolor style, soft colors, whimsical, magical, storybook art, professional illustration",
749
  "realistic": "photorealistic, detailed, natural lighting, professional photography",
750
  "fantasy": "fantasy art, magical, ethereal, digital painting, concept art",
751
  "anime": "anime style, Japanese animation, vibrant colors, detailed artwork",
@@ -753,22 +680,15 @@ def enhance_prompt_simple(scene_visual, style="childrens_book"):
753
  }
754
 
755
  style_prompt = style_templates.get(style, style_templates["childrens_book"])
756
-
757
  enhanced_prompt = f"{style_prompt}, {scene_visual}"
758
-
759
- negative_prompt = (
760
- "blurry, low quality, bad anatomy, deformed characters, "
761
- "wrong proportions, mismatched features"
762
- )
763
 
764
  return enhanced_prompt, negative_prompt
765
 
766
  def generate_image_simple(prompt, model_choice, style, scene_number, consistency_seed=None):
767
- """Generate image using pure prompts only"""
768
-
769
  enhanced_prompt, negative_prompt = enhance_prompt_simple(prompt, style)
770
 
771
- # FIXED: Safer seed calculation
772
  if consistency_seed is not None:
773
  scene_seed = (consistency_seed + scene_number) % 2147483647
774
  else:
@@ -788,18 +708,15 @@ def generate_image_simple(prompt, model_choice, style, scene_number, consistency
788
  ).images[0]
789
 
790
  print(f"βœ… Generated image for scene {scene_number}")
791
- print(f"🌱 Seed used: {scene_seed}")
792
- print(f"πŸ“ Pure prompt used: {prompt}")
793
-
794
  return image
795
 
796
  except Exception as e:
797
  print(f"❌ Generation failed: {str(e)}")
798
  raise
799
 
800
- # LOCAL FILE MANAGEMENT FUNCTIONS
801
  def save_image_to_local(image, prompt, style="test"):
802
- """Save image to local persistent storage"""
803
  try:
804
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
805
  safe_prompt = "".join(c for c in prompt[:50] if c.isalnum() or c in (' ', '-', '_')).rstrip()
@@ -823,7 +740,6 @@ def delete_local_image(filepath):
823
  try:
824
  if os.path.exists(filepath):
825
  os.remove(filepath)
826
- print(f"πŸ—‘οΈ Deleted local image: {filepath}")
827
  return True, f"βœ… Deleted: {os.path.basename(filepath)}"
828
  else:
829
  return False, f"❌ File not found: {filepath}"
@@ -890,7 +806,7 @@ def create_job(story_request: StorybookRequest) -> str:
890
  "pages": []
891
  }
892
 
893
- print(f"πŸ“ Created job {job_id} for story: {story_request.story_title}")
894
  return job_id
895
 
896
  def update_job_status(job_id: str, status: JobStatus, progress: int, message: str, result=None):
@@ -918,7 +834,6 @@ def update_job_status(job_id: str, status: JobStatus, progress: int, message: st
918
  "status": status.value,
919
  "progress": progress,
920
  "message": message,
921
- "story_title": request_data.get("story_title", request_data.get("title", "Unknown")),
922
  "timestamp": time.time()
923
  }
924
 
@@ -929,49 +844,21 @@ def update_job_status(job_id: str, status: JobStatus, progress: int, message: st
929
  }
930
 
931
  requests.post(callback_url, json=callback_data, timeout=5)
932
- print(f"πŸ“’ Callback sent to {callback_url}")
933
  except Exception as e:
934
  print(f"⚠️ Callback failed: {e}")
935
 
936
  return True
937
 
938
- def calculate_remaining_time(job_id, progress):
939
- """Calculate estimated time remaining"""
940
- if progress == 0:
941
- return "Calculating..."
942
-
943
- job_data = job_storage.get(job_id)
944
- if not job_data:
945
- return "Unknown"
946
-
947
- time_elapsed = time.time() - job_data["created_at"]
948
- if progress > 0:
949
- total_estimated = (time_elapsed / progress) * 100
950
- remaining = total_estimated - time_elapsed
951
- return f"{int(remaining // 60)}m {int(remaining % 60)}s"
952
-
953
- return "Unknown"
954
-
955
- # BACKGROUND TASK WITH HF DATASET
956
  def generate_storybook_background(job_id: str):
957
- """Background task to generate complete storybook and upload to HF Dataset"""
958
  try:
959
- # Ensure HF Dataset exists
960
  if HF_TOKEN:
961
  ensure_dataset_exists()
962
 
963
  job_data = job_storage[job_id]
964
  story_request = StorybookRequest(**job_data["request"])
965
-
966
  project_id = story_request.project_id or story_request.story_title.replace(' ', '_').lower()
967
 
968
- print(f"🎬 Starting storybook generation for job {job_id}")
969
- print(f"πŸ“– Story: {story_request.story_title}")
970
- print(f"πŸ“„ Scenes: {len(story_request.scenes)}")
971
- print(f"🎨 Style: {story_request.style}")
972
- print(f"πŸ“¦ Project ID: {project_id}")
973
- print(f"πŸ“€ Saving to HF Dataset: {DATASET_ID}")
974
-
975
  update_job_status(job_id, JobStatus.PROCESSING, 5, "Starting generation...")
976
 
977
  total_scenes = len(story_request.scenes)
@@ -981,19 +868,9 @@ def generate_storybook_background(job_id: str):
981
 
982
  for i, scene in enumerate(story_request.scenes):
983
  progress = 5 + int(((i + 1) / total_scenes) * 90)
984
-
985
- update_job_status(
986
- job_id,
987
- JobStatus.PROCESSING,
988
- progress,
989
- f"Generating page {i+1}/{total_scenes}"
990
- )
991
 
992
  try:
993
- print(f"πŸ–ΌοΈ Generating page {i+1}")
994
- print(f"πŸ“ Pure prompt: {scene.visual}")
995
-
996
- # Generate image
997
  image = generate_image_simple(
998
  scene.visual,
999
  story_request.model_choice,
@@ -1002,193 +879,82 @@ def generate_storybook_background(job_id: str):
1002
  story_request.consistency_seed
1003
  )
1004
 
1005
- # Save locally as backup
1006
  local_filepath, local_filename = save_image_to_local(image, scene.visual, story_request.style)
1007
- print(f"πŸ’Ύ Image saved locally as backup: {local_filename}")
1008
 
1009
- # Upload to HF Dataset
1010
  hf_url = None
1011
  if HF_TOKEN:
1012
- hf_url = upload_image_to_hf_dataset(
1013
- image,
1014
- project_id,
1015
- i + 1,
1016
- scene.visual,
1017
- story_request.style
1018
- )
1019
-
1020
  if hf_url:
1021
  image_urls.append(hf_url)
1022
- print(f"βœ… Uploaded to HF Dataset: {hf_url}")
1023
 
1024
- # Store page data
1025
- page_data = {
1026
  "page_number": i + 1,
1027
  "image_url": hf_url or f"local://{local_filepath}",
1028
- "hf_url": hf_url,
1029
  "text_content": scene.text,
1030
- "visual_description": scene.visual,
1031
- "prompt_used": scene.visual,
1032
- "local_backup_path": local_filepath
1033
- }
1034
- generated_pages.append(page_data)
1035
-
1036
- print(f"βœ… Page {i+1} completed")
1037
 
1038
  except Exception as e:
1039
  error_msg = f"Failed to generate page {i+1}: {str(e)}"
1040
- print(f"❌ {error_msg}")
1041
  update_job_status(job_id, JobStatus.FAILED, progress, error_msg)
1042
  return
1043
 
1044
- # Complete the job
1045
- generation_time = time.time() - start_time
1046
-
1047
- # Count successful HF uploads
1048
- hf_success_count = len(image_urls)
1049
- local_fallback_count = total_scenes - hf_success_count
1050
-
1051
  result = {
1052
  "story_title": story_request.story_title,
1053
  "project_id": project_id,
1054
  "total_pages": total_scenes,
1055
  "generated_pages": len(generated_pages),
1056
- "generation_time": round(generation_time, 2),
1057
- "hf_dataset_url": f"https://huggingface.co/datasets/{DATASET_ID}" if HF_TOKEN else None,
1058
  "image_urls": image_urls,
1059
- "pages": generated_pages,
1060
- "upload_summary": {
1061
- "hf_successful": hf_success_count,
1062
- "local_fallback": local_fallback_count,
1063
- "total_attempted": total_scenes
1064
- }
1065
  }
1066
 
1067
- # Report to tracker when all images are done
1068
  if project_id and image_urls:
1069
- report_to_tracker(
1070
- project_id=project_id,
1071
- service_type="image",
1072
- status="completed",
1073
- file_urls=image_urls
1074
- )
1075
-
1076
- status_message = f"πŸŽ‰ Storybook completed! {len(generated_pages)} pages created."
1077
- if hf_success_count > 0:
1078
- status_message += f" {hf_success_count} images uploaded to HF Dataset."
1079
- if local_fallback_count > 0:
1080
- status_message += f" {local_fallback_count} pages saved locally."
1081
-
1082
- update_job_status(
1083
- job_id,
1084
- JobStatus.COMPLETED,
1085
- 100,
1086
- status_message,
1087
- result
1088
- )
1089
 
1090
- print(f"πŸŽ‰ Storybook generation finished for job {job_id}")
1091
- print(f"πŸ“€ HF Uploads: {hf_success_count} successful")
1092
 
1093
  except Exception as e:
1094
  error_msg = f"Story generation failed: {str(e)}"
1095
- print(f"❌ {error_msg}")
1096
- traceback.print_exc()
1097
-
1098
- # Report failure to tracker
1099
- if 'project_id' in locals() and project_id:
1100
- report_to_tracker(
1101
- project_id=project_id,
1102
- service_type="image",
1103
- status="failed",
1104
- error=error_msg
1105
- )
1106
-
1107
  update_job_status(job_id, JobStatus.FAILED, 0, error_msg)
1108
 
1109
- # =============================================
1110
- # FIXED: Infographic Background Task
1111
- # =============================================
1112
  def generate_infographic_background(job_id: str):
1113
- """Background task with responsive layout and proper Chinese support"""
1114
  try:
1115
  job_data = job_storage[job_id]
1116
  request = InfographicRequest(**job_data["request"])
1117
-
1118
  project_id = request.project_id or f"infographic_{int(time.time())}"
1119
 
1120
- print(f"🎬 Starting infographic generation for job {job_id}")
1121
- print(f"πŸ“Š Title: {request.title}")
1122
- print(f"πŸ“¦ Items: {len(request.items)}")
1123
-
1124
  update_job_status(job_id, JobStatus.PROCESSING, 5, "Starting infographic generation...")
1125
 
1126
- # Limit items to prevent memory issues
1127
  items = request.items[:10] if len(request.items) > 10 else request.items
1128
- print(f"πŸ“¦ Processing {len(items)} items")
1129
 
1130
- # Download custom background if provided
1131
  background_image = None
1132
  if request.background_url:
1133
  try:
1134
- print(f"πŸ“₯ Downloading custom background from: {request.background_url}")
1135
  response = requests.get(request.background_url, timeout=30)
1136
  if response.status_code == 200:
1137
  background_image = Image.open(io.BytesIO(response.content)).convert('RGB')
1138
- print("βœ… Custom background loaded successfully")
1139
- else:
1140
- print(f"⚠️ Failed to download background: Status {response.status_code}")
1141
  except Exception as e:
1142
- print(f"⚠️ Error loading custom background: {e}")
1143
 
1144
- update_job_status(job_id, JobStatus.PROCESSING, 10, "Generating food images...")
1145
-
1146
- # Generate images for each item
1147
  generated_images = []
1148
  image_urls = []
1149
  items_data = []
1150
 
1151
  for i, item in enumerate(items):
1152
  progress = 10 + int(((i + 1) / len(items)) * 70)
1153
-
1154
- update_job_status(
1155
- job_id,
1156
- JobStatus.PROCESSING,
1157
- progress,
1158
- f"Generating image for {item.name} ({i+1}/{len(items)})"
1159
- )
1160
 
1161
  try:
1162
- print(f"πŸ–ΌοΈ Generating image for: {item.name}")
1163
-
1164
- # Enhanced prompt for better subject isolation
1165
- enhanced_prompt = f"professional food photography, {item.prompt}, " \
1166
- f"isolated on white background, cut out, transparent style, " \
1167
- f"sharp focus, studio lighting, clean edges"
1168
-
1169
- image = generate_image_simple(
1170
- enhanced_prompt,
1171
- "dreamshaper-8",
1172
- "food_photography",
1173
- i + 1,
1174
- None
1175
- )
1176
-
1177
  generated_images.append(image)
1178
 
1179
- # Save locally
1180
  local_filepath, _ = save_image_to_local(image, item.name, "infographic")
1181
 
1182
- # Upload to HF Dataset (limit to first 5 to save memory)
1183
  hf_url = None
1184
  if HF_TOKEN and i < 5:
1185
- hf_url = upload_image_to_hf_dataset(
1186
- image,
1187
- project_id,
1188
- i + 1,
1189
- item.name,
1190
- "infographic"
1191
- )
1192
  if hf_url:
1193
  image_urls.append(hf_url)
1194
 
@@ -1198,51 +964,28 @@ def generate_infographic_background(job_id: str):
1198
  "prompt": item.prompt
1199
  })
1200
 
1201
- print(f"βœ… Image generated for {item.name}")
1202
-
1203
  except Exception as e:
1204
- print(f"❌ Failed to generate for {item.name}: {e}")
1205
- # Create a placeholder image
1206
  placeholder = Image.new('RGB', (512, 512), color=(240, 240, 240))
1207
  generated_images.append(placeholder)
1208
 
1209
- # Force garbage collection
1210
  gc.collect()
1211
 
1212
- update_job_status(job_id, JobStatus.PROCESSING, 85, "Creating infographic layout...")
1213
 
1214
- # Create the infographic with responsive layout
1215
  layout_generator = InfographicLayoutGenerator()
1216
  items_with_prompts = [(item.name, item.prompt) for item in items]
 
1217
 
1218
- infographic = layout_generator.create_food_infographic(
1219
- request.title,
1220
- items_with_prompts,
1221
- generated_images,
1222
- background_image=background_image
1223
- )
1224
-
1225
- # Save infographic
1226
  infographic_path = os.path.join(PERSISTENT_IMAGE_DIR, f"infographic_{project_id}.png")
1227
  infographic.save(infographic_path)
1228
- print(f"πŸ’Ύ Infographic saved locally: {infographic_path}")
1229
 
1230
- # Upload infographic to HF Dataset
1231
  infographic_url = None
1232
  if HF_TOKEN:
1233
  img_bytes = io.BytesIO()
1234
  infographic.save(img_bytes, format='PNG')
1235
  img_bytes.seek(0)
1236
-
1237
- infographic_url = upload_to_hf_dataset(
1238
- img_bytes.getvalue(),
1239
- f"infographic_{project_id}.png",
1240
- f"projects/{project_id}"
1241
- )
1242
- if infographic_url:
1243
- print(f"βœ… Infographic uploaded: {infographic_url}")
1244
 
1245
- # Clean up
1246
  if background_image:
1247
  background_image.close()
1248
  for img in generated_images:
@@ -1251,72 +994,35 @@ def generate_infographic_background(job_id: str):
1251
  infographic.close()
1252
  gc.collect()
1253
 
1254
- update_job_status(job_id, JobStatus.PROCESSING, 95, "Finalizing...")
1255
-
1256
- # Prepare result
1257
  result = {
1258
  "project_id": project_id,
1259
  "title": request.title,
1260
  "total_items": len(items),
1261
  "infographic_url": infographic_url or f"local://{infographic_path}",
1262
  "item_images": image_urls,
1263
- "items": items_data,
1264
- "background_used": request.background_url is not None
1265
  }
1266
 
1267
- # Report to tracker
1268
  if project_id and infographic_url:
1269
- report_to_tracker(
1270
- project_id=project_id,
1271
- service_type="infographic",
1272
- status="completed",
1273
- file_urls=[infographic_url] + image_urls
1274
- )
1275
-
1276
- update_job_status(
1277
- job_id,
1278
- JobStatus.COMPLETED,
1279
- 100,
1280
- f"βœ… Infographic created with {len(items)} items",
1281
- result
1282
- )
1283
 
1284
- print(f"πŸŽ‰ Infographic generation finished for job {job_id}")
1285
 
1286
  except Exception as e:
1287
  error_msg = f"Infographic generation failed: {str(e)}"
1288
- print(f"❌ {error_msg}")
1289
- traceback.print_exc()
1290
-
1291
- if 'project_id' in locals() and project_id:
1292
- report_to_tracker(
1293
- project_id=project_id,
1294
- service_type="infographic",
1295
- status="failed",
1296
- error=error_msg
1297
- )
1298
-
1299
  update_job_status(job_id, JobStatus.FAILED, 0, error_msg)
1300
  gc.collect()
1301
 
1302
  # FASTAPI ENDPOINTS
1303
  @app.post("/api/generate-storybook")
1304
  async def generate_storybook(request: dict, background_tasks: BackgroundTasks):
1305
- """Main endpoint for n8n integration"""
1306
  try:
1307
- print(f"πŸ“₯ Received request for: {request.get('story_title', 'Unknown')}")
1308
-
1309
  if 'consistency_seed' not in request:
1310
  request['consistency_seed'] = random.randint(1000, 9999)
1311
-
1312
  if 'project_id' not in request:
1313
  request['project_id'] = request.get('story_title', 'unknown').replace(' ', '_').lower()
1314
 
1315
  story_request = StorybookRequest(**request)
1316
-
1317
- if not story_request.story_title or not story_request.scenes:
1318
- raise HTTPException(status_code=400, detail="story_title and scenes required")
1319
-
1320
  job_id = create_job(story_request)
1321
  background_tasks.add_task(generate_storybook_background, job_id)
1322
 
@@ -1324,34 +1030,19 @@ async def generate_storybook(request: dict, background_tasks: BackgroundTasks):
1324
  "status": "success",
1325
  "job_id": job_id,
1326
  "story_title": story_request.story_title,
1327
- "project_id": request['project_id'],
1328
  "total_scenes": len(story_request.scenes),
1329
- "hf_dataset": f"https://huggingface.co/datasets/{DATASET_ID}" if HF_TOKEN else None,
1330
  "estimated_time_seconds": len(story_request.scenes) * 35
1331
  }
1332
-
1333
  except Exception as e:
1334
- print(f"❌ Error: {e}")
1335
  raise HTTPException(status_code=500, detail=str(e))
1336
 
1337
- # =============================================
1338
- # Infographic API Endpoint
1339
- # =============================================
1340
  @app.post("/api/generate-infographic")
1341
  async def generate_infographic(request: dict, background_tasks: BackgroundTasks):
1342
- """Generate food infographic with images for each item"""
1343
  try:
1344
- print(f"πŸ“₯ Received infographic request for: {request.get('title', 'Unknown')}")
1345
-
1346
  if 'project_id' not in request:
1347
  request['project_id'] = f"infographic_{int(time.time())}"
1348
 
1349
  infographic_request = InfographicRequest(**request)
1350
-
1351
- if not infographic_request.title or not infographic_request.items:
1352
- raise HTTPException(status_code=400, detail="title and items required")
1353
-
1354
- # Create job
1355
  job_id = str(uuid.uuid4())
1356
  job_storage[job_id] = {
1357
  "status": JobStatus.PENDING,
@@ -1362,26 +1053,20 @@ async def generate_infographic(request: dict, background_tasks: BackgroundTasks)
1362
  "created_at": time.time(),
1363
  "updated_at": time.time()
1364
  }
1365
-
1366
- # Start background task
1367
  background_tasks.add_task(generate_infographic_background, job_id)
1368
 
1369
  return {
1370
  "status": "success",
1371
  "job_id": job_id,
1372
  "title": infographic_request.title,
1373
- "project_id": request['project_id'],
1374
  "total_items": len(infographic_request.items),
1375
  "estimated_time_seconds": min(len(infographic_request.items) * 30, 300)
1376
  }
1377
-
1378
  except Exception as e:
1379
- print(f"❌ Error: {e}")
1380
  raise HTTPException(status_code=500, detail=str(e))
1381
 
1382
  @app.get("/api/job-status/{job_id}")
1383
  async def get_job_status(job_id: str):
1384
- """Get job status"""
1385
  job_data = job_storage.get(job_id)
1386
  if not job_data:
1387
  raise HTTPException(status_code=404, detail="Job not found")
@@ -1394,46 +1079,21 @@ async def get_job_status(job_id: str):
1394
  "result": job_data["result"]
1395
  }
1396
 
1397
- @app.get("/api/project-images/{project_id}")
1398
- async def get_project_images(project_id: str):
1399
- """Get all images for a project from HF Dataset"""
1400
- try:
1401
- if not HF_TOKEN:
1402
- return {"error": "HF_TOKEN not set"}
1403
-
1404
- api = HfApi(token=HF_TOKEN)
1405
- files = api.list_repo_files(repo_id=DATASET_ID, repo_type="dataset")
1406
-
1407
- project_files = [f for f in files if f.startswith(f"data/projects/{project_id}/")]
1408
- urls = [f"https://huggingface.co/datasets/{DATASET_ID}/resolve/main/{f}" for f in project_files]
1409
-
1410
- return {
1411
- "project_id": project_id,
1412
- "total_images": len(urls),
1413
- "image_urls": urls
1414
- }
1415
- except Exception as e:
1416
- return {"error": str(e)}
1417
-
1418
  @app.get("/api/health")
1419
  async def health():
1420
- """Health check"""
1421
  return {
1422
  "status": "healthy",
1423
  "service": "storybook-infographic-generator",
1424
- "hf_dataset": DATASET_ID if HF_TOKEN else "Disabled",
1425
  "active_jobs": len(job_storage),
1426
  "models_loaded": list(model_cache.keys())
1427
  }
1428
 
1429
  @app.get("/api/memory-status")
1430
  async def memory_status():
1431
- """Get memory usage"""
1432
  return get_memory_usage()
1433
 
1434
  @app.post("/api/clear-memory")
1435
  async def clear_memory_api(request: MemoryClearanceRequest):
1436
- """Clear memory"""
1437
  return clear_memory(
1438
  clear_models=request.clear_models,
1439
  clear_jobs=request.clear_jobs,
@@ -1443,16 +1103,11 @@ async def clear_memory_api(request: MemoryClearanceRequest):
1443
 
1444
  @app.get("/api/local-images")
1445
  async def get_local_images():
1446
- """Get local images"""
1447
  return get_local_storage_info()
1448
 
1449
- # =============================================
1450
- # FIXED: Test Functions for Gradio
1451
- # =============================================
1452
  def test_infographic(title, items_text, background_image=None):
1453
- """Test function with proper Chinese support and responsive layout"""
1454
  try:
1455
- # Parse items (format: "name:prompt;name2:prompt2")
1456
  items = []
1457
  for item in items_text.split(';'):
1458
  if ':' in item:
@@ -1462,66 +1117,33 @@ def test_infographic(title, items_text, background_image=None):
1462
  if not items:
1463
  return None, "❌ Please enter at least one item"
1464
 
1465
- # Limit items
1466
  if len(items) > 10:
1467
  items = items[:10]
1468
- print("⚠️ Limited to 10 items for preview")
1469
-
1470
- print(f"🎨 Generating infographic with {len(items)} items...")
1471
 
1472
- # Generate images
1473
  generated_images = []
1474
- try:
1475
- for i, item in enumerate(items):
1476
- print(f" πŸ–ΌοΈ Generating image {i+1}/{len(items)}: {item['name']}")
1477
-
1478
- enhanced_prompt = f"professional food photography, {item['prompt']}, " \
1479
- f"isolated on white background, cut out, transparent style, " \
1480
- f"sharp focus, studio lighting, clean edges"
1481
-
1482
- img = generate_image_simple(
1483
- enhanced_prompt,
1484
- "dreamshaper-8",
1485
- "food_photography",
1486
- i+1,
1487
- None
1488
- )
1489
- generated_images.append(img)
1490
- time.sleep(0.5)
1491
-
1492
- print(" πŸ“ Creating responsive layout...")
1493
-
1494
- # Create infographic with responsive layout
1495
- layout_gen = InfographicLayoutGenerator()
1496
- items_with_prompts = [(item['name'], item['prompt']) for item in items]
1497
-
1498
- infographic = layout_gen.create_food_infographic(
1499
- title,
1500
- items_with_prompts,
1501
- generated_images,
1502
- background_image=background_image
1503
- )
1504
-
1505
- return infographic, f"βœ… Generated responsive infographic with {len(items)} items"
1506
-
1507
- finally:
1508
- # Clean up
1509
- for img in generated_images:
1510
- if img:
1511
- img.close()
1512
- gc.collect()
1513
 
1514
  except Exception as e:
1515
- print(f"❌ Error: {str(e)}")
1516
- traceback.print_exc()
1517
  return None, f"❌ Error: {str(e)}"
1518
 
1519
- # GRADIO INTERFACE
1520
  def create_gradio_interface():
1521
  def generate_test(prompt, model_choice, style_choice):
1522
  if not prompt.strip():
1523
  return None, "❌ Please enter a prompt"
1524
-
1525
  try:
1526
  image = generate_image_simple(prompt, model_choice, style_choice, 1)
1527
  filepath, filename = save_image_to_local(image, prompt, style_choice)
@@ -1529,11 +1151,8 @@ def create_gradio_interface():
1529
  except Exception as e:
1530
  return None, f"❌ Error: {str(e)}"
1531
 
1532
- def test_infographic_ui(title, items_text, background_image):
1533
- return test_infographic(title, items_text, background_image)
1534
-
1535
  with gr.Blocks(title="Storybook & Infographic Generator") as demo:
1536
- gr.Markdown("# 🎨 Storybook & Infographic Generator with HF Dataset")
1537
 
1538
  with gr.Tab("Storybook Generator"):
1539
  with gr.Row():
@@ -1542,11 +1161,9 @@ def create_gradio_interface():
1542
  style = gr.Dropdown(choices=["childrens_book", "realistic", "fantasy", "anime"], value="anime", label="Style")
1543
  prompt = gr.Textbox(label="Prompt", lines=3)
1544
  btn = gr.Button("Generate", variant="primary")
1545
-
1546
  with gr.Column():
1547
  output = gr.Image(label="Generated Image", height=500)
1548
  status = gr.Textbox(label="Status")
1549
-
1550
  btn.click(fn=generate_test, inputs=[prompt, model, style], outputs=[output, status])
1551
 
1552
  with gr.Tab("Infographic Generator"):
@@ -1555,41 +1172,24 @@ def create_gradio_interface():
1555
  infographic_title = gr.Textbox(
1556
  label="Title (Supports Chinese)",
1557
  value="7μ›”μ˜ λ§› (Taste of July)",
1558
- placeholder="Enter infographic title in Chinese/English..."
1559
  )
1560
  infographic_items = gr.Textbox(
1561
  label="Items (name:prompt;name2:prompt2)",
1562
  value="ι…Έθœ (Kimchi):traditional korean cabbage kimchi;萝卜 (Kkakdugi):korean radish kimchi, cubed;ζ΄Ύ (Pa-kimchi):green onion kimchi",
1563
- lines=5,
1564
- placeholder="item1:prompt1;item2:prompt2"
1565
  )
1566
-
1567
- # Add background image upload
1568
- background_image = gr.Image(
1569
- label="Custom Background (optional)",
1570
- type="pil",
1571
- height=200
1572
- )
1573
-
1574
  infographic_btn = gr.Button("Generate Infographic", variant="primary", size="lg")
1575
-
1576
  with gr.Column():
1577
  infographic_output = gr.Image(label="Generated Infographic", height=700)
1578
  infographic_status = gr.Textbox(label="Status")
1579
 
1580
  infographic_btn.click(
1581
- fn=test_infographic_ui,
1582
  inputs=[infographic_title, infographic_items, background_image],
1583
  outputs=[infographic_output, infographic_status]
1584
  )
1585
-
1586
- gr.Markdown("""
1587
- ### πŸ“ Tips:
1588
- - **Title**: Supports Chinese/English text
1589
- - **Items**: Format as `name:prompt;name2:prompt2`
1590
- - **Background**: Upload your own image for custom background
1591
- - **Responsive**: Layout adapts to number of items (1-10)
1592
- """)
1593
 
1594
  return demo
1595
 
@@ -1598,15 +1198,12 @@ demo = create_gradio_interface()
1598
  @app.get("/")
1599
  async def root():
1600
  return {
1601
- "message": "Storybook & Infographic Generator API with HF Dataset",
1602
- "hf_dataset": DATASET_ID if HF_TOKEN else "Disabled",
1603
- "tracker_url": TRACKER_URL,
1604
  "endpoints": {
1605
  "generate_storybook": "POST /api/generate-storybook",
1606
  "generate_infographic": "POST /api/generate-infographic",
1607
  "status": "GET /api/job-status/{job_id}",
1608
- "health": "GET /api/health",
1609
- "project_images": "GET /api/project-images/{project_id}"
1610
  },
1611
  "ui": "/ui"
1612
  }
@@ -1616,8 +1213,6 @@ if __name__ == "__main__":
1616
 
1617
  if os.environ.get('SPACE_ID'):
1618
  print("πŸš€ Running on Hugging Face Spaces")
1619
- print(f"πŸ“¦ HF Dataset: {DATASET_ID if HF_TOKEN else 'Disabled'}")
1620
- print(f"πŸ“‘ Tracker URL: {TRACKER_URL}")
1621
  gr.mount_gradio_app(app, demo, path="/ui")
1622
  uvicorn.run(app, host="0.0.0.0", port=7860, log_level="info")
1623
  else:
 
161
  gpu_memory_cached_mb: Optional[float] = None
162
  status: str
163
 
164
+ # HIGH-QUALITY MODEL SELECTION
165
  MODEL_CHOICES = {
166
  "dreamshaper-8": "lykon/dreamshaper-8",
167
  "realistic-vision": "SG161222/Realistic_Vision_V5.1",
 
182
  model_lock = threading.Lock()
183
 
184
  # =============================================
185
+ # FIXED: Infographic Layout Generator
186
  # =============================================
187
  class InfographicLayoutGenerator:
188
  def __init__(self):
 
194
 
195
  def get_font(self, size, bold=False):
196
  """Enhanced font loading with extensive Chinese font support"""
 
197
  font_paths = [
198
  # Windows fonts
199
+ "C:/Windows/Fonts/msyh.ttc",
200
+ "C:/Windows/Fonts/msyhbd.ttc",
201
+ "C:/Windows/Fonts/simhei.ttf",
202
+ "C:/Windows/Fonts/simsun.ttc",
203
+ "C:/Windows/Fonts/STSONG.TTF",
204
+ "C:/Windows/Fonts/STKAITI.TTF",
205
  # Linux fonts
206
  "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
207
  "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
 
230
  ]
231
  font_paths = bold_fonts + font_paths
232
 
 
233
  for font_path in font_paths:
234
  try:
235
  if os.path.exists(font_path):
 
237
  except:
238
  continue
239
 
 
240
  try:
241
  return ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", size)
242
  except:
 
245
  def smart_remove_background(self, image):
246
  """Intelligently remove background while keeping the main subject"""
247
  try:
 
248
  if image.mode != 'RGBA':
249
  image = image.convert('RGBA')
250
 
251
  data = image.getdata()
252
  new_data = []
253
 
 
254
  width, height = image.size
255
 
 
256
  edge_samples = []
 
257
  for x in range(0, width, 10):
258
+ if x < len(data):
259
+ edge_samples.append(data[x])
260
  for x in range(0, width, 10):
261
+ idx = (height-1) * width + x
262
+ if idx < len(data):
263
+ edge_samples.append(data[idx])
264
  for y in range(0, height, 10):
265
+ idx = y * width
266
+ if idx < len(data):
267
+ edge_samples.append(data[idx])
268
  for y in range(0, height, 10):
269
+ idx = y * width + (width-1)
270
+ if idx < len(data):
271
+ edge_samples.append(data[idx])
272
+
273
+ if not edge_samples:
274
+ return image
275
 
 
276
  avg_r = sum(s[0] for s in edge_samples) // len(edge_samples)
277
  avg_g = sum(s[1] for s in edge_samples) // len(edge_samples)
278
  avg_b = sum(s[2] for s in edge_samples) // len(edge_samples)
279
 
 
280
  threshold = 60
281
 
282
  for item in data:
 
283
  distance = ((item[0] - avg_r) ** 2 +
284
  (item[1] - avg_g) ** 2 +
285
  (item[2] - avg_b) ** 2) ** 0.5
286
 
 
287
  if distance < threshold:
288
  new_data.append((255, 255, 255, 0))
289
  else:
 
298
 
299
  def create_responsive_layout(self, num_items):
300
  """Calculate responsive layout based on number of items"""
 
301
  width = 900
302
  height = 1600
303
 
 
304
  if num_items <= 3:
 
305
  icon_size = 180
306
  columns = 1
307
  rows = num_items
 
308
  elif num_items <= 6:
 
309
  icon_size = 150
310
  columns = 2
311
  rows = (num_items + 1) // 2
 
312
  else:
 
313
  icon_size = 120
314
  columns = 2
315
  rows = (num_items + 1) // 2
 
316
 
 
317
  positions = []
318
+ available_height = height - 300
319
  start_y = 200
320
 
321
  for i in range(num_items):
 
330
  else:
331
  x = 3 * width // 4 - icon_size // 2
332
 
 
333
  if rows > 1:
334
  y = start_y + (row * (available_height // rows))
335
  else:
 
340
  return positions, icon_size, columns
341
 
342
  def create_food_infographic(self, title, items_with_prompts, generated_images, background_image=None):
343
+ """Create responsive 9:16 infographic with proper Chinese text"""
 
 
 
344
  width = 900
345
  height = 1600
346
 
347
+ # Create background
348
  if background_image:
 
349
  bg = background_image.resize((width, height), Image.Resampling.LANCZOS)
350
  canvas = bg.convert('RGB')
351
  else:
 
352
  canvas = Image.new('RGB', (width, height), (255, 255, 255))
353
+ draw_temp = ImageDraw.Draw(canvas)
 
 
354
  for y in range(height):
355
  if y < 20 or y > height-20:
356
+ draw_temp.line([(0, y), (width, y)], fill=(240, 240, 240))
 
 
 
357
 
358
  draw = ImageDraw.Draw(canvas)
 
359
  num_items = len(items_with_prompts)
 
 
360
  positions, icon_size, columns = self.create_responsive_layout(num_items)
361
 
362
+ # Draw title
363
  title_font = self.get_font(48, bold=True)
 
 
364
  title_bbox = draw.textbbox((0, 0), title, font=title_font)
365
  title_width = title_bbox[2] - title_bbox[0]
366
  title_x = (width - title_width) // 2
367
  title_y = 60
368
 
 
369
  draw.rounded_rectangle(
370
  [title_x-20, title_y-10, title_x+title_width+20, title_y+60],
371
  radius=20,
372
  fill=(250, 250, 250),
373
+ outline=None
374
  )
 
 
375
  draw.text((title_x, title_y), title, font=title_font, fill=self.title_color)
376
 
377
+ # Draw subtitle
378
  subtitle_font = self.get_font(24)
379
  subtitle = "Healthy Foods Β· ε₯εΊ·ι£Ÿε“"
380
  subtitle_bbox = draw.textbbox((0, 0), subtitle, font=subtitle_font)
 
383
  subtitle_y = title_y + 60
384
  draw.text((subtitle_x, subtitle_y), subtitle, font=subtitle_font, fill=(120, 120, 120))
385
 
386
+ # Place items
387
  for i, ((food_name, _), img) in enumerate(zip(items_with_prompts, generated_images)):
388
  if i >= len(positions):
389
  break
390
 
391
  x, y, size = positions[i]
392
 
 
393
  if img:
 
394
  icon_img = self.smart_remove_background(img)
 
 
395
  icon_img.thumbnail((size, size), Image.Resampling.LANCZOS)
396
 
 
397
  mask = Image.new('L', (size, size), 0)
398
  mask_draw = ImageDraw.Draw(mask)
399
  mask_draw.ellipse((0, 0, size, size), fill=255)
400
 
 
401
  icon_final = Image.new('RGBA', (size, size), (0, 0, 0, 0))
 
 
402
  paste_x = (size - icon_img.width) // 2
403
  paste_y = (size - icon_img.height) // 2
404
  icon_final.paste(icon_img, (paste_x, paste_y), icon_img)
405
 
 
406
  circle_bg = Image.new('RGBA', (size+20, size+20), (0, 0, 0, 0))
407
  circle_draw = ImageDraw.Draw(circle_bg)
408
  circle_draw.ellipse(
409
  [5, 5, size+15, size+15],
410
+ fill=(245, 245, 245),
411
+ outline=None
412
  )
 
 
413
  canvas.paste(circle_bg, (x-10, y-10), circle_bg)
 
 
414
  canvas.paste(icon_final, (x, y), icon_final)
415
 
 
416
  name_font = self.get_font(28 if num_items > 6 else 32, bold=True)
417
 
 
418
  if columns == 1:
 
419
  name_x = x + (size // 2)
420
  name_y = y + size + 15
 
 
 
421
  draw.text((name_x, name_y), food_name, font=name_font,
422
+ fill=self.text_color, anchor="mm")
 
 
423
  number_font = self.get_font(20)
424
+ draw.text((name_x, name_y + 35), f"#{i+1:02d}", font=number_font,
425
+ fill=(150, 150, 150), anchor="mm")
 
 
426
  else:
 
427
  name_x = x + size + 15
428
  name_y = y + (size // 2) - 20
 
 
429
  draw.text((name_x, name_y), food_name, font=name_font, fill=self.text_color)
 
 
430
  number_font = self.get_font(18)
431
+ draw.text((name_x, name_y + 30), f"#{i+1:02d}", font=number_font,
 
432
  fill=(150, 150, 150))
433
 
434
+ # Draw footer
435
  footer_font = self.get_font(20)
436
  footer_text = "Β· Fresh & Healthy Β·"
437
  footer_bbox = draw.textbbox((0, 0), footer_text, font=footer_font)
 
527
  memory_used_mb = memory_info.rss / (1024 * 1024)
528
  memory_percent = process.memory_percent()
529
 
 
530
  gpu_memory_allocated_mb = None
531
  gpu_memory_cached_mb = None
532
 
 
548
  """Clear memory by unloading models and cleaning up resources"""
549
  results = []
550
 
 
551
  if clear_models:
552
  with model_lock:
553
  models_cleared = len(model_cache)
554
  for model_name, pipe in model_cache.items():
555
  try:
 
556
  if hasattr(pipe, 'to'):
557
  pipe.to('cpu')
 
 
558
  del pipe
559
  results.append(f"Unloaded model: {model_name}")
560
  except Exception as e:
 
566
  current_model_name = None
567
  results.append(f"Cleared {models_cleared} models from cache")
568
 
 
569
  if clear_jobs:
570
  jobs_to_clear = []
571
  for job_id, job_data in job_storage.items():
 
578
 
579
  results.append(f"Cleared {len(jobs_to_clear)} completed/failed jobs")
580
 
 
581
  if clear_local_images:
582
  try:
583
  storage_info = get_local_storage_info()
 
591
  except Exception as e:
592
  results.append(f"Error clearing local images: {str(e)}")
593
 
 
594
  if force_gc:
595
  gc.collect()
596
  if torch.cuda.is_available():
 
599
  results.append("GPU cache cleared")
600
  results.append("Garbage collection forced")
601
 
 
602
  memory_status = get_memory_usage()
603
 
604
  return {
 
608
  }
609
 
610
  def load_model(model_name="dreamshaper-8"):
611
+ """Thread-safe model loading"""
612
  global model_cache, current_model_name, current_pipe
613
 
614
  with model_lock:
 
617
  current_model_name = model_name
618
  return current_pipe
619
 
620
+ print(f"πŸ”„ Loading model: {model_name}")
621
  try:
622
  model_id = MODEL_CHOICES.get(model_name, "lykon/dreamshaper-8")
 
623
  print(f"πŸ”§ Attempting to load: {model_id}")
624
 
625
  pipe = StableDiffusionPipeline.from_pretrained(
 
638
  current_pipe = pipe
639
  current_model_name = model_name
640
 
641
+ print(f"βœ… Model loaded: {model_name}")
642
  return pipe
643
 
644
  except Exception as e:
 
657
  current_pipe = pipe
658
  current_model_name = "sd-1.5"
659
 
660
+ print(f"βœ… Fallback model loaded")
661
  return pipe
662
 
663
  except Exception as fallback_error:
 
669
  load_model("dreamshaper-8")
670
  print("βœ… Model loaded and ready!")
671
 
 
672
  def enhance_prompt_simple(scene_visual, style="childrens_book"):
673
+ """Simple prompt enhancement"""
 
674
  style_templates = {
675
+ "childrens_book": "children's book illustration, watercolor style, soft colors, whimsical, magical, storybook art",
676
  "realistic": "photorealistic, detailed, natural lighting, professional photography",
677
  "fantasy": "fantasy art, magical, ethereal, digital painting, concept art",
678
  "anime": "anime style, Japanese animation, vibrant colors, detailed artwork",
 
680
  }
681
 
682
  style_prompt = style_templates.get(style, style_templates["childrens_book"])
 
683
  enhanced_prompt = f"{style_prompt}, {scene_visual}"
684
+ negative_prompt = "blurry, low quality, bad anatomy, deformed characters, wrong proportions"
 
 
 
 
685
 
686
  return enhanced_prompt, negative_prompt
687
 
688
  def generate_image_simple(prompt, model_choice, style, scene_number, consistency_seed=None):
689
+ """Generate image using pure prompts"""
 
690
  enhanced_prompt, negative_prompt = enhance_prompt_simple(prompt, style)
691
 
 
692
  if consistency_seed is not None:
693
  scene_seed = (consistency_seed + scene_number) % 2147483647
694
  else:
 
708
  ).images[0]
709
 
710
  print(f"βœ… Generated image for scene {scene_number}")
 
 
 
711
  return image
712
 
713
  except Exception as e:
714
  print(f"❌ Generation failed: {str(e)}")
715
  raise
716
 
717
+ # LOCAL FILE MANAGEMENT
718
  def save_image_to_local(image, prompt, style="test"):
719
+ """Save image to local storage"""
720
  try:
721
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
722
  safe_prompt = "".join(c for c in prompt[:50] if c.isalnum() or c in (' ', '-', '_')).rstrip()
 
740
  try:
741
  if os.path.exists(filepath):
742
  os.remove(filepath)
 
743
  return True, f"βœ… Deleted: {os.path.basename(filepath)}"
744
  else:
745
  return False, f"❌ File not found: {filepath}"
 
806
  "pages": []
807
  }
808
 
809
+ print(f"πŸ“ Created job {job_id}")
810
  return job_id
811
 
812
  def update_job_status(job_id: str, status: JobStatus, progress: int, message: str, result=None):
 
834
  "status": status.value,
835
  "progress": progress,
836
  "message": message,
 
837
  "timestamp": time.time()
838
  }
839
 
 
844
  }
845
 
846
  requests.post(callback_url, json=callback_data, timeout=5)
 
847
  except Exception as e:
848
  print(f"⚠️ Callback failed: {e}")
849
 
850
  return True
851
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
852
  def generate_storybook_background(job_id: str):
853
+ """Background task to generate storybook"""
854
  try:
 
855
  if HF_TOKEN:
856
  ensure_dataset_exists()
857
 
858
  job_data = job_storage[job_id]
859
  story_request = StorybookRequest(**job_data["request"])
 
860
  project_id = story_request.project_id or story_request.story_title.replace(' ', '_').lower()
861
 
 
 
 
 
 
 
 
862
  update_job_status(job_id, JobStatus.PROCESSING, 5, "Starting generation...")
863
 
864
  total_scenes = len(story_request.scenes)
 
868
 
869
  for i, scene in enumerate(story_request.scenes):
870
  progress = 5 + int(((i + 1) / total_scenes) * 90)
871
+ update_job_status(job_id, JobStatus.PROCESSING, progress, f"Generating page {i+1}/{total_scenes}")
 
 
 
 
 
 
872
 
873
  try:
 
 
 
 
874
  image = generate_image_simple(
875
  scene.visual,
876
  story_request.model_choice,
 
879
  story_request.consistency_seed
880
  )
881
 
 
882
  local_filepath, local_filename = save_image_to_local(image, scene.visual, story_request.style)
 
883
 
 
884
  hf_url = None
885
  if HF_TOKEN:
886
+ hf_url = upload_image_to_hf_dataset(image, project_id, i + 1, scene.visual, story_request.style)
 
 
 
 
 
 
 
887
  if hf_url:
888
  image_urls.append(hf_url)
 
889
 
890
+ generated_pages.append({
 
891
  "page_number": i + 1,
892
  "image_url": hf_url or f"local://{local_filepath}",
 
893
  "text_content": scene.text,
894
+ "visual_description": scene.visual
895
+ })
 
 
 
 
 
896
 
897
  except Exception as e:
898
  error_msg = f"Failed to generate page {i+1}: {str(e)}"
 
899
  update_job_status(job_id, JobStatus.FAILED, progress, error_msg)
900
  return
901
 
 
 
 
 
 
 
 
902
  result = {
903
  "story_title": story_request.story_title,
904
  "project_id": project_id,
905
  "total_pages": total_scenes,
906
  "generated_pages": len(generated_pages),
 
 
907
  "image_urls": image_urls,
908
+ "pages": generated_pages
 
 
 
 
 
909
  }
910
 
 
911
  if project_id and image_urls:
912
+ report_to_tracker(project_id=project_id, service_type="image", status="completed", file_urls=image_urls)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
913
 
914
+ update_job_status(job_id, JobStatus.COMPLETED, 100, "Storybook completed!", result)
 
915
 
916
  except Exception as e:
917
  error_msg = f"Story generation failed: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
918
  update_job_status(job_id, JobStatus.FAILED, 0, error_msg)
919
 
 
 
 
920
  def generate_infographic_background(job_id: str):
921
+ """Background task to generate infographic"""
922
  try:
923
  job_data = job_storage[job_id]
924
  request = InfographicRequest(**job_data["request"])
 
925
  project_id = request.project_id or f"infographic_{int(time.time())}"
926
 
 
 
 
 
927
  update_job_status(job_id, JobStatus.PROCESSING, 5, "Starting infographic generation...")
928
 
 
929
  items = request.items[:10] if len(request.items) > 10 else request.items
 
930
 
 
931
  background_image = None
932
  if request.background_url:
933
  try:
 
934
  response = requests.get(request.background_url, timeout=30)
935
  if response.status_code == 200:
936
  background_image = Image.open(io.BytesIO(response.content)).convert('RGB')
 
 
 
937
  except Exception as e:
938
+ print(f"⚠️ Error loading background: {e}")
939
 
 
 
 
940
  generated_images = []
941
  image_urls = []
942
  items_data = []
943
 
944
  for i, item in enumerate(items):
945
  progress = 10 + int(((i + 1) / len(items)) * 70)
946
+ update_job_status(job_id, JobStatus.PROCESSING, progress, f"Generating {item.name} ({i+1}/{len(items)})")
 
 
 
 
 
 
947
 
948
  try:
949
+ enhanced_prompt = f"professional food photography, {item.prompt}, isolated on white background"
950
+ image = generate_image_simple(enhanced_prompt, "dreamshaper-8", "food_photography", i + 1, None)
 
 
 
 
 
 
 
 
 
 
 
 
 
951
  generated_images.append(image)
952
 
 
953
  local_filepath, _ = save_image_to_local(image, item.name, "infographic")
954
 
 
955
  hf_url = None
956
  if HF_TOKEN and i < 5:
957
+ hf_url = upload_image_to_hf_dataset(image, project_id, i + 1, item.name, "infographic")
 
 
 
 
 
 
958
  if hf_url:
959
  image_urls.append(hf_url)
960
 
 
964
  "prompt": item.prompt
965
  })
966
 
 
 
967
  except Exception as e:
 
 
968
  placeholder = Image.new('RGB', (512, 512), color=(240, 240, 240))
969
  generated_images.append(placeholder)
970
 
 
971
  gc.collect()
972
 
973
+ update_job_status(job_id, JobStatus.PROCESSING, 85, "Creating layout...")
974
 
 
975
  layout_generator = InfographicLayoutGenerator()
976
  items_with_prompts = [(item.name, item.prompt) for item in items]
977
+ infographic = layout_generator.create_food_infographic(request.title, items_with_prompts, generated_images, background_image)
978
 
 
 
 
 
 
 
 
 
979
  infographic_path = os.path.join(PERSISTENT_IMAGE_DIR, f"infographic_{project_id}.png")
980
  infographic.save(infographic_path)
 
981
 
 
982
  infographic_url = None
983
  if HF_TOKEN:
984
  img_bytes = io.BytesIO()
985
  infographic.save(img_bytes, format='PNG')
986
  img_bytes.seek(0)
987
+ infographic_url = upload_to_hf_dataset(img_bytes.getvalue(), f"infographic_{project_id}.png", f"projects/{project_id}")
 
 
 
 
 
 
 
988
 
 
989
  if background_image:
990
  background_image.close()
991
  for img in generated_images:
 
994
  infographic.close()
995
  gc.collect()
996
 
 
 
 
997
  result = {
998
  "project_id": project_id,
999
  "title": request.title,
1000
  "total_items": len(items),
1001
  "infographic_url": infographic_url or f"local://{infographic_path}",
1002
  "item_images": image_urls,
1003
+ "items": items_data
 
1004
  }
1005
 
 
1006
  if project_id and infographic_url:
1007
+ report_to_tracker(project_id=project_id, service_type="infographic", status="completed", file_urls=[infographic_url] + image_urls)
 
 
 
 
 
 
 
 
 
 
 
 
 
1008
 
1009
+ update_job_status(job_id, JobStatus.COMPLETED, 100, f"βœ… Infographic created with {len(items)} items", result)
1010
 
1011
  except Exception as e:
1012
  error_msg = f"Infographic generation failed: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
1013
  update_job_status(job_id, JobStatus.FAILED, 0, error_msg)
1014
  gc.collect()
1015
 
1016
  # FASTAPI ENDPOINTS
1017
  @app.post("/api/generate-storybook")
1018
  async def generate_storybook(request: dict, background_tasks: BackgroundTasks):
 
1019
  try:
 
 
1020
  if 'consistency_seed' not in request:
1021
  request['consistency_seed'] = random.randint(1000, 9999)
 
1022
  if 'project_id' not in request:
1023
  request['project_id'] = request.get('story_title', 'unknown').replace(' ', '_').lower()
1024
 
1025
  story_request = StorybookRequest(**request)
 
 
 
 
1026
  job_id = create_job(story_request)
1027
  background_tasks.add_task(generate_storybook_background, job_id)
1028
 
 
1030
  "status": "success",
1031
  "job_id": job_id,
1032
  "story_title": story_request.story_title,
 
1033
  "total_scenes": len(story_request.scenes),
 
1034
  "estimated_time_seconds": len(story_request.scenes) * 35
1035
  }
 
1036
  except Exception as e:
 
1037
  raise HTTPException(status_code=500, detail=str(e))
1038
 
 
 
 
1039
  @app.post("/api/generate-infographic")
1040
  async def generate_infographic(request: dict, background_tasks: BackgroundTasks):
 
1041
  try:
 
 
1042
  if 'project_id' not in request:
1043
  request['project_id'] = f"infographic_{int(time.time())}"
1044
 
1045
  infographic_request = InfographicRequest(**request)
 
 
 
 
 
1046
  job_id = str(uuid.uuid4())
1047
  job_storage[job_id] = {
1048
  "status": JobStatus.PENDING,
 
1053
  "created_at": time.time(),
1054
  "updated_at": time.time()
1055
  }
 
 
1056
  background_tasks.add_task(generate_infographic_background, job_id)
1057
 
1058
  return {
1059
  "status": "success",
1060
  "job_id": job_id,
1061
  "title": infographic_request.title,
 
1062
  "total_items": len(infographic_request.items),
1063
  "estimated_time_seconds": min(len(infographic_request.items) * 30, 300)
1064
  }
 
1065
  except Exception as e:
 
1066
  raise HTTPException(status_code=500, detail=str(e))
1067
 
1068
  @app.get("/api/job-status/{job_id}")
1069
  async def get_job_status(job_id: str):
 
1070
  job_data = job_storage.get(job_id)
1071
  if not job_data:
1072
  raise HTTPException(status_code=404, detail="Job not found")
 
1079
  "result": job_data["result"]
1080
  }
1081
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1082
  @app.get("/api/health")
1083
  async def health():
 
1084
  return {
1085
  "status": "healthy",
1086
  "service": "storybook-infographic-generator",
 
1087
  "active_jobs": len(job_storage),
1088
  "models_loaded": list(model_cache.keys())
1089
  }
1090
 
1091
  @app.get("/api/memory-status")
1092
  async def memory_status():
 
1093
  return get_memory_usage()
1094
 
1095
  @app.post("/api/clear-memory")
1096
  async def clear_memory_api(request: MemoryClearanceRequest):
 
1097
  return clear_memory(
1098
  clear_models=request.clear_models,
1099
  clear_jobs=request.clear_jobs,
 
1103
 
1104
  @app.get("/api/local-images")
1105
  async def get_local_images():
 
1106
  return get_local_storage_info()
1107
 
1108
+ # GRADIO INTERFACE
 
 
1109
  def test_infographic(title, items_text, background_image=None):
 
1110
  try:
 
1111
  items = []
1112
  for item in items_text.split(';'):
1113
  if ':' in item:
 
1117
  if not items:
1118
  return None, "❌ Please enter at least one item"
1119
 
 
1120
  if len(items) > 10:
1121
  items = items[:10]
 
 
 
1122
 
 
1123
  generated_images = []
1124
+ for i, item in enumerate(items):
1125
+ enhanced_prompt = f"professional food photography, {item['prompt']}, isolated on white background"
1126
+ img = generate_image_simple(enhanced_prompt, "dreamshaper-8", "food_photography", i+1, None)
1127
+ generated_images.append(img)
1128
+ time.sleep(0.5)
1129
+
1130
+ layout_gen = InfographicLayoutGenerator()
1131
+ items_with_prompts = [(item['name'], item['prompt']) for item in items]
1132
+ infographic = layout_gen.create_food_infographic(title, items_with_prompts, generated_images, background_image)
1133
+
1134
+ for img in generated_images:
1135
+ img.close()
1136
+ gc.collect()
1137
+
1138
+ return infographic, f"βœ… Generated infographic with {len(items)} items"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1139
 
1140
  except Exception as e:
 
 
1141
  return None, f"❌ Error: {str(e)}"
1142
 
 
1143
  def create_gradio_interface():
1144
  def generate_test(prompt, model_choice, style_choice):
1145
  if not prompt.strip():
1146
  return None, "❌ Please enter a prompt"
 
1147
  try:
1148
  image = generate_image_simple(prompt, model_choice, style_choice, 1)
1149
  filepath, filename = save_image_to_local(image, prompt, style_choice)
 
1151
  except Exception as e:
1152
  return None, f"❌ Error: {str(e)}"
1153
 
 
 
 
1154
  with gr.Blocks(title="Storybook & Infographic Generator") as demo:
1155
+ gr.Markdown("# 🎨 Storybook & Infographic Generator")
1156
 
1157
  with gr.Tab("Storybook Generator"):
1158
  with gr.Row():
 
1161
  style = gr.Dropdown(choices=["childrens_book", "realistic", "fantasy", "anime"], value="anime", label="Style")
1162
  prompt = gr.Textbox(label="Prompt", lines=3)
1163
  btn = gr.Button("Generate", variant="primary")
 
1164
  with gr.Column():
1165
  output = gr.Image(label="Generated Image", height=500)
1166
  status = gr.Textbox(label="Status")
 
1167
  btn.click(fn=generate_test, inputs=[prompt, model, style], outputs=[output, status])
1168
 
1169
  with gr.Tab("Infographic Generator"):
 
1172
  infographic_title = gr.Textbox(
1173
  label="Title (Supports Chinese)",
1174
  value="7μ›”μ˜ λ§› (Taste of July)",
1175
+ placeholder="Enter infographic title..."
1176
  )
1177
  infographic_items = gr.Textbox(
1178
  label="Items (name:prompt;name2:prompt2)",
1179
  value="ι…Έθœ (Kimchi):traditional korean cabbage kimchi;萝卜 (Kkakdugi):korean radish kimchi, cubed;ζ΄Ύ (Pa-kimchi):green onion kimchi",
1180
+ lines=5
 
1181
  )
1182
+ background_image = gr.Image(label="Custom Background (optional)", type="pil", height=200)
 
 
 
 
 
 
 
1183
  infographic_btn = gr.Button("Generate Infographic", variant="primary", size="lg")
 
1184
  with gr.Column():
1185
  infographic_output = gr.Image(label="Generated Infographic", height=700)
1186
  infographic_status = gr.Textbox(label="Status")
1187
 
1188
  infographic_btn.click(
1189
+ fn=test_infographic,
1190
  inputs=[infographic_title, infographic_items, background_image],
1191
  outputs=[infographic_output, infographic_status]
1192
  )
 
 
 
 
 
 
 
 
1193
 
1194
  return demo
1195
 
 
1198
  @app.get("/")
1199
  async def root():
1200
  return {
1201
+ "message": "Storybook & Infographic Generator API",
 
 
1202
  "endpoints": {
1203
  "generate_storybook": "POST /api/generate-storybook",
1204
  "generate_infographic": "POST /api/generate-infographic",
1205
  "status": "GET /api/job-status/{job_id}",
1206
+ "health": "GET /api/health"
 
1207
  },
1208
  "ui": "/ui"
1209
  }
 
1213
 
1214
  if os.environ.get('SPACE_ID'):
1215
  print("πŸš€ Running on Hugging Face Spaces")
 
 
1216
  gr.mount_gradio_app(app, demo, path="/ui")
1217
  uvicorn.run(app, host="0.0.0.0", port=7860, log_level="info")
1218
  else: