Kuomin62 commited on
Commit
0ace500
·
verified ·
1 Parent(s): 9e61943

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +191 -105
main.py CHANGED
@@ -4,205 +4,291 @@ import requests
4
  from bs4 import BeautifulSoup
5
  import google.generativeai as genai
6
  from fastapi import FastAPI, Request, Header, BackgroundTasks, HTTPException
7
- from fastapi.responses import FileResponse
8
  from linebot import LineBotApi, WebhookHandler
9
  from linebot.exceptions import InvalidSignatureError
10
  from linebot.models import MessageEvent, TextMessage, TextSendMessage, ImageSendMessage
11
-
12
- import asyncio
13
- from pyppeteer import launch
 
14
 
15
  # --- 設定部分 ---
 
16
  app = FastAPI()
17
 
18
  # 初始化全域變數
19
  web_content = ""
20
  line_bot_api = None
21
  line_handler = None
22
- model = None
 
23
  TARGET_URL = "https://huggingface.co/spaces/AlanRex/AITEST" # 要分享的網址
24
- SNAPSHOT_FILE = "snapshot.png" # 截圖存放位置
25
-
26
-
27
- # -------- 截圖功能 --------
28
- async def take_screenshot(url, path=SNAPSHOT_FILE):
29
- """使用 pyppeteer 截圖指定網址"""
30
- browser = await launch(headless=True, args=["--no-sandbox"])
31
- page = await browser.newPage()
32
- await page.setViewport({"width": 1280, "height": 720})
33
- await page.goto(url, {"waitUntil": "networkidle2"})
34
- await page.screenshot({"path": path, "fullPage": True})
35
- await browser.close()
36
-
37
-
38
- @app.get("/snapshot")
39
- async def snapshot():
40
- """提供截圖圖片 API"""
41
- if not os.path.exists(SNAPSHOT_FILE):
42
- await take_screenshot(TARGET_URL, SNAPSHOT_FILE)
43
- return FileResponse(SNAPSHOT_FILE, media_type="image/png")
44
 
45
-
46
- # -------- 初始化服務 --------
47
  def initialize_services():
48
- global web_content, line_bot_api, line_handler, model
49
-
 
 
50
  google_api_key = os.getenv("GOOGLE_API_KEY")
51
  if google_api_key:
52
  genai.configure(api_key=google_api_key)
53
  print("Google AI API 已設定")
54
  else:
55
  print("警告: 未找到 GOOGLE_API_KEY 環境變數")
56
-
 
57
  channel_access_token = os.getenv("CHANNEL_ACCESS_TOKEN")
58
  channel_secret = os.getenv("CHANNEL_SECRET")
59
-
60
  if channel_access_token and channel_secret:
61
  line_bot_api = LineBotApi(channel_access_token)
62
  line_handler = WebhookHandler(channel_secret)
63
  print("Line Bot API 已設定")
64
  else:
65
  print("警告: 未找到 Line Bot 環境變數")
66
-
67
  try:
 
68
  print(f"正在抓取網頁內容: {TARGET_URL}")
69
  response = requests.get(TARGET_URL, timeout=10)
 
70
  response.raise_for_status()
71
- soup = BeautifulSoup(response.text, "html.parser")
72
- web_content = soup.get_text(separator=" ", strip=True)
 
 
 
73
  print(f"成功抓取網頁內容,長度: {len(web_content)} 字元")
 
74
  except requests.exceptions.RequestException as e:
 
75
  print(f"Error fetching the URL: {e}")
76
- web_content = "無法取得網頁內容。"
77
-
78
- system_instruction = web_content + f"\n\n您是一位問答助手,只能根據以上內容回答。" \
79
- f"更多資訊參考:{TARGET_URL}"
80
-
 
81
  if google_api_key:
82
  try:
83
- model = genai.GenerativeModel(
 
84
  model_name="gemini-2.5-flash",
85
  system_instruction=system_instruction
86
  )
 
 
 
 
