iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0

昨天完成「產生學習卡」表單後,App 終於可以知道使用者今天想學什麼。

測試時,我選擇:

7~8 歲兒童
浩瀚太空
3 張

React 也成功取得:

{
  "ageGroup": "7~8 歲兒童",
  "topic": "浩瀚太空",
  "cardCount": 3
}

不過昨天按下「產生學習卡」後,這些資料最後只去了:

console.log(outputData);

如果要真的變成「AI 智慧學習卡」,總不能每次按下按鈕後,只在 Console 跟自己打招呼。

所以今天終於要跨出重要的一步:

把 React Form 收集到的資料送給 Gemini API,讓自己的 App 第一次真正取得 AI 產生的內容。


今天先把 API 接通就好

如果一路往後想,事情其實還很多。

Gemini 回來的資料要不要是 JSON?怎麼轉成 LearningCard[]?怎麼放回畫面?失敗怎麼處理?

但如果今天全部一起做,大概會直接把未來好幾天的進度一次做完。

所以今天先把範圍縮小成:

React Form
    ↓
Gemini API
    ↓
Gemini Response

先確認 App 能不能真的跟 Gemini 說上話。

Structured Output、JSON Schema,以及把 Response 接回 Learning Card UI,今天全部先不做。


這次給 Gemini 的需求

我繼續在目前的 Google AI Studio 專案裡,請 Gemini 幫我完成 API 串接。

這次 Prompt 是:

目前 Learning Card App 已經有「產生學習卡」表單。

表單可以取得:

{
  ageGroup,
  topic,
  cardCount
}

目前按下「產生學習卡」後,只會使用 console.log 輸出表單資料。

現在請幫我加入 Gemini API,讓按下「產生學習卡」後,可以把 ageGroup、topic、cardCount 組成簡單的 Prompt,送給 Gemini,並取得 Gemini 回傳內容。

例如:

ageGroup: "7~8 歲兒童"
topic: "浩瀚太空"
cardCount: 3

可以組成類似:

「請幫我產生適合 7~8 歲兒童的『浩瀚太空』學習卡,共 3 張。」

這一版的目標只需要確認 Gemini API 可以成功呼叫並取得 Response。

請:
- 使用 Google AI Studio 建議的 server-side Gemini API 整合方式
- API Key 必須保留在 server-side,不要寫入 React client code
- 保留目前 CardGeneratorForm
- 保留目前 oceanCards
- 不要修改 LearningCard 資料結構
- 不要修改其他既有學習卡資料
- 不要移除目前既有功能

這一版先不要:
- 使用 Structured Output
- 設定 JSON Schema
- 把 Response 轉成 LearningCard[]
- 用 Gemini Response 取代目前 oceanCards
- 修改目前 Learning Card UI

Gemini Response 可以先顯示在表單下方或輸出到 Console。

這次只完成:

Form Data
→ Gemini API
→ Gemini Response

經過前幾天的經驗,現在除了告訴 Gemini「要做什麼」,也開始習慣把「今天先不要做什麼」一起寫清楚。

Day18 prompt


Gemini 花了 125 秒完成串接

這次使用 Gemini 3.8 Flash,大約執行了 125 秒。

它先檢查目前專案與 Gemini API 的使用方式,最後主要修改了:

server.ts
package.json
src/components/CardGeneratorForm.tsx

其中最大的改變,就是多了一層 Server。

原本 Day 17 的流程:

CardGeneratorForm
       ↓
console.log()

現在變成:

CardGeneratorForm
       ↓
POST /api/generate-cards
       ↓
server.ts
       ↓
Gemini API
       ↓
Gemini Response

這也讓我第一次比較具體地看到:

「在 React App 使用 Gemini」並不只是把 API Key 塞進前端然後 fetch()。

Day18 Gemini running


為什麼不直接在 React 裡放 API Key?

這次 Gemini 建立的 server.ts 會從 Server-side 取得:

process.env.GEMINI_API_KEY

而不是把 Gemini API Key 寫進 React Client。

概念上變成:

React Client
     ↓
/api/generate-cards
     ↓
Server
     ↓
GEMINI_API_KEY
     ↓
Gemini API

這一點很重要。

