{
  "title": "S06～S08｜AI 互動實現研究",
  "date": "2026-10-05",
  "status": "研究與開發規格；新能力尚未完成實作或真實模型／手機驗收。",
  "summary": "現有 Gemini 3.8 Flash 足以作為第一階段解題與追問的基礎。要讓設計真正可用，需要補上逐步教學契約、可恢復的作答與追問狀態，以及錄音確認管線。第一階段採短錄音確認後送出；連續即時 AI 語音另設驗證關卡。",
  "sections": [
    {
      "title": "1｜現況與證據",
      "table": [
        [
          "區域",
          "已存在",
          "需要補強"
        ],
        [
          "S06",
          "Question.vue 逐段展示已生成解答；grade() 可判課後練習",
          "每步試答、提示層級、步驟判分、進度與題目版本"
        ],
        [
          "S07",
          "文字追問；Provider 帶整題與最近 8 則訊息",
          "指定步驟、版本隔離、串流、執行狀態、重試去重與續看"
        ],
        [
          "S08",
          "speechSynthesis 朗讀；Voice.vue 為真人教室語音",
          "AI 錄音、轉文字、修改確認、取消與重錄；真人 RTC 不等於 AI 語音"
        ]
      ],
      "paragraphs": [
        "已查 frontend/app/src/components/Question.vue、frontend/app/src/api.ts、backend/src/app.js、backend/src/providers.js 與 backend/src/store.js。GeminiProvider 目前僅接受文字與圖片，使用 generateContent 整次回覆及 90 秒逾時。追問成功後才保存對話，失敗時沒有可恢復的服務端執行紀錄。改題覆寫解答但訊息仍留在同一題，須補版本隔離。",
        "本次完成程式審查與官方文件研究，沒有呼叫付費模型，也沒有修改或部署 App／API。既有解題 probe 不代表新增音訊、串流和互動判分已測通。"
      ]
    },
    {
      "title": "2｜模型與接法的選擇",
      "paragraphs": [
        "保留 Gemini 3.8 Flash 作解題與追問主模型。官方文件提供 JSON Schema 輸出與串流能力；格式符合 schema 並不保證數學正確，仍需規則驗算及教研樣本。[1][2]",
        "現有 generateContent 路線可先加強 schema 與 streamGenerateContent，不必為了文字串流全面遷移 Interactions。先在隔離測試確認所用模型、thinking 參數與 schema 相容性。[2][3]",
        "音訊理解文件以 gemini-3.8-flash 示範音訊與逐字稿，第一候選是同模型的錄音轉字 adapter；目前專案未串接這條能力。專用 gemini-3.5-transcribe 可列比較候選，須實測數學詞彙、權限及成本後才決定，不能自動換模型。[4][5]",
        "連續即時語音須另外使用 Live 能力，不能把普通 Flash 請求當成持續雙向音訊。Live 若讓瀏覽器直連，使用後端簽發短效權杖，長效金鑰仍留在服務端。[6][7]"
      ],
      "table": [
        [
          "能力",
          "第一階段",
          "後續提升"
        ],
        [
          "解題與提示",
          "完整生成 → schema 校驗 → 數學驗算 → 發布可用步驟",
          "依錯誤類型補充教學例子"
        ],
        [
          "追問",
          "同版本、指定步驟上下文；文字串流",
          "較長對話摘要與教材檢索，通過評測後擴充"
        ],
        [
          "語音輸入",
          "短錄音 → 轉文字 → 人工確認 → 同一追問服務",
          "Live 連續對話、插話與自動分段另案"
        ],
        [
          "語音輸出",
          "先用既有手動朗讀，新增經核對的 spokenText",
          "獨立 TTS 比較音質、延遲及成本後選用"
        ]
      ]
    },
    {
      "title": "3｜S06：從看解答，提升為真的自己解",
      "list": [
        "生成教學計畫時，把每一步拆成 prompt、inputKind、hints、explanation、穩定 stepId。答案規則 answerSpec 與 rubric 留在服務端，不直接把最後答案和未開啟提示全部送給孩子。",
        "綁定 questionRevision 與 solutionRevision。改題產生新版本，保留舊內容；舊試答及延遲結果不能回寫新版本。版本不符回 409，讓孩子確認是否重新提交。",
        "先驗算正解與步驟，再建立公開教學卡片。首發支援整數、小數、分數與明確單位換算；超出規則範圍的開放答案用 rubric 輔助，無法確定回 needs_review，不硬判對錯。",
        "數學規則依題意判斷：『最小公倍數』的 12 不能接受 24；『公倍數』則可能接受 24。分子填空與完整分數答案必須使用不同欄位規則。禁止任意 eval 解析答案。",
        "孩子先輸入，再按需看提示。提示分為方向、生活例子、局部操作；不提前展示後面步驟。AI 解釋本步時亦須限制揭露範圍，並評測是否偷跑完整答案。",
        "保存 activeStepId、每步 draftAnswer、hintLevel、attemptId、completionStatus 與閱讀位置。切頁、重整或追問返回都恢復原作答。先本機保存，再同步服務端；必須綁目前使用者與版本。",
        "下一步由程式依判分狀態開放，孩子自己點擊；AI 不直接控制頁面或宣告通關。找老師時帶題圖、卡關步驟、試答與追問紀錄。"
      ],
      "example": "3/4＋1/6：步驟 1 問 4 和 6 的最小公倍數，12 通過、24 回饋『這是公倍數，再找最小的』；步驟 2 填 9/12 與 2/12 的分子；步驟 3 確認 11/12。若題目要求最簡分數，22/24 先提示約分，不能只做數值相等判斷。"
    },
    {
      "title": "4｜S07：追問要知道孩子問哪一步",
      "list": [
        "每則提問帶 scope（step／question）、stepId、版本、當步試答與已看提示；歷史只取同版本，依 token 預算截取，不能只取最後 8 則混合對話。",
        "先交易保存學生訊息與 queued run，再呼叫模型。執行狀態 queued → running → completed／failed／cancelled；任何失敗仍看得到自己的問題。",
        "學生與 requestId 建立資料庫唯一鍵，同 key 同內容回同 run，同 key 不同內容回 409。不可只依賴單個程序內鎖，避免 rolling 部署或雙分頁重複執行。",
        "本系統只建立一次 run；若上游逾時後結果未知，不能宣稱保證模型只計費一次。重試策略先確認執行狀態，未知結果不得無條件再次生成。",
        "POST 啟動後以 fetch 讀 SSE 串流，保留 Bearer 認證。不要直接用無法自訂 Authorization 的原生 EventSource。事件有 runId、seq 與版本，可重連去重。",
        "串流顯示的是草稿文字；completed 通過校驗且持久化後才標成完成。S06 的半截 JSON 不能用來解鎖下一步。斷線先查 run 狀態，續看既有結果，不自動重新問一次。",
        "取消按鈕停止 run；單純關頁只停止觀看。服務端以原子終態決定完成／取消誰先生效，遲到回覆不重新覆寫。向上游取消是盡力而為，不承諾已產生費用能取消。",
        "Coolify API 與代理須確認不緩衝串流，並測試 heartbeat、連線逾時與 CORS；若串流失敗可輪詢同一 run，不能啟動第二次模型生成。"
      ]
    },
    {
      "title": "5｜S08：先確認聲音說了什麼，再讓 AI 回答",
      "list": [
        "流程：idle → permission → recording → transcribing → review → sending → awaiting → replied。每個錄音 draftId 綁定題目版本與步驟；重錄產生新草稿，舊轉錄回覆必須被忽略。",
        "點麥克風後才取得權限。停止後才把錄音交給轉錄服務；孩子要知道這是『轉成文字』，尚未送出為正式問題。只有按『送出問題』才建立 S07 訊息與 run。",
        "逐字稿保留原始文字，可編輯與重錄。數學口語如『四分之三』可提供 3/4 候選；『三除四』或『負二的平方』有歧義時先詢問，不依原題偷改孩子的話。",
        "轉錄只負責記錄話語，不作答、不補充孩子沒說的數字。無聲、噪音或無法辨識時回重錄／改文字；不將猜測的話自動送出。",
        "Chrome、Safari 的容器與編碼不同，先用 MediaRecorder.isTypeSupported 探測，再送實際 recorder.mimeType。後端檢查檔案內容、容量與時長，不能只看副檔名；不接受的編碼才做短暫轉碼。[8][9]",
        "新增專用音訊上傳方法：目前 api.ts 有 body 就強制 JSON，不能直接拿來傳 FormData。停止錄音要等最後 dataavailable 與 stop，再組 Blob；時間使用單調時鐘，不以分段事件數推算。權限提示若一直不回應，仍允許取消並回到文字。[8][9][10]",
        "取消、返回、重錄與切題時停止麥克風 tracks、清除暫存 Blob／URL、取消或失效舊請求。錄音草稿不自動永久保存，也不混入真人教室聲音。[11]",
        "開始錄音前停止朗讀；回覆由孩子按播放。spokenText 將已校驗的分數讀成『十二分之十一』，保留公式顯示；切步驟、離頁或重錄時停止舊播放。",
        "第一版建議每次錄音上限 60 秒、10 MB（待真機調整），具獨立配額與併發限制。錄音中若接電話、鎖屏、藍牙切換或網路中斷，保留可用草稿並提供文字替代。"
      ],
      "paragraphs": [
        "AI 提問與 Cloudflare RealtimeKit 真人教室是兩条不同用途的管線；本研究不改動既有老師雙向語音。"
      ]
    },
    {
      "title": "6｜建議 API 與資料契約",
      "table": [
        [
          "契約（待開發）",
          "作用"
        ],
        [
          "GET /questions/:id/learning",
          "當前版本、已核准教學步驟、可公開提示與進度"
        ],
        [
          "PATCH /questions/:id/progress",
          "保存當前步驟及草稿，校驗版本和 progressRevision"
        ],
        [
          "POST /questions/:id/steps/:stepId/attempts",
          "逐步試答；回 correct／incorrect／needs_review 與回饋"
        ],
        [
          "POST /questions/:id/ai-runs",
          "新增端點收 scope、stepId、版本、inputType、requestId，回 runId；保留現有 messages 回應相容"
        ],
        [
          "GET /ai-runs/:runId；GET /ai-runs/:runId/events",
          "恢復狀態與帶 seq 的串流；均需所有權驗證"
        ],
        [
          "POST /ai-runs/:runId/cancel",
          "取消執行，不等同離頁"
        ],
        [
          "POST /questions/:id/transcriptions",
          "音訊＋draftId＋版本；回 editable transcript，不新增對話"
        ],
        [
          "POST /questions/:id/steps/:stepId/hints",
          "依已開啟層級取得提示；GET learning 不一次回全部未開啟提示"
        ]
      ],
      "paragraphs": [
        "新增 solution_revisions、learning_progress、step_attempts、ai_runs、run_events 與 transcription_drafts（命名提案）。SQLite 可增量擴充，但去重必須有資料庫唯一索引、狀態轉移使用 compare-and-swap，不能只用目前 JSON entities 搜尋和程序鎖。",
        "公開回傳與服務端答案分離；所有新端點沿用 guest 身分與題目所有權，免登入不代表共用同一份進度。讀取其他孩子的 run、attempt 或逐字稿須拒絕。",
        "試答 attempts 也以使用者＋requestId 唯一鍵去重：同內容回原判分，不同內容回 409。progress 僅接受草稿、閱讀位置與可用步驟，不能由孩子提交 completionStatus 宣告通關；完成、提示解鎖與下一步權限由服務端導出。",
        "現有 messages 回 {message,reply}，直接改成 runId 會破壞舊 App。因此先新增 ai-runs 契約與能力版本，再更新 Pages 前端，最後考慮退役舊端點；保留分批部署和回滾能力。"
      ]
    },
    {
      "title": "7｜延遲與成本：先訂量測，不宣稱即時保證",
      "table": [
        [
          "量測",
          "建議試點目標（尚未實測）"
        ],
        [
          "點擊與本機狀態回饋",
          "100 ms 內更新；不等 AI 完成才顯示等待"
        ],
        [
          "數值規則判分",
          "API P95 小於 1 秒，需排除初次模型生成"
        ],
        [
          "文字首段／完整回覆",
          "P95 5 秒／15 秒，按字數、網路與模型分組"
        ],
        [
          "10 秒錄音轉字",
          "停止後 P95 5 秒；慢時保留草稿與取消入口"
        ],
        [
          "正確性與重複執行",
          "測試集中數值規則 100%；重試不得重建同一 run"
        ],
        [
          "費用",
          "按 run 記錄 provider、model、tokens／音訊時長與重試；以帳單核對"
        ]
      ],
      "paragraphs": [
        "提示優先讀取已核准教學計畫；按下一步與播放提示不必每次再叫模型。追問只送當步必要上下文，降低等待與費用。錄音停止後才轉錄；不為免登入孩子維持長時間空閒 Live 連線。",
        "將 Gemini 上游等待、vc66 處理與瀏覽器呈現分開記錄；網路及模型回應並無硬即時保證。本輪沒有新增成本實測，不提供假定每題單價。"
      ]
    },
    {
      "title": "8｜評測與出口",
      "list": [
        "教研集先建 80 個文字案例：20 個題目各含正答、典型錯答、模糊答案、追問；涵蓋分數、整數、小數、單位及開放說明。預期判定與提示由人工核對，不由同一模型自評。",
        "語音集先建 40 段授權錄音，包含數學詞彙、中文與英文混說、噪音、口音、停頓及歧義；另設無聲與損壞檔案，不用合成音訊代替孩子與真人測試。",
        "評測數字／正負號／分母／單位是否保留，比較 Gemini Flash 轉錄與專用轉錄候選；以人工確認後的送出內容為準，原始辨識率另列，不能混成 100%。",
        "重整／追問返回保留作答；改題後舊回覆不污染新題；雙擊、同 requestId 跨分頁、取消與完成競態只形成唯一結果；服務重啟後 run 可恢復或明確失敗。",
        "串流中途斷線可恢復同一 run，completed 重播不重複插入訊息；截斷、429、無配額、錯誤 JSON、錯誤公式皆保留孩子問題而不開放未校驗步驟。",
        "真機至少 iPhone Safari、Android Chrome、iPad Safari 與桌機：允許／拒絕麥克風、取消重錄、鎖屏、來電、藍牙切換、網路切換與播放中錄音。",
        "通過契約測試、真模型能力 probe、教研審查和裝置測試後，才推 develop 由 webhook 部署 Coolify，前端 Pages 另驗收。新能力不得以本研究或設計圖當成上線證據。"
      ]
    },
    {
      "title": "9｜落地工作順序",
      "table": [
        [
          "工作包",
          "交付與通過條件",
          "優先"
        ],
        [
          "版本與 schema",
          "不可變解答版本、穩定 stepId、private answerSpec、驗算與增量遷移",
          "P0"
        ],
        [
          "S06 作答閉環",
          "逐步草稿、分級提示、規則判分、進度恢復；示例題與錯答通過",
          "P0"
        ],
        [
          "S07 追問執行",
          "step scope、唯一 run、保存／取消／重試／查狀態；斷線不丟問題",
          "P0"
        ],
        [
          "S08 語音草稿",
          "錄音與清理、Flash 音訊 probe、轉字確認、同版本送出、文字退路",
          "P0"
        ],
        [
          "S07 串流與朗讀",
          "逐段文字、重連、代理設定、spokenText；不中斷既有非串流備援",
          "P1"
        ],
        [
          "Live AI 語音",
          "模型權限、短效 token、插話、上下文、成本與裝置比較；獨立決策",
          "P2"
        ]
      ],
      "paragraphs": [
        "先完成版本與執行狀態，S06／S08 可在共同契約確定後分工。工期須在能力 probe、測試樣本與裝置可用性確認後估算；不把新增 Live 作第一階段必要條件。現行 Vue 3、Gemini 主模型、vc66 Coolify 與 Pages 部署方式沿用。"
      ]
    },
    {
      "title": "10｜研究結論",
      "paragraphs": [
        "三個介面可以落地；可靠度主要取決於版本、持久狀態、可驗算的判分與確認流程。第一階段讓孩子能自己試答、針對一步追問、核對錄音文字後送出。AI 解釋與語音能力可逐步提升，頁面切換、權限、判分終態與資料恢復由程式管理。"
      ]
    }
  ],
  "sources": [
    [
      "1",
      "Gemini 結構化輸出",
      "https://ai.google.dev/gemini-api/docs/structured-output"
    ],
    [
      "2",
      "Generate Content 結構化輸出",
      "https://ai.google.dev/gemini-api/docs/generate-content/structured-output"
    ],
    [
      "3",
      "Generate Content／streamGenerateContent",
      "https://ai.google.dev/api/generate-content"
    ],
    [
      "4",
      "Gemini 音訊理解與轉錄範例",
      "https://ai.google.dev/gemini-api/docs/audio"
    ],
    [
      "5",
      "Gemini 專用語音轉錄",
      "https://ai.google.dev/gemini-api/docs/transcribe"
    ],
    [
      "6",
      "Gemini Live API",
      "https://ai.google.dev/gemini-api/docs/live-api"
    ],
    [
      "7",
      "Live 短效權杖",
      "https://ai.google.dev/gemini-api/docs/live-api/ephemeral-tokens"
    ],
    [
      "8",
      "MediaRecorder.isTypeSupported",
      "https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/isTypeSupported_static"
    ],
    [
      "9",
      "getUserMedia",
      "https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia"
    ],
    [
      "10",
      "MediaRecorder 最後資料事件",
      "https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/dataavailable_event"
    ],
    [
      "11",
      "停止麥克風 tracks",
      "https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/stop"
    ]
  ]
}