87
  print("Gemini 模型已初始化")
88
  except Exception as e:
89
  print(f"初始化 Gemini 模型時出錯: {e}")
90
- model = None
91
-
92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  @app.get("/")
94
  def root():
95
  return {
96
- "title": "Line Bot",
97
  "status": "running",
98
  "target_url": TARGET_URL,
99
  "web_content_loaded": len(web_content) > 0,
100
  "line_bot_configured": line_bot_api is not None,
101
- "gemini_configured": model is not None,
 
102
  }
103
 
104
-
105
  @app.get("/health")
106
  def health_check():
107
  return {"status": "healthy"}
108
 
109
-
110
  @app.post("/webhook")
111
- async def webhook(request: Request, background_tasks: BackgroundTasks, x_line_signature=Header(None)):
 
 
 
 
112
  if not line_handler:
113
  raise HTTPException(status_code=500, detail="Line Bot not configured")
114
-
 
115
  body = await request.body()
116
-
117
  try:
 
118
  background_tasks.add_task(
119
  line_handler.handle,
120
  body.decode("utf-8"),
121
  x_line_signature
122
  )
123
  except InvalidSignatureError:
 
124
  raise HTTPException(status_code=400, detail="Invalid signature")
125
-
126
  return "ok"
127
 
128
-
129
- # -------- 處理文字訊息 --------
130
  def setup_message_handler():
 
131
  if not line_handler:
132
  return
133
-
134
  @line_handler.add(MessageEvent, message=TextMessage)
135
  def handle_message(event):
 
136
  if event.message.type != "text":
137
  line_bot_api.reply_message(
138
  event.reply_token,
139
  TextSendMessage(text="請輸入文字訊息。")
140
  )
141
  return
142
-
143
- prompt = event.message.text.strip().lower()
144
-
145
- # 處理網址關鍵字 → 回傳圖片
146
- if any(keyword in prompt for keyword in ["網址", "連結", "url", "link", "網站", "網頁"]):
147
- try:
148
- # 每次都更新截圖
149
- asyncio.get_event_loop().run_until_complete(
150
- take_screenshot(TARGET_URL, SNAPSHOT_FILE)
151
- )
152
-
153
- image_url = f"{os.getenv('SPACE_URL', 'http://localhost:7860')}/snapshot"
154
-
155
- line_bot_api.reply_message(
156
- event.reply_token,
157
- ImageSendMessage(
158
- original_content_url=image_url,
159
- preview_image_url=image_url
160
- )
161
- )
162
- except Exception as e:
163
- print(f"傳送圖片時出錯: {e}")
164
- line_bot_api.reply_message(
165
- event.reply_token,
166
- TextSendMessage(text=f"無法取得圖片,請直接訪問:{TARGET_URL}")
167
- )
168
  return
169
-
170
- # 如果沒有模型
171
- if not model:
172
- out = f"抱歉,AI 模型尚未準備好。\n您可以直接訪問:{TARGET_URL}"
173
- else:
174
- try:
175
- generation_config = genai.types.GenerationConfig(
176
- max_output_tokens=1000,
177
- temperature=0.2,
178
- top_p=0.8
179
- )
180
- completion = model.generate_content(
181
- contents=prompt,
182
- generation_config=generation_config
183
- )
184
- out = completion.text if completion and completion.text else "模型沒有生成答案!"
185
- except Exception as e:
186
- print(f"Gemini 執行出錯: {e}")
187
- out = f"Gemini 出錯,請稍後再試。\n您也可以直接訪問:{TARGET_URL}"
188
-
189
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  line_bot_api.reply_message(
191
  event.reply_token,
192
- TextSendMessage(text=out)
193
  )
194
- except Exception as e:
195
- print(f"回覆訊息時出錯: {e}")
196
-
197
 
 
198
  @app.on_event("startup")
199
  async def startup_event():
 
200
  print("正在初始化服務...")
201
  initialize_services()
202
  setup_message_handler()
203
  print("服務初始化完成")
