tomo2chin2 commited on
Commit
eecf50e
·
verified ·
1 Parent(s): 89a1f9f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +308 -51
app.py CHANGED
@@ -15,6 +15,7 @@ import tempfile
15
  import time
16
  import os
17
  import logging
 
18
 
19
  # 正しいGemini関連のインポート
20
  import google.generativeai as genai
@@ -30,6 +31,7 @@ class GeminiRequest(BaseModel):
30
  extension_percentage: float = 6.0 # デフォルト値6%
31
  temperature: float = 1.0 # デフォルト値1.0の温度パラメータ
32
  trim_whitespace: bool = True # 余白トリミングオプション(デフォルト有効)
 
33
 
34
  class ScreenshotRequest(BaseModel):
35
  """スクリーンショットリクエストモデル"""
@@ -37,7 +39,7 @@ class ScreenshotRequest(BaseModel):
37
  extension_percentage: float = 6.0 # デフォルト値6%
38
  trim_whitespace: bool = True # 余白トリミングオプション(デフォルト有効)
39
 
40
- def generate_html_from_text(text, temperature=1.0):
41
  """テキストからHTMLを生成する"""
42
  try:
43
  # APIキーの取得と設定
@@ -53,8 +55,29 @@ def generate_html_from_text(text, temperature=1.0):
53
  # Gemini APIの設定
54
  genai.configure(api_key=api_key)
55
 
56
- # システムプロンプト(リクエスト例と同じものを使用)
57
- system_instruction = """# グラフィックレコーディング風インフォグラフィック変換プロンプト V2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  ## 目的
59
  以下の内容を、超一流デザイナーが作成したような、日本語で完璧なグラフィックレコーディング風のHTMLインフォグラフィックに変換してください。情報設計とビジュアルデザインの両面で最高水準を目指します。
60
  手書き風の図形やFont Awesomeアイコンを大きく活用して内容を視覚的かつ直感的に表現します。
@@ -101,8 +124,8 @@ def generate_html_from_text(text, temperature=1.0):
101
  - フォント指定:
102
  ```html
103
  <style>
104
- @ import url('https ://fonts.googleapis.com/css2?family=Kaisei+Decol&family=Yomogi&family=Zen+Kurenaido&display=swap');
105
- @ import url('https ://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css');
106
  </style>
107
  ```
108
  ### 5. レイアウト
@@ -113,6 +136,81 @@ def generate_html_from_text(text, temperature=1.0):
113
  - 適切にグラスモーフィズムを活用(背後にぼかしたFont Awesomeアイコンを配置)
114
  - 横幅は100%
115
  - 重要な要素は中央寄り、補足情報は周辺部に配置
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  ## グラフィックレコーディング表現技法
117
  - テキストと視覚要素のバランスを重視(文字情報の50%以上をFont Awesomeアイコンで視覚的に補完)
118
  - キーワードを囲み線や色で強調し、関連するFont Awesomeアイコンを必ず添える
@@ -152,7 +250,7 @@ def generate_html_from_text(text, temperature=1.0):
152
  ーーー<ユーザーが入力(または添付)>ーーー"""
153
 
154
  # モデルを初期化して処理
155
- logger.info(f"Gemini APIにリクエストを送信: テキスト長さ = {len(text)}, 温度 = {temperature}")
156
 
157
  # モデル初期化とフォールバック処理
158
  try:
@@ -191,6 +289,10 @@ def generate_html_from_text(text, temperature=1.0):
191
  html_start += 7 # "```html" の長さ分進める
192
  html_code = raw_response[html_start:html_end].strip()
193
  logger.info(f"HTMLの生成に成功: 長さ = {len(html_code)}")
 
 
 
 
194
  return html_code
195
  else:
196
  # HTMLタグが見つからない場合、レスポンス全体を返す
@@ -201,6 +303,100 @@ def generate_html_from_text(text, temperature=1.0):
201
  logger.error(f"HTML生成中にエラーが発生: {e}", exc_info=True)
