Spaces:
Sleeping
Sleeping
| import os | |
| import uvicorn | |
| import requests | |
| from bs4 import BeautifulSoup | |
| import google.generativeai as genai | |
| from fastapi import FastAPI, Request, Header, BackgroundTasks, HTTPException | |
| from linebot import LineBotApi, WebhookHandler | |
| from linebot.exceptions import InvalidSignatureError | |
| from linebot.models import MessageEvent, TextMessage, TextSendMessage, ImageSendMessage | |
| import tempfile | |
| import base64 | |
| from PIL import Image | |
| import io | |
| # --- 設定部分 --- | |
| # 建立 FastAPI 應用程式 | |
| app = FastAPI() | |
| # 初始化全域變數 | |
| web_content = "" | |
| line_bot_api = None | |
| line_handler = None | |
| text_model = None | |
| image_model = None | |
| TARGET_URL = "https://huggingface.co/spaces/AlanRex/AITEST" # 要分享的網址 | |
| def initialize_services(): | |
| """初始化各項服務""" | |
| global web_content, line_bot_api, line_handler, text_model, image_model | |
| # 設定 Google AI API 金鑰 | |
| google_api_key = os.getenv("GOOGLE_API_KEY") | |
| if google_api_key: | |
| genai.configure(api_key=google_api_key) | |
| print("Google AI API 已設定") | |
| else: | |
| print("警告: 未找到 GOOGLE_API_KEY 環境變數") | |
| # 設定 Line Bot 的 API 金鑰和秘密金鑰 | |
| channel_access_token = os.getenv("CHANNEL_ACCESS_TOKEN") | |
| channel_secret = os.getenv("CHANNEL_SECRET") | |
| if channel_access_token and channel_secret: | |
| line_bot_api = LineBotApi(channel_access_token) | |
| line_handler = WebhookHandler(channel_secret) | |
| print("Line Bot API 已設定") | |
| else: | |
| print("警告: 未找到 Line Bot 環境變數") | |
| try: | |
| # 使用 requests 取得網頁的 HTML 內容 | |
| print(f"正在抓取網頁內容: {TARGET_URL}") | |
| response = requests.get(TARGET_URL, timeout=10) | |
| # 檢查是否成功取得網頁 (HTTP status code 200) | |
| response.raise_for_status() | |
| # 使用 BeautifulSoup 解析 HTML | |
| soup = BeautifulSoup(response.text, 'html.parser') | |
| # 取得網頁中所有的文字內容,並移除多餘的空白 | |
| web_content = soup.get_text(separator=' ', strip=True) | |
| print(f"成功抓取網頁內容,長度: {len(web_content)} 字元") | |
| except requests.exceptions.RequestException as e: | |
| # 處理抓取網頁時發生的錯誤 | |
| print(f"Error fetching the URL: {e}") | |
| web_content = "無法取得網頁內容,請檢查網址或連線。" | |
| # 設定生成文字的角色扮演與限制 | |
| system_instruction = web_content + "\n\n您是一位問答助手。請僅限使用以上提供的內容來回答問題。請以簡潔明確的方式回答,並且能夠根據內容描述相關的視覺元素。" | |
| # 建立模型實例 | |
| if google_api_key: | |
| try: | |
| # 文字生成模型 | |
| text_model = genai.GenerativeModel( | |
| model_name="gemini-2.5-flash", | |
| system_instruction=system_instruction | |
| ) | |
| # 圖片生成模型 (使用 Imagen 3) | |
| image_model = genai.GenerativeModel("imagen-3.0-generate-001") | |
| print("Gemini 模型已初始化") | |
| except Exception as e: | |
| print(f"初始化 Gemini 模型時出錯: {e}") | |
| text_model = None | |
| image_model = None | |
| def generate_image_from_content(user_prompt): | |
| """根據網站內容和用戶提問生成圖片""" | |
| try: | |
| if not text_model or not image_model: | |
| return None, "AI 模型尚未準備就緒" | |
| # 先用文字模型分析內容並產生圖片描述 | |
| analysis_prompt = f""" | |
| 基於網站內容,請為以下問題產生一個適合的圖片描述(英文): | |
| 問題:{user_prompt} | |
| 請產生一個清楚、具體的圖片描述,描述應該: | |
| 1. 與網站內容相關 | |
| 2. 適合視覺化呈現 | |
| 3. 使用英文描述 | |
| 4. 長度在50-100字之間 | |
| 只回傳圖片描述,不需要其他解釋。 | |
| """ | |
| response = text_model.generate_content(analysis_prompt) | |
| if not response or not response.text: | |
| return None, "無法產生圖片描述" | |
| image_description = response.text.strip() | |
| print(f"圖片描述:{image_description}") | |
| # 使用圖片描述生成圖片 | |
| image_response = image_model.generate_content([image_description]) | |
| if not image_response or not image_response.candidates: | |
| return None, "無法生成圖片" | |
| # 取得生成的圖片 | |
| generated_image = image_response.candidates[0] | |
| if hasattr(generated_image, 'content') and hasattr(generated_image.content, 'parts'): | |
| image_part = generated_image.content.parts[0] | |
| if hasattr(image_part, 'inline_data'): | |
| image_data = image_part.inline_data.data | |
| # 將 base64 數據轉換為圖片 | |
| image_bytes = base64.b64decode(image_data) | |
| # 上傳圖片到臨時服務 (這裡需要實作圖片上傳邏輯) | |
| # 由於 Line Bot 需要公開的圖片網址,您需要將圖片上傳到雲端服務 | |
| # 這裡提供一個範例架構 | |
| return image_bytes, "圖片生成成功" | |
| return None, "圖片數據格式不正確" | |
| except Exception as e: | |
| print(f"生成圖片時出錯: {e}") | |
| return None, f"生成圖片時出錯: {str(e)}" | |
| def upload_image_to_cloud(image_bytes): | |
| """ | |
| 上傳圖片到雲端服務並回傳公開網址 | |
| 這裡需要實作實際的上傳邏輯,例如上傳到: | |
| - Imgur | |
| - Cloudinary | |
| - AWS S3 | |
| - Google Cloud Storage | |
| 等服務 | |
| """ | |
| # 範例:這裡應該實作實際的上傳邏輯 | |
| # 暫時回傳一個佔位網址 | |
| return "https://example.com/placeholder.jpg" | |
| # 處理根路徑請求 | |
| def root(): | |
| return { | |
| "title": "Line Bot with Image Generation", | |
| "status": "running", | |
| "target_url": TARGET_URL, | |
| "web_content_loaded": len(web_content) > 0, | |
| "line_bot_configured": line_bot_api is not None, | |
| "gemini_configured": text_model is not None, | |
| "image_generation_enabled": image_model is not None | |
| } | |
| # 健康檢查端點 | |
| def health_check(): | |
| return {"status": "healthy"} | |
| # 處理 Line Webhook 請求 | |
| async def webhook( | |
| request: Request, | |
| background_tasks: BackgroundTasks, | |
| x_line_signature=Header(None) | |
| ): | |
| if not line_handler: | |
| raise HTTPException(status_code=500, detail="Line Bot not configured") | |
| # 取得請求內容 | |
| body = await request.body() | |
| try: | |
| # 將處理 Line 事件的任務加入背景工作 | |
| background_tasks.add_task( | |
| line_handler.handle, | |
| body.decode("utf-8"), | |
| x_line_signature | |
| ) | |
| except InvalidSignatureError: | |
| # 處理無效的簽章錯誤 | |
| raise HTTPException(status_code=400, detail="Invalid signature") | |
| return "ok" | |
| # 處理文字訊息事件 | |
| def setup_message_handler(): | |
| """設定訊息處理器""" | |
| if not line_handler: | |
| return | |
| def handle_message(event): | |
| # 檢查是否為文字訊息 | |
| if event.message.type != "text": | |
| line_bot_api.reply_message( | |
| event.reply_token, | |
| TextSendMessage(text="請輸入文字訊息。") | |
| ) | |
| return | |
| # 取得使用者輸入的文字 | |
| prompt = event.message.text | |
| if not text_model: | |
| line_bot_api.reply_message( | |
| event.reply_token, | |
| TextSendMessage(text="抱歉,AI 模型尚未準備就緒。") | |
| ) | |
| return | |
| try: | |
| # 先生成文字回應 | |
| generation_config = genai.types.GenerationConfig( | |
| max_output_tokens=1000, | |
| temperature=0.3, | |
| top_p=0.5 | |
| ) | |
| text_response = text_model.generate_content( | |
| contents=prompt, | |
| generation_config=generation_config | |
| ) | |
| # 嘗試生成相關圖片 | |
| image_bytes, image_status = generate_image_from_content(prompt) | |
| messages = [] | |
| # 添加文字回應 | |
| if text_response and text_response.text: | |
| messages.append(TextSendMessage(text=text_response.text)) | |
| else: | |
| messages.append(TextSendMessage(text="抱歉,無法產生適當的回應。")) | |
| # 如果成功生成圖片,添加圖片訊息 | |
| if image_bytes: | |
| # 注意:這裡需要實作圖片上傳功能 | |
| # Line Bot 需要公開可訪問的圖片網址 | |
| # 上傳圖片並取得網址 | |
| image_url = upload_image_to_cloud(image_bytes) | |
| # 創建圖片訊息 | |
| # originalContentUrl: 原始圖片網址 | |
| # previewImageUrl: 預覽圖片網址 (通常使用相同網址) | |
| messages.append(ImageSendMessage( | |
| original_content_url=image_url, | |
| preview_image_url=image_url | |
| )) | |
| messages.append(TextSendMessage(text="✨ 根據內容為您生成了相關圖片")) | |
| else: | |
| messages.append(TextSendMessage(text=f"📝 文字回應已生成,圖片生成失敗:{image_status}")) | |
| # 發送所有訊息 | |
| line_bot_api.reply_message(event.reply_token, messages) | |
| except Exception as e: | |
| print(f"處理訊息時出錯: {e}") | |
| line_bot_api.reply_message( | |
| event.reply_token, | |
| TextSendMessage(text="處理您的訊息時發生錯誤,請稍後再試。") | |
| ) | |
| # 應用程式啟動事件 | |
| async def startup_event(): | |
| """應用程式啟動時執行的初始化""" | |
| print("正在初始化服務...") | |
| initialize_services() | |
| setup_message_handler() | |
| print("服務初始化完成") | |
| if __name__ == "__main__": | |
| # 啟動 FastAPI 應用程式 | |
| port = int(os.getenv("PORT", 7860)) | |
| uvicorn.run("main:app", host="0.0.0.0", port=port, reload=False) | |