Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -6,12 +6,24 @@ import gradio as gr
|
|
| 6 |
from google import genai
|
| 7 |
import resend
|
| 8 |
import html
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
class GeminiImageAnalyzer:
|
| 11 |
def __init__(self, gemini_api_key, resend_api_key):
|
| 12 |
"""初始化Gemini客戶端和Resend"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
self.gemini_api_key = gemini_api_key
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
| 15 |
resend.api_key = resend_api_key
|
| 16 |
|
| 17 |
def validate_image(self, image_path):
|
|
@@ -37,45 +49,40 @@ class GeminiImageAnalyzer:
|
|
| 37 |
mime_type = "image/jpeg"
|
| 38 |
elif file_extension == '.png':
|
| 39 |
mime_type = "image/png"
|
|
|
|
|
|
|
| 40 |
|
| 41 |
return encoded_string, mime_type
|
| 42 |
except Exception as e:
|
| 43 |
raise Exception(f"圖片編碼失敗: {str(e)}")
|
| 44 |
|
| 45 |
-
def analyze_image(self, image_path, prompt
|
| 46 |
"""使用Gemini API分析圖片"""
|
| 47 |
try:
|
| 48 |
# 驗證圖片
|
| 49 |
self.validate_image(image_path)
|
|
|
|
| 50 |
|
| 51 |
# 編碼圖片
|
| 52 |
encoded_image, mime_type = self.encode_image(image_path)
|
|
|
|
| 53 |
|
| 54 |
# 構建請求內容
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
"inline_data": {
|
| 62 |
-
"mime_type": mime_type,
|
| 63 |
-
"data": encoded_image
|
| 64 |
-
}
|
| 65 |
-
}
|
| 66 |
-
]
|
| 67 |
-
}
|
| 68 |
-
]
|
| 69 |
|
| 70 |
# 調用Gemini API
|
| 71 |
-
response = self.client.
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
)
|
| 75 |
-
|
| 76 |
return response.text
|
| 77 |
|
| 78 |
except Exception as e:
|
|
|
|
| 79 |
return f"分析失敗: {str(e)}"
|
| 80 |
|
| 81 |
def send_analysis_email(self, analysis_result, recipient_email, subject="圖片分析結果"):
|
|
@@ -87,23 +94,29 @@ class GeminiImageAnalyzer:
|
|
| 87 |
<head>
|
| 88 |
<meta charset="UTF-8">
|
| 89 |
<title>圖片分析結果</title>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
</head>
|
| 91 |
<body>
|
| 92 |
-
<
|
| 93 |
-
|
| 94 |
-
|
| 95 |
<p><strong>分析時間:</strong>{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</p>
|
| 96 |
<h3>分析內容:</h3>
|
| 97 |
-
<div
|
| 98 |
{html.escape(analysis_result).replace('\n', '<br>')}
|
| 99 |
</div>
|
|
|
|
| 100 |
</div>
|
| 101 |
-
<br>
|
| 102 |
-
<p style="color: #666; font-size: 12px;">此郵件由Gemini圖片分析系統自動發送</p>
|
| 103 |
</body>
|
| 104 |
</html>
|
| 105 |
"""
|
| 106 |
-
|
| 107 |
params = {
|
| 108 |
"from": "Acme <onboarding@resend.dev>",
|
| 109 |
"to": [recipient_email],
|
|
@@ -113,29 +126,26 @@ class GeminiImageAnalyzer:
|
|
| 113 |
}
|
| 114 |
|
| 115 |
email_response = resend.Emails.send(params)
|
|
|
|
| 116 |
return f"郵件發送成功!郵件ID: {email_response.get('id', 'Unknown')}"
|
| 117 |
|
| 118 |
except Exception as e:
|
|
|
|
| 119 |
return f"郵件發送失敗: {str(e)}"
|
| 120 |
|
| 121 |
def create_gradio_app():
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
if not GEMINI_API_KEY:
|
| 128 |
-
raise ValueError("請在HuggingFace Spaces設定中添加 GEMINI_API_KEY 環境變量")
|
| 129 |
-
if not RESEND_API_KEY:
|
| 130 |
-
raise ValueError("請在HuggingFace Spaces設定中添加 RESEND_API_KEY 環境變量")
|
| 131 |
-
|
| 132 |
# 創建分析器實例
|
| 133 |
-
analyzer = GeminiImageAnalyzer(
|
| 134 |
|
| 135 |
-
def process_image_and_send_email(
|
| 136 |
"""處理圖片分析並可選發送郵件"""
|
| 137 |
try:
|
| 138 |
-
if
|
| 139 |
return "請先上傳圖片", ""
|
| 140 |
|
| 141 |
# 使用默認提示詞如果為空
|
|
@@ -143,7 +153,7 @@ def create_gradio_app():
|
|
| 143 |
prompt = "請詳細描述這張圖片的內容,並提取圖片中的文字,使用繁體中文"
|
| 144 |
|
| 145 |
# 分析圖片
|
| 146 |
-
analysis_result = analyzer.analyze_image(
|
| 147 |
|
| 148 |
email_status = ""
|
| 149 |
if send_email_flag and recipient_email.strip():
|
|
@@ -166,28 +176,16 @@ def create_gradio_app():
|
|
| 166 |
return f"處理失敗: {str(e)}", ""
|
| 167 |
|
| 168 |
# 創建Gradio介面
|
| 169 |
-
with gr.Blocks(
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
css="""
|
| 173 |
-
.gradio-container {
|
| 174 |
-
max-width: 1200px !important;
|
| 175 |
-
}
|
| 176 |
-
"""
|
| 177 |
-
) as app:
|
| 178 |
-
gr.Markdown(
|
| 179 |
-
"# 🔍 Gemini 圖片分析與郵件發送系統\n"
|
| 180 |
-
"上傳圖片進行AI分析,並可選擇將結果發送到指定郵箱\n"
|
| 181 |
-
"---"
|
| 182 |
-
)
|
| 183 |
|
| 184 |
with gr.Row():
|
| 185 |
with gr.Column(scale=1):
|
| 186 |
# 圖片上傳
|
| 187 |
image_input = gr.Image(
|
| 188 |
type="filepath",
|
| 189 |
-
label="上傳圖片 (支援 JPG, PNG 格式)"
|
| 190 |
-
height=300
|
| 191 |
)
|
| 192 |
|
| 193 |
# 提示詞輸入
|
|
@@ -254,7 +252,6 @@ def create_gradio_app():
|
|
| 254 |
- 支援的圖片格式:JPG, JPEG, PNG
|
| 255 |
- 分析結果會以繁體中文顯示
|
| 256 |
- 郵件功能為可選,不影響圖片分析功能
|
| 257 |
-
- 本應用運行在 HuggingFace Spaces 上
|
| 258 |
""")
|
| 259 |
|
| 260 |
# 綁定事件
|
|
@@ -285,28 +282,9 @@ def create_gradio_app():
|
|
| 285 |
lambda x=example: x,
|
| 286 |
outputs=prompt_input
|
| 287 |
)
|
| 288 |
-
|
| 289 |
-
# 添加頁腳
|
| 290 |
-
gr.Markdown(
|
| 291 |
-
"---\n"
|
| 292 |
-
"💡 **提示**: 如果遇到API錯誤,請檢查環境變量設定是否正確\n"
|
| 293 |
-
"🔗 **技術**: 使用 Gemini 2.0 Flash 模型進行圖片分析"
|
| 294 |
-
)
|
| 295 |
|
| 296 |
return app
|
| 297 |
|
| 298 |
-
#
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
app = create_gradio_app()
|
| 302 |
-
app.launch() # HuggingFace Spaces 會自動處理部署配置
|
| 303 |
-
except Exception as e:
|
| 304 |
-
print(f"應用啟動失敗: {e}")
|
| 305 |
-
# 創建一個錯誤頁面
|
| 306 |
-
error_app = gr.Interface(
|
| 307 |
-
fn=lambda: f"配置錯誤: {str(e)}\n\n請檢查以下設定:\n1. GEMINI_API_KEY 環境變量\n2. RESEND_API_KEY 環境變量",
|
| 308 |
-
inputs=[],
|
| 309 |
-
outputs=gr.Textbox(label="錯誤信息"),
|
| 310 |
-
title="配置錯誤"
|
| 311 |
-
)
|
| 312 |
-
error_app.launch()
|
|
|
|
| 6 |
from google import genai
|
| 7 |
import resend
|
| 8 |
import html
|
| 9 |
+
import logging
|
| 10 |
+
|
| 11 |
+
# 設置日誌級別,以查看 Gradio 和底層庫的詳細資訊
|
| 12 |
+
logging.basicConfig(level=logging.INFO)
|
| 13 |
|
| 14 |
class GeminiImageAnalyzer:
|
| 15 |
def __init__(self, gemini_api_key, resend_api_key):
|
| 16 |
"""初始化Gemini客戶端和Resend"""
|
| 17 |
+
if not gemini_api_key:
|
| 18 |
+
raise ValueError("未找到 Gemini API 金鑰。請將其設置為環境變數。")
|
| 19 |
+
if not resend_api_key:
|
| 20 |
+
raise ValueError("未找到 Resend API 金鑰。請將其設置為環境變數。")
|
| 21 |
+
|
| 22 |
self.gemini_api_key = gemini_api_key
|
| 23 |
+
# 使用提供的金鑰初始化 Gemini 客戶端
|
| 24 |
+
genai.configure(api_key=gemini_api_key)
|
| 25 |
+
self.client = genai.GenerativeModel(model_name="gemini-1.5-flash")
|
| 26 |
+
# 使用提供的金鑰初始化 Resend
|
| 27 |
resend.api_key = resend_api_key
|
| 28 |
|
| 29 |
def validate_image(self, image_path):
|
|
|
|
| 49 |
mime_type = "image/jpeg"
|
| 50 |
elif file_extension == '.png':
|
| 51 |
mime_type = "image/png"
|
| 52 |
+
else:
|
| 53 |
+
mime_type = "application/octet-stream" # 預設類型
|
| 54 |
|
| 55 |
return encoded_string, mime_type
|
| 56 |
except Exception as e:
|
| 57 |
raise Exception(f"圖片編碼失敗: {str(e)}")
|
| 58 |
|
| 59 |
+
def analyze_image(self, image_path, prompt):
|
| 60 |
"""使用Gemini API分析圖片"""
|
| 61 |
try:
|
| 62 |
# 驗證圖片
|
| 63 |
self.validate_image(image_path)
|
| 64 |
+
logging.info(f"成功驗證圖片: {image_path}")
|
| 65 |
|
| 66 |
# 編碼圖片
|
| 67 |
encoded_image, mime_type = self.encode_image(image_path)
|
| 68 |
+
logging.info(f"圖片成功編碼為 {mime_type}")
|
| 69 |
|
| 70 |
# 構建請求內容
|
| 71 |
+
image_part = {
|
| 72 |
+
"mime_type": mime_type,
|
| 73 |
+
"data": base64.b64decode(encoded_image)
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
contents = [prompt, image_part]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
# 調用Gemini API
|
| 79 |
+
response = self.client.generate_content(contents)
|
| 80 |
+
logging.info("成功調用 Gemini API")
|
| 81 |
+
|
|
|
|
|
|
|
| 82 |
return response.text
|
| 83 |
|
| 84 |
except Exception as e:
|
| 85 |
+
logging.error(f"分析失敗: {str(e)}")
|
| 86 |
return f"分析失敗: {str(e)}"
|
| 87 |
|
| 88 |
def send_analysis_email(self, analysis_result, recipient_email, subject="圖片分析結果"):
|
|
|
|
| 94 |
<head>
|
| 95 |
<meta charset="UTF-8">
|
| 96 |
<title>圖片分析結果</title>
|
| 97 |
+
<style>
|
| 98 |
+
body {{ font-family: Arial, sans-serif; line-height: 1.6; color: #333; }}
|
| 99 |
+
.container {{ max-width: 600px; margin: 20px auto; padding: 20px; border: 1px solid #ddd; border-radius: 8px; }}
|
| 100 |
+
h2 {{ color: #0056b3; }}
|
| 101 |
+
.info-box {{ background-color: #f0f8ff; padding: 15px; border-radius: 5px; border-left: 4px solid #007bff; }}
|
| 102 |
+
.footer {{ margin-top: 20px; font-size: 12px; color: #777; }}
|
| 103 |
+
</style>
|
| 104 |
</head>
|
| 105 |
<body>
|
| 106 |
+
<div class="container">
|
| 107 |
+
<h2>圖片分析結果</h2>
|
| 108 |
+
<hr>
|
| 109 |
<p><strong>分析時間:</strong>{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</p>
|
| 110 |
<h3>分析內容:</h3>
|
| 111 |
+
<div class="info-box">
|
| 112 |
{html.escape(analysis_result).replace('\n', '<br>')}
|
| 113 |
</div>
|
| 114 |
+
<p class="footer">此郵件由Gemini圖片分析系統自動發送</p>
|
| 115 |
</div>
|
|
|
|
|
|
|
| 116 |
</body>
|
| 117 |
</html>
|
| 118 |
"""
|
| 119 |
+
|
| 120 |
params = {
|
| 121 |
"from": "Acme <onboarding@resend.dev>",
|
| 122 |
"to": [recipient_email],
|
|
|
|
| 126 |
}
|
| 127 |
|
| 128 |
email_response = resend.Emails.send(params)
|
| 129 |
+
logging.info(f"郵件發送成功!郵件ID: {email_response.get('id', 'Unknown')}")
|
| 130 |
return f"郵件發送成功!郵件ID: {email_response.get('id', 'Unknown')}"
|
| 131 |
|
| 132 |
except Exception as e:
|
| 133 |
+
logging.error(f"郵件發送失敗: {str(e)}")
|
| 134 |
return f"郵件發送失敗: {str(e)}"
|
| 135 |
|
| 136 |
def create_gradio_app():
|
| 137 |
+
"""創建 Gradio 應用介面"""
|
| 138 |
+
# 從環境變數中讀取 API 密鑰
|
| 139 |
+
gemini_api_key = os.getenv("GEMINI_API_KEY")
|
| 140 |
+
resend_api_key = os.getenv("RESEND_API_KEY")
|
| 141 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
# 創建分析器實例
|
| 143 |
+
analyzer = GeminiImageAnalyzer(gemini_api_key, resend_api_key)
|
| 144 |
|
| 145 |
+
def process_image_and_send_email(image_file, prompt, recipient_email, email_subject, send_email_flag):
|
| 146 |
"""處理圖片分析並可選發送郵件"""
|
| 147 |
try:
|
| 148 |
+
if image_file is None:
|
| 149 |
return "請先上傳圖片", ""
|
| 150 |
|
| 151 |
# 使用默認提示詞如果為空
|
|
|
|
| 153 |
prompt = "請詳細描述這張圖片的內容,並提取圖片中的文字,使用繁體中文"
|
| 154 |
|
| 155 |
# 分析圖片
|
| 156 |
+
analysis_result = analyzer.analyze_image(image_file, prompt)
|
| 157 |
|
| 158 |
email_status = ""
|
| 159 |
if send_email_flag and recipient_email.strip():
|
|
|
|
| 176 |
return f"處理失敗: {str(e)}", ""
|
| 177 |
|
| 178 |
# 創建Gradio介面
|
| 179 |
+
with gr.Blocks(title="Gemini 圖片分析與郵件發送系統") as app:
|
| 180 |
+
gr.Markdown("# 🔍 Gemini 圖片分析與郵件發送系統")
|
| 181 |
+
gr.Markdown("上傳圖片進行AI分析,並可選擇將結果發送到指定郵箱")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
|
| 183 |
with gr.Row():
|
| 184 |
with gr.Column(scale=1):
|
| 185 |
# 圖片上傳
|
| 186 |
image_input = gr.Image(
|
| 187 |
type="filepath",
|
| 188 |
+
label="上傳圖片 (支援 JPG, PNG 格式)"
|
|
|
|
| 189 |
)
|
| 190 |
|
| 191 |
# 提示詞輸入
|
|
|
|
| 252 |
- 支援的圖片格式:JPG, JPEG, PNG
|
| 253 |
- 分析結果會以繁體中文顯示
|
| 254 |
- 郵件功能為可選,不影響圖片分析功能
|
|
|
|
| 255 |
""")
|
| 256 |
|
| 257 |
# 綁定事件
|
|
|
|
| 282 |
lambda x=example: x,
|
| 283 |
outputs=prompt_input
|
| 284 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
|
| 286 |
return app
|
| 287 |
|
| 288 |
+
# 在 Hugging Face Spaces 上,應用會自動被 `app.py` 啟動,
|
| 289 |
+
# 所以我們只需將主函數的啟動邏輯放在檔案的頂層。
|
| 290 |
+
app = create_gradio_app()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|