204
 
205
-
206
  if __name__ == "__main__":
 
207
  port = int(os.getenv("PORT", 7860))
208
- uvicorn.run("app:app", host="0.0.0.0", port=port, reload=False)
 
4
  from bs4 import BeautifulSoup
5
  import google.generativeai as genai
6
  from fastapi import FastAPI, Request, Header, BackgroundTasks, HTTPException
 
7
  from linebot import LineBotApi, WebhookHandler
8
  from linebot.exceptions import InvalidSignatureError
9
  from linebot.models import MessageEvent, TextMessage, TextSendMessage, ImageSendMessage
10
+ import tempfile
11
+ import base64
12
+ from PIL import Image
13
+ import io
14
 
15
  # --- 設定部分 ---
16
+ # 建立 FastAPI 應用程式
17
  app = FastAPI()
18
 
19
  # 初始化全域變數
20
  web_content = ""
21
  line_bot_api = None
22
  line_handler = None
23
+ text_model = None
24
+ image_model = None
25
  TARGET_URL = "https://huggingface.co/spaces/AlanRex/AITEST" # 要分享的網址
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
 
 
27
  def initialize_services():
28
+ """初始化各項服務"""
29
+ global web_content, line_bot_api, line_handler, text_model, image_model
30
+
31
+ # 設定 Google AI API 金鑰
32
  google_api_key = os.getenv("GOOGLE_API_KEY")
33
  if google_api_key:
34
  genai.configure(api_key=google_api_key)
35
  print("Google AI API 已設定")
36
  else:
37
  print("警告: 未找到 GOOGLE_API_KEY 環境變數")
38
+
39
+ # 設定 Line Bot 的 API 金鑰和秘密金鑰
40
  channel_access_token = os.getenv("CHANNEL_ACCESS_TOKEN")
41
  channel_secret = os.getenv("CHANNEL_SECRET")
42
+
43
  if channel_access_token and channel_secret:
44
  line_bot_api = LineBotApi(channel_access_token)
45
  line_handler = WebhookHandler(channel_secret)
46
  print("Line Bot API 已設定")
47
  else:
48
  print("警告: 未找到 Line Bot 環境變數")
49
+
50
  try:
51
+ # 使用 requests 取得網頁的 HTML 內容
52
  print(f"正在抓取網頁內容: {TARGET_URL}")
53
  response = requests.get(TARGET_URL, timeout=10)
54
+ # 檢查是否成功取得網頁 (HTTP status code 200)
55
  response.raise_for_status()
56
+
57
+ # 使用 BeautifulSoup 解析 HTML
58
+ soup = BeautifulSoup(response.text, 'html.parser')
59
+ # 取得網頁中所有的文字內容,並移除多餘的空白
60
+ web_content = soup.get_text(separator=' ', strip=True)
61
  print(f"成功抓取網頁內容,長度: {len(web_content)} 字元")
62
+
63
  except requests.exceptions.RequestException as e:
64
+ # 處理抓取網頁時發生的錯誤
65
  print(f"Error fetching the URL: {e}")
66
+ web_content = "無法取得網頁內容,請檢查網址或連線。"
67
+
68
+ # 設定生成文字的角色扮演與限制
69
+ system_instruction = web_content + "\n\n您是一位問答助手。僅限使用以上提供的內容來回答問題。請以簡潔明確的方式回答,並且能夠根據內容描述相關的視覺元素。"
70
+
71
+ # 建立模型實例
72
  if google_api_key:
73
  try:
74
+ # 文字生成模型
75
+ text_model = genai.GenerativeModel(
76
  model_name="gemini-2.5-flash",
77
  system_instruction=system_instruction
78
  )
79
+
80
+ # 圖片生成模型 (使用 Imagen 3)
81
+ image_model = genai.GenerativeModel("imagen-3.0-generate-001")
82
+
83
  print("Gemini 模型已初始化")
84
  except Exception as e:
85
  print(f"初始化 Gemini 模型時出錯: {e}")
