iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
Build on Google AI

AI 策展人:用 Google ADK 打造會思考、會介紹的 3D 展示平台系列 第 14 篇

Day 14|斷網也能播:把展示櫃裝進一只行李箱(`exhibit-kiosk`)

  • 分享至 

  • xImage
  •  

前三天,我們把展示櫃(viewer-core)蓋好了:能轉、能點、能帶你看重點。但它還住在「工廠」裡,得有人在旁邊開著開發伺服器才看得到。展場不是這樣:網路可能很爛、根本沒有,現場人員也不會下工程指令。

今天的任務:把整座展示櫃裝進一只行李箱,到現場打開就能演。這只行李箱在程式裡叫「離線播放包」(exhibit_pack/),做出它的工具叫 exhibit-kiosk(kiosk 就是展場那種放著給人戳的螢幕)。

行李箱裡裝什麼(比喻→實際檔案)

行李箱裡的東西 實際是(demo 實測大小) 內容 白話
入口 index.html(1.3 KB) 依序載入下面幾個 script 唯一要雙擊的檔案
開場海報 poster.png(124 KB) 來自資產的等角截圖,沒有就自己畫佔位圖 櫃子還沒開燈時的招牌
說明書 scene.json(5 KB) window.__EXHIBIT_SCENE__ = {...};(結構欄位:id/anchor/camera/order) 鏡頭怎麼擺、哪些重點、怎麼巡場(Day 03、11 的說明書)
解說文字 hotspots.json(0.6 KB) window.__EXHIBIT_HOTSPOTS__ = {...};(title/body/media) 每個熱點的標題與文案,單獨抽出來給現場人員改
展品(備份) exhibit_kiosk.glb(61 KB) 模型原檔,執行時不讀,只給安檢看 那顆齒輪減速機,原樣一份
展品(實際用的) assets/model-data.js(81 KB) 同一顆模型轉成 base64 文字:window.__EXHIBIT_ASSETS_B64__ = {...}; 那顆齒輪減速機,包成文字塞進包裹的另一份
展示櫃的機關 assets/kiosk.bundle.js(736 KB) esbuild 打包的 viewer-core +展場行為(iife、壓縮) 打包好的檢視器加上展場專用的行為
備用電 assets/poster.js(1 KB) 保底狀態機,最先載入 一個很小、最先載入的保底程式
樣式 assets/kiosk.css(3 KB) 放大字體、加粗按鈕、海報樣式 裝飾與尺寸
解碼器(備而不用) assets/decoders/(約 2.4 MB) three.js 的 Draco/KTX2 解碼器本機副本 目前的模型沒壓縮,用不上
給現場的說明 README.txt(3 KB) 白話的使用與改字教學 現場人員的說明書

我用真實資產(ast_gearbox_demo)重新打包一次,整個資料夾約 3.4 MB,其中主程式(機關)約 736 KB、模型本身只有約 61 KB;剩下大部分是預先放進去的壓縮解碼器(約 2.4 MB,下面「還沒做」會說明為什麼現在用不上)。

最大的坑:瀏覽器對「本機檔案」超級多疑