202
  raise Exception(f"Gemini APIでのHTML生成に失敗しました: {e}")
203
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  # 画像から余分な空白領域をトリミングする関数
205
  def trim_image_whitespace(image, threshold=250, padding=10):
206
  """
@@ -291,6 +487,10 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
291
  # Font Awesomeが読み込まれない場合があるため、読み込み待機時間を長く設定
292
  options.add_argument("--disable-features=NetworkService")
293
  options.add_argument("--dns-prefetch-disable")
 
 
 
 
294
 
295
  try:
296
  logger.info("Initializing WebDriver...")
@@ -312,8 +512,8 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
312
  )
313
  logger.info("Body element found. Waiting for potential resource loading...")
314
 
315
- # リソース読み込みの待機時間(コンテンツの種類に関わらず同じ待機時間)
316
- time.sleep(5) # 十分な待機時間
317
 
318
  # 5) Hide scrollbars via CSS
319
  try:
@@ -392,8 +592,8 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
392
  driver.set_window_size(adjusted_width, adjusted_height)
393
  logger.info("Waiting for layout stabilization after resize...")
394
 
395
- # レイアウト安定化のための待機
396
- time.sleep(4) # 統一した待機時間
397
 
398
  # 外部リソースの読み込み状態を確認
399
  try:
@@ -409,10 +609,43 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
409
  # ドキュメントの読み込みが完了していない場合、追加で待機
410
  if resource_state['readyState'] != 'complete':
411
  logger.info("Document still loading, waiting additional time...")
412
- time.sleep(2)
413
  except Exception as e:
414
  logger.warning(f"Error checking resource state: {e}")
415
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
  # Scroll to top just in case
417
  try:
418
  driver.execute_script("window.scrollTo(0, 0)")
@@ -459,11 +692,12 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
459
  logger.error(f"Error removing temporary file {tmp_path}: {e}")
460
 
461
  # --- Geminiを使った新しい関数 ---
462
- def text_to_screenshot(text: str, extension_percentage: float, temperature: float = 1.0, trim_whitespace: bool = True) -> Image.Image:
 
463
  """テキストをGemini APIでHTMLに変換し、スクリーンショットを生成する統合関数"""
464
  try:
465
- # 1. テキストからHTMLを生成(温度パラメータも渡す)
466
- html_code = generate_html_from_text(text, temperature)
467
 
468
  # 2. HTMLからスクリーンショットを生成
469
  return render_fullpage_screenshot(html_code, extension_percentage, trim_whitespace)
@@ -560,14 +794,15 @@ async def api_text_to_screenshot(request: GeminiRequest):
560
  テキストからHTMLインフォグラフィックを生成してスクリーンショットを返すAPIエンドポイント
561
  """
562
  try:
563
- logger.info(f"テキスト→スクリーンショットAPIリクエスト受信。テキスト長さ: {len(request.text)}, 拡張率: {request.extension_percentage}%, 温度: {request.temperature}")
564
 
565
- # テキストからHTMLを生成してスクリーンショットを作成(温度パラメータも渡す)
566
  pil_image = text_to_screenshot(
567
  request.text,
568
  request.extension_percentage,
569
  request.temperature,
570
- request.trim_whitespace
 
571
  )
572
 
573
  if pil_image.size == (1, 1):
@@ -588,14 +823,14 @@ async def api_text_to_screenshot(request: GeminiRequest):
588
 
589
  # --- Gradio Interface Definition ---
590
  # 入力モードの選択用Radioコンポーネント
591
- def process_input(input_mode, input_text, extension_percentage, temperature, trim_whitespace):
592
  """入力モードに応じて適切な処理を行う"""
593
  if input_mode == "HTML入力":
594
  # HTMLモードの場合は既存の処理
595
  return render_fullpage_screenshot(input_text, extension_percentage, trim_whitespace)