如果直接把 API Key 寫進前端程式,最後送到瀏覽器的 JavaScript 就可能讓使用者取得 Key。

因此需要保密的憑證留在 Server-side,由自己的 Server 代替前端呼叫 Gemini API。

前端只需要知道:

我要呼叫 /api/generate-cards。

至於 Gemini API Key,前端不需要知道。


從 Form Data 組成第一個 API Prompt

Day 17 已經有:

ageGroup
topic
cardCount

今天則第一次把它們組成真正要送給 Gemini 的 Prompt。

目前版本非常簡單:

請幫我產生適合 {ageGroup} 的『{topic}』學習卡,共 {cardCount} 張。

例如:

{
  "ageGroup": "7~8 歲兒童",
  "topic": "浩瀚太空",
  "cardCount": 3
}

就會變成:

請幫我產生適合 7~8 歲兒童 的『浩瀚太空』學習卡,共 3 張。

跟前面在 Google AI Studio 手動輸入 Prompt 最大的差別是:

這次 Prompt 的內容來自使用者操作 App 的結果。


第一次從自己的 App 拿到 Gemini Response

API 串接完成後,我回到 Preview 實際測試。

第一組條件使用:

學習對象:7~8 歲兒童
學習主題:浩瀚太空
卡片數量:3 張

Client 先取得:

{
  "ageGroup": "7~8 歲兒童",
  "topic": "浩瀚太空",
  "cardCount": 3
}

接著組成:

請幫我產生適合 7~8 歲兒童 的『浩瀚太空』學習卡,共 3 張。

Server 收到 Request 後,開始呼叫:

gemini-2.5-flash

最後 Console 終於出現:

[Client] 成功取得 Gemini Response

Server 也記錄:

[Server] 模型 gemini-2.5-flash 回應成功,字數: 1764

而且 Gemini 真的產生了三張與太空相關的內容,包括:

地球與月亮
太陽系
星星、銀河系與太空人

網站上也成功出現:

Gemini 回應成功!

到這裡,第一次完整的資料流程終於跑通了。

React Form
     ↓
Form Data
     ↓
Prompt
     ↓
Server
     ↓
Gemini API
     ↓
Gemini Response
     ↓
React

前面十幾天都是我自己到 Google AI Studio 按 Run。

今天第一次變成:

我的 App 幫我去問 Gemini。

Day18 first try gemini api response


再換一組條件看看

不過只成功一次還不夠。

如果程式裡其實有什麼東西被寫死,我可能也不會發現。

所以第二次我把三個條件全部換掉:

學習對象:3~4 歲幼兒
學習主題:日常好習慣
卡片數量:7 張

Client 取得:

{
  "ageGroup": "3~4 歲幼兒",
  "topic": "日常好習慣",
  "cardCount": 7
}

並組成新的 Prompt:

請幫我產生適合 3~4 歲幼兒 的『日常好習慣』學習卡,共 7 張。

這次 Gemini 也成功回傳內容。

Server Log 顯示:

[Server] 模型 gemini-2.5-flash 回應成功,字數: 1404

而且真的產生七張不同的日常習慣學習卡:

好好洗手手
刷刷牙齒亮晶晶
玩具回家囉
好好吃飯飯
我是小禮貌
準時睡覺覺
一起玩,真快樂

所以現在可以確認:

7~8 歲+浩瀚太空+3 張
              ↓
        3 張太空內容

3~4 歲+日常好習慣+7 張
              ↓
        7 張生活習慣內容

表單不只是裝飾。

使用者輸入的條件,真的已經開始影響 Gemini 產生的結果。

Day18 second try gemini api response ui 01

Day18 second try gemini api response ui 02


API 成功了,但新的問題也出現了

看到這裡,好像可以宣布:

Learning Card Generator 完成!

先等等。

把兩次 Gemini Response 放在一起看,就會發現一件很有趣的事情。

「浩瀚太空」那次,Gemini 自己設計的格式包含:

卡片主題
主要顏色
卡片插畫建議
學習內容
你知道嗎?
動動腦時間

但換成「日常好習慣」之後,又變成:

卡片名稱
說明文字
情境圖示建議

最後甚至自己多附上一段:

使用建議

