【文章翻譯】How we built a Flutter-powered AI coffee shop

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

Dash 坐在咖啡店的收銀檯上喝著拿鐵,上面印有 Flutter 標誌

如果你開始贈送咖啡,人們就會出現。Flutter 團隊曾嘗試過一段時間,想辦法將這個原則應用到一個從 Flutter 應用程式開始,以一杯熱騰騰美味飲料結束的演示中。問題是如何讓它更有趣、更個人化,而不僅僅是點餐和送餐。咖啡很酷,但它不像「你絕對會記得 Google I/O 的一件事」那麼酷。

那麼,什麼改變了呢?兩種新技術出現了。一個是 Nano Banana,它能夠快速生成客製化圖片。第二個是 GenUI,它能夠在執行時動態生成使用者介面,並為參與者提供個人化問題,詢問他們希望如何修改這些圖片。將這兩者與 Flutter 結合起來,你就可以讓大家有機會點一杯咖啡,上面印著他們快樂地方的圖片,使用噴墨印表機列印出來。那才真是令人難忘。

我們所要做的就是執行。事實證明是兩次——這個演示在 Cloud Next 上被稱為「GenLatte」,在 Google I/O 上被稱為「Antigravity Coffee Co.」。為了簡單起見,並為為您總結這篇文章的代理人節省一些標記,下面將使用「GenLatte」這個名稱。

我們開了一家咖啡店

實現 GenLatte 這樣雄心勃勃的願景需要很多人。真的,是很多人。有團隊來規劃實體空間並決定「顧客」[1] 將如何流動。有品牌和概念團隊來創造美學。當然,還有施工團隊去五金店購買木材。最終,還有軟體工程師將程式碼組合在一起以構建各種應用程式,這就是我碰巧參與的地方。這篇部落格文章只會概述軟體部分,但這並不是要淡化 GenLatte 最終開門所需的許多其他層面所付出的令人難以置信的工作。

[1] 如果不收費,他們還是顧客嗎?

GenLatte 的運作方式

GenLatte 的核心是一個 Flutter 應用程式,帶有 Firebase 後端。

再深入一步,它是一個 monorepo,包含獨立的 Flutter、Firebase 和共用程式碼專案。使用 monorepo 讓我們能夠重複使用依賴項,共用業務邏輯,並執行原子部署。Flutter 應用程式也運行在 iOS 和 Android 上,但客戶只與網頁版本互動。我們使用 Cloud Build 和 Firebase Hosting 來保持我們的部署故事,而不是「某人在他們的筆記型電腦上運行 $firebase deploy」。在伺服器上,我們混合使用了觸發函數和 HTTP 可呼叫函數,賦予應用程式在明確的資料流和反應式資料管理之間靈活性。

在 Flutter 應用程式本身中,我們構建了 5 個獨立的使用者介面:瞌睡的會議參與者點咖啡的 kiosk 螢幕、顯示新訂單的咖啡師螢幕、驗證每個提交內容安全性的協調者螢幕、一個大型隊列螢幕,讓客戶大致了解何時會收到他們的飲料,以及一個純粹異想天開的「近期訂單」螢幕,顯示最後 13 個訂單作為浮動氣泡,讓每個人都有最後一次機會欣賞他們的數位創作。

演示級別的安全性

