iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0
Build on Google AI

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

Day 15 -【檔案管線】Server Actions + Google AI File API 多模態檔案處理管線:巨型影音與 PDF 異步上傳與狀態監控實戰

  • 分享至 

  • xImage
  •  

在昨天的 [Day 14] 中,我們成功將 Google Stitch 產出的 UI 原型解耦重構,封裝成具備現代質感與良好狀態控制的 Next.js 15 儀表板元件。

現在,我們面臨全棧 AI 應用開發中最具挑戰性的一道關卡 —— 「檔案管線(File Pipeline)」。

在 OmniVibe AI 的實際場景中,用戶不可能只傳幾 KB 的小文字檔。典型的使用案例是上傳 100MB 以上的 MP4 演講影片、高音質 Podcast 音訊(MP3),或是數百頁的 PDF 白皮書。

如果在後端直接把大檔案轉成 Base64 字串硬丟給 Gemini API(Inline Data),將會面臨:

  1. 記憶體暴增與崩潰:Base64 編碼會額外膨脹 33% 的體積,容易引發 Node.js Out-of-Memory(OOM)。
  2. HTTP 請求逾時(Timeout):巨大的 Payload 導致前端發送 HTTP 請求時觸發網關 30 秒或 60 秒的逾時限制。
  3. 影片處理尚未就緒(Processing State):Gemini 處理長影片需要幾秒至幾十秒的後台轉碼時間,直接對未準備好的影片發起提問會直接回報錯誤。

今天,我們將引進 Google 官方推薦的 Google AI File API,並結合 Next.js 15 的 Server Actions,搭建一條能穩定吞吐巨型檔案、支援異步狀態監控(Polling)的生產級檔案處理解決方案!


💡 為什麼選擇 Google AI File API?

Google Gen AI SDK 提供兩種餵給模型多模態資產的方式:

方案 適用情境 檔案大小上限 優缺點分析
Inline Data (Base64) 小型圖片(JPEG/PNG)、短音訊片段 < 20 MB ⚡ 優點:無需額外 API 呼叫,直接隨著 Prompt 送出。

❌ 缺點:消耗大量 Payload 頻寬,不支援長影片與大檔案。 |
| Google AI File API | 巨型 MP4 影片、長 Podcast、百頁 PDF | 單檔最大 2 GB

(每專案上限 20 GB) | ⚡ 優點:上傳後返回可重複使用的 fileUri,支援 Context Caching,上傳一次即可無限次提問。

❌ 缺點:影片需要非同步轉碼,需實作狀態輪詢機制。 |

對於 OmniVibe AI 這種內容提煉 SaaS 來說,Google AI File API 是絕對的不二之選。


🔄 檔案管線異步架構圖

為了確保使用者介面不會死鎖,我們將檔案處理解耦為三個獨立階段:

sequenceDiagram
    autonumber
    participant Client as 前端 (Dashboard UI)
    participant ServerAction as Next.js Server Action
    participant FileManager as Google AI File API
    participant Gemini as Gemini 1.5 大腦

    Client->>ServerAction: 1. 提交 FormData (影音/PDF 檔案)
    ServerAction->>FileManager: 2. 呼叫 fileManager.uploadFile() 暫存至 Google 雲端
    FileManager-->>ServerAction: 3. 回傳 File metadata (狀態: PROCESSING)
    
    loop 狀態監控 (State Polling)
        ServerAction->>FileManager: 4. 輪詢 fileManager.getFile(fileName)
        FileManager-->>ServerAction: 5. 回傳最新狀態 (PROCESSING / ACTIVE)
    end

    ServerAction-->>Client: 6. 返回就緒的 fileUri 與 mimeType
    Client->>ServerAction: 7. 發起內容提煉請求 (附帶 fileUri)
    ServerAction->>Gemini: 8. generateContent([fileUri, prompt])
    Gemini-->>Client: 9. 回傳高價值摘要與社群貼文


💻 實戰演練:搭建檔案處理管線

我們將在 src/lib/gemini/file-manager.ts 封裝 SDK 的上傳與監控邏輯,並在 src/app/actions/upload-asset.ts 中暴露 Server Action。

1. 封裝 File API 管理工具 (src/lib/gemini/file-manager.ts)

我們使用 @google/generative-ai/server 提供的 GoogleAIFileManager:

