iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0
Build on Google AI

用 Google AI 生態系 30 天從零打造一個全棧 AI SaaS 服務系列 第 23 篇

Day 23 -【多通道 Agent 擴充】實戰 LINE Bot + Gemini Multi-modal Webhook 整合與語音 / 連結極速提煉

  • 分享至 

  • xImage
  •  

在昨天的 [Day 22] 中,我們成功開發了 Chrome Extension 與 Side Panel,實現了網頁與影片的即時側邊欄提煉。

雖然網頁端與瀏覽器外掛解決了桌面端的場景,但在行動端(特別是台灣與亞洲市場),使用者最頻繁使用的互動管道其實是通訊軟體。當朋友在 LINE 群組裡丟了一支 1 小時的 Podcast 連結、或是發送了一段長達 2 分鐘的語音訊息時,如果每次都要複製連結切換應用程式,體驗依然不夠順暢。

今天,我們將把 OmniVibe AI 拓展為多通道 Agent,打造 LINE Bot + Gemini 1.5 原生多模態 Webhook 整合!使用者只要在 LINE 聊天室中轉傳影片連結、文字專欄,甚至直接錄製語音訊息,LINE Bot 就能利用 Gemini 1.5 Flash 秒級處理語音與影片,並透過美麗的 LINE Flex Message 回傳精華提煉結果!


📱 LINE Bot 多模態架構設計 (Webhook Pipeline)

sequenceDiagram
    autonumber
    actor User as 使用者 (LINE App)
    participant LineGateway as LINE Messaging API
    participant Webhook as Next.js Webhook Router
    participant LineSDK as @line/bot-sdk
    participant Gemini as Gemini 1.5 Flash API

    User->>LineGateway: 發送語音訊息 (.m4a) / YouTube 連結
    LineGateway->>Webhook: POST /api/line/webhook (帶有 HMAC 簽章)
    Webhook->>LineSDK: 驗證 Signature & 解析 Event
    
    alt 訊息類型:語音 (Audio)
        Webhook->>LineGateway: 下載語音 Binary Stream
        Webhook->>Gemini: 直接傳送 Audio Buffer (Multi-modal)
    else 訊息類型:連結 / 文字 (Text)
        Webhook->>Gemini: 呼叫 Fast Distill 提煉引擎
    end

    Gemini-->>Webhook: 回傳結構化摘要與洞見
    Webhook->>LineGateway: 回傳 Flex Message 結構化卡片
    LineGateway-->>User: 於 LINE 聊天室渲染漂亮提煉卡片


🛠️ 第一步:實作 LINE Webhook 與 簽章安全驗證 (src/app/api/line/webhook/route.ts)

LINE Messaging API 會透過 HTTP POST 發送 Webhook 請求,我們必須使用通道密鑰(Channel Secret)驗證 x-line-signature 標頭,確保請求確實來自 LINE 伺服器,防止惡意偽造請求:

// src/app/api/line/webhook/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { Client, WebhookEvent, validateSignature } from '@line/bot-sdk';
import { handleLineEvent } from '@/lib/line/event-handler';

const lineConfig = {
  channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN || '',
  channelSecret: process.env.LINE_CHANNEL_SECRET || '',
};

export const lineClient = new Client(lineConfig);

export async function POST(req: NextRequest) {
  try {
    const bodyText = await req.text();
    const signature = req.headers.get('x-line-signature') || '';

    // 1. 嚴格驗證 LINE 簽章 (HMAC-SHA256)
    if (!validateSignature(bodyText, lineConfig.channelSecret, signature)) {
      console.error('[LINE Webhook] 簽章驗證失敗!');
      return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
    }

    const { events } = JSON.parse(bodyText);

    // 2. 異步處理所有 LINE 事件 (避免 LINE Webhook Timeout)
    Promise.all(events.map((event: WebhookEvent) => handleLineEvent(event))).catch((err) =>
      console.error('[LINE Event Processing Error]', err)
    );

    // 迅速回應 LINE 伺服器 200 OK
    return NextResponse.json({ success: true });
  } catch (err: any) {
    console.error('[LINE Webhook Error]', err);
    return NextResponse.json({ error: err.message }, { status: 500 });
  }
}


🎙️ 第二步:處理多模態訊息 (語音 Binary 與 連結解析) (src/lib/line/event-handler.ts)

Gemini 1.5 Flash 具備強大的原生語音音訊處理能力(不需要先通過 Whisper 轉文字再送 LLM),我們可以將 LINE 使用者錄製的 .m4a 語音檔直接串接至 Gemini API:

// src/lib/line/event-handler.ts
import { WebhookEvent, MessageEvent } from '@line/bot-sdk';
import { lineClient } from '@/app/api/line/webhook/route';
import { GoogleGenerativeAI } from '@google/generative-ai';
import { buildFlexMessage } from './flex-builder';

const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || '');