86
+ text_model = None
87
+ image_model = None
88
 
89
+ def generate_image_from_content(user_prompt):
90
+ """根據網站內容和用戶提問生成圖片"""
91
+ try:
92
+ if not text_model or not image_model:
93
+ return None, "AI 模型尚未準備就緒"
94
+
95
+ # 先用文字模型分析內容並產生圖片描述
96
+ analysis_prompt = f"""
97
+ 基於網站內容,請為以下問題產生一個適合的圖片描述(英文):
98
+ 問題:{user_prompt}
99
+
100
+ 請產生一個清楚、具體的圖片描述,描述應該:
101
+ 1. 與網站內容相關
102
+ 2. 適合視覺化呈現
103
+ 3. 使用英文描述
104
+ 4. 長度在50-100字之間
105
+
106
+ 只回傳圖片描述,不需要其他解釋。
107
+ """
108
+
109
+ response = text_model.generate_content(analysis_prompt)
110
+ if not response or not response.text:
111
+ return None, "無法產生圖片描述"
112
+
113
+ image_description = response.text.strip()
114
+ print(f"圖片描述:{image_description}")
115
+
116
+ # 使用圖片描述生成圖片
117
+ image_response = image_model.generate_content([image_description])
118
+
119
+ if not image_response or not image_response.candidates:
120
+ return None, "無法生成圖片"
121
+
122
+ # 取得生成的圖片
123
+ generated_image = image_response.candidates[0]
124
+ if hasattr(generated_image, 'content') and hasattr(generated_image.content, 'parts'):
125
+ image_part = generated_image.content.parts[0]
126
+ if hasattr(image_part, 'inline_data'):
127
+ image_data = image_part.inline_data.data
128
+
129
+ # 將 base64 數據轉換為圖片
130
+ image_bytes = base64.b64decode(image_data)
131
+
132
+ # 上傳圖片到臨時服務 (這裡需要實作圖片上傳邏輯)
133
+ # 由於 Line Bot 需要公開的圖片網址,您需要將圖片上傳到雲端服務
134
+ # 這裡提供一個範例架構
135
+
136
+ return image_bytes, "圖片生成成功"
137
+
138
+ return None, "圖片數據格式不正確"
139
+
140
+ except Exception as e:
141
+ print(f"生成圖片時出錯: {e}")
142
+ return None, f"生成圖片時出錯: {str(e)}"
143
+
144
+ def upload_image_to_cloud(image_bytes):
145
+ """
146
+ 上傳圖片到雲端服務並回傳公開網址
147
+ 這裡需要實作實際的上傳邏輯,例如上傳到:
148
+ - Imgur
149
+ - Cloudinary
150
+ - AWS S3
151
+ - Google Cloud Storage
152
+ 等服務
153
+ """
154
+ # 範例:這裡應該實作實際的上傳邏輯
155
+ # 暫時回傳一個佔位網址
156
+ return "https://example.com/placeholder.jpg"
157
+
158
+ # 處理根路徑請求
159
  @app.get("/")
160
  def root():
161
  return {
162
+ "title": "Line Bot with Image Generation",
163
  "status": "running",
164
  "target_url": TARGET_URL,
165
  "web_content_loaded": len(web_content) > 0,
166
  "line_bot_configured": line_bot_api is not None,
167
+ "gemini_configured": text_model is not None,
168
+ "image_generation_enabled": image_model is not None
169
  }
170
 
171
+ # 健康檢查端點
172
  @app.get("/health")
173
  def health_check():
174
  return {"status": "healthy"}
175
 
176
+ # 處理 Line Webhook 請求
177
  @app.post("/webhook")
178
+ async def webhook(
179
+ request: Request,
180
+ background_tasks: BackgroundTasks,
181
+ x_line_signature=Header(None)
182
+ ):
183
  if not line_handler:
184
  raise HTTPException(status_code=500, detail="Line Bot not configured")