// src/lib/gemini/file-manager.ts
import { GoogleAIFileManager, FileState } from '@google/generative-ai/server';

const apiKey = process.env.GEMINI_API_KEY || '';
export const fileManager = new GoogleAIFileManager(apiKey);

export interface UploadedFileResult {
  uri: string;
  mimeType: string;
  name: string;
  displayName: string;
  sizeBytes: string;
}

/**
 * 上傳檔案至 Google AI File API 並等待其狀態轉為 ACTIVE (適用於影音後台轉碼)
 */
export async function uploadAndPollFile(
  filePath: string,
  mimeType: string,
  displayName: string
): Promise<UploadedFileResult> {
  try {
    console.log(`[File API] 開始上傳檔案: ${displayName} (${mimeType})...`);

    // 1. 上傳檔案至 Google 雲端暫存
    const uploadResult = await fileManager.uploadFile(filePath, {
      mimeType,
      displayName,
    });

    const fileName = uploadResult.file.name;
    console.log(`[File API] 上傳完成,檔案識別碼: ${fileName},等待轉碼就緒...`);

    // 2. 針對影音等需要後台處理的檔案進行狀態輪詢 (Polling)
    let file = await fileManager.getFile(fileName);
    let attempts = 0;
    const maxAttempts = 30; // 最多等待 90 秒 (3s * 30)

    while (file.state === FileState.PROCESSING && attempts < maxAttempts) {
      console.log(`[File API] 檔案轉碼中... (${attempts + 1}/${maxAttempts})`);
      await new Promise((resolve) => setTimeout(resolve, 3000)); // 每 3 秒輪詢一次
      file = await fileManager.getFile(fileName);
      attempts++;
    }

    if (file.state === FileState.FAILED) {
      throw new Error('Google AI File API 檔案轉碼失敗');
    }

    if (file.state !== FileState.ACTIVE) {
      throw new Error('檔案轉碼逾時,請稍後重試');
    }

    console.log(`[File API] 檔案狀態已就緒 (ACTIVE)!URI: ${file.uri}`);

    return {
      uri: file.uri,
      mimeType: file.mimeType,
      name: file.name,
      displayName: file.displayName || displayName,
      sizeBytes: file.sizeBytes,
    };
  } catch (error: any) {
    console.error('[File API Error]:', error);
    throw new Error(`檔案處理解析失敗: ${error.message}`);
  }
}


2. 實作 Next.js 15 Server Action (src/app/actions/upload-asset.ts)

在 Next.js 15 中,Server Actions 允許前端以原生調用 Function 的方式發起伺服器端操作。我們需要處理傳入的 FormData,將上傳的檔案暫存至 Node.js 臨時目錄,傳送至 Gemini 後再乾淨地清理暫存檔:

// src/app/actions/upload-asset.ts
'use server';

import { uploadAndPollFile } from '@/lib/gemini/file-manager';
import fs from 'node:fs/promises';
import path from 'node:path';
import os from 'node:os';

export async function uploadAssetAction(formData: FormData) {
  let tempFilePath: string | null = null;

  try {
    const file = formData.get('file') as File | null;

    if (!file) {
      return { success: false, error: '未檢測到上傳檔案' };
    }

    console.log(`[Server Action] 接收到上傳請求: ${file.name}, 大小: ${(file.size / 1024 / 1024).toFixed(2)} MB`);

    // 1. 將前端傳入的 File 轉為 Buffer 並寫入系統臨時目錄 (/tmp)
    const arrayBuffer = await file.arrayBuffer();
    const buffer = Buffer.from(arrayBuffer);
    
    tempFilePath = path.join(os.tmpdir(), `omnivibe_${Date.now()}_${file.name}`);
    await fs.writeFile(tempFilePath, buffer);

    // 2. 調用 File API 進行遠端上傳與狀態輪詢
    const fileResult = await uploadAndPollFile(
      tempFilePath,
      file.type,
      file.name
    );

    // 3. 回傳安全的 JSON 資訊給前端 Dashboard
    return {
      success: true,
      data: fileResult,
    };
  } catch (error: any) {
    console.error('[Upload Action Error]:', error);
    return {
      success: false,
      error: error.message || '檔案上傳過程發生未知錯誤',
    };
  } finally {
    // 4. 清理本機伺服器的臨時檔案,避免硬碟空間爆滿
    if (tempFilePath) {
      try {
        await fs.unlink(tempFilePath);
        console.log(`[Server Action] 臨時檔案已成功清理: ${tempFilePath}`);
      } catch (cleanupErr) {
        console.error('[Server Action] 清理臨時檔案失敗:', cleanupErr);
      }
    }
  }
}