596
  else:
597
  # テキスト入力モードの場合はGemini APIを使用
598
- return text_to_screenshot(input_text, extension_percentage, temperature, trim_whitespace)
599
 
600
  # Gradio UIの定義
601
  with gr.Blocks(title="Full Page Screenshot (テキスト変換対応)", theme=gr.themes.Base()) as iface:
@@ -617,61 +852,83 @@ with gr.Blocks(title="Full Page Screenshot (テキスト変換対応)", theme=gr
617
  )
618
 
619
  with gr.Row():
620
- extension_percentage = gr.Slider(
621
- minimum=0,
622
- maximum=30,
623
- step=1.0,
624
- value=6, # デフォルト値6%
625
- label="上下高さ拡張率(%)"
626
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
627
 
628
- # 温度調整スライダー(テキストモード時のみ表示)
629
- temperature = gr.Slider(
630
- minimum=0.0,
631
- maximum=1.4,
632
- step=0.1,
633
- value=1.0, # デフォルト値1.0
634
- label="生成時の温度(創造性)",
635
- visible=False # 最初は非表示
636
- )
637
-
638
- # 余白トリミングオプション
639
- trim_whitespace = gr.Checkbox(
640
- label="余白を自動トリミング",
641
- value=True,
642
- info="生成される画像から余分な空白領域を自動的に削除します"
643
- )
644
 
645
- submit_btn = gr.Button("生成")
646
  output_image = gr.Image(type="pil", label="ページ全体のスクリーンショット")
647
 
648
- # 入力モード変更時のイベント処理(テキストモード時のみ温度スライダーを表示)
649
- def update_temperature_visibility(mode):
650
- # Gradio 4.x用のアップデート方法
651
- return {"visible": mode == "テキスト入力", "__type__": "update"}
 
 
652
 
653
  input_mode.change(
654
- fn=update_temperature_visibility,
655
  inputs=input_mode,
656
- outputs=temperature
657
  )
658
 
659
  # 生成ボタンクリック時のイベント処理
660
  submit_btn.click(
661
  fn=process_input,
662
- inputs=[input_mode, input_text, extension_percentage, temperature, trim_whitespace],
663
  outputs=output_image
664
  )
665
 
666
- # 環境変数情報を表示
667
  gemini_model = os.environ.get("GEMINI_MODEL", "gemini-1.5-pro")
668
  gr.Markdown(f"""
 
 
 
 
 
669
  ## APIエンドポイント
670
  - `/api/screenshot` - HTMLコードからスクリーンショットを生成
671
  - `/api/text-to-screenshot` - テキストからインフォグラフィックスクリーンショットを生成
672
 
673
  ## 設定情報
674
  - 使用モデル: {gemini_model} (環境変数 GEMINI_MODEL で変更可能)
 
 
 
 
675
  """)
676
 
677
  # --- Mount Gradio App onto FastAPI ---
 
15
  import time
16
  import os
17
  import logging
18
+ import re
19
 
20
  # 正しいGemini関連のインポート
21
  import google.generativeai as genai
 
31
  extension_percentage: float = 6.0 # デフォルト値6%
32
  temperature: float = 1.0 # デフォルト値1.0の温度パラメータ
33
  trim_whitespace: bool = True # 余白トリミングオプション(デフォルト有効)
34
+ layout_type: str = "standard" # レイアウトタイプ(standard, spaced, compact)
35
 
36
  class ScreenshotRequest(BaseModel):
37
  """スクリーンショットリクエストモデル"""
 
39
  extension_percentage: float = 6.0 # デフォルト値6%
40
  trim_whitespace: bool = True # 余白トリミングオプション(デフォルト有効)
41
 
42
+ def generate_html_from_text(text, temperature=1.0, layout_type="standard"):
43
  """テキストからHTMLを生成する"""
44
  try:
45
  # APIキーの取得と設定
 
55
  # Gemini APIの設定
56
  genai.configure(api_key=api_key)
57
 
58
+ # レイアウトタイプに応じた追加設定
59
+ layout_adjustments = ""
60
+ if layout_type == "spaced":
61
+ layout_adjustments = """
62
+ ## 特別なスタイル設定
63
+ - 全てのテキスト要素には最低2rem以上の行間と要素間マージンを設定する
64
+ - 各カードのパディングを1.5倍に増やす
65
+ - 箇条書きリストのアイテム間は1.5emの間隔を確保する
66
+ - すべての文字サイズを15%大きくする
67
+ - 内容量に応じて自動的にグリッドやフレックスレイアウトを調整する
68
+ - テキストが重ならないように要素間に十分なスペースを強制的に設ける
69
+ """
70
+ elif layout_type == "compact":
71
+ layout_adjustments = """
72
+ ## 特別なスタイル設定
73
+ - レイアウトをよりコンパクトに配置
74
+ - 小さめのアイコンサイズを使用(標準の75%)
75
+ - フォントサイズを10%小さく設定
76
+ - テキスト量が多い場合は自動的に複数列に分割表示
77
+ """
78
+
79
+ # システムプロンプト(強化版)
80
+ system_instruction = f"""# グラフィックレコーディング風インフォグラフィック変換プロンプト V3
81
  ## 目的
82
  以下の内容を、超一流デザイナーが作成したような、日本語で完璧なグラフィックレコーディング風のHTMLインフォグラフィックに変換してください。情報設計とビジュアルデザインの両面で最高水準を目指します。
83
  手書き風の図形やFont Awesomeアイコンを大きく活用して内容を視覚的かつ直感的に表現します。
 
124
  - フォント指定:
125
  ```html
126
  <style>
127
+ @import url('https://fonts.googleapis.com/css2?family=Kaisei+Decol&family=Yomogi&family=Zen+Kurenaido&display=swap');
128
+ @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css');
129
  </style>
130
  ```
131
  ### 5. レイアウト
 
136
  - 適切にグラスモーフィズムを活用(背後にぼかしたFont Awesomeアイコンを配置)
137
  - 横幅は100%
138
  - 重要な要素は中央寄り、補足情報は周辺部に配置
139
+
140
+ ## レイアウト崩れ防止のための追加設定
141
+ - テキストが重ならないようにカードやセクション間に十分なマージンを設定(最低25px)
142
+ - テキスト量に応じて自動的に要素の高さが伸びるフレックスレイアウトを採用
143
+ - テキストが多い場合は自動的にフォントサイズを調整する機能を実装
144
+ - スマートスペーシング:内容量に応じて自動的に間隔を調整
145
+ - 以下のCSSを必ず追加して、テキストの重なりを防止する:
146
+ ```css
147
+ .card, .section, .info-box {
148
+ overflow: hidden;
149
+ display: flex;
150
+ flex-direction: column;
151
+ margin-bottom: 1.5rem;
152
+ min-height: fit-content;
153
+ }
154
+ .card-content {
155
+ flex: 1;
156
+ display: flex;
157
+ flex-direction: column;
158
+ }
159
+ .card-title {
160
+ font-weight: bold;
161
+ margin-bottom: 0.75rem;
162
+ line-height: 1.4;
163
+ }
164
+ .card-text {
165
+ line-height: 1.6;
166
+ margin-bottom: 0.5rem;
167
+ }
168
+ .info-item {
169
+ margin-bottom: 0.75rem;
170
+ line-height: 1.5;
171
+ }
172
+ li {
173
+ margin-bottom: 0.5rem;
174
+ line-height: 1.5;
175
+ }
176
+ p {
177
+ margin-bottom: 0.75rem;
178
+ line-height: 1.5;
179
+ }
180
+ ```
181
+
182
+ ## 自動テキスト調整機能
183
+ 以下のJavaScriptを必ず追加して、テキスト量に応じたフォントサイズの自動調整を行う:
184
+ ```html
185
+ <script>
186
+ document.addEventListener('DOMContentLoaded', function() {
187
+ // カード内のテキスト量に基づいてフォントサイズを調整
188
+ const cards = document.querySelectorAll('.card, .section, .info-box');
189
+ cards.forEach(card => {
190
+ const cardContent = card.querySelector('.card-content') || card;
191
+ const textLength = cardContent.textContent.length;
192
+
193
+ // テキスト量に応じてフォントサイズを調整
194
+ if (textLength > 200) {
195
+ card.style.fontSize = '0.9em';
196
+ }
197
+ if (textLength > 300) {
198
+ card.style.fontSize = '0.85em';
199
+ }
200
+
201
+ // カード内の要素間の余白を確保
202
+ const elements = card.querySelectorAll('p, h3, h4, ul, ol, .info-item');
203
+ elements.forEach(el => {
204
+ el.style.marginBottom = '0.75rem';
205
+ el.style.lineHeight = '1.5';
206
+ });
207
+ });
208
+ });
209
+ </script>
210
+ ```
211
+
212
+ {layout_adjustments}
213
+
214
  ## グラフィックレコーディング表現技法
215
  - テキストと視覚要素のバランスを重視(文字情報の50%以上をFont Awesomeアイコンで視覚的に補完)
216
  - キーワードを囲み線や色で強調し、関連するFont Awesomeアイコンを必ず添える
 
250
  ーーー<ユーザーが入力(または添付)>ーーー"""
251
 
252
  # モデルを初期化して処理
253
+ logger.info(f"Gemini APIにリクエストを送信: テキスト長さ = {len(text)}, 温度 = {temperature}, レイアウトタイプ = {layout_type}")
254
 
255
  # モデル初期化とフォールバック処理
256
  try:
 
289
  html_start += 7 # "```html" の長さ分進める
290
  html_code = raw_response[html_start:html_end].strip()
291
  logger.info(f"HTMLの生成に成功: 長さ = {len(html_code)}")
292
+
293
+ # HTML後処理:テキスト重なり防止のためのクラス・スタイル追加
294
+ html_code = enhance_html_for_layout(html_code)
295
+
296
  return html_code
297
  else:
298
  # HTMLタグが見つからない場合、レスポンス全体を返す
 
303
  logger.error(f"HTML生成中にエラーが発生: {e}", exc_info=True)
304
  raise Exception(f"Gemini APIでのHTML生成に失敗しました: {e}")
305
 
306
+ # HTMLを後処理してレイアウト崩れを防止する関数
307
+ def enhance_html_for_layout(html_code):
308
+ """HTMLを後処理してレイアウト崩れを防止するための調整を行う"""
309
+ try:
310
+ # テキスト重なり防止のためのスタイルが含まれているか確認
311
+ if "<style>" in html_code and ".card-content" not in html_code:
312
+ # スタイルタグを見つけてレイアウト崩れ防止用のCSSを追加
313
+ style_pos = html_code.find("</style>")
314
+ if style_pos != -1:
315
+ # レイアウト崩れ防止用のCSS
316
+ layout_css = """
317
+ /* レイアウト崩れ防止用スタイル */
318
+ .card, .section, .info-box {
319
+ overflow: hidden;
320
+ display: flex;
321
+ flex-direction: column;
322
+ margin-bottom: 1.5rem !important;
323
+ min-height: fit-content;
324
+ padding: 1rem !important;
325
+ }
326
+ .card-content {
327
+ flex: 1;
328
+ display: flex;
329
+ flex-direction: column;
330
+ }
331
+ .card-title {
332
+ font-weight: bold;
333
+ margin-bottom: 0.75rem !important;
334
+ line-height: 1.4;
335
+ }
336
+ .card-text {
337
+ line-height: 1.6;
338
+ margin-bottom: 0.5rem !important;
339
+ }
340
+ .info-item {
341
+ margin-bottom: 0.75rem !important;
342
+ line-height: 1.5;
343
+ }
344
+ li {
345
+ margin-bottom: 0.5rem !important;
346
+ line-height: 1.5;
347
+ }
348
+ p {
349
+ margin-bottom: 0.75rem !important;
350
+ line-height: 1.5;
351
+ }
352
+ """
353
+ # スタイルタグの閉じ直前に挿入
354
+ html_code = html_code[:style_pos] + layout_css + html_code[style_pos:]
355
+
356
+ # テキスト自動調整用のスクリプトが含まれていなければ追加
357
+ if "</body>" in html_code and "document.addEventListener('DOMContentLoaded'" not in html_code:
358
+ # ボディタグの閉じ直前にスクリプトを追加
359
+ body_end_pos = html_code.find("</body>")
360
+ if body_end_pos != -1:
361
+ # テキスト自動調整用のスクリプト
362
+ auto_adjust_script = """
363
+ <script>
364
+ document.addEventListener('DOMContentLoaded', function() {
365
+ // カード内のテキスト量に基づいてフォントサイズを調整
366
+ const cards = document.querySelectorAll('.card, .section, .info-box');
367
+ cards.forEach(card => {
368
+ const cardContent = card.querySelector('.card-content') || card;
369
+ const textLength = cardContent.textContent.length;
370
+
371
+ // テキスト量に応じてフォントサイズを調整
372
+ if (textLength > 200) {
373
+ card.style.fontSize = '0.9em';
374
+ }
375
+ if (textLength > 300) {
376
+ card.style.fontSize = '0.85em';
377
+ }
378
+
379
+ // カード内の要素間の余白を確保
380
+ const elements = card.querySelectorAll('p, h3, h4, ul, ol, .info-item');
381
+ elements.forEach(el => {
382
+ el.style.marginBottom = '0.75rem';
383
+ el.style.lineHeight = '1.5';
384
+ });
385
+ });
386
+ });
387
+ </script>
388
+ """
389
+ # ボディタグの閉じ直前に挿入
390
+ html_code = html_code[:body_end_pos] + auto_adjust_script + html_code[body_end_pos:]
391
+
392
+ # カード要素にクラス追加
393
+ html_code = re.sub(r'<div class="([^"]*)"([^>]*)>', lambda m: f'<div class="{m.group(1)} card"{m.group(2)}>', html_code)
394
+
395
+ return html_code
396
+ except Exception as e:
397
+ logger.warning(f"HTMLの後処理中にエラーが発生しました: {e}")
398
+ return html_code # エラー時は元のHTMLを返す
399
+
400
  # 画像から余分な空白領域をトリミングする関数
401
  def trim_image_whitespace(image, threshold=250, padding=10):
402
  """
 
487
  # Font Awesomeが読み込まれない場合があるため、読み込み待機時間を長く設定
488
  options.add_argument("--disable-features=NetworkService")
489
  options.add_argument("--dns-prefetch-disable")
490
+
491
+ # 新規追加:CORSエラー防止のためのフラグ
492
+ options.add_argument("--disable-web-security")
493
+ options.add_argument("--allow-file-access-from-files")
494
 
495
  try:
496
  logger.info("Initializing WebDriver...")
 
512
  )
513
  logger.info("Body element found. Waiting for potential resource loading...")
514
 
515
+ # リソース読み込みの待機時間(増加:5→8秒)
516
+ time.sleep(8) # 十分な待機時間
517
 
518
  # 5) Hide scrollbars via CSS
519
  try:
 
592
  driver.set_window_size(adjusted_width, adjusted_height)
593
  logger.info("Waiting for layout stabilization after resize...")
594
 
595
+ # レイアウト安定化のための待機(増加:4→6秒)
596
+ time.sleep(6) # 統一した待機時間
597
 
598
  # 外部リソースの読み込み状態を確認
599
  try:
 
609
  # ドキュメントの読み込みが完了していない場合、追加で待機
610
  if resource_state['readyState'] != 'complete':
611
  logger.info("Document still loading, waiting additional time...")
612
+ time.sleep(3) # 待機時間増加:2→3秒
613
  except Exception as e:
614
  logger.warning(f"Error checking resource state: {e}")
615
 
616
+ # レイアウト崩れ修正のためのJavaScriptを実行
617
+ try:
618
+ fix_layout_script = """
619
+ // カード内のテキスト量に基づいてフォントサイズを調整
620
+ const cards = document.querySelectorAll('.card, .section, .info-box');
621
+ cards.forEach(card => {
622
+ const cardContent = card.querySelector('.card-content') || card;
623
+ const textLength = cardContent.textContent.length;
624
+
625
+ // テキスト量に応じてフォントサイズを調整
626
+ if (textLength > 200) {
627
+ card.style.fontSize = '0.9em';
628
+ }
629
+ if (textLength > 300) {
630
+ card.style.fontSize = '0.85em';
631
+ }
632
+
633
+ // カード内の要素間の余白を確保
634
+ const elements = card.querySelectorAll('p, h3, h4, ul, ol, .info-item');
635
+ elements.forEach(el => {
636
+ el.style.marginBottom = '0.75rem';
637
+ el.style.lineHeight = '1.5';
638
+ });
639
+ });
640
+ """
641
+ driver.execute_script(fix_layout_script)
642
+ logger.info("Executed layout fix JavaScript")
643
+
644
+ # 追加の待機時間
645
+ time.sleep(1)
646
+ except Exception as e:
647
+ logger.warning(f"Failed to execute layout fix script: {e}")
648
+
649
  # Scroll to top just in case
650
  try:
651
  driver.execute_script("window.scrollTo(0, 0)")
 
692
  logger.error(f"Error removing temporary file {tmp_path}: {e}")
693
 
694
  # --- Geminiを使った新しい関数 ---
695
+ def text_to_screenshot(text: str, extension_percentage: float, temperature: float = 1.0,
696
+ trim_whitespace: bool = True, layout_type: str = "standard") -> Image.Image:
697
  """テキストをGemini APIでHTMLに変換し、スクリーンショットを生成する統合関数"""
698
  try:
699
+ # 1. テキストからHTMLを生成(温度パラメータとレイアウトタイプも渡す)
700
+ html_code = generate_html_from_text(text, temperature, layout_type)
701
 
702
  # 2. HTMLからスクリーンショットを生成
703
  return render_fullpage_screenshot(html_code, extension_percentage, trim_whitespace)
 
794
  テキストからHTMLインフォグラフィックを生成してスクリーンショットを返すAPIエンドポイント
795
  """
796
  try:
797
+ logger.info(f"テキスト→スクリーンショットAPIリクエスト受信。テキスト長さ: {len(request.text)}, 拡張率: {request.extension_percentage}%, 温度: {request.temperature}, レイアウト: {request.layout_type}")
798
 
799
+ # テキストからHTMLを生成してスクリーンショットを作成(温度パラメータとレイアウトタイプも渡す)
800
  pil_image = text_to_screenshot(
801
  request.text,
802
  request.extension_percentage,
803
  request.temperature,
804
+ request.trim_whitespace,
805
+ request.layout_type
806
  )
807
 
808
  if pil_image.size == (1, 1):
 
823
 
824
  # --- Gradio Interface Definition ---
825
  # 入力モードの選択用Radioコンポーネント
826
+ def process_input(input_mode, input_text, extension_percentage, temperature, trim_whitespace, layout_type):
827
  """入力モードに応じて適切な処理を行う"""
828
  if input_mode == "HTML入力":
829
  # HTMLモードの場合は既存の処理
830
  return render_fullpage_screenshot(input_text, extension_percentage, trim_whitespace)
831
  else:
832
  # テキスト入力モードの場合はGemini APIを使用
833
+ return text_to_screenshot(input_text, extension_percentage, temperature, trim_whitespace, layout_type)
834
 
835
  # Gradio UIの定義
836
  with gr.Blocks(title="Full Page Screenshot (テキスト変換対応)", theme=gr.themes.Base()) as iface:
 
852
  )
