from fastapi.middleware.cors import CORSMiddleware from fastapi import FastAPI, Request, Header, BackgroundTasks, HTTPException, status from fastapi.staticfiles import StaticFiles from gradio_client import Client import json import os import requests import base64 from linebot import ( LineBotApi, WebhookHandler ) from linebot.exceptions import ( InvalidSignatureError ) from linebot.models import ( MessageEvent, TextMessage, ImageMessage, TextSendMessage, ImageSendMessage, AudioMessage ) # 設定 GeminiRAG 的HF網址 # client = Client(os.environ["GeminiRAGapi"]) # 設定 Line Bot 的 API 金鑰和秘密金鑰 line_bot_api = LineBotApi(os.environ["CHANNEL_ACCESS_TOKEN"]) line_handler = WebhookHandler(os.environ["CHANNEL_SECRET"]) # 設定是否正在與使用者交談 working_status = os.getenv("DEFALUT_TALKING", default = "true").lower() == "true" # 建立 FastAPI 應用程式 app = FastAPI() app.mount("/static", StaticFiles(directory="static"), name="static") # Server URL server_url= 'ning8429-ragline.hf.space' # https://ning8429-ragline.hf.space/static/temp.txt api_img2text_url = 'https://ning8429-flask-docker.hf.space/predict' api_text2img_url = 'https://ning8429-flask-docker.hf.space/text2img' # 設定 CORS,允許跨域請求 app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # 處理根路徑請求 @app.get("/") def root(): return {"title": "Line Bot"} # 處理 Line Webhook 請求 @app.post("/webhook") async def webhook( request: Request, background_tasks: BackgroundTasks, x_line_signature=Header(None), ): # 取得請求內容 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" # 處理接收的訊息事件 @line_handler.add(MessageEvent, message=(ImageMessage, TextMessage)) # 可捕捉處理的訊息類型:圖片、文字訊息 def handle_message(event): global working_status # 檢查事件是否為圖片訊息 if isinstance(event.message, ImageMessage): print(f"***** START 圖轉文 *****") # 回覆已成功接收圖片的訊息 line_bot_api.reply_message( event.reply_token, TextSendMessage(text="已成功接收您的圖片!正在為您搜尋寶寶名稱,請稍候片刻 😊") ) # 嘗試獲取圖片內容 try: message_content = line_bot_api.get_message_content(event.message.id) except Exception as e: print(f"Error fetching image content: {e}") # 圖片存進hugging face docker static資料夾 input_name = 'input_'+event.message.id+'.jpg' path = './static/'+ input_name with open(path, 'wb') as fd: for chunk in message_content.iter_content(): print(f"Saving image to {path}") fd.write(chunk) img2_url='https://%s/static/'% server_url # Call Flask API after saving the image try: with open(path, 'rb') as image_file: # Prepare payload with image file and message ID files = {'image': image_file} data = { 'message_id': event.message.id, "choice": "find_similar_words", "word": None, "top_k": 3 } # Make a POST request to Flask API response = requests.post(api_img2text_url, files=files, data=data) # Check the response from the external API if response.status_code == 200: api_response = response.json() else: api_response = "Error calling FLASK API_response.status_code" except Exception as e: print(f"Error sending request to external API: {e}") api_response = "Error calling FLASK API_Exception" image_messages = [] # image_del_paths = [path] # 10/28 add-初始化要刪除的圖片路徑清單 # 遍歷 CLIP api_response 中的 objects for i, obj in enumerate(api_response['objects']): element = obj['element'] encoded_image = obj['images']['encoded_image'] description = obj['images']['description_list'].replace("\n", " ") print(f"===== 【{path}--> {element}】clip check: 【{description}】 =====") img_data = base64.b64decode(encoded_image) # 每個圖像的文件名以message id和索引作為文件名 image_name = f"yolo_{event.message.id}_{i}.jpg" image_path = './static/' + image_name # image_del_paths.append(image_path) # 10/28 add-加入新的圖片路徑 # 將圖片保存到 static 資料夾 with open(image_path, 'wb') as file: file.write(img_data) # 構建可以從 static 資料夾存取的 URL output_url = f'https://{server_url}/static/{image_name}' # 每張圖回傳一張圖片和對應的描述 image_messages.append( [ ImageSendMessage( original_content_url=output_url, preview_image_url=output_url ), TextSendMessage(text=f"⬆ 這隻可能是💫~\n{description}") ] ) # 傳送所有圖片和描述,使用 push_message for message_group in image_messages: line_bot_api.push_message( event.source.user_id, # 使用 user_id 發送推送訊息 message_group ) # # 10/28 add-刪除已傳送的圖片,釋放空間 # for image_del in image_del_paths: # try: # os.remove(image_del) # print(f"Deleted file: {image_del}") # except Exception as e: # print(f"Error deleting file {image_del}: {e}") elif isinstance(event.message, TextMessage): if event.message.text =="開始使用": # 回覆提示訊息,不調用外部 API line_bot_api.reply_message( event.reply_token, TextSendMessage(text="您好😊 請傳送寶寶照片找尋名字或是輸入關鍵字找尋圖片") ) return # 終止處理,避免調用外部API # 其他文字訊息的處理邏輯 line_bot_api.reply_message( event.reply_token, TextSendMessage(text="已成功接收您的搜尋文字!正在找尋對應的寶寶,請稍候片刻 😊") ) # 將文字訊息內容當作參數傳送給外部 API text_message_content = event.message.text print(f"***** START 文轉圖 *****") # 呼叫外部API try: data = { 'message_id': event.message.id, "choice": "find_image_for_word", "word": text_message_content, "top_k": 3 } # 發送 POST 請求至 Flask API,附帶文字內容 response = requests.post(api_text2img_url, data=data) # 確認 API 回傳結果 if response.status_code == 200: api_response = response.json() # 解析 API 回應的資料 encoded_image = api_response["encoded_image"] description = api_response["description"] print(f"===== 文轉圖 api_response:{api_response} =====") # 將 base64 編碼的圖片轉換為圖片URL img_data = base64.b64decode(encoded_image) # 每個圖像的文件名以message id和索引作為文件名 image_name = f"text2img_{event.message.id}.jpg" image_path = './static/' + image_name # 將圖片保存到 static 資料夾 with open(image_path, 'wb') as file: file.write(img_data) # 構建可以從 static 資料夾存取的 URL output_url = f'https://{server_url}/static/{image_name}' # 傳送圖片和描述給用戶 line_bot_api.push_message( event.source.user_id, [ ImageSendMessage( original_content_url=output_url, preview_image_url=output_url ), TextSendMessage(text=f"⬆️這是根據您的文字找到的寶寶💕\n{description}") ] ) else: line_bot_api.push_message( event.source.user_id, TextSendMessage(text="Error calling /text2img API: 非預期的狀態碼") ) except Exception as e: print(f"Error sending request to external API: {e}") line_bot_api.push_message( event.source.user_id, TextSendMessage(text="Error calling FLASK API: 發送請求時出現異常") ) else: # 若不是圖片或文字則回覆提示訊息 line_bot_api.reply_message( event.reply_token, TextSendMessage(text="我們只接受圖片或文字訊息唷") ) # # 檢查使用者是否輸入 "再見" # elif event.message.text == "再見": # # 回覆 "Bye!" # line_bot_api.reply_message( # event.reply_token, # TextSendMessage(text="Bye!") # ) # return # # 檢查是否正在與使用者交談 # elif working_status: # try: # # 取得使用者輸入的文字 # prompt = event.message.text # # 使用 GeminiRAGapi # completion = client.predict(question=prompt, api_name="/predict") # # 檢查生成結果是否為空 # if (completion != None): # # 取得生成結果 # out = completion # else: # # 回覆 "Gemini沒答案!請換個說法!" # out = "Gemini沒答案!請換個說法!" # except: # # 處理錯誤 # out = "Gemini執行出錯!請換個說法!" # # 回覆生成結果 # line_bot_api.reply_message( # event.reply_token, # TextSendMessage(text=out)) if __name__ == "__main__": # 啟動 FastAPI 應用程式 uvicorn.run("main:app", host="0.0.0.0", port=7860, reload=True) # 註解說明: # import 導入必要的套件 # line_bot_api 和 line_handler 設定 Line Bot API 和 webhook 處理器 # working_status 設定是否正在與使用者交談 # app 建立 FastAPI 應用程式 # app.add_middleware 設定 CORS # @app.get("/") 處理根路徑請求 # @app.post("/webhook") 處理 Line Webhook 請求 # @line_handler.add(MessageEvent, message=TextMessage) 處理文字訊息事件 # if __name__ == "__main__": 啟動 FastAPI 應用程式 # 程式碼功能說明: # 接著會建立 FastAPI 應用程式,並設定 CORS。 # 程式碼會定義兩個函數: # root() 處理根路徑請求,返回一個簡單的 JSON 訊息。 # webhook() 處理 Line Webhook 請求,將處理 Line 事件的任務加入背景工作,並處理無效的簽章錯誤。 # 程式碼還定義一個函數 handle_message() 來處理文字訊息事件,它會檢查事件類型和訊息類型,並根據使用者輸入執行不同的動作: # 如果使用者輸入 "再見",回覆 "Bye!"。 # 如果正在與使用者交談,則會使用 Gemini 模型生成文字,並將結果回覆給使用者。 # 最後,程式碼會啟動 FastAPI 應用程式,開始監聽 HTTP 請求。 # 程式碼運行方式: # 將程式碼存為 main.py 文件。 # 在環境變數中設定 GeminiRAGapi, CHANNEL_ACCESS_TOKEN 和 CHANNEL_SECRET。 # 執行 uvicorn main:app --host 0.0.0.0 --port 7860 --reload 命令啟動 FastAPI 應用程式。 # 使用 Line 帳戶與 Line Bot 進行對話。 # 注意: # 程式碼中使用os.environ["GeminiRAGapi"], os.environ["CHANNEL_ACCESS_TOKEN"] 和 os.environ["CHANNEL_SECRET"] 來存取環境變數,需要先在環境變數中設定這些值。 # 程式碼中使用 uvicorn 執行 FastAPI 應用程式,需要先安裝 uvicorn 套件。 # 程式碼中使用 linebot 套件,需要先安裝 linebot 套件