Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
from fastapi import FastAPI, HTTPException, Body
|
| 3 |
-
from fastapi.responses import StreamingResponse
|
| 4 |
from fastapi.staticfiles import StaticFiles
|
| 5 |
from fastapi.middleware.cors import CORSMiddleware
|
| 6 |
from pydantic import BaseModel
|
|
@@ -15,11 +15,13 @@ import tempfile
|
|
| 15 |
import time
|
| 16 |
import os
|
| 17 |
import logging
|
| 18 |
-
import numpy as np
|
| 19 |
-
import threading
|
| 20 |
-
import queue
|
| 21 |
-
|
| 22 |
-
from
|
|
|
|
|
|
|
| 23 |
|
| 24 |
# 正しいGemini関連のインポート
|
| 25 |
import google.generativeai as genai
|
|
@@ -28,6 +30,77 @@ import google.generativeai as genai
|
|
| 28 |
logging.basicConfig(level=logging.INFO)
|
| 29 |
logger = logging.getLogger(__name__)
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
# --- WebDriverプールの実装 ---
|
| 32 |
class WebDriverPool:
|
| 33 |
"""WebDriverインスタンスを再利用するためのプール"""
|
|
@@ -126,6 +199,13 @@ class ScreenshotRequest(BaseModel):
|
|
| 126 |
trim_whitespace: bool = True # 余白トリミングオプション(デフォルト有効)
|
| 127 |
style: str = "standard" # デフォルトはstandard
|
| 128 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
# HTMLのFont Awesomeレイアウトを改善する関数 - プリロード機能を追加
|
| 130 |
def enhance_font_awesome_layout(html_code):
|
| 131 |
"""Font Awesomeレイアウトを改善し、プリロードタグを追加"""
|
|
@@ -602,8 +682,13 @@ def render_fullpage_screenshot(html_code: str, extension_percentage: float = 6.0
|
|
| 602 |
|
| 603 |
# --- 並列処理を活用した新しい関数 ---
|
| 604 |
def text_to_screenshot_parallel(text: str, extension_percentage: float, temperature: float = 0.3,
|
| 605 |
-
trim_whitespace: bool = True, style: str = "standard") ->
|
| 606 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 607 |
start_time = time.time()
|
| 608 |
logger.info("並列処理によるテキスト→スクリーンショット生成を開始")
|
| 609 |
|
|
@@ -749,13 +834,17 @@ def text_to_screenshot_parallel(text: str, extension_percentage: float, temperat
|
|
| 749 |
img = trim_image_whitespace(img, threshold=248, padding=20)
|
| 750 |
logger.info(f"トリミング後のサイズ: {img.width}x{img.height}")
|
| 751 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 752 |
elapsed = time.time() - start_time
|
| 753 |
-
logger.info(f"並列処理による生成完了。所要時間: {elapsed:.2f}
|
| 754 |
-
return img
|
| 755 |
|
| 756 |
except Exception as e:
|
| 757 |
logger.error(f"スクリーンショット生成中にエラー: {e}", exc_info=True)
|
| 758 |
-
return Image.new('RGB', (1, 1), color=(0, 0, 0))
|
| 759 |
finally:
|
| 760 |
# WebDriverプールに戻す
|
| 761 |
if driver_from_pool:
|
|
@@ -769,15 +858,36 @@ def text_to_screenshot_parallel(text: str, extension_percentage: float, temperat
|
|
| 769 |
|
| 770 |
except Exception as e:
|
| 771 |
logger.error(f"並列処理中のエラー: {e}", exc_info=True)
|
| 772 |
-
return Image.new('RGB', (1, 1), color=(0, 0, 0)) #
|
| 773 |
|
| 774 |
# 従来の非並列版も残す(互換性のため)
|
| 775 |
def text_to_screenshot(text: str, extension_percentage: float, temperature: float = 0.3,
|
| 776 |
-
trim_whitespace: bool = True, style: str = "standard") ->
|
| 777 |
"""テキストをGemini APIでHTMLに変換し、スクリーンショットを生成する統合関数(レガシー版)"""
|
| 778 |
# 並列処理版を呼び出す
|
| 779 |
return text_to_screenshot_parallel(text, extension_percentage, temperature, trim_whitespace, style)
|
| 780 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 781 |
# --- FastAPI Setup ---
|
| 782 |
app = FastAPI()
|
| 783 |
|
|
@@ -821,59 +931,56 @@ if os.path.exists(cdn_dir):
|
|
| 821 |
app.mount("/cdn", StaticFiles(directory=cdn_dir), name="cdn")
|
| 822 |
|
| 823 |
|
| 824 |
-
# API Endpoint for screenshot generation
|
| 825 |
@app.post("/api/screenshot",
|
| 826 |
-
|
| 827 |
tags=["Screenshot"],
|
| 828 |
-
summary="Render HTML to Full Page Screenshot",
|
| 829 |
-
description="Takes HTML code and an optional vertical extension percentage, renders it using a headless browser, and returns the
|
| 830 |
async def api_render_screenshot(request: ScreenshotRequest):
|
| 831 |
"""
|
| 832 |
-
API endpoint to render HTML and return
|
| 833 |
"""
|
| 834 |
try:
|
| 835 |
logger.info(f"API request received. Extension: {request.extension_percentage}%")
|
| 836 |
-
|
| 837 |
-
|
|
|
|
| 838 |
request.html_code,
|
| 839 |
request.extension_percentage,
|
| 840 |
-
request.trim_whitespace
|
|
|
|
| 841 |
)
|
| 842 |
|
| 843 |
-
if pil_image.size == (1, 1):
|
| 844 |
-
logger.error("Screenshot generation failed,
|
| 845 |
-
|
| 846 |
-
# raise HTTPException(status_code=500, detail="Failed to generate screenshot")
|
| 847 |
-
|
| 848 |
-
# Convert PIL Image to PNG bytes
|
| 849 |
-
img_byte_arr = BytesIO()
|
| 850 |
-
pil_image.save(img_byte_arr, format='PNG')
|
| 851 |
-
img_byte_arr.seek(0) # Go to the start of the BytesIO buffer
|
| 852 |
|
| 853 |
-
|
| 854 |
-
|
|
|
|
| 855 |
|
| 856 |
except Exception as e:
|
| 857 |
logger.error(f"API Error: {e}", exc_info=True)
|
| 858 |
raise HTTPException(status_code=500, detail=f"Internal Server Error: {e}")
|
| 859 |
|
| 860 |
-
# --- 新しいGemini API
|
| 861 |
@app.post("/api/text-to-screenshot",
|
| 862 |
-
|
| 863 |
tags=["Screenshot", "Gemini"],
|
| 864 |
-
summary="
|
| 865 |
-
description="テキストをGemini APIを使ってHTML
|
| 866 |
async def api_text_to_screenshot(request: GeminiRequest):
|
| 867 |
"""
|
| 868 |
-
テキストからHTML
|
| 869 |
"""
|
| 870 |
try:
|
| 871 |
logger.info(f"テキスト→スクリーンショットAPIリクエスト受信。テキスト長さ: {len(request.text)}, "
|
| 872 |
f"拡張率: {request.extension_percentage}%, 温度: {request.temperature}, "
|
| 873 |
f"スタイル: {request.style}")
|
| 874 |
|
| 875 |
-
# 並列処理版を使用
|
| 876 |
-
pil_image = text_to_screenshot_parallel(
|
| 877 |
request.text,
|
| 878 |
request.extension_percentage,
|
| 879 |
request.temperature,
|
|
@@ -881,16 +988,13 @@ async def api_text_to_screenshot(request: GeminiRequest):
|
|
| 881 |
request.style
|
| 882 |
)
|
| 883 |
|
| 884 |
-
if pil_image.size == (1, 1):
|
| 885 |
-
logger.error("
|
|
|
|
| 886 |
|
| 887 |
-
#
|
| 888 |
-
|
| 889 |
-
|
| 890 |
-
img_byte_arr.seek(0) # BytesIOバッファの先頭に戻る
|
| 891 |
-
|
| 892 |
-
logger.info("スクリーンショットをPNGストリームとして返します。")
|
| 893 |
-
return StreamingResponse(img_byte_arr, media_type="image/png")
|
| 894 |
|
| 895 |
except Exception as e:
|
| 896 |
logger.error(f"API Error: {e}", exc_info=True)
|
|
@@ -901,11 +1005,24 @@ async def api_text_to_screenshot(request: GeminiRequest):
|
|
| 901 |
def process_input(input_mode, input_text, extension_percentage, temperature, trim_whitespace, style):
|
| 902 |
"""入力モードに応じて適切な処理を行う"""
|
| 903 |
if input_mode == "HTML入力":
|
| 904 |
-
# HTML
|
| 905 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 906 |
else:
|
| 907 |
-
# テキスト入力モードの場合はGemini API
|
| 908 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 909 |
|
| 910 |
# Gradio UIの定義
|
| 911 |
with gr.Blocks(title="Full Page Screenshot (テキスト変換対応)", theme=gr.themes.Base()) as iface:
|
|
@@ -965,7 +1082,13 @@ with gr.Blocks(title="Full Page Screenshot (テキスト変換対応)", theme=gr
|
|
| 965 |
)
|
| 966 |
|
| 967 |
submit_btn = gr.Button("生成")
|
| 968 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 969 |
|
| 970 |
# 入力モード変更時のイベント処理(テキストモード時のみ温度スライダーとスタイルドロップダウンを表示)
|
| 971 |
def update_controls_visibility(mode):
|
|
@@ -986,20 +1109,22 @@ with gr.Blocks(title="Full Page Screenshot (テキスト変換対応)", theme=gr
|
|
| 986 |
submit_btn.click(
|
| 987 |
fn=process_input,
|
| 988 |
inputs=[input_mode, input_text, extension_percentage, temperature, trim_whitespace, style_dropdown],
|
| 989 |
-
outputs=output_image
|
| 990 |
)
|
| 991 |
|
| 992 |
# 環境変数情報を表示
|
| 993 |
gemini_model = os.environ.get("GEMINI_MODEL", "gemini-1.5-pro")
|
|
|
|
| 994 |
gr.Markdown(f"""
|
| 995 |
## APIエンドポイント
|
| 996 |
-
- `/api/screenshot` - HTML
|
| 997 |
-
- `/api/text-to-screenshot` -
|
| 998 |
|
| 999 |
## 設定情報
|
| 1000 |
- 使用モデル: {gemini_model} (環境変数 GEMINI_MODEL で変更可能)
|
| 1001 |
-
-
|
| 1002 |
- WebDriverプール最大数: {driver_pool.max_drivers} (環境変数 MAX_WEBDRIVERS で変更可能)
|
|
|
|
| 1003 |
""")
|
| 1004 |
|
| 1005 |
# --- Mount Gradio App onto FastAPI ---
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
from fastapi import FastAPI, HTTPException, Body
|
| 3 |
+
from fastapi.responses import StreamingResponse, JSONResponse
|
| 4 |
from fastapi.staticfiles import StaticFiles
|
| 5 |
from fastapi.middleware.cors import CORSMiddleware
|
| 6 |
from pydantic import BaseModel
|
|
|
|
| 15 |
import time
|
| 16 |
import os
|
| 17 |
import logging
|
| 18 |
+
import numpy as np
|
| 19 |
+
import threading
|
| 20 |
+
import queue
|
| 21 |
+
import uuid
|
| 22 |
+
from datetime import datetime
|
| 23 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 24 |
+
from huggingface_hub import hf_hub_download, upload_file, login
|
| 25 |
|
| 26 |
# 正しいGemini関連のインポート
|
| 27 |
import google.generativeai as genai
|
|
|
|
| 30 |
logging.basicConfig(level=logging.INFO)
|
| 31 |
logger = logging.getLogger(__name__)
|
| 32 |
|
| 33 |
+
# --- HuggingFace Hub アップロード機能 ---
|
| 34 |
+
class HuggingFaceUploader:
|
| 35 |
+
"""HuggingFace Hubへ画像をアップロードする機能を提供するクラス"""
|
| 36 |
+
def __init__(self):
|
| 37 |
+
self.repo_id = os.environ.get("HF_REPO_ID", "tomo2chin2/SUPER_TENSAI_JIN")
|
| 38 |
+
self.token = os.environ.get("HF_TOKEN", None)
|
| 39 |
+
if self.token:
|
| 40 |
+
try:
|
| 41 |
+
login(token=self.token)
|
| 42 |
+
logger.info(f"HuggingFace Hubにログインしました。リポジトリ: {self.repo_id}")
|
| 43 |
+
except Exception as e:
|
| 44 |
+
logger.error(f"HuggingFace Hubへのログインに失敗: {e}")
|
| 45 |
+
else:
|
| 46 |
+
logger.warning("HF_TOKEN環境変数が設定されていません。アップロード機能は制限されます。")
|
| 47 |
+
|
| 48 |
+
def upload_image(self, image, prefix="generated"):
|
| 49 |
+
"""
|
| 50 |
+
PIL Imageをアップロードし、アクセス可能なURLを返す
|
| 51 |
+
|
| 52 |
+
Args:
|
| 53 |
+
image: PIL.Image - アップロードする画像
|
| 54 |
+
prefix: str - ファイル名のプレフィックス
|
| 55 |
+
|
| 56 |
+
Returns:
|
| 57 |
+
str - アップロードされた画像のURL
|
| 58 |
+
"""
|
| 59 |
+
try:
|
| 60 |
+
if not self.token:
|
| 61 |
+
logger.error("HF_TOKENが設定されていないため、アップロードできません")
|
| 62 |
+
return None
|
| 63 |
+
|
| 64 |
+
# ユニークなファイル名を生成
|
| 65 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 66 |
+
unique_id = str(uuid.uuid4())[:8]
|
| 67 |
+
filename = f"{prefix}_{timestamp}_{unique_id}.png"
|
| 68 |
+
path_in_repo = f"images/{filename}"
|
| 69 |
+
|
| 70 |
+
# 一時ファイルに保存
|
| 71 |
+
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_file:
|
| 72 |
+
tmp_path = tmp_file.name
|
| 73 |
+
image.save(tmp_path, format="PNG")
|
| 74 |
+
|
| 75 |
+
logger.info(f"画像を一時ファイルに保存: {tmp_path}")
|
| 76 |
+
|
| 77 |
+
# HuggingFaceにアップロード
|
| 78 |
+
logger.info(f"HuggingFace Hubにアップロード中: {path_in_repo}")
|
| 79 |
+
upload_info = upload_file(
|
| 80 |
+
path_or_fileobj=tmp_path,
|
| 81 |
+
path_in_repo=path_in_repo,
|
| 82 |
+
repo_id=self.repo_id,
|
| 83 |
+
repo_type="dataset"
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
# 一時ファイルを削除
|
| 87 |
+
try:
|
| 88 |
+
os.remove(tmp_path)
|
| 89 |
+
except Exception as e:
|
| 90 |
+
logger.warning(f"一時ファイル削除エラー: {e}")
|
| 91 |
+
|
| 92 |
+
# URLを構築して返す
|
| 93 |
+
url = f"https://huggingface.co/{self.repo_id}/resolve/main/{path_in_repo}"
|
| 94 |
+
logger.info(f"アップロード成功: {url}")
|
| 95 |
+
return url
|
| 96 |
+
|
| 97 |
+
except Exception as e:
|
| 98 |
+
logger.error(f"HuggingFace Hubへのアップロード中にエラー: {e}", exc_info=True)
|
| 99 |
+
return None
|
| 100 |
+
|
| 101 |
+
# グローバルなアップローダーインスタンスを作成
|
| 102 |
+
hf_uploader = HuggingFaceUploader()
|
| 103 |
+
|
| 104 |
# --- WebDriverプールの実装 ---
|
| 105 |
class WebDriverPool:
|
| 106 |
"""WebDriverインスタンスを再利用するためのプール"""
|
|
|
|
| 199 |
trim_whitespace: bool = True # 余白トリミングオプション(デフォルト有効)
|
| 200 |
style: str = "standard" # デフォルトはstandard
|
| 201 |
|
| 202 |
+
# --- レスポンスモデル ---
|
| 203 |
+
class ImageUrlResponse(BaseModel):
|
| 204 |
+
"""画像URLのレスポンスモデル"""
|
| 205 |
+
url: str
|
| 206 |
+
status: str = "success"
|
| 207 |
+
message: str = "画像が正常に生成されました"
|
| 208 |
+
|
| 209 |
# HTMLのFont Awesomeレイアウトを改善する関数 - プリロード機能を追加
|
| 210 |
def enhance_font_awesome_layout(html_code):
|
| 211 |
"""Font Awesomeレイアウトを改善し、プリロードタグを追加"""
|
|
|
|
| 682 |
|
| 683 |
# --- 並列処理を活用した新しい関数 ---
|
| 684 |
def text_to_screenshot_parallel(text: str, extension_percentage: float, temperature: float = 0.3,
|
| 685 |
+
trim_whitespace: bool = True, style: str = "standard") -> tuple:
|
| 686 |
+
"""
|
| 687 |
+
テキストをGemini APIでHTMLに変換し、並列処理でスクリーンショットを生成する関数
|
| 688 |
+
|
| 689 |
+
Returns:
|
| 690 |
+
tuple - (PIL.Image, URL) - 生成された画像とHuggingFaceのURL
|
| 691 |
+
"""
|
| 692 |
start_time = time.time()
|
| 693 |
logger.info("並列処理によるテキスト→スクリーンショット生成を開始")
|
| 694 |
|
|
|
|
| 834 |
img = trim_image_whitespace(img, threshold=248, padding=20)
|
| 835 |
logger.info(f"トリミング後のサイズ: {img.width}x{img.height}")
|
| 836 |
|
| 837 |
+
# 画像をHuggingFaceにアップロード
|
| 838 |
+
prefix = f"infographic_{style}"
|
| 839 |
+
image_url = hf_uploader.upload_image(img, prefix=prefix)
|
| 840 |
+
|
| 841 |
elapsed = time.time() - start_time
|
| 842 |
+
logger.info(f"並列処理による生成完了。所要時間: {elapsed:.2f}秒、URL: {image_url}")
|
| 843 |
+
return img, image_url
|
| 844 |
|
| 845 |
except Exception as e:
|
| 846 |
logger.error(f"スクリーンショット生成中にエラー: {e}", exc_info=True)
|
| 847 |
+
return Image.new('RGB', (1, 1), color=(0, 0, 0)), None
|
| 848 |
finally:
|
| 849 |
# WebDriverプールに戻す
|
| 850 |
if driver_from_pool:
|
|
|
|
| 858 |
|
| 859 |
except Exception as e:
|
| 860 |
logger.error(f"並列処理中のエラー: {e}", exc_info=True)
|
| 861 |
+
return Image.new('RGB', (1, 1), color=(0, 0, 0)), None # エラー時は黒画像とNone URL
|
| 862 |
|
| 863 |
# 従来の非並列版も残す(互換性のため)
|
| 864 |
def text_to_screenshot(text: str, extension_percentage: float, temperature: float = 0.3,
|
| 865 |
+
trim_whitespace: bool = True, style: str = "standard") -> tuple:
|
| 866 |
"""テキストをGemini APIでHTMLに変換し、スクリーンショットを生成する統合関数(レガシー版)"""
|
| 867 |
# 並列処理版を呼び出す
|
| 868 |
return text_to_screenshot_parallel(text, extension_percentage, temperature, trim_whitespace, style)
|
| 869 |
|
| 870 |
+
# 新しい関数: HTMLからスクリーンショットを生成し、HuggingFaceにアップロード
|
| 871 |
+
def render_and_upload_screenshot(html_code: str, extension_percentage: float = 10.0,
|
| 872 |
+
trim_whitespace: bool = True, prefix: str = "screenshot") -> tuple:
|
| 873 |
+
"""
|
| 874 |
+
HTMLコードからスクリーンショットを生成し、HuggingFaceにアップロードする
|
| 875 |
+
|
| 876 |
+
Returns:
|
| 877 |
+
tuple - (PIL.Image, URL) - 生成された画像とHuggingFaceのURL
|
| 878 |
+
"""
|
| 879 |
+
try:
|
| 880 |
+
# スクリーンショット生成
|
| 881 |
+
img = render_fullpage_screenshot(html_code, extension_percentage, trim_whitespace)
|
| 882 |
+
|
| 883 |
+
# 画像をHuggingFaceにアップロード
|
| 884 |
+
image_url = hf_uploader.upload_image(img, prefix=prefix)
|
| 885 |
+
|
| 886 |
+
return img, image_url
|
| 887 |
+
except Exception as e:
|
| 888 |
+
logger.error(f"スクリーンショット生成とアップロード中にエラー: {e}", exc_info=True)
|
| 889 |
+
return Image.new('RGB', (1, 1), color=(0, 0, 0)), None
|
| 890 |
+
|
| 891 |
# --- FastAPI Setup ---
|
| 892 |
app = FastAPI()
|
| 893 |
|
|
|
|
| 931 |
app.mount("/cdn", StaticFiles(directory=cdn_dir), name="cdn")
|
| 932 |
|
| 933 |
|
| 934 |
+
# API Endpoint for screenshot generation - 更新版(URLを返すように変更)
|
| 935 |
@app.post("/api/screenshot",
|
| 936 |
+
response_model=ImageUrlResponse,
|
| 937 |
tags=["Screenshot"],
|
| 938 |
+
summary="Render HTML to Full Page Screenshot and Upload to HuggingFace",
|
| 939 |
+
description="Takes HTML code and an optional vertical extension percentage, renders it using a headless browser, uploads to HuggingFace, and returns the URL.")
|
| 940 |
async def api_render_screenshot(request: ScreenshotRequest):
|
| 941 |
"""
|
| 942 |
+
API endpoint to render HTML, upload to HuggingFace, and return the URL.
|
| 943 |
"""
|
| 944 |
try:
|
| 945 |
logger.info(f"API request received. Extension: {request.extension_percentage}%")
|
| 946 |
+
|
| 947 |
+
# スクリーンショット生成とアップロード
|
| 948 |
+
pil_image, image_url = render_and_upload_screenshot(
|
| 949 |
request.html_code,
|
| 950 |
request.extension_percentage,
|
| 951 |
+
request.trim_whitespace,
|
| 952 |
+
prefix="screenshot"
|
| 953 |
)
|
| 954 |
|
| 955 |
+
if pil_image.size == (1, 1) or not image_url:
|
| 956 |
+
logger.error("Screenshot generation failed, or upload failed.")
|
| 957 |
+
raise HTTPException(status_code=500, detail="Failed to generate or upload screenshot")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 958 |
|
| 959 |
+
# URLを返す
|
| 960 |
+
logger.info(f"返却URL: {image_url}")
|
| 961 |
+
return ImageUrlResponse(url=image_url)
|
| 962 |
|
| 963 |
except Exception as e:
|
| 964 |
logger.error(f"API Error: {e}", exc_info=True)
|
| 965 |
raise HTTPException(status_code=500, detail=f"Internal Server Error: {e}")
|
| 966 |
|
| 967 |
+
# --- 新しいGemini API連携エンドポイント(並列処理版)- URLを返すよう更新 ---
|
| 968 |
@app.post("/api/text-to-screenshot",
|
| 969 |
+
response_model=ImageUrlResponse,
|
| 970 |
tags=["Screenshot", "Gemini"],
|
| 971 |
+
summary="テキストからインフォグラフィックを生成しHuggingFaceにアップロード",
|
| 972 |
+
description="テキストをGemini APIを使ってHTMLインフォグラフィックに変換し、HuggingFaceにアップロードしたURLを返します。")
|
| 973 |
async def api_text_to_screenshot(request: GeminiRequest):
|
| 974 |
"""
|
| 975 |
+
テキストからHTMLインフォグラフィックを生成してアップロードし、URLを返すAPIエンドポイント
|
| 976 |
"""
|
| 977 |
try:
|
| 978 |
logger.info(f"テキスト→スクリーンショットAPIリクエスト受信。テキスト長さ: {len(request.text)}, "
|
| 979 |
f"拡張率: {request.extension_percentage}%, 温度: {request.temperature}, "
|
| 980 |
f"スタイル: {request.style}")
|
| 981 |
|
| 982 |
+
# 並列処理版を使用 - 画像とURLを取得
|
| 983 |
+
pil_image, image_url = text_to_screenshot_parallel(
|
| 984 |
request.text,
|
| 985 |
request.extension_percentage,
|
| 986 |
request.temperature,
|
|
|
|
| 988 |
request.style
|
| 989 |
)
|
| 990 |
|
| 991 |
+
if pil_image.size == (1, 1) or not image_url:
|
| 992 |
+
logger.error("スクリーンショット生成に失敗したか、アップロードに失敗しました。")
|
| 993 |
+
raise HTTPException(status_code=500, detail="Failed to generate or upload screenshot")
|
| 994 |
|
| 995 |
+
# URLを返す
|
| 996 |
+
logger.info(f"返却URL: {image_url}")
|
| 997 |
+
return ImageUrlResponse(url=image_url)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 998 |
|
| 999 |
except Exception as e:
|
| 1000 |
logger.error(f"API Error: {e}", exc_info=True)
|
|
|
|
| 1005 |
def process_input(input_mode, input_text, extension_percentage, temperature, trim_whitespace, style):
|
| 1006 |
"""入力モードに応じて適切な処理を行う"""
|
| 1007 |
if input_mode == "HTML入力":
|
| 1008 |
+
# HTMLモードの場合はレンダリングとアップロード
|
| 1009 |
+
img, url = render_and_upload_screenshot(
|
| 1010 |
+
input_text,
|
| 1011 |
+
extension_percentage,
|
| 1012 |
+
trim_whitespace,
|
| 1013 |
+
prefix="html_screenshot"
|
| 1014 |
+
)
|
| 1015 |
+
return img, url if url else "アップロード失敗またはURL取得できませんでした"
|
| 1016 |
else:
|
| 1017 |
+
# テキスト入力モードの場合はGemini API使用(並列処理版)
|
| 1018 |
+
img, url = text_to_screenshot_parallel(
|
| 1019 |
+
input_text,
|
| 1020 |
+
extension_percentage,
|
| 1021 |
+
temperature,
|
| 1022 |
+
trim_whitespace,
|
| 1023 |
+
style
|
| 1024 |
+
)
|
| 1025 |
+
return img, url if url else "アップロード失敗またはURL取得できませんでした"
|
| 1026 |
|
| 1027 |
# Gradio UIの定義
|
| 1028 |
with gr.Blocks(title="Full Page Screenshot (テキスト変換対応)", theme=gr.themes.Base()) as iface:
|
|
|
|
| 1082 |
)
|
| 1083 |
|
| 1084 |
submit_btn = gr.Button("生成")
|
| 1085 |
+
|
| 1086 |
+
# 出力部分をRowで分ける
|
| 1087 |
+
with gr.Row():
|
| 1088 |
+
with gr.Column(scale=1):
|
| 1089 |
+
output_image = gr.Image(type="pil", label="ページ全体のスクリーンショット")
|
| 1090 |
+
with gr.Column(scale=1):
|
| 1091 |
+
output_url = gr.Textbox(label="画像URL(HuggingFace)", info="生成された画像のURLです。このURLを使用して画像にアクセスできます。")
|
| 1092 |
|
| 1093 |
# 入力モード変更時のイベント処理(テキストモード時のみ温度スライダーとスタイルドロップダウンを表示)
|
| 1094 |
def update_controls_visibility(mode):
|
|
|
|
| 1109 |
submit_btn.click(
|
| 1110 |
fn=process_input,
|
| 1111 |
inputs=[input_mode, input_text, extension_percentage, temperature, trim_whitespace, style_dropdown],
|
| 1112 |
+
outputs=[output_image, output_url]
|
| 1113 |
)
|
| 1114 |
|
| 1115 |
# 環境変数情報を表示
|
| 1116 |
gemini_model = os.environ.get("GEMINI_MODEL", "gemini-1.5-pro")
|
| 1117 |
+
hf_repo = os.environ.get("HF_REPO_ID", "tomo2chin2/SUPER_TENSAI_JIN")
|
| 1118 |
gr.Markdown(f"""
|
| 1119 |
## APIエンドポイント
|
| 1120 |
+
- `/api/screenshot` - HTMLコードからスクリーンショットを生成し、URLを返します
|
| 1121 |
+
- `/api/text-to-screenshot` - テキストからインフォグラフィックスクリーンショットを生成し、URLを返します
|
| 1122 |
|
| 1123 |
## 設定情報
|
| 1124 |
- 使用モデル: {gemini_model} (環境変数 GEMINI_MODEL で変更可能)
|
| 1125 |
+
- HuggingFaceリポジトリ: {hf_repo} (環境変数 HF_REPO_ID で変更可能)
|
| 1126 |
- WebDriverプール最大数: {driver_pool.max_drivers} (環境変数 MAX_WEBDRIVERS で変更可能)
|
| 1127 |
+
- 対応スタイル: standard, cute, resort, cool, dental
|
| 1128 |
""")
|
| 1129 |
|
| 1130 |
# --- Mount Gradio App onto FastAPI ---
|