853
 
854
  with gr.Row():
855
+ with gr.Column(scale=1):
856
+ extension_percentage = gr.Slider(
857
+ minimum=0,
858
+ maximum=30,
859
+ step=1.0,
860
+ value=6, # デフォルト値6%
861
+ label="上下高さ拡張率(%)"
862
+ )
863
+
864
+ # 温度調整スライダー(テキストモード時のみ表示)
865
+ temperature = gr.Slider(
866
+ minimum=0.0,
867
+ maximum=1.4,
868
+ step=0.1,
869
+ value=1.0, # デフォルト値1.0
870
+ label="生成時の温度(創造性)",
871
+ visible=False # 最初は非表示
872
+ )
873
+
874
+ # 余白トリミングオプション
875
+ trim_whitespace = gr.Checkbox(
876
+ label="余白を自動トリミング",
877
+ value=True,
878
+ info="生成される画像から余分な空白領域を自動的に削除します"
879
+ )
880
 
881
+ with gr.Column(scale=1):
882
+ # 新機能:レイアウトタイプの選択(テキストモード時のみ表示)
883
+ layout_type = gr.Radio(
884
+ ["standard", "spaced", "compact"],
885
+ label="レイアウトタイプ",
886
+ value="standard",
887
+ info="standard: 標準レイアウト, spaced: 広々レイアウト, compact: コンパクトレイアウト",
888
+ visible=False
889
+ )
 
 
 
 
 
 
 
