tomo2chin2 commited on
Commit
0e7da95
·
verified ·
1 Parent(s): 80b6431

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +199 -113
app.py CHANGED
@@ -171,8 +171,8 @@ def generate_html_from_text(text, temperature=0.3):
171
  - フォント指定:
172
  ```html
173
  <style>
174
- @ import url('https ://fonts.googleapis.com/css2?family=Kaisei+Decol&family=Yomogi&family=Zen+Kurenaido&display=swap');
175
- @ import url('https ://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css');
176
  </style>
177
  ```
178
  ### 5. レイアウト
@@ -184,16 +184,6 @@ def generate_html_from_text(text, temperature=0.3):
184
  - 横幅は100%
185
  - 重要な要素は中央寄り、補足情報は周辺部に配置
186
 
187
- ### 重要なレイアウト注意事項
188
- - Font Awesomeアイコンと文字が重ならないよう適切なマージンを設定する
189
- - アイコンには必ず`margin-right: 10px;`や`margin-left: 10px;`などの余白を設定する
190
- - アイコンと文字列は必ず`display: inline-block;`または`display: inline-flex;`で配置し、`align-items: center;`を使用する
191
- - 複数のアイコンを配置する場合は十分な余白を確保する
192
- - レイアウト崩れを防ぐために、親要素には`overflow: hidden;`を適用する
193
- - アイコンの配置はコンテンツフローを妨げないよう注意する
194
- - カード内でのアイコン配置は、絶対位置指定ではなく相対位置で設定する
195
- - モバイル表示も考慮し、レスポンシブ対応を徹底する
196
-
197
  ## グラフィックレコーディング表現技法
198
  - テキストと視覚要素のバランスを重視(文字情報の50%以上をFont Awesomeアイコンで視覚的に補完)
199
  - キーワードを囲み線や色で強調し、関連するFont Awesomeアイコンを必ず添える
@@ -363,8 +353,7 @@ def trim_image_whitespace(image, threshold=250, padding=10):
363
  logger.info(f"画像をトリミングしました: 元サイズ {width}x{height} → トリミング後 {trimmed.width}x{trimmed.height}")
364
  return trimmed
365
 
366
- # 非同期スクリプトを使わず、同期的なスクリプトのみ使用する改善版
367
-
368
  def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0,
369
  trim_whitespace: bool = True) -> Image.Image:
370
  """
@@ -372,14 +361,14 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
372
 
373
  Args:
374
  html_code: The HTML source code string.
375
- extension_percentage: Percentage of extra space to add vertically.
376
  trim_whitespace: Whether to trim excess whitespace from the image.
377
 
378
  Returns:
379
- A PIL Image object of the screenshot.
380
  """
381
- tmp_path = None
382
- driver = None
383
 
384
  # 1) Save HTML code to a temporary file
385
  try:
@@ -389,7 +378,7 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
389
  logger.info(f"HTML saved to temporary file: {tmp_path}")
390
  except Exception as e:
391
  logger.error(f"Error writing temporary HTML file: {e}")
392
- return Image.new('RGB', (1, 1), color=(0, 0, 0))
393
 
394
  # 2) Headless Chrome(Chromium) options
395
  options = Options()
@@ -397,6 +386,7 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
397
  options.add_argument("--no-sandbox")
398
  options.add_argument("--disable-dev-shm-usage")
399
  options.add_argument("--force-device-scale-factor=1")
 
400
  options.add_argument("--disable-features=NetworkService")
401
  options.add_argument("--dns-prefetch-disable")
402
 
@@ -405,7 +395,7 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
405
  driver = webdriver.Chrome(options=options)
406
  logger.info("WebDriver initialized.")
407
 
408
- # 3) 初期ウィンドウサイズを設定
409
  initial_width = 1200
410
  initial_height = 1000
411
  driver.set_window_size(initial_width, initial_height)
@@ -413,58 +403,97 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
413
  logger.info(f"Navigating to {file_url}")
414
  driver.get(file_url)
415
 
416
- # 4) ページ読み込み待機
417
  logger.info("Waiting for body element...")