在自己電腦上開發時,我們開一個小伺服器,用網址看網頁,瀏覽器覺得「這是正常網站」。但展場是直接打開檔案(網址以 file:// 開頭),瀏覽器覺得「這是來歷不明的檔案,憑什麼去翻別的檔案?」於是有一條規矩:

這個檔案不准打電話給別人要資料(不能用 fetch、XMLHttpRequest 這類「請求」,也不准用 type="module" 的載入方式)。
但直接裝在包裹裡送到手上的(一般的 <script src> 標籤)可以。

為了確認這件事是規矩、不是某個瀏覽器的毛病,我寫了一個十行的小測試頁,請人在 Safari、Chrome、Edge、Arc 各雙擊一次。fetch 和 XMLHttpRequest 兩種「打電話」方式,四個瀏覽器全部失敗。

所以打包方式整個順著規矩改:

原本的做法(打電話) 現在的做法(拆包裹)
說明書是純 JSON 檔,執行時去讀 包成一小段程式 window.__EXHIBIT_SCENE__ = {...};,用 <script src> 載入
解說文字同上 同上(window.__EXHIBIT_HOTSPOTS__)
模型(GLB)由 viewer-core 內部呼叫 fetch() 讀取 模型轉成 base64 文字,同樣用 <script src> 載入;再對全域 fetch 攔截:請求的檔名對得上手上有的,就直接組一個回應遞過去
打包成 ES module(type="module") 改成一般 script(format: "iife")

最後那個攔截很好玩:viewer-core 完全不知道自己沒有真的打電話,它以為模型是從網路載來的,所以檢視器一行都不用改。

教訓:「本機伺服器上看起來正常」跟「真的雙擊」是兩種不同的安全規則。這個專案的驗收寫成「拔網路線、雙擊」,就是因為這件事。

「JS 賦值」是什麼意思?(上表最後兩欄的來源)

上表 scene.json 那欄寫的 window.__EXHIBIT_SCENE__ = {...}; 就是「把資料改寫成一行 JavaScript 的變數賦值」:window 是瀏覽器頁面的全域物件,__EXHIBIT_SCENE__ 是我們取的變數名,等號右邊的 {...} 跟原本 JSON 的內容一模一樣。瀏覽器用 <script src="./scene.json"> 載入它(就是 HTML 引用外部腳本檔,不是把程式碼塞進別處),執行這一行後,資料就已經放在 window 上,展示櫃的機關直接讀就好,完全不必「打電話」去要。

這算後門嗎?不是。它沒有繞過任何限制,只是走了瀏覽器本來就允許的那條路(file:// 下 <script src> 可以載入同資料夾的腳本);很多離線文件(例如程式庫的離線搜尋索引)也是這樣做的。代價是檔名雖叫 .json,內容不是純 JSON,所以拿去做 JSON 格式檢查(linter:專門檢查 JSON 括號、逗號、引號有沒有寫對的工具)會報錯。

出門前的安檢:check

行李箱打包完,要過一道安檢(exhibit-kiosk check),回傳一行結果,欄位有:

  • external_requests:有沒有任何東西偷偷想連外面的網路(必須是空的)
  • missing_files:index.html 提到的檔案,行李箱裡都在嗎
  • invalid_glb:模型有沒有壞
  • verdict:三項都乾淨才是 pass

安檢本身修過兩次:

  • 第一版「全文掃網址」會把函式庫裡一個從沒被用到的預設網址誤當成外部請求,所以改成只認「真的會觸發載入」的位置(HTML 的 src/href、fetch() 呼叫、CSS 的 url())。
  • 只看模型檔的封面(開頭的簽名與版本)抓不到「封面好、後半段被截斷」的壞檔,所以現在連封面上宣告的總大小也跟實際大小比對,而且內嵌的文字版模型也會解開來檢查。

還沒開燈時的招牌,和備用電

模型載入需要一點時間,這段空檔不能是白畫面:

  • 開場海報(poster):先顯示一張圖,載入完成後淡出。圖優先取資產裡現成的截圖(這次拿到的是 reports/views/iso.png),沒有就由程式自己畫一張中性的佔位圖。
  • 出錯保底:萬一模型壞了或載入超過 20 秒,海報留在畫面上,加一行提示請通知現場人員。

保底的狀態機刻意放在一個約 1 KB、最先載入的獨立檔案(poster.js)。理由是:如果它跟大檔案綁在一起,大檔案壞了,保底也跟著壞,等於停電時備用電也一起斷。

驗證方式是「一個一個把東西弄壞」:

弄壞什麼 結果
內嵌模型截掉三分之二 停在海報並顯示提示
主程式被截斷 約 20 秒後出現逾時提示
主程式整個不見 約 1 秒內出現提示
解說文字檔少一個逗號 3D 照常,但熱點標題退成 hs_01、解說消失

給參觀者用:放大、加粗、防亂按

  • 字最小 20px、可以點的東西最小 48px(驗證是在頁面裡跑腳本,逐一檢查每個元素的尺寸,四種畫面狀態都是零違規)。
  • 右鍵選單、整頁捲動、文字選取關掉。但零件清單自己要能捲,所以「禁止滾輪」要放過它——這是實機測才發現:一開始整頁一律鎖,13 個零件只看得到 3 個。
  • 游標閒置 3 秒自動藏起來。
  • 零件清單占了右邊一塊,熱點特寫的鏡頭不能被它擋住,所以打包程式會量清單實際寬度,回報給檢視器做「留白」(V2-m4;2026-09-28 使用者實機雙擊的截圖裡,特寫落在清單左側的可視區,跟這個機制一致)。

沒人操作時:三段閒置

閒置多久 發生什麼 由誰決定
20 秒 展品自己慢慢轉(每秒 12 度) 說明書的 auto_rotate
60 秒 鏡頭回到初始位置 說明書的 idle_reset_s
120 秒 自動巡場:依序飛到 8 個重點,每站停 8 秒,繞完再繞 說明書的 tour+展場程式的預設值

60 秒跟 120 秒是兩個互相不認識的計時器:前者只看「有沒有人在畫布上拖曳」,後者看整個頁面有沒有人按下去。這樣不必去改檢視器。

實機才抓得到的 bug:我原本把「滑鼠動了」也算成「有人來了」。在真的電腦上,滑鼠放著不動也會有微小飄移,所以「閒置 120 秒」幾乎永遠等不到,但 60 秒那個因為只認拖曳,一直正常。兩者行為對不起來,才被抓到。現在只認真的按下去、滑了一下、按了鍵。

現場人員怎麼改字

解說文字單獨在 hotspots.json,用記事本打開、改標題或內文、存檔、重新整理就生效,不必重新打包。說明檔還特別寫了:開頭那行不要刪;改壞了不會當機,只是標題變編號,改回去就好。

下一篇:檢查規則與場景包。


上一篇
Day 13|熱點鏡頭品質:從固定角度到避開遮擋,以及爆炸圖的現況
下一篇
Day 15|回顧:出貨前的品管員,和一份寫給每個展覽館的規則書
系列文
AI 策展人:用 Google ADK 打造會思考、會介紹的 3D 展示平台 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言