export async function handleLineEvent(event: WebhookEvent) {
  if (event.type !== 'message') return;

  const replyToken = event.replyToken;
  const message = event.message;

  try {
    // 處理情境 A:使用者發送「語音訊息 (Audio)」
    if (message.type === 'audio') {
      // 1. 回覆讀取中提示
      await lineClient.showLoadingAnimation({ chatGuid: event.source.userId!, loadingSeconds: 10 });

      // 2. 自 LINE 伺服器串流下載語音檔 Binary
      const audioStream = await lineClient.getMessageContent(message.id);
      const chunks: Buffer[] = [];
      for await (const chunk of audioStream) {
        chunks.push(chunk as Buffer);
      }
      const audioBuffer = Buffer.concat(chunks);

      // 3. 直接將 Audio Buffer 餵給 Gemini 1.5 Flash 進行語音理解與提煉
      const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' });
      const result = await model.generateContent([
        {
          inlineData: {
            mimeType: 'audio/m4a',
            data: audioBuffer.toString('base64'),
          },
        },
        '這是一段使用者錄製的語音訊息。請幫我摘要這段語音的核心重點,並列出 3 個關鍵 Action Items。',
      ]);

      const responseText = result.response.text();

      // 4. 以 LINE Flex Message 回傳卡片
      const flexMsg = buildFlexMessage('🎙️ 語音訊息精華提煉', responseText);
      await lineClient.replyMessage(replyToken, flexMsg);
    }

    // 處理情境 B:使用者發送「文字 / YouTube 連結」
    else if (message.type === 'text') {
      const userText = message.text;

      // 判斷是否包含 URL 連結
      const urlRegex = /(https?:\/\/[^\s]+)/g;
      const hasUrl = urlRegex.test(userText);

      if (hasUrl) {
        await lineClient.showLoadingAnimation({ chatGuid: event.source.userId!, loadingSeconds: 15 });

        const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' });
        const result = await model.generateContent([
          `請針對以下網址/內容進行內容提煉與重點總結:\n${userText}`,
        ]);

        const flexMsg = buildFlexMessage('🎬 網址/影片內容速報', result.response.text());
        await lineClient.replyMessage(replyToken, flexMsg);
      }
    }
  } catch (error) {
    console.error('[LINE Handler Error]', error);
    await lineClient.replyMessage(replyToken, {
      type: 'text',
      text: '🤖 抱歉,OmniVibe 提煉過程中發生異常,請稍後再試!',
    });
  }
}


🎨 第三步:設計行動端友好的 LINE Flex Message (src/lib/line/flex-builder.ts)

為了讓提煉成果在手機螢幕上易於閱讀,我們利用 LINE 原生的 Flex Message (JSON UI Component) 來建構排版:

// src/lib/line/flex-builder.ts
import { FlexMessage } from '@line/bot-sdk';

export function buildFlexMessage(title: string, content: string): FlexMessage {
  return {
    type: 'flex',
    altText: `OmniVibe AI 提煉結果:${title}`,
    contents: {
      type: 'bubble',
      styles: {
        header: { backgroundColor: '#0F172A' },
        body: { backgroundColor: '#1E293B' },
        footer: { backgroundColor: '#0F172A' },
      },
      header: {
        type: 'box',
        layout: 'vertical',
        contents: [
          {
            type: 'text',
            text: title,
            weight: 'bold',
            color: '#38BDF8',
            size: 'sm',
          },
        ],
      },
      body: {
        type: 'box',
        layout: 'vertical',
        contents: [
          {
            type: 'text',
            text: content,
            color: '#E2E8F0',
            size: 'xs',
            wrap: true,
            maxLines: 15,
          },
        ],
      },
      footer: {
        type: 'box',
        layout: 'vertical',
        contents: [
          {
            type: 'button',
            action: {
              type: 'uri',
              label: '🌐 開啟 Web 完整畫廊',
              uri: 'https://app.omnivibe.ai/dashboard',
            },
            style: 'primary',
            color: '#6366F1',
            height: 'sm',
          },
        ],
      },
    },
  };
}


🧪 實測體驗:LINE 聊天室隨手語音提煉

  1. 傳送語音:在 LINE 聊天室中對著 Bot 錄製一段長度約 45 秒的對話語音:「我們明天開會要討論第三季的行銷預算,請行銷團隊準備簡報,設計團隊負責更新 Banner。」
  2. 語音直接理解:Gemini 1.5 Flash 直接解析語音音訊,不需要先轉成純文字,反應時間僅約 1.8 秒!
  3. 卡片渲染:LINE 聊天室自動彈出具備科技感的 Flex Message,條列出:
  • 📌 主題:第三季行銷預算會議
  • 🎯 Action Items:
  1. 行銷團隊:準備簡報資料
  2. 設計團隊:更新視覺 Banner

🎯 總結與明日預告

今天我們成功為 OmniVibe AI 建立了跨平台多通道(Multi-channel)Agent 架構:

  1. 整合了 LINE Messaging API Webhook 與 HMAC-SHA256 安全簽章機制。
  2. 實現了 Gemini 1.5 原生的 Audio 語音與多模態直接輸入,免除轉寫步驟降低 Latency。
  3. 利用 LINE Flex Message 提供了行動端最優化的視覺閱讀卡片體驗。

截至目前為止,我們已經建構了完整且強大的全棧 AI 服務!但當我們需要將這個包含 Web、Extension 與 LINE Bot 的系統進行大規模自動化測試與跨跨部署時,我們該如何構建穩健的 CI/CD 管線?

👉 明天(Day 24),我們將進入【自動化與雲端部署篇】:實戰 Google Cloud Build + Cloud Run + Docker 自動化 CI/CD 流水線!看我們如何實現零停機(Zero-downtime)一鍵部署!

我們明天見!🔥


上一篇
Day 22 -【跨平台生態擴充】實戰 Chrome Extension 瀏覽器外掛開發與 Side Panel 側邊欄雙端同步
下一篇
Day 24 -【自動化與雲端部署】Google Cloud Build + Cloud Run + Docker 自動化 CI/CD 流水線與零停機部署實戰
系列文
用 Google AI 生態系 30 天從零打造一個全棧 AI SaaS 服務 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言