iT邦幫忙

0

# Day 11 打造 LINE Flex Message 互動輪播卡片

n8n
  • 分享至 

  • xImage
  •  

在昨天的文章中,我們完成了 10 天基礎核心架構的總驗收:從使用者的口語輸入,到 Dify 提取意圖、FastAPI 篩選餐廳,最後透過 LINE Reply API 把推薦文字送回手機聊天室。

不過,傳統的純文字訊息排版單調,使用者很難一目了然地比較店家資訊,也無法直接點擊查看地圖或店家照片。

今天 Day 11,我們將正式跨入進階優化階段:利用 LINE 強大的 Flex Message(彈性訊息) 與 Carousel(輪播卡片),將推薦結果升級為圖文並茂、具備評分標籤與一鍵導航按鈕的高質感互動小卡!


一、什麼是 LINE Flex Message?

Flex Message 是 LINE 官方提供的一種高自由度訊息格式,基於 CSS Flexible Box(Flexbox)的概念設計:

  • 自訂版面配置:可以像寫網頁一樣自由排版文字大小、字型顏色、圖片比例與背景間距。
  • 輪播支援(Carousel):使用者可以在手機上左右水平滑動卡片,非常適合用來展示多筆餐廳推薦名單。
  • 內建互動按鈕:每張卡片底部可以直接掛上 URL 動作按鈕(如開啟 Google Maps 導航)或發送訊息動作(Postback / Message Action)。

LINE 官方甚至提供了視覺化的線上編輯工具——LINE Flex Message Simulator,可以直接預覽卡片排版。


二、設計單張餐廳 Flex Bubble 結構

在 LINE 的規範中,Carousel 是由多個 Bubble(對話泡泡) 組成的一組陣列。

我們先為每一間推薦店家設計單個 Bubble 範本:包含店面封面圖、店名、評分星星、平均消費,以及點擊前往 Google Maps 導航的動作按鈕。

單張 Bubble 的 JSON 結構範例如下:

{
  "type": "bubble",
  "hero": {
    "type": "image",
    "url": "[https://images.unsplash.com/photo-1551024709-8f23befc6f87?w=600](https://images.unsplash.com/photo-1551024709-8f23befc6f87?w=600)",
    "size": "full",
    "aspectRatio": "20:13",
    "aspectMode": "cover"
  },
  "body": {
    "type": "box",
    "layout": "vertical",
    "contents": [
      {
        "type": "text",
        "text": "恬淡日常法式甜點",
        "weight": "bold",
        "size": "xl"
      },
      {
        "type": "box",
        "layout": "baseline",
        "margin": "md",
        "contents": [
          { "type": "text", "text": "⭐️ 4.7", "size": "sm", "color": "#FF9900", "weight": "bold" },
          { "type": "text", "text": "甜點/下午茶", "size": "sm", "color": "#999999", "margin": "md" },
          { "type": "text", "text": "約 280 元", "size": "sm", "color": "#666666", "margin": "md" }
        ]
      },
      {
        "type": "text",
        "text": "招牌:草莓戚風蛋糕、伯爵生乳捲",
        "size": "xs",
        "color": "#444444",
        "margin": "md",
        "wrap": true
      },
      {
        "type": "text",
        "text": "巷弄隱密甜點店,氣氛溫馨悠閒,適合放鬆聊天。",
        "size": "xs",
        "color": "#888888",
        "margin": "xs",
        "wrap": true
      }
    ]
  },
  "footer": {
    "type": "box",
    "layout": "vertical",
    "contents": [
      {
        "type": "button",
        "style": "primary",
        "color": "#06C755",
        "height": "sm",
        "action": {
          "type": "uri",
          "label": "開啟地圖導航",
          "uri": "[https://www.google.com/maps/search/?api=1&query=板橋區恬淡日常法式甜點](https://www.google.com/maps/search/?api=1&query=板橋區恬淡日常法式甜點)"
        }
      }
    ]
  }
}

三、在 n8n 中動態組裝 Flex Carousel

現在我們要在 n8n 工作流中,將原先輸出純文字的節點,替換為動態組裝上述 Flex 結構的 JavaScript 邏輯。

Step 1:替換「Format Reply Message」節點程式碼

點開昨天的 Code 節點(原先負責文字排版的節點),將其改寫如下:

const responseData = $input.first().json;
const replyToken = $('Parse AI JSON').first().json.reply_token;

const recommendations = responseData.recommendations || [];

// 1. 若查無店家,回傳純文字提示
if (recommendations.length === 0) {
  return {
    reply_token: replyToken,
    messages: [
      {
        type: "text",
        text: "抱歉,目前在該區域與預算條件下找不到合適的店家,建議放寬預算或更換料理分類再試試看哦!"
      }
    ]
  };
}

