使用樹莓派,用人工智能創(chuàng)造一個真正“神奇”的智能鏡子
在這個項目中,我將帶你通過使用樹莓派和雙子座制作一個神奇的鏡子。我特別使用Gemini Live API,它目前處于預(yù)覽狀態(tài),所以如果你在發(fā)布后一段時間查看本教程,可能需要稍后更新,但沒關(guān)系!玩得開心,我希望你能學(xué)到一些有用的東西。
這個項目也使用了新的JavaScript/TypeScript SDK,所以你可以在這里找到擴展項目的文檔。
初始硬件設(shè)置
這個項目是基于這個魔鏡項目。雖然我非常接近他們建造鏡子的方式,但我確實做了一些不同的事情,我將在這里介紹。也就是說,由于顯示器的尺寸、可用的材料和工具以及其他一些因素,你如何構(gòu)建鏡子很有可能會有所不同,所以這更多的是關(guān)于我如何構(gòu)建鏡子的解釋,希望它是對原始(偉大)教程的有益補充。
這里是我在這個項目中使用的材料列表(我將在亞馬遜上鏈接到它們,盡管取決于你什么時候讀到這篇文章,這些鏈接可能不再有效,所以我將盡我最大的努力來描述這些項目)。我想說的是,我和我在這里買的/列出的任何東西都沒有關(guān)系,它們只是碰巧是我挑選的或者已經(jīng)讓這個項目成功的東西——如果你有你認為會更好的東西,絕對要使用它,并在評論中告訴別人。
?樹莓派。我使用了3B,因為我有大量的3B,我想如果我能讓這個項目與只有1GB RAM的設(shè)備一起工作,那么如果其他人有更強大的主板,他們應(yīng)該是黃金。確保你有一根適合Pi的電源線——我用的是一根我非常喜歡的帶電源開關(guān)的電源線,這樣我就可以很容易地打開和關(guān)閉東西。
?SD卡。我用的是32gb的存儲卡,但有人可以使用更小的存儲卡,也不會有問題。
?一個迷你HDMI到HDMI電纜。我使用一個使用左90度角,以更好地適應(yīng)/隱藏它的設(shè)置。如果你使用的樹莓派與3B的版本不同,或者是沒有迷你HDMI接口的顯示器,你可能需要一個不同的連接器。
?這款顯示器(KYY便攜式顯示器15.6英寸)因為它又薄又小,最重要的是價格實惠。
?18x24x0.04英寸雙向鏡像丙烯酸。你要確保你買的任何東西都是雙向鏡面玻璃,因為它的設(shè)置方式是,顯示器放在它后面,所以你想要有一個反射表面,讓光線通過它。
?11.69x16.53x0.08英寸透明亞克力板。厚度并不重要,只是高度和寬度。這將被用作將樹莓派和監(jiān)視器內(nèi)部連接在一起的一個部件。
?黑色的卡片紙。這是為了將鏡子的邊緣隱藏在顯示器周圍,所以你只需要確保它足夠大,可以被切割成16x10.75”
?一個麥克風(fēng)。我用的是AT2020,因為我已經(jīng)擁有它了,但你應(yīng)該可以使用任何你可用的。當我們進入本教程的代碼時,我將指出需要更改的一個值以匹配麥克風(fēng)的輸入采樣率(例如,我為AT2020使用44,000,但其他麥克風(fēng)可能是16,000)。
?黑色管道膠帶,用于粘貼東西和阻擋光線從卡紙的邊緣進入顯示器。
?雙面安裝膠帶。這用于將監(jiān)視器內(nèi)部連接回它。
?M3螺釘和墊片。您將需要一些不同的長度,在原始教程中提到過。我個人選擇了一個更大的品種盒,因為這些東西在很多不同的項目中都有使用。
?監(jiān)控框拆卸工具。我買了一套便宜的電子開啟工具,效果很好,但我只使用了幾件。你還需要一個熱風(fēng)槍或吹風(fēng)機來加熱和軟化將顯示器電子設(shè)備粘在塑料框架上的膠水。
?可選材料的站立框架。我使用了一些我從另一個項目中得到的廢棄中密度纖維板,并用激光切割了一個我在網(wǎng)上找到的免費支架(盡管我確實按比例放大了),但我認為你也可以使用畫架或其他任何適合你的東西。我強烈建議在這個項目中使用某種支架。
好了,現(xiàn)在我想所有的材料都準備好了,讓我們開始做這個很棒的項目吧。我將跳過如何拆卸顯示器,因為我認為這在最初的教程中已經(jīng)講得很好了,我認為對于一個優(yōu)秀的項目應(yīng)該給予贊揚,所以去看看吧。
首先,我沒有最穩(wěn)定的手來得分和折斷鏡像丙烯酸,并以各種方式切割造成了很多碎片。經(jīng)過幾次失敗的嘗試(和破壞丙烯酸。對不起谷歌,但謝謝你讓我花這個!),我把所有的床單切割成兩個12“x18”的桌子鋸,這工作得很好。如果你對其他方法更熟悉,那很好,但我只想說,這不是最容易處理的材料。
在我的鏡像丙烯酸樹脂變成一個可行的尺寸后,我想我可以省去下一個頭痛,把所有的東西都移到激光切割機上。注意,如果你對激光切割機不太熟悉,我建議你在切割過程中,尤其是在切割卡紙的時候,把它放在寶寶身邊。我第一次嘗試的時候是不是把東西點著了?絕對的。以后我一不小心還會放火嗎?有可能,但希望不是!一定要注意你的工具。
如果你碰巧使用我上面鏈接的顯示器,我發(fā)現(xiàn)整體鏡子的尺寸是16“乘10.75”,黑色卡紙上有一個13.25“乘7.25”的缺口。我還在M3螺絲的邊緣添加了孔,這樣你就不需要鉆它們或3D打印夾具(盡管我確實在單獨的嘗試上打印了這些夾具,而且它們確實很好!)。我已經(jīng)附上了一個PDF,這是96 DPI的文件,我設(shè)計的親和設(shè)計師。你需要在每一塊(鏡像丙烯酸、透明丙烯酸和卡紙)上剪下綠色和紅色的部分,但是中間的藍色矩形只從卡紙上剪下來。
在這結(jié)束時,你應(yīng)該有三個單獨的材料全部削減到匹配的尺寸與對齊孔。在把所有東西擰在一起之前,別忘了把兩個亞克力板上的塑料蓋去掉!
對于此項目程序集的其余部分,請遵循原始教程。安裝好樹莓派后,你需要完成安裝魔鏡軟件的步驟,確保顯示器旋轉(zhuǎn)90度,刻度正確,一切都在啟動時啟動。一旦有了基礎(chǔ)項目,就可以深入研究新的Gemini連接魔鏡代碼了。
我強烈建議能夠SSH到您的樹莓派,因為本教程中的其他所有內(nèi)容都將通過終端和git完成,盡管您也可以將鍵盤連接到鏡像并直接與設(shè)備的終端交互。
初始代碼設(shè)置
這個項目的所有代碼都附在這個黑客身上。不過,如果你想直接在鏡像上運行最新的代碼,而不是從頭開始構(gòu)建它,你可以將這個github項目克隆到樹莓派上Magic mirror項目下的modules文件夾中,運行npm install,然后更新你的配置文件以顯示該模塊。這將持續(xù)播放音頻,所以你可能想要修改項目以考慮預(yù)算問題(例如,添加麥克風(fēng)上的對話按鈕),但由于這是一個黑客項目,你應(yīng)該以任何對你有意義的方式進行修改。如果您處于嘈雜的環(huán)境中,請注意API非常敏感,并且在檢測到新聲音時容易中斷。
您還需要更新配置文件以包含Gemini API密鑰,該密鑰可以在谷歌的AI Studio中創(chuàng)建或找到。
雖然作為該項目的核心的Live API每天可以免費提供有限數(shù)量的請求,但圖像生成不是免費的(在撰寫本文時),因此您可能需要根據(jù)您想要做的事情更改應(yīng)用程序。您可以在這里找到定價的完整描述,因為新型號不斷推出,具有不同的功能和定價。
這個項目的基礎(chǔ)是魔鏡模塊模板,如果您想從頭開始,可以在這里找到并克隆它。有了基本項目之后,就該更新魔鏡的config/config.js文件了。作為參考,我對這個模塊的添加看起來像這樣:
最重要的部分是配置文件中的API密鑰,因為這是驅(qū)動設(shè)置和可用信息的部分,并且它是本機的。
我們將在這個項目中使用三個主要文件:mm - gemini .js(盡管如果你克隆了模板,它將被稱為mm - template .js),它處理所有的UI, mm - gemini .css,它為UI設(shè)置樣式,以及node_helper.js,它是所有繁重工作發(fā)生的地方。為了簡單起見,我將跳過css文件,但是您可以在這個項目頁面或完成的GitHub項目中找到它的代碼。
至于UI文件,我們基本上創(chuàng)建了一個UI狀態(tài)機,它經(jīng)歷了initialize、READY、RECORDING和ERROR。socketNotificationReceived函數(shù)可以從helper接收有效負載和通知,幫助器告訴它應(yīng)該在UI中顯示哪個狀態(tài),然后它將更新DOM。
該函數(shù)還將接受GEMINI_IMAGE_GENERATING和GEMINI_IMAGE_GENERATED的通知,以便在調(diào)用該操作時顯示進度旋轉(zhuǎn)器或生成的圖像(以base64格式接收),或者它將顯示Gemini Live API生成的任何文本,直到收到turnComplete響應(yīng),然后在發(fā)送下一個響應(yīng)時清除該文本。您可以找到這個項目附帶的UI的所有代碼,并將其作為起點。
如果一切工作如預(yù)期,你應(yīng)該有一個類似于這樣的UI:
將核心應(yīng)用程序放在一起后,是時候深入研究node_helper.js文件,并真正使用Gemini了解這個魔鏡的功能了!
設(shè)置Gemini Live API和語音輸入
讓魔鏡工作的第一個主要步驟是,我們需要能夠與魔鏡對話,將音頻數(shù)據(jù)實時發(fā)送到Gemini Live API,并等待響應(yīng)。為了簡單起見,我們將從請求以文本形式發(fā)送的響應(yīng)開始,我們的UI將顯示它。進入node_helper.js文件,讓我們添加整個項目中需要的各種常量。
NodeHelper常量應(yīng)該已經(jīng)存在于您的基本項目中,因為Magic Mirror項目使用它來知道這是模塊的幫助器。
由于這個項目使用的是最新的JavaScript/TypeScript SDK,我們需要導(dǎo)入谷歌/genai庫,并包含多個將在整個項目中使用的類型對象。您可以從官方文檔或源代碼中找到有關(guān)這些類型的更多信息。
緩沖器和揚聲器都與輸出音頻有關(guān),我們將在本項目的后面進行。記錄器是我們將用來記錄從Pi的麥克風(fēng)發(fā)送到live API的實時音頻流。
進入下一個模塊,INPUT_SAMPLE_RATE與您連接到樹莓派上的麥克風(fēng)有關(guān)。因為我使用的是AT2020,我的采樣率是44100,但你的麥克風(fēng)可能有不同的值。
OUTPUT_SAMPLE_RATE是我們開始播放接收到的音頻時Gemini API的預(yù)期采樣率。API目前僅輸出24kHz。API使用一個通道,輸出原始PCM音頻數(shù)據(jù),編碼為帶符號整數(shù),16位。GEMINI_INPUT_MIME_TYPE是您將從Pi發(fā)送到API的數(shù)據(jù)類型。GEMINI_SESSION_HANDLE是一個值,我們稍后將使用它來保持關(guān)閉和重新打開之間的會話連續(xù)性,因為目前Gemini Live API將在大約十分鐘后自動關(guān)閉。
最后,我們有GEMINI_MODEL。在撰寫本文時,Gemini Live API處于預(yù)覽階段,因此該模型絕對會隨著時間的推移而改變,這取決于您閱讀本教程的時間。您需要檢查Live API文檔,以確保您使用的是項目的最佳選項。
有了這個集合,現(xiàn)在是時候添加將在整個項目中使用的所有變量了。我在NodeHelper.create()中初始化它們,然后我有一個applyDefaultState()函數(shù),該函數(shù)可用于在會話關(guān)閉或發(fā)生錯誤時重置所有內(nèi)容。我還添加了一組用于調(diào)試的日志功能。你不需要包括這些,但我發(fā)現(xiàn)它們在整個項目中很有用,所以我將把它們留在本文中。我還創(chuàng)建了一個名為sendToFrontend的輔助函數(shù),用于封裝將套接字通知發(fā)送到UI前端(mm - gemini .js)。
其中大部分用于維護狀態(tài),再加上一個closePersistentSpeaker()函數(shù),我們將在音頻輸出時添加該函數(shù)。如果您正在跟隨,請稍后再對其進行注釋。您還會注意到genAI和imaGenAI的值。genAI將管理我們的實時會話,而imaGenAI是將用于圖像生成的Gemini對象。如果你沒有在項目中使用圖像生成,你可以刪除它。
現(xiàn)在讓我們進入一些好東西。我有一個名為initialize的函數(shù),它將用于啟動這個項目所需的大部分內(nèi)容?,F(xiàn)在我將發(fā)布一個編輯后的版本,我們可以隨著時間的推移添加。
這段代碼驗證API密鑰是否可用,創(chuàng)建用于使用Gemini API的GoogleGenAI對象,然后創(chuàng)建一個新的LiveSession。這個實時會話使用SDK內(nèi)置的web套接字框架來處理雙子座模型和樹莓派之間的發(fā)送和接收數(shù)據(jù),它有一組回調(diào),將驅(qū)動設(shè)備的狀態(tài)。最重要的是onmessage,它將把雙子座的響應(yīng)發(fā)送到一個新函數(shù),該函數(shù)將決定鏡子應(yīng)該如何反應(yīng)。這里還有onclose的代碼,它將重置回放狀態(tài)和一些其他尚未編寫的東西,所以可以隨意注釋掉onclose回調(diào),直到最后。
您還會注意到一個配置對象。這將是這個項目的核心部分,因為它將包含與我們正在做的鏡子和雙子座API相關(guān)的每一個設(shè)置。現(xiàn)在它只有一個響應(yīng)的模態(tài)。TEXT,這意味著我們希望Gemini模型只在onmessage回調(diào)中使用文本進行響應(yīng)。
下面轉(zhuǎn)到helper的socketNotificationReceived函數(shù),我們想要進入通知開關(guān)語句并為START_CONNECTION和START_CONTINUOUS_RECORDING添加新的用例。START_CONNECTION將調(diào)用initialize,它將告訴前端在初始化完成后更新它的UI狀態(tài)。一旦UI狀態(tài)被更新,就會收到另一個開始錄制的通知。完整的函數(shù)是這樣的:
實際上錄音是一個很大的步驟,所以我會把它分成更小的部分。我們將從創(chuàng)建一個名為startRecording()的新函數(shù)開始。這將檢查設(shè)備是否已經(jīng)在錄制,連接是否已經(jīng)打開,或者直播流是否正在運行。如果其中任何一個為真,則該函數(shù)將提前退出。
如果一切都沒有問題,那么就可以開始錄音了。我們可以更新isRecording狀態(tài)值,然后創(chuàng)建一個recorderOptions對象。
從這里開始,是時候調(diào)用我們在文件頂部定義的記錄器上的record,然后存儲對音頻流的引用。我還在這里創(chuàng)建了一個chunkCounter用于調(diào)試,但是如果您想要更簡潔的代碼,可以忽略它。
audioStream將為通過的任何數(shù)據(jù)提供偵聽器。如果它不是空的(如果你拔掉麥克風(fēng),或者當你不使用麥克風(fēng)時設(shè)置了一鍵接通),那么它會將音頻數(shù)據(jù)轉(zhuǎn)換為base64編碼的字符串,然后使用liveSession將其作為新的JSON有效負載發(fā)送到Gemini Live API。sendRealtimeInput函數(shù)。
對于這個函數(shù)的其余部分,我有用于錯誤、結(jié)束和退出的偵聽器,我將在這里包括它們以完成。
我還有一個stopRecording()函數(shù),用于錯誤情況和直播流關(guān)閉時。重置所有內(nèi)容和更新UI非常簡單,所以我不會在本教程中深入討論。
最后,讓我們添加handleGeminiResponse()函數(shù)。該塊將針對從Gemini Live API獲得的各種類型的響應(yīng)進行更新,但對于基本版本,我們只需要檢索消息的內(nèi)容并檢查是否存在文本。如果是,我們會將文本塊發(fā)送到UI顯示。我也有一個if語句來檢查安裝是否完成,但我目前沒有做任何與我的這個項目的版本。
此外,我們可以檢查Live API是否說我們已經(jīng)完成了響應(yīng)回合。這是一個非常有價值的響應(yīng),因為它告訴我們模型何時完成生成文本,之后它會告訴我們何時應(yīng)該完成音頻響應(yīng)的播放。你現(xiàn)在可以將這個塊添加到handlegeminiResponse中,因為它將被UI用于清除響應(yīng)之間的文本。
好了,這是很多,我略過了一些因為有很多狀態(tài)管理的模板,但在這一點上你應(yīng)該能夠和鏡子對話并顯示來自Gemini Live API的文本響應(yīng)。
音頻響應(yīng)和中斷
既然我們有了文本,讓我們深入研究如何獲得音頻,因為老實說,魔鏡應(yīng)該真的在和你說話。其工作方式是,我們將responseModality設(shè)置為modal。當Gemini Live API響應(yīng)時,它將發(fā)送一個base64編碼的字符串,用于多個可以在設(shè)備上播放的音頻塊。因為這些回應(yīng)來得很快,而不是在他們大聲播放的時候,我們還需要創(chuàng)建一個排隊系統(tǒng),當任何當前的音頻播放完畢時,它就會轉(zhuǎn)移到下一個音頻塊。最重要的是,Gemini Live API支持中斷,所以如果用戶在播放音頻時說了什么,我們可以清除隊列,讓揚聲器保持打開狀態(tài),等待下一個音頻響應(yīng)從API返回。
讓我們從更新實時會話responseModality開始。
我們還將創(chuàng)建一個名為processQueue的新函數(shù)來處理回放邏輯。讓我們一步一步地復(fù)習(xí)一下。首先,我們想看看隊列中是否有任何東西。如果沒有,那么我們可以關(guān)閉揚聲器,假設(shè)隊列沒有被中斷清除,并期待很快會有更多的音頻塊。
接下來,我們可以將processingQueue標志設(shè)置為true,用于狀態(tài)管理。
然后我們要檢查我們的揚聲器是否已經(jīng)創(chuàng)建,否則我們將創(chuàng)建一個新的。我想在這里指出的一件事是,我特別使用了一個存儲在類級別的持久揚聲器,因為我們想要最小化揚聲器被創(chuàng)建和銷毀的次數(shù)。這是一個很好的內(nèi)存平衡,如果你使用的是內(nèi)存超過1GB的新樹莓派,這可能不是一個問題。
一旦我們知道我們有一個可用的揚聲器,我們就可以從隊列中檢索base64編碼的音頻剪輯字符串,并在將緩沖區(qū)發(fā)送給揚聲器播放之前將其寫入緩沖區(qū)。
如果到目前為止一切順利,我們將希望查看隊列中是否還有剩余的內(nèi)容,然后讓processQueue函數(shù)調(diào)用本身移動到下一個塊,否則我們將轉(zhuǎn)義整個播放循環(huán)。
在這里,讓我們定義用于錯誤情況的closePersistentSpeaker函數(shù)。這并沒有做太多可怕的事情,除了關(guān)閉揚聲器,移除聽眾,并試圖清理我們的狀態(tài)。
最后,在測試之前,讓我們確保我們正在處理從Gemini Live API返回的音頻和中斷消息類型。我們可以通過向handleGeminiResponse函數(shù)中添加以下代碼塊來實現(xiàn)這一點。
現(xiàn)在,您應(yīng)該能夠重新啟動鏡像模塊以與它進行對話,也可以在音頻播放過程中中斷它以更改對話的過程。很酷,對吧?
函數(shù)調(diào)用,搜索基礎(chǔ)和圖像生成
現(xiàn)在我們已經(jīng)有了項目的核心,是時候更進一步了。函數(shù)調(diào)用是我最喜歡的Gemini API特性之一,因為它可以讓任何使用Gemini的設(shè)備或應(yīng)用基于與模型的交互來做一些非常有趣的事情。為了使用鏡像啟用函數(shù)調(diào)用,我們需要回到initialize中的配置對象,并添加一個tools數(shù)組。這將包括一個functionDeclarations數(shù)組,其中包含一個生成圖像的函數(shù)(我將其命名為generate_image),以及Gemini模型用來知道何時應(yīng)該調(diào)用該函數(shù)的描述,以及與該函數(shù)相關(guān)的任何其他指令。對于這個案例,我告訴鏡報,它應(yīng)該是異想天開和有趣的,同時使用幻想的繪畫風(fēng)格。我們會在鏡子中添加更多的個性。在這個單獨的函數(shù)中,我們還需要為將用于生成圖像的提示符包含一個參數(shù)。
除了函數(shù)調(diào)用之外,我還在本節(jié)中向鏡像添加了另外兩個工具。第一個是谷歌搜索。這使得Gemini模型可以使用谷歌提供的各種工具,例如查找天氣或當前時間。我還啟用了googleSearchRetrieval,允許鏡像進行谷歌搜索,在適用的情況下找到有關(guān)請求的最新和最相關(guān)的信息。您可以在functionDeclarations上面的tools數(shù)組中添加這兩個工具。
此時,我們應(yīng)該能夠期望Gemini Live API觸發(fā)函數(shù)調(diào)用,因此讓我們確保在handleGeminiResponse中接受這些消息。返回到該函數(shù),我們可以檢查消息中是否存在函數(shù)調(diào)用塊,然后我們可以將函數(shù)調(diào)用負載發(fā)送到將處理該代碼的單獨函數(shù)。
在handleFunctionCall中,我們將確保擁有函數(shù)所需的所有信息,然后使用switch語句確定調(diào)用了哪個函數(shù)。因為我們現(xiàn)在只支持一個函數(shù),所以我們要么生成一個圖像,要么退出這個函數(shù)。
由于這使用了一個獨立的模型,而不是用于Live API的Gemini模型,因此我們還需要確保在initialize中初始化了適當?shù)腉oogleGenAI對象。
現(xiàn)在讓我們試一試,讓鏡子在給我們講故事的同時創(chuàng)造出一個物體的圖像。
由于我們已經(jīng)啟用了搜索接地功能,我們可以詢問當前的事情,比如我的家鄉(xiāng)科羅拉多州博爾德的時間和天氣。
添加個性
經(jīng)過我們到目前為止所做的一切,我們終于有了一面可以工作的魔法鏡子,但它感覺并不“神奇”,不是嗎?讓我們使用Gemini SDK中提供的一些工具來修復(fù)這個問題,這些工具可以賦予模型一點個性。回到我們的配置對象,讓我們添加一個systemInstruction對象。我們會告訴人工智能,它是一個無所不知的、強大的魔法鏡子,有趣、異想天開、輕松愉快,它從與人互動中獲得快樂,用自己的知識和能力讓人驚嘆。
我們還可以添加一個新的speechConfig對象,它允許我們將聲音配置為一些不同的東西。有一個視圖的聲音是可用的,所以你應(yīng)該玩不同的,看看哪一個最適合你。以下是目前可用的簡短列表,但未來可能會擴展:Puck, Charon, Kore, Fenrir, Aoede, Leda, Orus和Zephyr。
如果我們想要更多的定制聲音,我們也可以給它一個語言代碼。就我個人而言,我覺得魔鏡應(yīng)該說法語,所以我的語音配置是這樣的,盡管我也很喜歡英語的“Puck”的聲音。
結(jié)束
在這一點上,事情看起來很棒,所以讓我們做更多的潤飾項目,以真正使這個項目脫穎而出。我遇到的一個問題是,我需要不斷地告訴AI完成它的故事,所以讓我們在系統(tǒng)指令中添加一個新句子:“當你從一個故事中中斷以顯示故事中的圖像時,請在調(diào)用函數(shù)后繼續(xù)講述故事,而不需要提示。你還應(yīng)該盡可能在沒有用戶輸入的情況下繼續(xù)寫故事——你是無所不知的鏡子,用你對故事的了解讓觀眾驚訝。”
在對話過程中,鏡子也會嘗試恢復(fù)為英語,所以讓我們直接告訴它以用戶使用的語言與鏡子交談,通過在系統(tǒng)指令中添加另一句話來響應(yīng)用戶:“如果檢測到非英語語言,則以說話者輸入的音頻語言進行響應(yīng)?!闭堄谜f話者通過音頻輸入的語言準確無誤地回答?!币驗檫@是我們可以開箱即用來支持多種語言的東西,所以我非常喜歡它。
這就是這個項目的全部內(nèi)容!你還可以做很多事情來修改它,所以如果你建造了自己的魔鏡,一定要玩得開心。添加一個攝像頭,嘗試生成視頻顯示給用戶,玩語言和個性,或嘗試創(chuàng)建代理系統(tǒng),真正把鏡子變成你自己的個人神奇助手。
本文編譯自hackster.io