3. 將前端 DropzoneArea 與 Server Action 連接 (src/components/dashboard/DropzoneArea.tsx)

現在,我們可以在 Days 14 寫好的前端 UI 中,輕鬆觸發上傳動作並更新狀態:

// 在 src/app/dashboard/page.tsx 中整合 Actions
import { uploadAssetAction } from '@/app/actions/upload-asset';

// ... (內部觸發邏輯)
const handleStartDistill = async () => {
  if (!selectedFile) return;
  setIsLoading(true);

  // 建立 FormData
  const formData = new FormData();
  formData.append('file', selectedFile);

  // 呼叫 Server Action 處理上傳與轉碼
  const uploadRes = await uploadAssetAction(formData);

  if (!uploadRes.success || !uploadRes.data) {
    alert(`上傳失敗: ${uploadRes.error}`);
    setIsLoading(false);
    return;
  }

  console.log('取得 Gemini File URI:', uploadRes.data.uri);

  // 取得 fileUri 後發起 Gemini 推論 (Day 07/12 的管線)
  // ...
};


🧪 實測驗證:檢視伺服器轉碼日誌

我們試著上傳一份 85MB 的高畫質演講影音檔 ai_keynote_2026.mp4:

後端 Terminal 輸出日誌:

[Server Action] 接收到上傳請求: ai_keynote_2026.mp4, 大小: 85.40 MB
[File API] 開始上傳檔案: ai_keynote_2026.mp4 (video/mp4)...
[File API] 上傳完成,檔案識別碼: files/ab12cd34ef56,等待轉碼就緒...
[File API] 檔案轉碼中... (1/30)
[File API] 檔案轉碼中... (2/30)
[File API] 檔案狀態已就緒 (ACTIVE)!URI: https://generativelanguage.googleapis.com/v1beta/files/ab12cd34ef56
[Server Action] 臨時檔案已成功清理: /tmp/omnivibe_1759048000_ai_keynote_2026.mp4

關鍵價值體現:

  1. 零記憶體洩漏:伺服器在上傳完畢後立即觸發 fs.unlink 清理 tmp 檔案,保護 Node.js runtime 健全度。
  2. 安全轉碼屏障:透過 FileState.PROCESSING 輪詢,確保 Gemini 1.5 接收到影音 URI 時 100% 已可被讀取與分析,徹底告別 API 轉碼中的 Crash 錯誤。
  3. 無縫接合快取:此處回傳的 fileUri 可直接作為 Day 12 所介紹的 Context Caching 輸入,一次上傳,無限次極速追問!

🎯 總結與明日預告

今天我們打通了 OmniVibe AI 多模態資產處理的「最後一公里」:

  1. 掌握了 Google AI File API 與傳統 Inline Base64 的架構差異與優勢。
  2. 實作了基於 Next.js 15 Server Actions 的大檔案上傳管線與暫存空間安全清理機制。
  3. 建立起了穩健的影音異步轉碼輪詢(Polling)邏輯,保障推論階段的百分之百可靠度。

擁有了極致的前端介面與穩健的後端檔案上傳管線,但如果用戶點擊提煉後,需要乾等 10 秒才能一次看到所有結果,體驗依舊不夠流暢。

👉 明天(Day 16),我們將進入【流式傳輸與打字機篇】:實戰 Server-Sent Events (SSE) 與 Gemini Stream 串流打字機!看我們如何用代碼實現逐字噴發的打字機效果,帶給使用者極致流暢的 AI 互動體驗!

我們明天見!🔥


上一篇
Day 14 -【元件化重構】將 Stitch 原型全面移植至 Next.js 15 + Tailwind CSS + shadcn/ui:打造生產級模組化 UI
下一篇
Day 16 -【流式傳輸】實戰 Server-Sent Events 與 Gemini Stream:打造秒級回應、逐字噴發的絲滑 AI 互動體驗
系列文
用 Google AI 生態系 30 天從零打造一個全棧 AI SaaS 服務 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言