418
  WebDriverWait(driver, 15).until(
419
  EC.presence_of_element_located((By.TAG_NAME, "body"))
420
  )
421
- logger.info("Body element found. Waiting for resource loading...")
422
-
423
- # 5) 基本的なリソース読み込み待機 - タイムアウト回避
424
- time.sleep(3)
425
-
426
- # Font Awesome読み込み確認 - 非同期を使わない
427
- logger.info("Checking for Font Awesome resources...")
428
- fa_count = driver.execute_script("""
429
- var icons = document.querySelectorAll('.fa, .fas, .far, .fab, [class*="fa-"]');
430
- return icons.length;
431
- """)
432
- logger.info(f"Found {fa_count} Font Awesome elements")
433
-
434
- # リソース読み込み状態を確認
435
- doc_ready = driver.execute_script("return document.readyState;")
436
- logger.info(f"Document ready state: {doc_ready}")
437
-
438
- # Font Awesomeが多い場合は追加待機
439
- if fa_count > 50:
440
- logger.info("Many Font Awesome icons detected, waiting additional time")
441
- time.sleep(2)
442
 
443
- # 6) コンテンツレンダリングのためのスクロール処理 - 同期的に実行
444
- logger.info("Performing content rendering scroll...")
445
- total_height = driver.execute_script("return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);")
446
- viewport_height = driver.execute_script("return window.innerHeight;")
447
- scrolls_needed = max(1, total_height // viewport_height)
448
 
449
- for i in range(scrolls_needed + 1):
450
- scroll_pos = i * (viewport_height - 200) # オーバーラップさせる
451
- driver.execute_script(f"window.scrollTo(0, {scroll_pos});")
452
- time.sleep(0.2) # 短い待機
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
453
 
454
- # トップに戻る
455
- driver.execute_script("window.scrollTo(0, 0);")
456
- time.sleep(0.5)
457
- logger.info("Scroll rendering completed")
458
-
459
- # 7) スクロールバーを非表示に
460
- driver.execute_script("""
461
- document.documentElement.style.overflow = 'hidden';
462
- document.body.style.overflow = 'hidden';
463
- """)
464
- logger.info("Scrollbars hidden")
465
-
466
- # 8) ページの寸法を取得
467
- dimensions = driver.execute_script("""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  return {
469
  width: Math.max(
470
  document.documentElement.scrollWidth,
@@ -483,80 +512,137 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
483
  document.body ? document.body.clientHeight : 0
484
  )
485
  };
486
- """)
487
- scroll_width = dimensions['width']
488
- scroll_height = dimensions['height']
489
- logger.info(f"Detected dimensions: width={scroll_width}, height={scroll_height}")
490
-
491
- # 再検証 - 短いスクロールで再確認
492
- driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
493
- time.sleep(0.5)
494
- driver.execute_script("window.scrollTo(0, 0);")
495
- time.sleep(0.5)
496
-
497
- dimensions_after = driver.execute_script("return {height: Math.max(document.documentElement.scrollHeight, document.body.scrollHeight)};")
498
- scroll_height = max(scroll_height, dimensions_after['height'])
499
- logger.info(f"After scroll check, height={scroll_height}")
500
-
501
- # 最小/最大値の設定
502
- scroll_width = max(scroll_width, 100)
503
- scroll_height = max(scroll_height, 100)
504
- scroll_width = min(scroll_width, 2000)
505
- scroll_height = min(scroll_height, 4000)
506
-
507
- # 9) レイアウト安定化のための単純な待機 - タイムアウト回避
508
- logger.info("Waiting for layout stabilization...")
509
- time.sleep(2)
 
 
 
 
 
 
 
 
510
 
511
- # 10) 高さに余白を追加
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
512
  adjusted_height = int(scroll_height * (1 + extension_percentage / 100.0))
513
- adjusted_height = max(adjusted_height, scroll_height, 100)
 
514
  logger.info(f"Adjusted height calculated: {adjusted_height} (extension: {extension_percentage}%)")
515
 
516
- # 11) ウィンドウサイズを調整
517
  adjusted_width = scroll_width
518
  logger.info(f"Resizing window to: width={adjusted_width}, height={adjusted_height}")
519
  driver.set_window_size(adjusted_width, adjusted_height)
520
- time.sleep(1)
521
 
522
- # リソース状態を確認 - 同期的スクリプト
523
- resource_state = driver.execute_script("""
 
 
 
 
524
  return {
525
  readyState: document.readyState,
526
  resourcesComplete: !document.querySelector('img:not([complete])') &&
527
- !document.querySelector('link[rel="stylesheet"]:not([loaded])')
528
  };
529
- """)
530
- logger.info(f"Resource state: {resource_state}")
531
-
532
- if resource_state['readyState'] != 'complete':
533
- logger.info("Document still loading, waiting additional time...")
 
 
 
 
 
 
 
 
534
  time.sleep(1)
535
-
536
- # トップにスクロール
537
- driver.execute_script("window.scrollTo(0, 0);")
538
- time.sleep(0.5)
539
- logger.info("Scrolled to top.")
540
 
541
- # 12) スクリーンショット取得
542
  logger.info("Taking screenshot...")
543
  png = driver.get_screenshot_as_png()
544
  logger.info("Screenshot taken successfully.")
545
 
546
- # PIL画像に変換
547
  img = Image.open(BytesIO(png))
 
 
548
  logger.info(f"Screenshot dimensions: {img.width}x{img.height}")
549
 
550
- # 余白トリミング
551
  if trim_whitespace:
 
552
  img = trim_image_whitespace(img, threshold=248, padding=20)
553
  logger.info(f"Trimmed dimensions: {img.width}x{img.height}")
554
 
555
  return img
556
 
557
  except Exception as e:
558
- logger.error(f"Error during screenshot generation: {e}")
559
- return Image.new('RGB', (1, 1), color=(0, 0, 0))
560
  finally:
561
  logger.info("Cleaning up...")
562
  if driver:
@@ -571,7 +657,7 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
571
  logger.info(f"Temporary file {tmp_path} removed.")
572
  except Exception as e:
573
  logger.error(f"Error removing temporary file {tmp_path}: {e}")
574
-
575
  # --- Geminiを使った新しい関数 ---
576
  def text_to_screenshot(text: str, extension_percentage: float, temperature: float = 0.3, trim_whitespace: bool = True) -> Image.Image:
577
  """テキストをGemini APIでHTMLに変換し、スクリーンショットを生成する統合関数"""
@@ -795,4 +881,4 @@ app = gr.mount_gradio_app(app, iface, path="/")
795
  if __name__ == "__main__":
796
  import uvicorn
797
  logger.info("Starting Uvicorn server for local development...")
798
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
171
  - フォント指定:
172
  ```html
173
  <style>
174
+ @ import url('https://fonts.googleapis.com/css2?family=Kaisei+Decol&family=Yomogi&family=Zen+Kurenaido&display=swap');
175
+ @ import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css');
176
  </style>
177
  ```
178
  ### 5. レイアウト
 
184
  - 横幅は100%
185
  - 重要な要素は中央寄り、補足情報は周辺部に配置
186
 
 
 
 
 
 
 
 
 
 
 
187
  ## グラフィックレコーディング表現技法
188
  - テキストと視覚要素のバランスを重視(文字情報の50%以上をFont Awesomeアイコンで視覚的に補完)
189
  - キーワードを囲み線や色で強調し、関連するFont Awesomeアイコンを必ず添える
 
353
  logger.info(f"画像をトリミングしました: 元サイズ {width}x{height} → トリミング後 {trimmed.width}x{trimmed.height}")
354
  return trimmed
355
 
356
+ # --- Core Screenshot Logic ---
 
357
  def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0,
358
  trim_whitespace: bool = True) -> Image.Image:
359
  """
 
361
 
362
  Args:
363
  html_code: The HTML source code string.
364
+ extension_percentage: Percentage of extra space to add vertically (e.g., 4 means 4% total).
365
  trim_whitespace: Whether to trim excess whitespace from the image.
366
 
367
  Returns:
368
+ A PIL Image object of the screenshot. Returns a 1x1 black image on error.
369
  """
370
+ tmp_path = None # 初期化
371
+ driver = None # 初期化
372
 
373
  # 1) Save HTML code to a temporary file
374
  try:
 
378
  logger.info(f"HTML saved to temporary file: {tmp_path}")
379
  except Exception as e:
380
  logger.error(f"Error writing temporary HTML file: {e}")
381
+ return Image.new('RGB', (1, 1), color=(0, 0, 0)) # エラー時は黒画像
382
 
383
  # 2) Headless Chrome(Chromium) options
384
  options = Options()
 
386
  options.add_argument("--no-sandbox")
387
  options.add_argument("--disable-dev-shm-usage")
388
  options.add_argument("--force-device-scale-factor=1")
389
+ # Font Awesomeが読み込まれない場合があるため、読み込み待機時間を長く設定
390
  options.add_argument("--disable-features=NetworkService")
391
  options.add_argument("--dns-prefetch-disable")