185
+
186
+ # 取得請求內容
187
  body = await request.body()
188
+
189
  try:
190
+ # 將處理 Line 事件的任務加入背景工作
191
  background_tasks.add_task(
192
  line_handler.handle,
193
  body.decode("utf-8"),
194
  x_line_signature
195
  )
196
  except InvalidSignatureError:
197
+ # 處理無效的簽章錯誤
198
  raise HTTPException(status_code=400, detail="Invalid signature")
199
+
200
  return "ok"
201
 
202
+ # 處理文字訊息事件
 
203
  def setup_message_handler():
204
+ """設定訊息處理器"""
205
  if not line_handler:
206
  return
207
+
208
  @line_handler.add(MessageEvent, message=TextMessage)
209
  def handle_message(event):
210
+ # 檢查是否為文字訊息
211
  if event.message.type != "text":
212
  line_bot_api.reply_message(
213
  event.reply_token,
214
  TextSendMessage(text="請輸入文字訊息。")
215
  )
216
  return
217
+
218
+ # 取得使用者輸入的文字
219
+ prompt = event.message.text
220
+
221
+ if not text_model:
222
+ line_bot_api.reply_message(
223
+ event.reply_token,
224
+ TextSendMessage(text="抱歉,AI 模型尚未準備就緒。")
225
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  return
227
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  try:
229
+ # 先生成文字回應
230
+ generation_config = genai.types.GenerationConfig(
231
+ max_output_tokens=1000,
232
+ temperature=0.3,
233
+ top_p=0.5
234
+ )
235
+
236
+ text_response = text_model.generate_content(
237
+ contents=prompt,
238
+ generation_config=generation_config
239
+ )
240
+
241
+ # 嘗試生成相關圖片
242
+ image_bytes, image_status = generate_image_from_content(prompt)
243
+
244
+ messages = []
245
+
246
+ # 添加文字回應
247
+ if text_response and text_response.text:
248
+ messages.append(TextSendMessage(text=text_response.text))
249
+ else:
250
+ messages.append(TextSendMessage(text="抱歉,無法產生適當的回應。"))
251
+
252
+ # 如果成功生成圖片,添加圖片訊息
253
+ if image_bytes:
254
+ # 注意:這裡需要實作圖片上傳功能
255
+ # Line Bot 需要公開可訪問的圖片網址
256
+
257
+ # 上傳圖片並取得網址
258
+ image_url = upload_image_to_cloud(image_bytes)
259
+
260
+ # 創建圖片訊息
261
+ # originalContentUrl: 原始圖片網址
262
+ # previewImageUrl: 預覽圖片網址 (通常使用相同網址)
263
+ messages.append(ImageSendMessage(
264
+ original_content_url=image_url,
265
+ preview_image_url=image_url
266
+ ))
267
+
268
+ messages.append(TextSendMessage(text="✨ 根據內容為您生成了相關圖片"))
269
+ else:
270
+ messages.append(TextSendMessage(text=f"📝 文字回應已生成,圖片生成失敗:{image_status}"))
271
+
272
+ # 發送所有訊息
273
+ line_bot_api.reply_message(event.reply_token, messages)
274
+
275
+ except Exception as e:
276
+ print(f"處理訊息時出錯: {e}")
277
  line_bot_api.reply_message(
278
  event.reply_token,
279
+ TextSendMessage(text="處理您的訊息時發生錯誤,請稍後再試。")
280
  )
 
 
 
281
 
282
+ # 應用程式啟動事件
283
  @app.on_event("startup")
284
  async def startup_event():
285
+ """應用程式啟動時執行的初始化"""
286
  print("正在初始化服務...")
287
  initialize_services()
288
  setup_message_handler()
289
  print("服務初始化完成")
290
 
 
291
  if __name__ == "__main__":
292
+ # 啟動 FastAPI 應用程式
293
  port = int(os.getenv("PORT", 7860))
294
+ uvicorn.run("main:app", host="0.0.0.0", port=port, reload=False)