Google 在任何涉及使用者生成內容的演示中,最主要的一個顧慮是防止志願者「慷慨捐贈」他們的紅隊專業知識,發布不適當的內容供公眾消費。實際上,這體現在幾個不同的方面:

  • 防止授權使用者建立 NSFW 內容: 為了防止這種情況,GenLatte 實施了三層方法。首先,原始的「快樂地點」字串被發送到 Gemini Flash 進行快速審核,並附帶大約三十個範例,概述我們希望阻止的令人不快的內容。然後,一旦「快樂地點」獲得批准,我們就開始生成圖片,並依靠 Nano Banana 自己的內部訓練來只生成 SFW 內容。在流程結束時,我們的人工審核員會手動批准每張圖片。有了這一切,我們非常有信心 GenLatte 不會讓 Google 難堪。
  • 防止管理員帳戶遭到冒用: 授權使用者可能破壞您應用程式的另一種方式是訪問您不打算共享的授予提升權限的螢幕。鑑於 GenLatte 的後台審核員和咖啡師螢幕,它顯然存在這種風險。我們選擇部署到網頁,使用者可以使用瀏覽器網址欄任意導航,這進一步增加了壓力。為了解決這個問題,我們的 GoRouter 實作具有集中式的宣告式路由檢查,以確保每次頁面訪問對於給定的帳戶都是合法的,這由 Firebase 自定義聲明決定。
  • 防止未經授權寫入 Firestore: 授權使用者使用應用程式是一回事,但有人閱讀 firebase_options.dart 的內容,啟動一個新專案,並查看他們能造成什麼破壞,則是另一回事。為了解決這個問題,我們將所有讀取和寫入都綁定到具有嚴格安全規則的嚴格帳戶憑證,您可以將其作為您自己專案的靈感

傳遞有趣的圖片

一旦我們確信我們的應用程式在演示現場不會被駭客入侵,我們就開始確保 Gemini 可以提供一致的圖片,這些圖片在咖啡上看起來很棒。這個專案部分可能涉及到在辦公室裡過度咖啡因化的下午,所以我們飛快地完成了它也就不足為奇了。

核心是,我們的系統涉及將使用者的「快樂地點」(一個字元上限為 50 個,但通常只有一兩個字詞的提示)提升為四個獨特且高度特定的圖像請求。例如,對於輸入「和家人在家」作為快樂地點的使用者,GenLatte 可能會向 Nano Banana 請求一張「樹林裡古樸的家,壁爐裡有火,餐桌上準備好晚餐。家裡滿是家人和朋友,大家都在慶祝和享受彼此的陪伴。燈光和氛圍是…」(依此類推)的圖像。Gemini 負責這個提升過程,這意味著四個提示都不同;提供了使用者在應用程式中看到的各種圖像。

我們也針對令人愉悅的構圖優化了 Nano Banana;特別是「主體呈現領先的 S 形曲線」,它在道路、河流、山丘、海岸線或其他抽象之間輪流。這個提示技巧大大提高了 Nano Banana 吸引人、可用的圖像的百分比。

A circular image of a husky walking down a winding path through the woods. The image is Sepia-toned and has blurry rings along the outside that are suggestive of espresso and milk foam.
範例 Nano Banana 圖片:小狗在小徑上。
A large compass sits atop a hill with winding paths. The image is sepia-toned and dark, blurry rings along the outside are suggestive of espresso and milk foam.
Nano Banana 圖片範例:山丘上的羅盤。
A smiling moon looks down on a landscape of trees and rolling hills. The image is sepia-toned and dark, blurry rings along the outside are suggestive of espresso and milk foam.
Nano Banana 圖片範例:異想天開的森林。

為了 GenLatte 的第二次亮相,也就是今年五月在山景城 I/O 的 Antigravity Coffee Co.,我們更新了我們的提示,使其可以在不同的建議構圖之間輪流。這讓每天看到數百張圖片的活動工作人員和只想要為他們咖啡提供引人入勝的圖片的個別客人都能保持新鮮感。

即時修改圖片

GenLatte 的真正魔力不僅來自於快速製作圖片並將其列印在咖啡上;而是來自於根據個人確切的口味進行精確調整。在「調整」按鈕後面有四個問題,由 Gemini 預先撰寫,用於說明圖片可能如何修改。

這些圖片源自於原始的、升級後的提示本身;因此回到我們之前舒適的家庭快樂之地,Gemini 可能會提供關於餐桌上食物種類、人數多寡,或者慶祝活動是否是為了任何特定人生事件的問題。使用者看到的「和家人在家」的其他快樂之地可能會走向不同的方向,因此調整這些選擇會浮現出適合這些圖片的不同問題。