Gemini 確實理解了:

給誰學
學什麼
要幾張

但是它不知道我的 React App 真正需要的是:

type LearningCard = {
  id: string;
  emoji: string;
  title: string;
  content: string;
  question: string;
  answer: string;
};

換句話說:

Gemini API 呼叫成功          ✓
主題有跟著表單改變           ✓
卡片數量有跟著表單改變       ✓

Response 可以直接變成
LearningCard[]               ✗

這個問題其實有點熟悉。

前面 Day 09~Day 12 在 Google AI Studio 測試 Prompt 時,我就已經碰過類似問題:

人看得懂的 AI 回答,不代表程式好處理。

現在只是同一個問題,正式從 AI Studio 搬進自己的 App。


今天真正完成的是哪一段?

所以今天先不急著處理 Response 格式。

目前真正完成的是:

使用者
  ↓
Learning Card Form
  ↓
ageGroup
topic
cardCount
  ↓
Prompt
  ↓
POST /api/generate-cards
  ↓
Server
  ↓
Gemini API
  ↓
自然語言 Response

跟昨天相比,最大的差別只有最後幾步。

Day 17:

Form → console.log()

Day 18:

Form → Server → Gemini → Response

看起來只多了幾個箭頭,但這幾個箭頭終於讓目前的 Prototype 開始真的有 AI 能力。


Gemini 3.8 Flash 和 gemini-2.5-flash?

這次畫面上其實同時出現兩個 Gemini Model 名稱。

協助我修改這個 App 的是:

Gemini 3.8 Flash

而 server.ts 實際呼叫、負責產生 Learning Card 內容的模型則是:

gemini-2.5-flash

兩者在這次實驗中扮演的角色不同。

可以把它理解成:

Gemini 3.8 Flash
→ 幫我開發 App

gemini-2.5-flash
→ 被我的 App 呼叫
→ 產生 Learning Card

這也是今天開始跟前面幾天不太一樣的地方。

Gemini 不再只是我開發時使用的工具,也開始成為 App 本身的一部分。


今天的小結

今天終於把昨天停在 Console 裡的:

{
  "ageGroup": "3~4 歲幼兒",
  "topic": "日常好習慣",
  "cardCount": 7
}

真的送進 Gemini API。

而且經過兩組不同條件測試後,可以確認:

React Form
     ↓
動態學習條件
     ↓
Server-side API
     ↓
Gemini
     ↓
不同的學習內容

整條流程已經成功接通。

不過今天也同時發現兩個後面一定要處理的問題。

第一個是 API 不保證每次都成功,開發測試時就真的遇到了 503 UNAVAILABLE。

第二個則是更直接影響 Learning Card App:

Gemini 有回答,不代表 Gemini 回傳的資料可以直接給程式使用。

目前 Prompt 只有:

請幫我產生適合 {ageGroup} 的
『{topic}』學習卡,共 {cardCount} 張。

對 Gemini 來說,自由發揮的空間還非常大。

也因此兩次測試雖然都成功產生學習卡,回傳格式卻完全不一樣。

所以 API 接通之後,下一個問題已經很明顯了:

到底該怎麼把 Prompt 管理好?


明天預告

Day 19|API 接通了,但 Prompt 太簡單:把學習條件整理成 Prompt Template

今天只用一句:

請幫我產生適合 3~4 歲幼兒的
「日常好習慣」學習卡,共 7 張。

Gemini 就自己決定了卡片要有哪些欄位、要不要插畫建議,甚至連最後的使用建議都一起補上。

明天準備重新整理這段 Prompt。

把:

ageGroup
topic
cardCount

不只是塞進一句話,而是整理成比較完整、可維護的 Prompt Template。

看看當 Prompt 寫得更清楚之後,Gemini 的內容能不能更接近我們真正想要的 Learning Card。

至於「固定 JSON 格式」這件事——

前面學過的 Structured Output,也差不多該準備回來了。


上一篇
Day 17|不想每次都改 Prompt:做一個 Learning Card 產生表單
下一篇
Day 19|API 接通了,但 Prompt 太簡單:把學習條件整理成 Prompt Template
系列文
今天學什麼?30 天用 Google AI 打造智慧學習卡 共 20 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言