Spaces:
				
			
			
	
			
			
		Sleeping
		
	
	
	
			
			
	
	
	
	
		
		
		Sleeping
		
	首次上傳 LINE Bot
Browse files
    	
        gemini.py
    CHANGED
    
    | @@ -3,7 +3,7 @@ import logging | |
| 3 | 
             
            import os
         | 
| 4 | 
             
            import tempfile
         | 
| 5 | 
             
            import uuid
         | 
| 6 | 
            -
            from io import BytesIO
         | 
| 7 |  | 
| 8 | 
             
            import markdown
         | 
| 9 | 
             
            from bs4 import BeautifulSoup
         | 
| @@ -44,7 +44,11 @@ google_search_tool = Tool( | |
| 44 | 
             
            chat = client.chats.create(
         | 
| 45 | 
             
                model="gemini-2.0-flash",
         | 
| 46 | 
             
                config=GenerateContentConfig(
         | 
| 47 | 
            -
                    system_instruction= | 
|  | |
|  | |
|  | |
|  | |
| 48 | 
             
                    tools=[google_search_tool],
         | 
| 49 | 
             
                    response_modalities=["TEXT"],
         | 
| 50 | 
             
                )
         | 
| @@ -220,63 +224,115 @@ def handle_image_message(event): | |
| 220 |  | 
| 221 | 
             
            # === 處理影片訊息 ===
         | 
| 222 |  | 
| 223 | 
            -
            @handler.add(MessageEvent, message= | 
| 224 | 
            -
            def  | 
| 225 | 
            -
                 | 
| 226 | 
            -
             | 
| 227 | 
            -
             | 
| 228 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 229 |  | 
| 230 | 
            -
                # 儲存影片到本地
         | 
| 231 | 
            -
                if video_data is None:
         | 
| 232 | 
            -
                    err_msg = "抱歉,無法取得影片內容。"
         | 
| 233 | 
            -
                    app.logger.error(err_msg)
         | 
| 234 | 
             
                    with ApiClient(configuration) as api_client:
         | 
| 235 | 
             
                        line_bot_api = MessagingApi(api_client)
         | 
| 236 | 
             
                        line_bot_api.reply_message(
         | 
| 237 | 
             
                            ReplyMessageRequest(
         | 
| 238 | 
             
                                reply_token=event.reply_token,
         | 
| 239 | 
            -
                                messages=[TextMessage(text= | 
| 240 | 
             
                            )
         | 
| 241 | 
             
                        )
         | 
| 242 | 
            -
                    return
         | 
| 243 | 
            -
             | 
| 244 | 
            -
                with tempfile.NamedTemporaryFile(
         | 
| 245 | 
            -
                    dir=static_tmp_path, suffix=".mp4", delete=False
         | 
| 246 | 
            -
                ) as tf:
         | 
| 247 | 
            -
                    tf.write(video_data)
         | 
| 248 | 
            -
                    filename = os.path.basename(tf.name)
         | 
| 249 |  | 
| 250 | 
            -
                 | 
| 251 | 
            -
                 | 
| 252 | 
            -
             | 
| 253 | 
            -
             | 
| 254 | 
            -
             | 
| 255 | 
            -
             | 
| 256 | 
            -
             | 
| 257 | 
            -
                    response = client.models.generate_content(
         | 
| 258 | 
            -
                        model="gemini-2.5-flash-preview-05-20",
         | 
| 259 | 
            -
                        config=types.GenerateContentConfig(
         | 
| 260 | 
            -
                            system_instruction="你是一個專業的影片解說員,請用繁體中文簡要說明這段影片的內容。",
         | 
| 261 | 
            -
                            response_modalities=["TEXT"],
         | 
| 262 | 
            -
                            tools=[google_search_tool],
         | 
| 263 | 
            -
                        ),
         | 
| 264 | 
            -
                        contents=[video_bytes, "用繁體中文描述這段影片"],
         | 
| 265 | 
            -
                    )
         | 
| 266 | 
            -
                    description = response.text
         | 
| 267 | 
            -
                except Exception as e:
         | 
| 268 | 
            -
                    app.logger.error(f"Gemini API error (video): {e}")
         | 
| 269 | 
            -
                    description = "抱歉,無法解釋這段影片內容。"
         | 
| 270 |  | 
| 271 | 
            -
             | 
| 272 | 
            -
             | 
| 273 | 
            -
             | 
| 274 | 
            -
             | 
| 275 | 
            -
             | 
| 276 | 
            -
                            reply_token=event.reply_token,
         | 
| 277 | 
            -
                            messages=[
         | 
| 278 | 
            -
                                TextMessage(text=f"影片連結:{video_url}"),
         | 
| 279 | 
            -
                                TextMessage(text=description),
         | 
| 280 | 
            -
                            ],
         | 
| 281 | 
             
                        )
         | 
| 282 | 
            -
                    )
         | 
|  | |
| 3 | 
             
            import os
         | 
| 4 | 
             
            import tempfile
         | 
| 5 | 
             
            import uuid
         | 
| 6 | 
            +
            from io import BytesIO 
         | 
| 7 |  | 
| 8 | 
             
            import markdown
         | 
| 9 | 
             
            from bs4 import BeautifulSoup
         | 
|  | |
| 44 | 
             
            chat = client.chats.create(
         | 
| 45 | 
             
                model="gemini-2.0-flash",
         | 
| 46 | 
             
                config=GenerateContentConfig(
         | 
| 47 | 
            +
                    system_instruction=(
         | 
| 48 | 
            +
                    "你是一位專業健身教練與營養顧問,擁有多年重量訓練與健身飲食指導經驗。"
         | 
| 49 | 
            +
                    "請使用繁體中文,針對使用者的健身問題提供專業建議,包含動作教學、訓練計畫、飲食建議與常見錯誤修正等。"
         | 
| 50 | 
            +
                ),
         | 
| 51 | 
            +
             | 
| 52 | 
             
                    tools=[google_search_tool],
         | 
| 53 | 
             
                    response_modalities=["TEXT"],
         | 
| 54 | 
             
                )
         | 
|  | |
| 224 |  | 
| 225 | 
             
            # === 處理影片訊息 ===
         | 
| 226 |  | 
| 227 | 
            +
            @handler.add(MessageEvent, message=TextMessageContent)
         | 
| 228 | 
            +
            def handle_text_message(event):
         | 
| 229 | 
            +
                user_input = event.message.text.strip()
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                # === 使用 Gemini 生成圖片(AI xxx) ===
         | 
| 232 | 
            +
                if user_input.startswith("AI "):
         | 
| 233 | 
            +
                    prompt = user_input[3:].strip()
         | 
| 234 | 
            +
                    try:
         | 
| 235 | 
            +
                        response = client.models.generate_content(
         | 
| 236 | 
            +
                            model="gemini-2.0-flash-exp-image-generation",
         | 
| 237 | 
            +
                            contents=prompt,
         | 
| 238 | 
            +
                            config=types.GenerateContentConfig(
         | 
| 239 | 
            +
                                response_modalities=["TEXT", "IMAGE"]
         | 
| 240 | 
            +
                            ),
         | 
| 241 | 
            +
                        )
         | 
| 242 | 
            +
             | 
| 243 | 
            +
                        for part in response.candidates[0].content.parts:
         | 
| 244 | 
            +
                            if part.inline_data is not None:
         | 
| 245 | 
            +
                                image = Image.open(BytesIO(part.inline_data.data))
         | 
| 246 | 
            +
                                filename = f"{uuid.uuid4().hex}.png"
         | 
| 247 | 
            +
                                image_path = os.path.join(static_tmp_path, filename)
         | 
| 248 | 
            +
                                image.save(image_path)
         | 
| 249 | 
            +
             | 
| 250 | 
            +
                                image_url = f"https://{base_url}/images/{filename}"
         | 
| 251 | 
            +
                                app.logger.info(f"Image URL: {image_url}")
         | 
| 252 | 
            +
             | 
| 253 | 
            +
                                with ApiClient(configuration) as api_client:
         | 
| 254 | 
            +
                                    line_bot_api = MessagingApi(api_client)
         | 
| 255 | 
            +
                                    line_bot_api.reply_message(
         | 
| 256 | 
            +
                                        ReplyMessageRequest(
         | 
| 257 | 
            +
                                            reply_token=event.reply_token,
         | 
| 258 | 
            +
                                            messages=[
         | 
| 259 | 
            +
                                                ImageMessage(
         | 
| 260 | 
            +
                                                    original_content_url=image_url,
         | 
| 261 | 
            +
                                                    preview_image_url=image_url,
         | 
| 262 | 
            +
                                                )
         | 
| 263 | 
            +
                                            ],
         | 
| 264 | 
            +
                                        )
         | 
| 265 | 
            +
                                    )
         | 
| 266 | 
            +
             | 
| 267 | 
            +
                    except Exception as e:
         | 
| 268 | 
            +
                        app.logger.error(f"Gemini API error: {e}")
         | 
| 269 | 
            +
                        with ApiClient(configuration) as api_client:
         | 
| 270 | 
            +
                            line_bot_api = MessagingApi(api_client)
         | 
| 271 | 
            +
                            line_bot_api.reply_message(
         | 
| 272 | 
            +
                                ReplyMessageRequest(
         | 
| 273 | 
            +
                                    reply_token=event.reply_token,
         | 
| 274 | 
            +
                                    messages=[TextMessage(text="抱歉,生成圖片時發生錯誤。")],
         | 
| 275 | 
            +
                                )
         | 
| 276 | 
            +
                            )
         | 
| 277 | 
            +
             | 
| 278 | 
            +
                # === 處理「菜單 xxx」功能 ===
         | 
| 279 | 
            +
                elif user_input.startswith("菜單 "):
         | 
| 280 | 
            +
                    muscle_group = user_input[3:].strip()
         | 
| 281 | 
            +
                    if not muscle_group:
         | 
| 282 | 
            +
                        with ApiClient(configuration) as api_client:
         | 
| 283 | 
            +
                            line_bot_api = MessagingApi(api_client)
         | 
| 284 | 
            +
                            line_bot_api.reply_message(
         | 
| 285 | 
            +
                                ReplyMessageRequest(
         | 
| 286 | 
            +
                                    reply_token=event.reply_token,
         | 
| 287 | 
            +
                                    messages=[TextMessage(text="請輸入要安排的部位,例如:菜單 胸肌、菜單 臀部")]
         | 
| 288 | 
            +
                                )
         | 
| 289 | 
            +
                            )
         | 
| 290 | 
            +
                        return
         | 
| 291 | 
            +
             | 
| 292 | 
            +
                    # 同義詞簡化
         | 
| 293 | 
            +
                    synonym_map = {
         | 
| 294 | 
            +
                        "胸": "胸肌",
         | 
| 295 | 
            +
                        "腿": "腿部",
         | 
| 296 | 
            +
                        "肩": "肩膀",
         | 
| 297 | 
            +
                        "背": "背肌",
         | 
| 298 | 
            +
                        "手": "手臂",
         | 
| 299 | 
            +
                        "手臂": "手臂",
         | 
| 300 | 
            +
                        "核心": "核心肌群",
         | 
| 301 | 
            +
                        "臀": "臀部",
         | 
| 302 | 
            +
                        "臀部": "臀部",
         | 
| 303 | 
            +
                        "全身": "全身初學者",
         | 
| 304 | 
            +
                        "初學者": "全身初學者",
         | 
| 305 | 
            +
                    }
         | 
| 306 | 
            +
                    muscle_group = synonym_map.get(muscle_group, muscle_group)
         | 
| 307 | 
            +
             | 
| 308 | 
            +
                    # 發送訓練菜單請求
         | 
| 309 | 
            +
                    prompt = (
         | 
| 310 | 
            +
                        f"請依據「{muscle_group}」提供一份健身訓練菜單。"
         | 
| 311 | 
            +
                        "每份菜單包含 3~5 個動作,建議組數與次數,以及適當的休息時間。"
         | 
| 312 | 
            +
                        "請以繁體中文簡潔列出,使用條列方式排版,適合 LINE 顯示格式。"
         | 
| 313 | 
            +
                    )
         | 
| 314 | 
            +
                    response = query(prompt)
         | 
| 315 |  | 
|  | |
|  | |
|  | |
|  | |
| 316 | 
             
                    with ApiClient(configuration) as api_client:
         | 
| 317 | 
             
                        line_bot_api = MessagingApi(api_client)
         | 
| 318 | 
             
                        line_bot_api.reply_message(
         | 
| 319 | 
             
                            ReplyMessageRequest(
         | 
| 320 | 
             
                                reply_token=event.reply_token,
         | 
| 321 | 
            +
                                messages=[TextMessage(text=response)],
         | 
| 322 | 
             
                            )
         | 
| 323 | 
             
                        )
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 324 |  | 
| 325 | 
            +
                # === 處理一般文字訊息 ===
         | 
| 326 | 
            +
                else:
         | 
| 327 | 
            +
                    with ApiClient(configuration) as api_client:
         | 
| 328 | 
            +
                        line_bot_api = MessagingApi(api_client)
         | 
| 329 | 
            +
                        response = query(event.message.text)
         | 
| 330 | 
            +
                        html_msg = markdown.markdown(response)
         | 
| 331 | 
            +
                        soup = BeautifulSoup(html_msg, "html.parser")
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 332 |  | 
| 333 | 
            +
                        line_bot_api.reply_message_with_http_info(
         | 
| 334 | 
            +
                            ReplyMessageRequest(
         | 
| 335 | 
            +
                                reply_token=event.reply_token,
         | 
| 336 | 
            +
                                messages=[TextMessage(text=soup.get_text())],
         | 
| 337 | 
            +
                            )
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
| 338 | 
             
                        )
         | 
|  |