一旦使用者回答完問題,我們就帶著相同的提示回到 Nano Banana,同時提交多次以獲得新的藝術作品。提示看起來像這樣:

1
2
3
4
5
6
7
8
9
10
11
12
在看到來自此原始提示的圖片後:

{{original_prompt}}

使用者要求這些更改:

{{questions[0].body}}: {{questions[0].answer}}
{{questions[1].body}}: {{questions[1].answer}}
{{questions[2].body}}: {{questions[2].answer}}
{{questions[3].body}}: {{questions[3].answer}}

請生成此新圖片。

從拉斯維加斯到山景城,經過數月打造 GenLatte 後,看到人們因修改後的圖片而臉上發光,對我們來說是多麼神奇!

A2UI 和 GenLatte

GenLatte 不僅僅是 Flutter 和 Firebase 演示,甚至不是生成式 AI 演示;它是一個 GenUI 演示。GenUI 可以以許多不同的形式出現,但對於 GenLatte 而言,它連接了向使用者顯示正確的使用者介面以回答 Gemini 問題之間的點。關鍵細節是並非所有問題都最適合相同的 UI。像「您的家人正在慶祝特定的人生事件嗎?」這樣一個開放式問題需要一個文字欄位;但「現在是一天的什麼時候?」可能只有幾個相關選項,因此最適合單選按鈕。

在應用程式中實作 GenUI 有多種方式。保守的一端是嚴格控制、按部就班的體驗,代理程式從粗粒度 widget 目錄中選擇以渲染可預測的 UI。更具冒險精神的應用程式可以給予代理程式更多自由,從更細粒度的目錄中組合 UI,以渲染幾乎任何可以想像的東西。對於 GenLatte,我們選擇了保守的一端:公開一個小的品牌化 widget 目錄,代理程式可以使用它來顯示問題。這讓 Gemini 能夠在當下選擇正確的 widget,同時確保整體構圖符合我們的設計準則。

如果您想了解 Gemini 如何在更大的自由度下發揮作用,請查看 Hatcha,這是一個派對規劃應用程式。它透過將更多的執行時職責委派給 Gemini,進一步深入 GenUI。

三千杯咖啡之後…

在 Google Cloud Next 上供應了 1,200 杯拿鐵,在 Google I/O 上又供應了 1,800 杯,可以肯定地說 GenLatte 取得了成功。在建立這個演示或在這兩個活動中工作期間,我很少像現在這樣咖啡因過量,我也從未見過人們在使用應用程式後立即露出如此愉悅的笑容。

Flutter 讓異想天開的使用者介面建置過程充滿樂趣;從它對動態佈局和自訂著色器的優雅支援,到它無縫地針對多個平台的能力。Firebase 讓後端變得輕而易舉:為我們處理部署和擴展的麻煩,並提供反應式、全棧資料綁定。Gemini 和 Nano Banana(當然)將我們使用者的快樂地點提示變成了奇幻的藝術,看到它實體化令人愉悅。當然,操作真正的英雄是我們的咖啡師,他們製作了出色的拿鐵——因為如果咖啡不好喝,整個活動都會失敗!

若要閱讀 GenLatte 的程式碼,請訪問其在 flutter/demos 儲存庫中的資料夾。但是請注意,該儲存庫中的程式碼未經維護,僅供參考。

我們對 2026 年的應用程式開發未來感到興奮。有了 Flutter、Firebase 和 Gemini 等工具,您可以實際提供給使用者的體驗範圍呈指數級增長。

若要開始使用 GenUI,請訪問 Flutter 的文件。如果您對如何在自己的工作中運用這些模式有任何想法,請告訴我們,在此之前——我們迫不及待想看到您的創作!


我們如何建立一間由 Flutter 驅動的 AI 咖啡店 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。