Spaces:
Sleeping
Sleeping
| import os | |
| import csv | |
| import base64 | |
| from datetime import datetime | |
| from pathlib import Path | |
| import gradio as gr | |
| import google.generativeai as genai | |
| from PIL import Image | |
| import resend | |
| import tempfile | |
| import json | |
| class GeminiImageAnalyzer: | |
| def __init__(self, api_key): | |
| """初始化Gemini客戶端""" | |
| self.api_key = api_key | |
| # 正確的配置方式 | |
| genai.configure(api_key=api_key) | |
| # 創建模型實例 | |
| self.model = genai.GenerativeModel('gemini-2.0-flash-exp') | |
| def validate_image(self, image_path): | |
| """驗證圖片格式和存在性""" | |
| if not os.path.exists(image_path): | |
| raise FileNotFoundError(f"圖片檔案不存在: {image_path}") | |
| file_extension = Path(image_path).suffix.lower() | |
| if file_extension not in ['.jpg', '.jpeg', '.png', '.gif', '.webp']: | |
| raise ValueError("支援 .jpg, .jpeg, .png, .gif, .webp 格式的圖片") | |
| return True | |
| def analyze_single_image(self, image_path, prompt="請詳細描述這張圖片的內容,並提取圖片中的文字,使用繁體中文"): | |
| """分析單張圖片""" | |
| try: | |
| # 驗證圖片 | |
| self.validate_image(image_path) | |
| # 直接使用PIL打開圖片 | |
| image = Image.open(image_path) | |
| # 調用Gemini API的正確方式 | |
| response = self.model.generate_content([prompt, image]) | |
| return response.text | |
| except Exception as e: | |
| return f"分析失敗: {str(e)}" | |
| def analyze_multiple_images(self, image_paths, comparison_prompt=None): | |
| """分析多張圖片並進行比較""" | |
| try: | |
| if not image_paths or len(image_paths) == 0: | |
| return "❌ 請至少上傳一張圖片" | |
| # 如果只有一張圖片,使用單圖片分析 | |
| if len(image_paths) == 1: | |
| return self.analyze_single_image( | |
| image_paths[0], | |
| comparison_prompt or "請詳細描述這張圖片的內容,並提取圖片中的文字,使用繁體中文" | |
| ) | |
| # 多圖片分析 | |
| images = [] | |
| image_info = [] | |
| for i, image_path in enumerate(image_paths): | |
| # 驗證圖片 | |
| self.validate_image(image_path) | |
| # 打開圖片 | |
| image = Image.open(image_path) | |
| images.append(image) | |
| image_info.append(f"圖片 {i+1}: {os.path.basename(image_path)}") | |
| # 構建比較提示詞 | |
| if not comparison_prompt: | |
| comparison_prompt = f""" | |
| 請詳細分析和比較這 {len(images)} 張圖片。請從以下方面進行分析: | |
| 1. **個別圖片描述**: | |
| - 分別描述每張圖片的主要內容 | |
| - 識別每張圖片中的文字、物品、人物等 | |
| 2. **相似之處**: | |
| - 找出圖片之間的共同點 | |
| - 相同的元素、主題、風格等 | |
| 3. **差異比較**: | |
| - 指出圖片之間的明顯差異 | |
| - 不同的顏色、構圖、內容、角度等 | |
| 4. **整體總結**: | |
| - 這些圖片可能的關聯性 | |
| - 整體給人的印象或傳達的訊息 | |
| 請使用繁體中文回答,條理清晰地組織你的分析。 | |
| """ | |
| # 準備內容列表 | |
| content_parts = [comparison_prompt] | |
| content_parts.extend(images) | |
| # 調用API進行比較分析 | |
| response = self.model.generate_content(content_parts) | |
| return response.text | |
| except Exception as e: | |
| return f"多圖片分析失敗: {str(e)}" | |
| def batch_analyze_images(self, image_paths, individual_prompt=None): | |
| """批量分析圖片(每張圖片單獨分析)""" | |
| try: | |
| if not image_paths or len(image_paths) == 0: | |
| return "❌ 請至少上傳一張圖片" | |
| results = {} | |
| individual_prompt = individual_prompt or "請詳細描述這張圖片的內容,並提取圖片中的文字,使用繁體中文" | |
| for i, image_path in enumerate(image_paths): | |
| image_name = os.path.basename(image_path) | |
| print(f"正在分析圖片 {i+1}/{len(image_paths)}: {image_name}") | |
| result = self.analyze_single_image(image_path, individual_prompt) | |
| results[f"圖片 {i+1} ({image_name})"] = result | |
| # 格式化輸出 | |
| formatted_result = "📊 **批量圖片分析結果**\n\n" | |
| for image_key, analysis in results.items(): | |
| formatted_result += f"## {image_key}\n\n{analysis}\n\n---\n\n" | |
| return formatted_result | |
| except Exception as e: | |
| return f"批量分析失敗: {str(e)}" | |
| class MultiClientEmailSender: | |
| def __init__(self): | |
| """初始化多客戶郵件發送器""" | |
| self.email_clients = {} | |
| def add_client(self, client_name, recipient_email, resend_api_key): | |
| """添加客戶端配置(包含專屬的Resend API Key)""" | |
| self.email_clients[client_name] = { | |
| 'email': recipient_email, | |
| 'api_key': resend_api_key | |
| } | |
| def send_analysis_email(self, client_name, analysis_result, image_count=1, analysis_type="單圖片分析"): | |
| """向指定客戶發送分析結果郵件""" | |
| try: | |
| if client_name not in self.email_clients: | |
| return f"❌ 客戶 {client_name} 未配置" | |
| client_config = self.email_clients[client_name] | |
| # 為每個客戶設定專屬的Resend API Key | |
| resend.api_key = client_config['api_key'] | |
| # 構建HTML郵件內容 | |
| html_content = f""" | |
| <html> | |
| <head> | |
| <style> | |
| body {{ | |
| font-family: 'Microsoft JhengHei', Arial, sans-serif; | |
| line-height: 1.6; | |
| color: #333; | |
| max-width: 900px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| }} | |
| .header {{ | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 20px; | |
| text-align: center; | |
| border-radius: 10px; | |
| margin-bottom: 20px; | |
| }} | |
| .content {{ | |
| background-color: #f9f9f9; | |
| padding: 25px; | |
| border-radius: 10px; | |
| border-left: 5px solid #667eea; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| }} | |
| .analysis-type {{ | |
| background-color: #e3f2fd; | |
| padding: 10px; | |
| border-radius: 5px; | |
| margin-bottom: 15px; | |
| font-weight: bold; | |
| color: #1976d2; | |
| }} | |
| .client-info {{ | |
| background-color: #f3e5f5; | |
| padding: 10px; | |
| border-radius: 5px; | |
| margin-bottom: 15px; | |
| font-weight: bold; | |
| color: #7b1fa2; | |
| }} | |
| .timestamp {{ | |
| color: #666; | |
| font-size: 0.9em; | |
| margin-top: 20px; | |
| text-align: right; | |
| border-top: 1px solid #ddd; | |
| padding-top: 10px; | |
| }} | |
| pre {{ | |
| white-space: pre-wrap; | |
| background: white; | |
| padding: 20px; | |
| border-radius: 8px; | |
| border: 1px solid #e0e0e0; | |
| font-family: 'Microsoft JhengHei', Arial, sans-serif; | |
| font-size: 14px; | |
| line-height: 1.5; | |
| }} | |
| </style> | |
| </head> | |
| <body> | |
| <div class="header"> | |
| <h1>🤖 Gemini AI 圖片分析結果</h1> | |
| <p>智能圖片分析與比較系統</p> | |
| </div> | |
| <div class="content"> | |
| <div class="client-info"> | |
| 👤 專屬客戶:{client_name} | 📧 收件人:{client_config['email']} | |
| </div> | |
| <div class="analysis-type"> | |
| 📋 分析類型:{analysis_type} | 📸 圖片數量:{image_count} 張 | |
| </div> | |
| <h2>🔍 分析結果</h2> | |
| <pre>{analysis_result}</pre> | |
| </div> | |
| <div class="timestamp"> | |
| 🕐 產生時間:{datetime.now().strftime("%Y年%m月%d日 %H:%M:%S")} | |
| <br> | |
| 🚀 Powered by Google Gemini 2.0 & Resend | |
| <br> | |
| 📋 專屬報告 for {client_name} | |
| <br> | |
| 🔑 使用專屬 API Key:{client_config['api_key'][:8]}... | |
| </div> | |
| </body> | |
| </html> | |
| """ | |
| params = { | |
| "from": f"Gemini AI <onboarding@resend.dev>", | |
| "to": [client_config['email']], | |
| "subject": f"🤖 AI圖片分析結果 - {client_name} - {analysis_type} ({image_count}張圖片)", | |
| "html": html_content | |
| } | |
| email = resend.Emails.send(params) | |
| api_key_display = client_config['api_key'][:8] + '...' | |
| return f"✅ 發送成功!客戶:{client_name}(API: {api_key_display}),郵件ID: {email.get('id', 'N/A')}" | |
| except Exception as e: | |
| api_key_display = self.email_clients[client_name]['api_key'][:8] + '...' if client_name in self.email_clients else 'N/A' | |
| return f"❌ 發送失敗({client_name}, API: {api_key_display}): {str(e)}" | |
| def send_to_all_clients(self, analysis_result, image_count=1, analysis_type="單圖片分析"): | |
| """向所有客戶發送郵件""" | |
| results = [] | |
| for client_name in self.email_clients.keys(): | |
| result = self.send_analysis_email(client_name, analysis_result, image_count, analysis_type) | |
| results.append(result) | |
| return results | |
| def parse_client_configs(config_text): | |
| """解析客戶配置文本(包含專屬的Resend API Key)""" | |
| clients = [] | |
| if not config_text.strip(): | |
| return clients | |
| lines = config_text.strip().split('\n') | |
| for line_num, line in enumerate(lines, 1): | |
| line = line.strip() | |
| if not line or line.startswith('#'): # 忽略空行和註釋行 | |
| continue | |
| parts = line.split('|') | |
| if len(parts) != 3: | |
| raise ValueError(f"第{line_num}行格式錯誤:'{line}'\n正確格式:客戶名稱|郵箱|Resend API Key") | |
| client_name = parts[0].strip() | |
| email = parts[1].strip() | |
| api_key = parts[2].strip() | |
| if not client_name or not email or not api_key: | |
| raise ValueError(f"第{line_num}行有空白欄位:'{line}'") | |
| # 簡單的郵箱格式驗證 | |
| if '@' not in email or '.' not in email.split('@')[1]: | |
| raise ValueError(f"第{line_num}行郵箱格式錯誤:'{email}'") | |
| # Resend API Key 格式驗證 | |
| if not api_key.startswith('re_'): | |
| raise ValueError(f"第{line_num}行 Resend API Key 格式錯誤:'{api_key}' (應以 're_' 開頭)") | |
| clients.append({ | |
| 'name': client_name, | |
| 'email': email, | |
| 'api_key': api_key | |
| }) | |
| return clients | |
| def analyze_images_and_send_emails(images, analysis_mode, custom_prompt, client_configs, gemini_key): | |
| """分析圖片並向多個客戶發送郵件""" | |
| if not images or len(images) == 0: | |
| return "❌ 請至少上傳一張圖片", "", "" | |
| if not client_configs.strip(): | |
| return "❌ 請輸入至少一個客戶配置", "", "" | |
| if not gemini_key: | |
| return "❌ 請輸入Gemini API Key", "", "" | |
| try: | |
| # 解析客戶配置 | |
| clients = parse_client_configs(client_configs) | |
| if not clients: | |
| return "❌ 沒有有效的客戶配置", "", "" | |
| # 初始化服務 | |
| analyzer = GeminiImageAnalyzer(gemini_key) | |
| email_sender = MultiClientEmailSender() | |
| # 添加所有客戶配置(包含專屬API Key) | |
| for client in clients: | |
| email_sender.add_client(client['name'], client['email'], client['api_key']) | |
| # 獲取圖片路徑列表 | |
| image_paths = images if isinstance(images, list) else [images] | |
| image_count = len(image_paths) | |
| # 根據分析模式選擇分析方法 | |
| if analysis_mode == "比較分析": | |
| analysis_result = analyzer.analyze_multiple_images(image_paths, custom_prompt) | |
| analysis_type = "多圖片比較分析" | |
| elif analysis_mode == "批量分析": | |
| analysis_result = analyzer.batch_analyze_images(image_paths, custom_prompt) | |
| analysis_type = "批量個別分析" | |
| else: # 單張分析 | |
| if len(image_paths) > 1: | |
| # 如果上傳多張但選擇單張分析,只分析第一張 | |
| analysis_result = analyzer.analyze_single_image(image_paths[0], custom_prompt) | |
| analysis_type = "單圖片分析(僅分析第一張)" | |
| image_count = 1 | |
| else: | |
| analysis_result = analyzer.analyze_single_image(image_paths[0], custom_prompt) | |
| analysis_type = "單圖片分析" | |
| if analysis_result.startswith(("分析失敗", "多圖片分析失敗", "批量分析失敗", "❌")): | |
| return f"❌ {analysis_result}", analysis_result, "" | |
| # 向所有客戶發送郵件 | |
| email_results = email_sender.send_to_all_clients(analysis_result, image_count, analysis_type) | |
| # 統計發送結果 | |
| success_count = sum(1 for result in email_results if result.startswith("✅")) | |
| total_count = len(email_results) | |
| status = f"""✅ 處理完成! | |
| 📊 分析模式:{analysis_type} | |
| 📸 處理圖片:{image_count} 張 | |
| 👥 客戶數量:{total_count} 個 | |
| 📧 發送成功:{success_count}/{total_count} | |
| 🔑 使用客戶專屬 API Keys | |
| 📋 詳細結果: | |
| {chr(10).join(email_results)}""" | |
| email_summary = "\n".join(email_results) | |
| return status, analysis_result, email_summary | |
| except ValueError as e: | |
| error_msg = f"❌ 配置錯誤: {str(e)}" | |
| return error_msg, "", "" | |
| except Exception as e: | |
| error_msg = f"❌ 處理失敗: {str(e)}" | |
| return error_msg, "", "" | |
| def save_results_to_csv(images, analysis_mode, custom_prompt, analysis_result, client_configs): | |
| """儲存結果到CSV""" | |
| try: | |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| temp_dir = tempfile.gettempdir() | |
| csv_filename = os.path.join(temp_dir, f"multi_client_analysis_{timestamp}.csv") | |
| with open(csv_filename, 'w', newline='', encoding='utf-8-sig') as csvfile: | |
| fieldnames = ['時間戳記', '分析模式', '圖片數量', '圖片列表', '自訂提示', '客戶數量', '客戶清單', '分析結果'] | |
| writer = csv.DictWriter(csvfile, fieldnames=fieldnames) | |
| writer.writeheader() | |
| # 處理圖片列表 | |
| if images: | |
| image_list = [os.path.basename(img) if isinstance(img, str) else f"圖片_{i+1}" | |
| for i, img in enumerate(images)] | |
| image_names = "; ".join(image_list) | |
| image_count = len(images) | |
| else: | |
| image_names = "無" | |
| image_count = 0 | |
| # 處理客戶配置 | |
| try: | |
| clients = parse_client_configs(client_configs) | |
| client_list = "; ".join([f"{client['name']}({client['email']})" for client in clients]) | |
| client_count = len(clients) | |
| except: | |
| client_list = "配置解析失敗" | |
| client_count = 0 | |
| writer.writerow({ | |
| '時間戳記': datetime.now().strftime("%Y-%m-%d %H:%M:%S"), | |
| '分析模式': analysis_mode, | |
| '圖片數量': image_count, | |
| '圖片列表': image_names, | |
| '自訂提示': custom_prompt or "預設提示", | |
| '客戶數量': client_count, | |
| '客戶清單': client_list, | |
| '分析結果': analysis_result | |
| }) | |
| return csv_filename | |
| except Exception as e: | |
| return f"儲存失敗: {str(e)}" | |
| # 建立Gradio介面 | |
| def create_interface(): | |
| with gr.Blocks( | |
| title="🤖 Gemini 多客戶圖片分析系統 - 專屬API版", | |
| theme=gr.themes.Soft(), | |
| css=""" | |
| .gradio-container { | |
| max-width: 1400px !important; | |
| margin: auto !important; | |
| } | |
| .main-header { | |
| text-align: center; | |
| margin-bottom: 2rem; | |
| padding: 1.5rem; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| border-radius: 15px; | |
| box-shadow: 0 4px 15px rgba(0,0,0,0.2); | |
| } | |
| .api-warning { | |
| background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%); | |
| border: 1px solid #ffeaa7; | |
| border-radius: 10px; | |
| padding: 15px; | |
| margin: 15px 0; | |
| color: #856404; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
| } | |
| .feature-highlight { | |
| background: linear-gradient(135deg, #e8f5e8 0%, #c8e6c9 100%); | |
| border: 2px solid #4caf50; | |
| border-radius: 10px; | |
| padding: 15px; | |
| margin: 15px 0; | |
| color: #2e7d32; | |
| } | |
| .client-config-area { | |
| background: linear-gradient(135deg, #f3e5f5 0%, #e1bee7 100%); | |
| border: 2px solid #9c27b0; | |
| border-radius: 10px; | |
| padding: 15px; | |
| margin: 15px 0; | |
| } | |
| """ | |
| ) as demo: | |
| gr.HTML(""" | |
| <div class="main-header"> | |
| <h1>🤖 Gemini 多客戶圖片分析系統</h1> | |
| <p>專屬API Key版本 - 每個客戶使用專屬的Resend API</p> | |
| <p><small>🚀 部署於 Hugging Face Spaces | 🔥 支援最新 Gemini 2.0 | 🔑 客戶專屬API管理</small></p> | |
| </div> | |
| """) | |
| # 新功能亮點 | |
| gr.HTML(""" | |
| <div class="feature-highlight"> | |
| <h3>🎯 專屬API版本功能亮點:</h3> | |
| <ul> | |
| <li>👥 <strong>多客戶支援</strong>:一次分析,同時發送給多個客戶</li> | |
| <li>🔑 <strong>專屬API配置</strong>:每個客戶使用專屬的Resend API Key</li> | |
| <li>📧 <strong>個性化郵件</strong>:每封郵件都標註專屬客戶資訊和API來源</li> | |
| <li>💰 <strong>獨立計費</strong>:每個客戶的API使用量獨立計算</li> | |
| <li>🔐 <strong>安全隔離</strong>:客戶間的API Key完全隔離,提升安全性</li> | |
| <li>📊 <strong>詳細追蹤</strong>:顯示每個客戶使用的API Key資訊</li> | |
| </ul> | |
| </div> | |
| """) | |
| # API 安全提醒 | |
| gr.HTML(""" | |
| <div class="api-warning"> | |
| <strong>⚠️ 專屬API版本重要提醒:</strong><br> | |
| • 每個客戶需要配置專屬的 Resend API Key<br> | |
| • 客戶的 API Key 使用量和費用完全獨立<br> | |
| • 請確保每個 API Key 都有足夠的發送配額<br> | |
| • 建議客戶自行管理和監控API使用量<br> | |
| • 此應用程式不會永久儲存任何 API 密鑰<br> | |
| • 提供更高的安全性和使用量透明度 | |
| </div> | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 📁 圖片上傳") | |
| images_input = gr.File( | |
| label="選擇圖片 (支援多張上傳)", | |
| file_count="multiple", | |
| file_types=["image"], | |
| height=200 | |
| ) | |
| # 顯示上傳的圖片預覽 | |
| images_gallery = gr.Gallery( | |
| label="圖片預覽", | |
| show_label=True, | |
| elem_id="gallery", | |
| columns=3, | |
| rows=2, | |
| height=300, | |
| object_fit="contain" | |
| ) | |
| gr.Markdown("### 🎯 分析模式") | |
| analysis_mode = gr.Radio( | |
| choices=["單張分析", "比較分析", "批量分析"], | |
| value="比較分析", | |
| label="選擇分析模式", | |
| info=""" | |
| • 單張分析:分析單張圖片(如上傳多張僅分析第一張) | |
| • 比較分析:比較多張圖片的差異和相似點(推薦) | |
| • 批量分析:對每張圖片進行單獨分析 | |
| """ | |
| ) | |
| gr.Markdown("### ✏️ 分析設定") | |
| prompt_input = gr.Textbox( | |
| label="自訂提示詞 (可選)", | |
| placeholder="留空將使用智能預設提示詞...", | |
| lines=3, | |
| info="不同分析模式會自動使用相應的最佳化提示詞" | |
| ) | |
| gr.Markdown("### 🔑 Gemini API Key") | |
| gemini_key = gr.Textbox( | |
| label="Gemini API Key", | |
| placeholder="AIza...", | |
| type="password" | |
| ) | |
| with gr.Column(scale=1): | |
| gr.HTML(""" | |
| <div class="client-config-area"> | |
| <h3>👥 多客戶專屬API配置</h3> | |
| <p>配置格式:<strong>客戶名稱|收件人郵箱|Resend API Key</strong></p> | |
| <p>每行一個客戶,每個客戶使用專屬的API Key</p> | |
| <p><em>🔑 專屬版本:每個客戶獨立API管理,費用分離!</em></p> | |
| </div> | |
| """) | |
| client_configs = gr.Textbox( | |
| label="🏢 客戶專屬API配置列表", | |
| placeholder="""客戶A|clientA@example.com|re_xxxxxxxxx_A | |
| 客戶B|clientB@example.com|re_xxxxxxxxx_B | |
| 客戶C|clientC@example.com|re_xxxxxxxxx_C | |
| # 這是註釋行,會被忽略 | |
| 客戶D|clientD@example.com|re_xxxxxxxxx_D""", | |
| lines=10, | |
| info="每行一個客戶,格式:客戶名稱|郵箱|Resend API Key。支援 # 開頭的註釋行" | |
| ) | |
| with gr.Accordion("如何獲取 API Keys", open=False): | |
| gr.Markdown(""" | |
| **Gemini API Key:** | |
| 1. 前往 [Google AI Studio](https://aistudio.google.com/app/apikey) | |
| 2. 創建新的 API Key | |
| 3. 複製 API Key(格式:AIza...) | |
| **Resend API Key (每個客戶專屬):** | |
| 1. 前往 [Resend](https://resend.com/api-keys) | |
| 2. 為每個客戶註冊獨立帳號或創建專屬 API Key | |
| 3. 複製各自的 API Key(格式:re_...) | |
| 4. 每個客戶的使用量和費用完全獨立 | |
| **專屬API配置格式:** | |
| ``` | |
| 客戶名稱|client@email.com|re_客戶專屬API金鑰 | |
| 公司A|companyA@domain.com|re_ABC123_companyA | |
| 團隊B|teamB@company.org|re_DEF456_teamB | |
| ``` | |
| **專屬版本優勢:** | |
| - 🔐 每個客戶的API Key完全獨立 | |
| - 💰 使用量和費用分離,便於管理 | |
| - 📊 可以追蹤每個客戶的發送狀況 | |
| - 🛡️ 更高的安全性和隱私保護 | |
| - 🎯 客戶可以自行控制發送配額 | |
| **注意事項:** | |
| - 確保每個API Key格式正確(以re_開頭) | |
| - 建議客戶自行管理API Key和配額 | |
| - 測試時請確認所有API Key都有效 | |
| - 一次最多建議配置 20 個客戶 | |
| """) | |
| gr.Markdown("### 🚀 執行操作") | |
| analyze_btn = gr.Button( | |
| "🔍 開始AI分析並發送給所有客戶", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| gr.Markdown("### 📊 處理結果") | |
| with gr.Row(): | |
| status_output = gr.Textbox( | |
| label="📈 處理狀態與發送統計", | |
| lines=8, | |
| interactive=False | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| analysis_output = gr.Textbox( | |
| label="🤖 AI分析結果", | |
| lines=15, | |
| interactive=False | |
| ) | |
| with gr.Column(): | |
| email_output = gr.Textbox( | |
| label="📧 多客戶專屬API郵件發送結果", | |
| lines=15, | |
| interactive=False | |
| ) | |
| with gr.Row(): | |
| download_btn = gr.Button("💾 下載完整CSV報告", variant="secondary") | |
| csv_file = gr.File(label="CSV檔案", visible=False) | |
| # 事件綁定 | |
| def update_gallery(files): | |
| if files: | |
| return [file.name for file in files] | |
| return [] | |
| images_input.change( | |
| fn=update_gallery, | |
| inputs=[images_input], | |
| outputs=[images_gallery] | |
| ) | |
| analyze_btn.click( | |
| fn=analyze_images_and_send_emails, | |
| inputs=[images_input, analysis_mode, prompt_input, client_configs, gemini_key], | |
| outputs=[status_output, analysis_output, email_output] | |
| ) | |
| def download_csv(images, analysis_mode, custom_prompt, analysis_result, client_configs): | |
| if analysis_result and not analysis_result.startswith("❌"): | |
| filename = save_results_to_csv(images, analysis_mode, custom_prompt, analysis_result, client_configs) | |
| if not filename.startswith("儲存失敗"): | |
| return gr.update(value=filename, visible=True) | |
| return gr.update(visible=False) | |
| download_btn.click( | |
| fn=download_csv, | |
| inputs=[images_input, analysis_mode, prompt_input, analysis_output, client_configs], | |
| outputs=csv_file | |
| ) | |
| # 使用說明 | |
| with gr.Accordion("📝 專屬API系統使用說明", open=False): | |
| gr.Markdown(""" | |
| ### 🏢 專屬API客戶配置格式 | |
| **基本格式:** | |
| ``` | |
| 客戶名稱|收件人郵箱|Resend API Key | |
| ``` | |
| **配置範例:** | |
| ``` | |
| 科技公司A|tech@companyA.com|re_ABC123_techA | |
| 設計工作室B|design@studioB.net|re_DEF456_designB | |
| 行銷團隊C|marketing@teamC.org|re_GHI789_marketC | |
| # 這是註釋,可以用來分組或說明 | |
| 個人客戶D|personal@clientD.com|re_JKL012_personalD | |
| ``` | |
| ### 🔑 專屬API版本特色 | |
| **核心優勢:** | |
| - ✅ 每個客戶使用專屬的Resend API Key | |
| - ✅ 使用量和費用完全獨立,便於管理 | |
| - ✅ 提供更高的安全性和隱私保護 | |
| - ✅ 可以追蹤每個客戶的發送狀況 | |
| - ✅ 支援客戶自主控制發送配額 | |
| - ✅ 個性化郵件標示API來源 | |
| **vs 統一API版本差異:** | |
| | 功能 | 統一API版本 | 專屬API版本 | | |
| |------|-------------|-------------| | |
| | API Key數量 | 1個共用 | 每客戶1個 | | |
| | 費用管理 | 統一計費 | 獨立計費 | | |
| | 安全性 | 一般 | 更高 | | |
| | 配額控制 | 統一控制 | 客戶自控 | | |
| | 追蹤能力 | 基本 | 詳細 | | |
| ### 👥 客戶管理功能 | |
| **支援特性:** | |
| - ✅ 多客戶同時發送(建議1-20個客戶) | |
| - ✅ 專屬 API Key 管理,安全性更高 | |
| - ✅ 個性化郵件內容(包含客戶名稱和API資訊) | |
| - ✅ 發送狀態統計和詳細報告 | |
| - ✅ 註釋行支援(# 開頭) | |
| - ✅ API Key 驗證和格式檢查 | |
| **配置規則:** | |
| - 每行一個客戶配置 | |
| - 使用 `|` 分隔三個欄位 | |
| - 客戶名稱不可重複 | |
| - 郵箱必須是有效格式 | |
| - Resend API Key 必須以 `re_` 開頭 | |
| - 每個API Key必須有效且有足夠配額 | |
| ### 📧 專屬API郵件發送流程 | |
| **1. 配置驗證階段** | |
| - 驗證客戶配置格式正確性 | |
| - 檢查API Key格式(必須以re_開頭) | |
| - 驗證郵箱地址格式 | |
| - 確認客戶名稱唯一性 | |
| **2. 圖片分析階段** | |
| - 上傳圖片並選擇分析模式 | |
| - 使用Gemini AI 進行圖片內容分析 | |
| - 生成統一的分析報告 | |
| **3. 專屬API發送階段** | |
| - 為每個客戶設定專屬的API Key | |
| - 使用對應的API Key發送郵件 | |
| - 在郵件中標示使用的API Key資訊 | |
| - 記錄每個客戶的發送結果 | |
| **4. 結果統計與追蹤** | |
| - 顯示每個客戶的發送狀態 | |
| - 標示使用的API Key(前8碼) | |
| - 統計總成功/失敗數量 | |
| - 提供詳細的錯誤資訊 | |
| ### ⚡ 效能與限制 | |
| **建議使用量:** | |
| - **客戶數量**:1-20個客戶為最佳 | |
| - **圖片數量**:2-10張圖片 | |
| - **圖片大小**:單張不超過 10MB | |
| - **處理時間**:隨客戶數量線性增加 | |
| **API 配額考量:** | |
| - **Gemini API**:一次分析消耗一次配額 | |
| - **Resend API**:每個客戶使用專屬配額 | |
| - **獨立計費**:每個客戶的費用完全分離 | |
| - **配額管理**:客戶可自行監控和控制 | |
| **專屬版本優勢:** | |
| - 🔐 **更高安全性**:API Key完全隔離 | |
| - 💰 **費用透明**:每個客戶獨立計費 | |
| - 📊 **精確追蹤**:可追蹤每個API的使用狀況 | |
| - 🎯 **自主控制**:客戶可控制發送配額 | |
| - 🛡️ **隱私保護**:客戶間資訊完全隔離 | |
| ### 🛠️ 故障排除 | |
| **常見錯誤:** | |
| - **配置格式錯誤**:檢查 `|` 分隔符數量(應為2個) | |
| - **API Key 格式錯誤**:確認以 `re_` 開頭 | |
| - **API Key 無效**:檢查每個客戶的API Key有效性 | |
| - **郵箱格式錯誤**:確保包含 @ 和有效域名 | |
| - **重複客戶名稱**:每個客戶名稱必須唯一 | |
| - **配額不足**:檢查特定客戶的API配額 | |
| **專屬版除錯建議:** | |
| - 先用 1 個客戶測試配置 | |
| - 逐一驗證每個API Key的有效性 | |
| - 確認每個API Key都有足夠配額 | |
| - 檢查客戶端的API Key權限設定 | |
| - 查看具體的API Key錯誤訊息 | |
| - 建議客戶監控自己的API使用量 | |
| ### 💡 最佳實踐 | |
| **專屬API配置管理:** | |
| - 要求客戶自行註冊Resend帳號 | |
| - 建立標準化的API Key命名規範 | |
| - 定期檢查各客戶的API Key狀態 | |
| - 建議客戶設定使用量警告 | |
| **發送策略:** | |
| - 測試時先用1個客戶驗證配置 | |
| - 確認每個API Key都有足夠配額 | |
| - 建議客戶分散API使用時間 | |
| - 提供API使用量監控建議 | |
| **安全考量:** | |
| - 提醒客戶妥善保管API Key | |
| - 建議定期輪換API Key | |
| - 不在日誌中記錄完整API Key | |
| - 建議客戶監控異常使用 | |
| ### 🔄 版本比較 | |
| **專屬API版本 vs 統一API版本:** | |
| | 特性 | 專屬API版本 | 統一API版本 | | |
| |------|-------------|-------------| | |
| | **安全性** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | | |
| | **配置複雜度** | ⭐⭐ | ⭐⭐⭐⭐⭐ | | |
| | **費用管理** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | | |
| | **追蹤能力** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | | |
| | **客戶自主性** | ⭐⭐⭐⭐⭐ | ⭐⭐ | | |
| | **部署簡易度** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | | |
| **適用場景:** | |
| - **專屬API版本**:企業客戶、長期合作、高安全要求 | |
| - **統一API版本**:快速部署、測試環境、小型客戶 | |
| ### 📋 配置檢查清單 | |
| **部署前檢查:** | |
| - [ ] 每個客戶都有有效的Resend API Key | |
| - [ ] 所有API Key都以 `re_` 開頭 | |
| - [ ] 客戶名稱沒有重複 | |
| - [ ] 郵箱地址格式正確 | |
| - [ ] Gemini API Key有效且有足夠配額 | |
| - [ ] 測試環境中先驗證少數客戶 | |
| **發送前檢查:** | |
| - [ ] 確認所有API Key都有足夠發送配額 | |
| - [ ] 圖片格式和大小符合要求 | |
| - [ ] 選擇適當的分析模式 | |
| - [ ] 預覽客戶配置列表正確 | |
| - [ ] 備份重要的配置資訊 | |
| """) | |
| # 提示詞範例 | |
| with gr.Accordion("🎯 提示詞範例", open=False): | |
| gr.Markdown("### 📸 單張分析提示詞") | |
| gr.Examples( | |
| examples=[ | |
| ["請詳細描述這張圖片,包括主要物件、顏色、構圖和可能的背景故事"], | |
| ["提取並分析圖片中的所有文字內容,並解釋其含義"], | |
| ["分析圖片的藝術風格、攝影技法和視覺效果"], | |
| ["識別圖片中的品牌、標誌和商業元素"], | |
| ["描述圖片中人物的情緒、動作和互動關係"] | |
| ], | |
| inputs=prompt_input, | |
| label="點擊使用單張分析範例" | |
| ) | |
| gr.Markdown("### 🔄 比較分析提示詞") | |
| gr.Examples( | |
| examples=[ | |
| ["比較這些圖片的相似點和差異,分析它們的關聯性"], | |
| ["分析這些圖片在構圖、色彩和風格上的異同"], | |
| ["比較圖片中產品或物件的特徵差異"], | |
| ["分析這些圖片的時間順序和變化過程"], | |
| ["比較不同角度或場景下同一主題的表現差異"] | |
| ], | |
| inputs=prompt_input, | |
| label="點擊使用比較分析範例" | |
| ) | |
| # 客戶配置範例 | |
| with gr.Accordion("🏢 專屬API客戶配置範例", open=False): | |
| gr.Markdown("### 配置範例") | |
| gr.Examples( | |
| examples=[ | |
| ["""科技公司A|tech@companyA.com|re_ABC123_techA | |
| 設計工作室B|design@studioB.net|re_DEF456_designB | |
| 行銷團隊C|marketing@teamC.org|re_GHI789_marketC"""], | |
| ["""# 企業客戶群組 | |
| 大企業甲|corp@bigcompany.com|re_CORP001_enterprise | |
| 中小企業乙|info@smallbiz.net|re_SME002_business | |
| # 個人客戶群組 | |
| 個人客戶A|john@gmail.com|re_PERSONAL003_john | |
| 個人客戶B|mary@yahoo.com|re_PERSONAL004_mary"""], | |
| ["""測試客戶1|test1@example.com|re_TEST001_client1 | |
| 測試客戶2|test2@example.com|re_TEST002_client2"""] | |
| ], | |
| inputs=client_configs, | |
| label="點擊使用專屬API配置範例" | |
| ) | |
| # 版本資訊 | |
| gr.HTML(""" | |
| <div style="text-align: center; margin-top: 2rem; padding: 1.5rem; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);"> | |
| <p><small> | |
| 🚀 Powered by <strong>Google Gemini 2.0</strong> & <strong>Resend</strong><br> | |
| 📍 Deployed on <strong>Hugging Face Spaces</strong><br> | |
| 🔥 <strong>專屬API Key版本 4.0</strong> | Made with ❤️ using Gradio<br> | |
| 🔑 每客戶專屬API管理 | ⭐ 支援最多10張圖片同時分析比較<br> | |
| 📧 <strong>新功能</strong>:客戶專屬API Key批量郵件發送系統<br> | |
| 🎯 <strong>v4.0 特色</strong>:獨立API管理,更高安全性,費用透明化<br> | |
| 💰 <strong>專屬版優勢</strong>:獨立計費 | 🔐 安全隔離 | 📊 精確追蹤 | |
| </small></p> | |
| </div> | |
| """) | |
| return demo | |
| # 主程式入口 | |
| if __name__ == "__main__": | |
| # 建立並啟動介面 | |
| demo = create_interface() | |
| # Hugging Face Spaces 部署設定 | |
| demo.launch( | |
| share=False, | |
| show_error=True, | |
| show_api=False, | |
| quiet=False | |
| ) |