890
 
891
+ submit_btn = gr.Button("生成", variant="primary")
892
  output_image = gr.Image(type="pil", label="ページ全体のスクリーンショット")
893
 
894
+ # 入力モード変更時のイベント処理(テキストモード時のみ温度スライダーとレイアウトタイプを表示)
895
+ def update_ui_visibility(mode):
896
+ return [
897
+ {"visible": mode == "テキスト入力", "__type__": "update"}, # temperature
898
+ {"visible": mode == "テキスト入力", "__type__": "update"} # layout_type
899
+ ]
900
 
901
  input_mode.change(
902
+ fn=update_ui_visibility,
903
  inputs=input_mode,
904
+ outputs=[temperature, layout_type]
905
  )
906
 
907
  # 生成ボタンクリック時のイベント処理
908
  submit_btn.click(
909
  fn=process_input,
910
+ inputs=[input_mode, input_text, extension_percentage, temperature, trim_whitespace, layout_type],
911
  outputs=output_image
912
  )
913
 
914
+ # バージョン情報と説明
915
  gemini_model = os.environ.get("GEMINI_MODEL", "gemini-1.5-pro")
916
  gr.Markdown(f"""
917
+ ## 更新情報
918
+ - テキスト重なり自動修正機能を追加
919
+ - レイアウトタイプ選択機能を追加(テキストモード時)
920
+ - フォントサイズ自動調整機能を追加
921
+
922
  ## APIエンドポイント
923
  - `/api/screenshot` - HTMLコードからスクリーンショットを生成
924
  - `/api/text-to-screenshot` - テキストからインフォグラフィックスクリーンショットを生成
925
 
926
  ## 設定情報
927
  - 使用モデル: {gemini_model} (環境変数 GEMINI_MODEL で変更可能)
928
+ - レイアウトタイプ:
929
+ - standard: 標準レイアウト
930
+ - spaced: 広々レイアウト(テキストが重なりやすい場合に推奨)
931
+ - compact: コンパクトレイアウト(テキストが少ない場合に推奨)
932
  """)
933
 
934
  # --- Mount Gradio App onto FastAPI ---