Update app.py
Browse files
app.py
CHANGED
|
@@ -16,6 +16,20 @@ handler = WebhookHandler(os.environ.get('LINE_CHANNEL_SECRET'))
|
|
| 16 |
genai.configure(api_key=os.environ.get('GEMINI_API_KEY'))
|
| 17 |
model = genai.GenerativeModel('gemini-2.5-flash')
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
# ================= 2. 建立狀態儲存區(暫存每個用戶的進度) =================
|
| 20 |
user_states = {}
|
| 21 |
|
|
@@ -51,12 +65,16 @@ def handle_image(event):
|
|
| 51 |
|
| 52 |
# 2. 設定 Prompt,要求 Gemini 直接分析這張圖片
|
| 53 |
vision_prompt = """
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
你現在是一位專業的台灣藥妝購物精靈。請看這張圖片中的產品:
|
| 55 |
1. 辨識它是什麼真實的品牌與品名。
|
| 56 |
2. 估算它在台灣藥妝店(如屈臣氏、康是美)的合理售價。
|
| 57 |
3. 指出哪個商家買得到。
|
| 58 |
|
| 59 |
-
請直接輸出以下格式的純文字(不需要包含其他廢話):
|
| 60 |
品名:[產品完整名稱]
|
| 61 |
售價:$[估計價格]
|
| 62 |
商家:[推薦藥妝店名稱]
|
|
@@ -64,21 +82,133 @@ def handle_image(event):
|
|
| 64 |
|
| 65 |
# 3. 把圖片和 Prompt 一起丟給 Gemini (支援多模態)
|
| 66 |
response = model.generate_content([vision_prompt, pil_image])
|
| 67 |
-
|
| 68 |
|
| 69 |
-
#
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
-
|
| 76 |
del user_states[user_id]
|
| 77 |
|
| 78 |
except Exception as e:
|
| 79 |
line_bot_api.push_message(
|
| 80 |
user_id,
|
| 81 |
-
TextSendMessage(text=f"❌ 哎呀!精靈
|
| 82 |
)
|
| 83 |
|
| 84 |
# ================= 3. 多輪對話核心邏輯 =================
|
|
|
|
| 16 |
genai.configure(api_key=os.environ.get('GEMINI_API_KEY'))
|
| 17 |
model = genai.GenerativeModel('gemini-2.5-flash')
|
| 18 |
|
| 19 |
+
# 建立我們的虛擬藥妝資料庫 (Mock Database)
|
| 20 |
+
fake_database = {
|
| 21 |
+
"CeraVe適樂膚 長效潤澤修護霜 340g": [
|
| 22 |
+
{"store": "屈臣氏", "name": "CeraVe適樂膚 長效潤澤修護霜 340g", "price": "619","link": "https://www.watsons.com.tw/cerave-cerave%E9%81%A9%E6%A8%82%E8%86%9A%E9%95%B7%E6%95%88%E6%BD%A4%E6%BE%A4%E4%BF%AE%E8%AD%B7%E9%9C%9C-340g/p/BP_153671"},
|
| 23 |
+
{"store": "康是美", "name": "CeraVe適樂膚 長效潤澤修護霜 340g", "price": "619", "link": "https://shop.cosmed.com.tw/SalePage/Index/6414249"},
|
| 24 |
+
{"store": "寶雅", "name": "CeraVe適樂膚 長效潤澤修護霜 340g", "price": "619","link": "https://www.poyabuy.com.tw/SalePage/Index/10104261"}
|
| 25 |
+
],
|
| 26 |
+
"Avene 雅漾舒護活泉水150ml": [
|
| 27 |
+
{"store": "屈臣氏", "name": "Avene 雅漾舒護活泉水150ml", "price": "299","discount": "🔥限時67折", "link": "https://www.watsons.com.tw/avene-%E9%9B%85%E6%BC%BE-%E9%9B%85%E6%BC%BE%E8%88%92%E8%AD%B7%E6%B4%BB%E6%B3%89%E6%B0%B4150ml/p/BP_291419"},
|
| 28 |
+
{"store": "康是美", "name": "Avene 雅漾舒護活泉水150ml", "price": "299","discount": "🔥限時67折", "link": "https://shop.cosmed.com.tw/SalePage/Index/5767522"},
|
| 29 |
+
{"store": "寶雅", "name": "Avene 雅漾舒護活泉水150ml", "price": "450", "link": "https://www.poyabuy.com.tw/SalePage/Index/9929185"}
|
| 30 |
+
]
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
# ================= 2. 建立狀態儲存區(暫存每個用戶的進度) =================
|
| 34 |
user_states = {}
|
| 35 |
|
|
|
|
| 65 |
|
| 66 |
# 2. 設定 Prompt,要求 Gemini 直接分析這張圖片
|
| 67 |
vision_prompt = """
|
| 68 |
+
請辨識這張圖片中的保養品。
|
| 69 |
+
如果是 CeraVe 的 PM 乳液,請直接回答:CeraVe長效清爽保濕乳PM
|
| 70 |
+
如果是 理膚寶水 B5,請直接回答:理膚寶水B5全面修復霜
|
| 71 |
+
如果都不是
|
| 72 |
你現在是一位專業的台灣藥妝購物精靈。請看這張圖片中的產品:
|
| 73 |
1. 辨識它是什麼真實的品牌與品名。
|
| 74 |
2. 估算它在台灣藥妝店(如屈臣氏、康是美)的合理售價。
|
| 75 |
3. 指出哪個商家買得到。
|
| 76 |
|
| 77 |
+
並請直接輸出以下格式的純文字(不需要包含其他廢話):
|
| 78 |
品名:[產品完整名稱]
|
| 79 |
售價:$[估計價格]
|
| 80 |
商家:[推薦藥妝店名稱]
|
|
|
|
| 82 |
|
| 83 |
# 3. 把圖片和 Prompt 一起丟給 Gemini (支援多模態)
|
| 84 |
response = model.generate_content([vision_prompt, pil_image])
|
| 85 |
+
product_name = response.text.strip()
|
| 86 |
|
| 87 |
+
# --- 第 3 步:雙軌制 (假資料庫 vs AI估價) ---
|
| 88 |
+
if product_name in fake_database:
|
| 89 |
+
# 🌟 路線 A:命中資料庫,使用妳設定的真實數據!
|
| 90 |
+
data_list = fake_database[product_name]
|
| 91 |
+
price_tag = "資料庫真實價" # 讓圖卡顯示這是真實數據
|
| 92 |
+
else:
|
| 93 |
+
# 🤖 路線 B:資料庫沒有,呼叫 AI 進行現場估價!
|
| 94 |
+
line_bot_api.push_message(user_id, TextSendMessage(text=f"🔍 辨識出「{product_name}」,資料庫無建檔,啟動 AI 現場估價..."))
|
| 95 |
+
|
| 96 |
+
estimate_prompt = f"""
|
| 97 |
+
請估算「{product_name}」這款產品在台灣屈臣氏、康是美、寶雅的合理售價。
|
| 98 |
+
請務必「只回傳」一個標準的 JSON 陣列,不要有任何額外的說明文字。格式嚴格如下:
|
| 99 |
+
[
|
| 100 |
+
{{"store": "屈臣氏", "name": "{product_name}", "price": "估計數字"}},
|
| 101 |
+
{{"store": "康是美", "name": "{product_name}", "price": "估計數字"}},
|
| 102 |
+
{{"store": "寶雅", "name": "{product_name}", "price": "估計數字"}}
|
| 103 |
+
]
|
| 104 |
+
"""
|
| 105 |
+
est_response = model.generate_content(estimate_prompt)
|
| 106 |
+
clean_text = est_response.text.replace("```json", "").replace("```", "").strip()
|
| 107 |
+
data_list = json.loads(clean_text)
|
| 108 |
+
price_tag = "AI 估計價" # 讓圖卡顯示這是 AI 猜的
|
| 109 |
+
|
| 110 |
+
# --- 第 4 步:將資料塞進橫向圖卡 (不管哪條路線,最後都會來到這裡) ---
|
| 111 |
+
# --- 第 4 步:將資料塞進橫向圖卡 ---
|
| 112 |
+
bubbles = []
|
| 113 |
+
for item in data_list:
|
| 114 |
+
|
| 115 |
+
# --- 這裡用來判斷要不要顯示折扣標籤 ---
|
| 116 |
+
discount_box = []
|
| 117 |
+
# 只有當資料庫裡的 discount 不是空字串時,才生出這塊紅色的標籤
|
| 118 |
+
if item.get("discount"):
|
| 119 |
+
discount_box = [
|
| 120 |
+
{
|
| 121 |
+
"type": "box",
|
| 122 |
+
"layout": "baseline",
|
| 123 |
+
"spacing": "sm",
|
| 124 |
+
"contents": [
|
| 125 |
+
{
|
| 126 |
+
"type": "text",
|
| 127 |
+
"text": item["discount"],
|
| 128 |
+
"color": "#ffffff",
|
| 129 |
+
"size": "xs",
|
| 130 |
+
"weight": "bold",
|
| 131 |
+
"margin": "sm"
|
| 132 |
+
}
|
| 133 |
+
],
|
| 134 |
+
"backgroundColor": "#ff3b30", # 紅色底
|
| 135 |
+
"cornerRadius": "md",
|
| 136 |
+
"paddingAll": "2px",
|
| 137 |
+
"paddingStart": "8px",
|
| 138 |
+
"paddingEnd": "8px",
|
| 139 |
+
"margin": "sm"
|
| 140 |
+
}
|
| 141 |
+
]
|
| 142 |
+
|
| 143 |
+
# 這是原本卡片的身體內容,我們把剛剛可能生出來的折扣標籤塞進去
|
| 144 |
+
card_contents = [
|
| 145 |
+
{"type": "text", "text": item["store"], "weight": "bold", "color": "#1DB446", "size": "md"},
|
| 146 |
+
{"type": "text", "text": item["name"], "weight": "bold", "size": "lg", "margin": "md", "wrap": True},
|
| 147 |
+
]
|
| 148 |
+
|
| 149 |
+
# 把折扣標籤接在商品名稱後面
|
| 150 |
+
card_contents.extend(discount_box)
|
| 151 |
+
|
| 152 |
+
# 補上最後的價格線
|
| 153 |
+
card_contents.extend([
|
| 154 |
+
{"type": "separator", "margin": "lg"},
|
| 155 |
+
{
|
| 156 |
+
"type": "box",
|
| 157 |
+
"layout": "horizontal",
|
| 158 |
+
"margin": "md",
|
| 159 |
+
"contents": [
|
| 160 |
+
{"type": "text", "text": price_tag, "size": "sm", "color": "#555555"},
|
| 161 |
+
{"type": "text", "text": f"${item['price']}", "size": "lg", "color": "#111111", "align": "end", "weight": "bold"}
|
| 162 |
+
]
|
| 163 |
+
}
|
| 164 |
+
])
|
| 165 |
+
|
| 166 |
+
# 組裝最終的卡片
|
| 167 |
+
card = {
|
| 168 |
+
"type": "bubble",
|
| 169 |
+
"body": {
|
| 170 |
+
"type": "box",
|
| 171 |
+
"layout": "vertical",
|
| 172 |
+
"contents": card_contents
|
| 173 |
+
},
|
| 174 |
+
"footer": {
|
| 175 |
+
"type": "box",
|
| 176 |
+
"layout": "vertical",
|
| 177 |
+
"contents": [
|
| 178 |
+
{
|
| 179 |
+
"type": "button",
|
| 180 |
+
"style": "primary",
|
| 181 |
+
"color": "#1DB446",
|
| 182 |
+
"height": "sm",
|
| 183 |
+
"action": {
|
| 184 |
+
"type": "uri",
|
| 185 |
+
"label": "前往商家購買",
|
| 186 |
+
# 這裡改用資料庫裡設定的專屬網址!(如果沒設定,就預設用 Google 搜尋)
|
| 187 |
+
"uri": item.get("link", f"https://www.google.com/search?q={item['store']}+{item['name']}")
|
| 188 |
+
}
|
| 189 |
+
}
|
| 190 |
+
]
|
| 191 |
+
}
|
| 192 |
+
}
|
| 193 |
+
bubbles.append(card)
|
| 194 |
+
|
| 195 |
+
# --- 第 5 步:打包發送圖卡 ---
|
| 196 |
+
flex_message = FlexSendMessage.new_from_json_dict({
|
| 197 |
+
"type": "flex",
|
| 198 |
+
"alt_text": "🔍 藥妝精靈比價報告來囉!",
|
| 199 |
+
"contents": {
|
| 200 |
+
"type": "carousel",
|
| 201 |
+
"contents": bubbles
|
| 202 |
+
}
|
| 203 |
+
})
|
| 204 |
|
| 205 |
+
line_bot_api.push_message(user_id, flex_message)
|
| 206 |
del user_states[user_id]
|
| 207 |
|
| 208 |
except Exception as e:
|
| 209 |
line_bot_api.push_message(
|
| 210 |
user_id,
|
| 211 |
+
TextSendMessage(text=f"❌ 哎呀!精靈遇到了一點問題。錯誤原因:\n{e}")
|
| 212 |
)
|
| 213 |
|
| 214 |
# ================= 3. 多輪對話核心邏輯 =================
|