// 2. 預備幾張乾淨的 Unsplash 美食佔位圖(若資料庫無圖片可用)
const defaultImages = [
  "[https://images.unsplash.com/photo-1551024709-8f23befc6f87?w=600](https://images.unsplash.com/photo-1551024709-8f23befc6f87?w=600)",
  "[https://images.unsplash.com/photo-1555396273-367ea4eb4db5?w=600](https://images.unsplash.com/photo-1555396273-367ea4eb4db5?w=600)",
  "[https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=600](https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=600)"
];

// 3. 將每筆推薦餐廳轉換為 Flex Bubble 結構(最多顯示 5 家,避免超過 LINE 上限)
const bubbles = recommendations.slice(0, 5).map((shop, index) => {
  const mapQuery = encodeURIComponent(`${shop.location} ${shop.name}`);
  const imageUrl = defaultImages[index % defaultImages.length];

  return {
    type: "bubble",
    hero: {
      type: "image",
      url: imageUrl,
      size: "full",
      aspectRatio: "20:13",
      aspectMode: "cover"
    },
    body: {
      type: "box",
      layout: "vertical",
      contents: [
        {
          type: "text",
          text: shop.name,
          weight: "bold",
          size: "lg",
          wrap: true
        },
        {
          type: "box",
          layout: "baseline",
          margin: "sm",
          contents: [
            { type: "text", text: `⭐️ ${shop.rating}`, size: "sm", color: "#FF9900", weight: "bold" },
            { type: "text", text: shop.cuisine, size: "sm", color: "#888888", margin: "md" },
            { type: "text", text: `約 ${shop.price_avg} 元`, size: "sm", color: "#555555", margin: "md" }
          ]
        },
        {
          type: "text",
          text: `招牌:${shop.signature_dish}`,
          size: "xs",
          color: "#333333",
          margin: "md",
          wrap: true
        },
        {
          type: "text",
          text: shop.reason || "精選優質店家,值得一試!",
          size: "xs",
          color: "#888888",
          margin: "xs",
          wrap: true
        }
      ]
    },
    footer: {
      type: "box",
      layout: "vertical",
      contents: [
        {
          type: "button",
          style: "primary",
          color: "#06C755",
          height: "sm",
          action: {
            type: "uri",
            label: "開啟 Google 地圖",
            uri: `[https://www.google.com/maps/search/?api=1&query=$](https://www.google.com/maps/search/?api=1&query=$){mapQuery}`
          }
        }
      ]
    }
  };
});

// 4. 打包成 LINE Flex Carousel 格式
const flexMessage = {
  type: "flex",
  altText: `為您推薦 ${recommendations.length} 間精選美食餐廳!`,
  contents: {
    type: "carousel",
    contents: bubbles
  }
};

return {
  reply_token: replyToken,
  messages: [flexMessage]
};

Step 2:更新 LINE Reply HTTP Request 節點

將最後一個呼叫 LINE Reply API 的 HTTP Request 節點做小幅調整,直接帶入上一個節點組合好的 messages 陣列:
Method:POST

URL:https://api.line.me/v2/bot/message/reply

Authentication:Header Auth(保持不變)

Headers:Content-Type: application/json

Specify Body:選擇 Using JSON

JSON Body:

{
  "replyToken": "={{ $json.reply_token }}",
  "messages": "={{ $json.messages }}"
}

四、真機滑動效果驗收

儲存並確保工作流為 Active 狀態後,打開手機 LINE 聊天室,發送一句包含多個結果或寬鬆條件的測試語句:

使用者輸入:
「我想要看板橋好吃的甜點或餐廳,預算 500 以內都可以!」
驗收畫面亮點:
多卡輪播(Carousel):手機聊天室不再只是一長串冰冷的文字,而是展示精緻的橫向滑動卡片!

直覺資訊階層:店名加粗、評分亮黃顯示、均消清楚列出。

一鍵外開地圖:點擊卡片下方的綠色按鈕 「開啟 Google 地圖」,手機會自動喚起 Google Maps 並搜尋該店家,實現無縫導航體驗!
今日結語與明日預告
今天我們成功把原本陽春的純文字回覆,升級為現代商業等級的 LINE Flex Message Carousel,讓整體的使用者體驗有了質的飛躍!

目前我們的資料庫仍然是 Day 05 寫在程式內的靜態 Mock 假資料。
明天 Day 12,我們將要把微服務接軌真實世界:串接 Google Maps Places API(或在地開放資料),讓推薦 Agent 具備查詢真實世界商家、即時營業狀態與真實評論的能力! 敬請期待!


*提醒邦友,使用第三方服務/API 時,請務必評估資安風險與隱私保護
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言