Update app.py
Browse files
app.py
CHANGED
|
@@ -19,14 +19,14 @@ model = genai.GenerativeModel('gemini-2.5-flash')
|
|
| 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 |
|
|
@@ -109,81 +109,90 @@ def handle_image(event):
|
|
| 109 |
|
| 110 |
# --- 第 4 步:將資料塞進橫向圖卡 (不管哪條路線,最後都會來到這裡) ---
|
| 111 |
# --- 第 4 步:將資料塞進橫向圖卡 ---
|
|
|
|
|
|
|
| 112 |
bubbles = []
|
| 113 |
for item in data_list:
|
| 114 |
|
| 115 |
-
#
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
{
|
| 121 |
"type": "box",
|
| 122 |
-
"layout": "
|
| 123 |
-
"spacing": "sm",
|
| 124 |
"contents": [
|
| 125 |
-
{
|
| 126 |
-
|
| 127 |
-
|
| 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 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 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":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
},
|
| 174 |
"footer": {
|
| 175 |
"type": "box",
|
| 176 |
"layout": "vertical",
|
| 177 |
"contents": [
|
|
|
|
| 178 |
{
|
| 179 |
"type": "button",
|
| 180 |
"style": "primary",
|
| 181 |
-
"color": "#
|
| 182 |
-
"height": "
|
| 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 |
}
|
|
@@ -191,6 +200,7 @@ def handle_image(event):
|
|
| 191 |
}
|
| 192 |
}
|
| 193 |
bubbles.append(card)
|
|
|
|
| 194 |
|
| 195 |
# --- 第 5 步:打包發送圖卡 ---
|
| 196 |
flex_message = FlexSendMessage.new_from_json_dict({
|
|
@@ -201,16 +211,17 @@ def handle_image(event):
|
|
| 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. 多輪對話核心邏輯 =================
|
| 215 |
@handler.add(MessageEvent, message=TextMessage)
|
| 216 |
def handle_message(event):
|
|
|
|
| 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","image": "https://img.91app.com/webapi/imagesV3/Original/SalePage/10104261/0/639160140536830000?v=1"},
|
| 23 |
+
{"store": "康是美", "name": "CeraVe適樂膚 長效潤澤修護霜 340g", "price": "619", "link": "https://shop.cosmed.com.tw/SalePage/Index/6414249","image": "https://img.91app.com/webapi/imagesV3/Original/SalePage/10104261/0/639160140536830000?v=1"},
|
| 24 |
+
{"store": "寶雅", "name": "CeraVe適樂膚 長效潤澤修護霜 340g", "price": "619","link": "https://www.poyabuy.com.tw/SalePage/Index/10104261","image": "https://img.91app.com/webapi/imagesV3/Original/SalePage/10104261/0/639160140536830000?v=1"}
|
| 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","image": "https://img.91app.com/webapi/imagesV3/Original/SalePage/9929185/0/639160572853670000?v=1"},
|
| 28 |
+
{"store": "康是美", "name": "Avene 雅漾舒護活泉水150ml", "price": "299","discount": "🔥限時67折", "link": "https://shop.cosmed.com.tw/SalePage/Index/5767522"},"image": "https://img.91app.com/webapi/imagesV3/Original/SalePage/9929185/0/639160572853670000?v=1",
|
| 29 |
+
{"store": "寶雅", "name": "Avene 雅漾舒護活泉水150ml", "price": "450", "link": "https://www.poyabuy.com.tw/SalePage/Index/9929185","image": "https://img.91app.com/webapi/imagesV3/Original/SalePage/9929185/0/639160572853670000?v=1"}
|
| 30 |
]
|
| 31 |
}
|
| 32 |
|
|
|
|
| 109 |
|
| 110 |
# --- 第 4 步:將資料塞進橫向圖卡 (不管哪條路線,最後都會來到這裡) ---
|
| 111 |
# --- 第 4 步:將資料塞進橫向圖卡 ---
|
| 112 |
+
# --- 第 4 步:將資料塞進 Toyota 風格的旗艦版圖卡 ---
|
| 113 |
+
# --- 第 4 步:將資料塞進 Toyota 風格的旗艦版圖卡 ---
|
| 114 |
bubbles = []
|
| 115 |
for item in data_list:
|
| 116 |
|
| 117 |
+
# 如果是 AI 估價,可能不會有圖片,我們給一張預設的漂亮漸層圖防呆
|
| 118 |
+
image_url = item.get("image", "https://via.placeholder.com/600x400/eeeeee/999999?text=Image+Not+Found")
|
| 119 |
+
|
| 120 |
+
# 建立中間的「規格表」 (像 Toyota 顯示全長全寬那樣)
|
| 121 |
+
specs_box = {
|
| 122 |
+
"type": "box",
|
| 123 |
+
"layout": "vertical",
|
| 124 |
+
"spacing": "sm",
|
| 125 |
+
"margin": "lg",
|
| 126 |
+
"contents": [
|
| 127 |
+
# 第一列:通路
|
| 128 |
+
{
|
| 129 |
+
"type": "box",
|
| 130 |
+
"layout": "horizontal",
|
| 131 |
+
"contents": [
|
| 132 |
+
{"type": "text", "text": "販售通路", "size": "md", "color": "#8c8c8c", "flex": 1},
|
| 133 |
+
{"type": "text", "text": item["store"], "size": "md", "color": "#111111", "weight": "bold", "flex": 2}
|
| 134 |
+
]
|
| 135 |
+
},
|
| 136 |
+
# 第二列:價格
|
| 137 |
{
|
| 138 |
"type": "box",
|
| 139 |
+
"layout": "horizontal",
|
|
|
|
| 140 |
"contents": [
|
| 141 |
+
{"type": "text", "text": price_tag, "size": "md", "color": "#8c8c8c", "flex": 1},
|
| 142 |
+
{"type": "text", "text": f"${item['price']}", "size": "md", "color": "#111111", "weight": "bold", "flex": 2}
|
| 143 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
}
|
| 145 |
]
|
| 146 |
+
}
|
| 147 |
|
| 148 |
+
# 如果有折扣,完美融入規格表的第三列
|
| 149 |
+
if item.get("discount"):
|
| 150 |
+
specs_box["contents"].append(
|
| 151 |
+
{
|
| 152 |
+
"type": "box",
|
| 153 |
+
"layout": "horizontal",
|
| 154 |
+
"contents": [
|
| 155 |
+
{"type": "text", "text": "獨家優惠", "size": "md", "color": "#8c8c8c", "flex": 1},
|
| 156 |
+
{"type": "text", "text": item["discount"], "size": "md", "color": "#ff3b30", "weight": "bold", "flex": 2}
|
| 157 |
+
]
|
| 158 |
+
}
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
# 組裝最終的百萬級卡片
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
card = {
|
| 163 |
"type": "bubble",
|
| 164 |
+
"size": "mega", # 設定跟 Toyota 一樣的寬版大卡片
|
| 165 |
+
"hero": {
|
| 166 |
+
"type": "image",
|
| 167 |
+
"url": image_url,
|
| 168 |
+
"size": "full",
|
| 169 |
+
"aspectRatio": "4:3", # 調整成最適合保養品展示的比例
|
| 170 |
+
"aspectMode": "fit", # 確保瓶身完整顯示不被裁切
|
| 171 |
+
"backgroundColor": "#FFFFFF"
|
| 172 |
+
},
|
| 173 |
"body": {
|
| 174 |
"type": "box",
|
| 175 |
"layout": "vertical",
|
| 176 |
+
"contents": [
|
| 177 |
+
# 標題:超大粗體商品名 (像 Toyota 的 bZ4X)
|
| 178 |
+
{"type": "text", "text": item["name"], "weight": "bold", "size": "xl", "wrap": True},
|
| 179 |
+
# 塞入剛剛做好的規格表
|
| 180 |
+
specs_box
|
| 181 |
+
]
|
| 182 |
},
|
| 183 |
"footer": {
|
| 184 |
"type": "box",
|
| 185 |
"layout": "vertical",
|
| 186 |
"contents": [
|
| 187 |
+
# 底部黑底白字的大按鈕
|
| 188 |
{
|
| 189 |
"type": "button",
|
| 190 |
"style": "primary",
|
| 191 |
+
"color": "#000000", # Toyota 經典純黑
|
| 192 |
+
"height": "md",
|
| 193 |
"action": {
|
| 194 |
"type": "uri",
|
| 195 |
"label": "前往商家購買",
|
|
|
|
| 196 |
"uri": item.get("link", f"https://www.google.com/search?q={item['store']}+{item['name']}")
|
| 197 |
}
|
| 198 |
}
|
|
|
|
| 200 |
}
|
| 201 |
}
|
| 202 |
bubbles.append(card)
|
| 203 |
+
|
| 204 |
|
| 205 |
# --- 第 5 步:打包發送圖卡 ---
|
| 206 |
flex_message = FlexSendMessage.new_from_json_dict({
|
|
|
|
| 211 |
"contents": bubbles
|
| 212 |
}
|
| 213 |
})
|
|
|
|
| 214 |
line_bot_api.push_message(user_id, flex_message)
|
| 215 |
del user_states[user_id]
|
|
|
|
| 216 |
except Exception as e:
|
| 217 |
line_bot_api.push_message(
|
| 218 |
user_id,
|
| 219 |
TextSendMessage(text=f"❌ 哎呀!精靈遇到了一點問題。錯誤原因:\n{e}")
|
| 220 |
)
|
| 221 |
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
|
| 225 |
# ================= 3. 多輪對話核心邏輯 =================
|
| 226 |
@handler.add(MessageEvent, message=TextMessage)
|
| 227 |
def handle_message(event):
|