392
 
 
395
  driver = webdriver.Chrome(options=options)
396
  logger.info("WebDriver initialized.")
397
 
398
+ # 3) 初期ウィンドウサイズを設定(コンテンツの種類に関わらず同じサイズ)
399
  initial_width = 1200
400
  initial_height = 1000
401
  driver.set_window_size(initial_width, initial_height)
 
403
  logger.info(f"Navigating to {file_url}")
404
  driver.get(file_url)
405
 
406
+ # 4) Wait for page load with extended timeout
407
  logger.info("Waiting for body element...")
408
  WebDriverWait(driver, 15).until(
409
  EC.presence_of_element_located((By.TAG_NAME, "body"))
410
  )
411
+ logger.info("Body element found. Waiting for potential resource loading...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
 
413
+ # リソース読み込みの待機時間
414
+ time.sleep(3) # 初期待機
 
 
 
415
 
416
+ # 5) Font Awesomeと外部リソースの読み込み完了を確認
417
+ try:
418
+ # 修正後のresource_check_script: arrow functionを廃止し、function式に置換
419
+ resource_check_script = """
420
+ return new Promise(function(resolve) {
421
+ function checkFontAwesome() {
422
+ var icons = document.querySelectorAll('.fa, .fas, .far, .fab, [class*=\\"fa-\\" ]');
423
+ if (icons.length > 0) {
424
+ console.log('Font Awesome icons found:', icons.length);
425
+ document.fonts.ready.then(function() {
426
+ console.log('All fonts loaded');
427
+ setTimeout(resolve, 1000);
428
+ });
429
+ } else {
430
+ document.fonts.ready.then(function() { setTimeout(resolve, 500); });
431
+ }
432
+ }
433
+ if (document.readyState === 'complete') {
434
+ checkFontAwesome();
435
+ } else {
436
+ window.addEventListener('load', checkFontAwesome);
437
+ }
438
+ });
439
+ """
440
+
441
+ logger.info("Waiting for Font Awesome and other resources to load...")
442
+ driver.set_script_timeout(15) # 15秒のタイムアウト
443
+ driver.execute_async_script("const callback = arguments[arguments.length - 1]; " + resource_check_script.strip() + ".then(callback);")
444
+ logger.info("Resources loaded successfully")
445
+ except Exception as e:
446
+ logger.warning(f"Resource loading check failed: {e}. Using fallback wait.")
447
+ time.sleep(8) # より長い待機時間を設定
448
 
449
+ # 6) スクロールを制御してコンテンツ全体が描画されるようにする
450
+ try:
451
+ // 修正後のscroll_script: function式に置換
452
+ scroll_script = """
453
+ return new Promise(function(resolve) {
454
+ var height = Math.max(
455
+ document.documentElement.scrollHeight,
456
+ document.body.scrollHeight
457
+ );
458
+ var viewportHeight = window.innerHeight;
459
+ var scrollStep = Math.floor(viewportHeight * 0.8);
460
+ var currentPos = 0;
461
+ function scrollDown() {
462
+ if (currentPos < height) {
463
+ window.scrollTo(0, currentPos);
464
+ currentPos += scrollStep;
465
+ setTimeout(scrollDown, 100);
466
+ } else {
467
+ window.scrollTo(0, 0);
468
+ setTimeout(resolve, 300);
469
+ }
470
+ }
471
+ scrollDown();
472
+ });
473
+ """
474
+
475
+ logger.info("Ensuring all content is rendered...")
476
+ driver.execute_async_script("const callback = arguments[arguments.length - 1]; " + scroll_script.strip() + ".then(callback);")
477
+ except Exception as e:
478
+ logger.warning(f"Content rendering check failed: {e}")
479
+ # スクロールを元の位置に戻す
480
+ driver.execute_script("window.scrollTo(0, 0);")
481
+ time.sleep(1)
482
+
483
+ # 7) Hide scrollbars via CSS
484
+ try:
485
+ driver.execute_script(
486
+ "document.documentElement.style.overflow = 'hidden';"
487
+ "document.body.style.overflow = 'hidden';"
488
+ )
489
+ logger.info("Scrollbars hidden via JS.")
490
+ except Exception as e:
491
+ logger.warning(f"Could not hide scrollbars via JS: {e}")
492
+
493
+ # 8) Get full page dimensions accurately with improved script
494
+ try:
495
+ // より正確なページ寸法を取得するためのJavaScriptコード
496
+ dimensions_script = """
497
  return {
498
  width: Math.max(
499
  document.documentElement.scrollWidth,
 
512
  document.body ? document.body.clientHeight : 0
513
  )
514
  };
515
+ """
516
+ dimensions = driver.execute_script(dimensions_script)
517
+ scroll_width = dimensions['width']
518
+ scroll_height = dimensions['height']
519
+
520
+ logger.info(f"Detected dimensions: width={scroll_width}, height={scroll_height}")
521
+
522
+ # スクロールして確認する追加の検証
523
+ driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
524
+ time.sleep(1) # スクロール完了を待つ
525
+ driver.execute_script("window.scrollTo(0, 0);")
526
+ time.sleep(1) # 元の位置に戻す
527
+
528
+ # 再検証
529
+ dimensions_after_scroll = driver.execute_script(dimensions_script)
530
+ scroll_height = max(scroll_height, dimensions_after_scroll['height'])
531
+
532
+ logger.info(f"After scroll check, height={scroll_height}")
533
+
534
+ # 最小値と最大値の設定
535
+ scroll_width = max(scroll_width, 100) # 最小幅
536
+ scroll_height = max(scroll_height, 100) # 最小高さ
537
+
538
+ scroll_width = min(scroll_width, 2000) # 最大幅
539
+ scroll_height = min(scroll_height, 6000) # 最大高さ
540
+
541
+ except Exception as e:
542
+ logger.error(f"Error getting page dimensions: {e}")
543
+ # フォールバックとしてデフォルト値を設定
544
+ scroll_width = 1200
545
+ scroll_height = 1000
546
+ logger.warning(f"Falling back to dimensions: width={scroll_width}, height={scroll_height}")
547
 
548
+ # 9) レイアウト安定化の確認
549
+ try:
550
+ // 修正後のstability_script: arrow functionをfunction式に置換
551
+ stability_script = """
552
+ return new Promise(function(resolve) {
553
+ var lastHeight = document.body.offsetHeight;
554
+ var lastWidth = document.body.offsetWidth;
555
+ var stableCount = 0;
556
+ function checkStability() {
557
+ var currentHeight = document.body.offsetHeight;
558
+ var currentWidth = document.body.offsetWidth;
559
+ if (currentHeight === lastHeight && currentWidth === lastWidth) {
560
+ stableCount++;
561
+ if (stableCount >= 3) {
562
+ resolve(true);
563
+ return;
564
+ }
565
+ } else {
566
+ stableCount = 0;
567
+ lastHeight = currentHeight;
568
+ lastWidth = currentWidth;
569
+ }
570
+ setTimeout(checkStability, 200);
571
+ }
572
+ checkStability();
573
+ });
574
+ """
575
+
576
+ logger.info("Checking layout stability...")
577
+ driver.execute_async_script("const callback = arguments[arguments.length - 1]; " + stability_script.strip() + ".then(callback);")
578
+ logger.info("Layout is stable")
579
+ except Exception as e:
580
+ logger.warning(f"Layout stability check failed: {e}")
581
+ time.sleep(2) # 追加待機
582
+
583
+ # 10) Calculate adjusted height with user-specified margin
584
  adjusted_height = int(scroll_height * (1 + extension_percentage / 100.0))
585
+ # Ensure adjusted height is not excessively large or small
586
+ adjusted_height = max(adjusted_height, scroll_height, 100) # 最小高さを確保
587
  logger.info(f"Adjusted height calculated: {adjusted_height} (extension: {extension_percentage}%)")
588
 
589
+ # 11) Set window size to full page dimensions
590
  adjusted_width = scroll_width
591
  logger.info(f"Resizing window to: width={adjusted_width}, height={adjusted_height}")
592
  driver.set_window_size(adjusted_width, adjusted_height)
593
+ logger.info("Waiting for layout stabilization after resize...")
594
 
595
+ # レイアウト安定化のための待機
596
+ time.sleep(4) # 統一した待機時間
597
+
598
+ # 外部リソースの読み込み状態を確認
599
+ try:
600
+ resource_state = driver.execute_script("""
601
  return {
602
  readyState: document.readyState,
603
  resourcesComplete: !document.querySelector('img:not([complete])') &&
604
+ !document.querySelector('link[rel="stylesheet"]:not([loaded])')
605
  };
606
+ """)
607
+ logger.info(f"Resource state: {resource_state}")
608
+
609
+ # ドキュメントの読み込みが完了していない場合、追加で待機
610
+ if resource_state['readyState'] != 'complete':
611
+ logger.info("Document still loading, waiting additional time...")
612
+ time.sleep(2)
613
+ except Exception as e:
614
+ logger.warning(f"Error checking resource state: {e}")
615
+
616
+ # Scroll to top just in case
617
+ try:
618
+ driver.execute_script("window.scrollTo(0, 0)")
619
  time.sleep(1)
620
+ logger.info("Scrolled to top.")
621
+ except Exception as e:
622
+ logger.warning(f"Could not scroll to top: {e}")
 
 
623
 
624
+ # 12) Take screenshot
625
  logger.info("Taking screenshot...")
626
  png = driver.get_screenshot_as_png()
627
  logger.info("Screenshot taken successfully.")
628
 
629
+ # Convert to PIL Image
630
  img = Image.open(BytesIO(png))
631
+
632
+ # 画像サイズの確認とログ
633
  logger.info(f"Screenshot dimensions: {img.width}x{img.height}")
634
 
635
+ # 余白トリミングが有効な場合
636
  if trim_whitespace:
637
+ # 余分な空白をトリミング
638
  img = trim_image_whitespace(img, threshold=248, padding=20)
639
  logger.info(f"Trimmed dimensions: {img.width}x{img.height}")
640
 
641
  return img
642
 
643
  except Exception as e:
644
+ logger.error(f"An error occurred during screenshot generation: {e}", exc_info=True)
645
+ return Image.new('RGB', (1, 1), color=(0, 0, 0)) # Return black 1x1 image on error
646
  finally:
647
  logger.info("Cleaning up...")
648
  if driver:
 
657
  logger.info(f"Temporary file {tmp_path} removed.")
658
  except Exception as e:
659
  logger.error(f"Error removing temporary file {tmp_path}: {e}")
660
+
661
  # --- Geminiを使った新しい関数 ---
662
  def text_to_screenshot(text: str, extension_percentage: float, temperature: float = 0.3, trim_whitespace: bool = True) -> Image.Image:
663
  """テキストをGemini APIでHTMLに変換し、スクリーンショットを生成する統合関数"""
 
881
  if __name__ == "__main__":
882
  import uvicorn
883
  logger.info("Starting Uvicorn server for local development...")
884
+ uvicorn.run(app, host="0.0.0.0", port=7860)