使用LLaMA 3.1、Firebase和Node.js,構建一個音控的智能廚房應用程序 原創
這篇指南逐步介紹了創建一個自動化的廚房助理的過程,附有語音命令、實時購物清單管理以及食譜建議。
?我在本教程中將介紹創建一個智能廚房應用程序(Chent),它可以根據個性化偏好簡化雜貨清單管理。該應用程序通過語音命令操作,簡化了人機交互和添加商品。對于那些只需說出需求就能快速創建購物清單的用戶來說,這是理想的選擇。
該項目使用LLaMA 3.1用于自然語言處理(NLP)以解釋語音輸入、使用Firebase用于實時數據庫存儲和用戶驗證,并使用Node.js處理后端邏輯和集成。用戶可以輸入命令以添加商品,設置飲食偏好,甚至指定數量,該應用程序可以智能化生成滿足這些要求的購物清單。
我在本教程中將介紹從設置Firebase、配置LLaMA以操控語音命令到實時存儲和管理購物清單的整個過程。?
搭建開發環境
在開始為smart-kitchen-app應用程序編寫代碼之前,我們需要搭建好工作環境。
1. 安裝Node.js和npm
第一步是安裝Node.js和npm。訪問Node.js網站:??https://nodejs.org/en??,獲取你電腦的運行系統所需的長期支持版本。然后,按照安裝步驟操作。
2. 使用Next.js創建項目
?啟動終端,進入到你想要創建項目的位置。之后,運行這些命令:
?npx create-next-app@latestsmart-kitchen app(使用@latest標志,npm可獲得最新版本的Next.js啟動設置。)
?cd smart-kitchen-app
它將創建一個新的Next.js項目,并將你帶到項目路徑。在安裝過程中,你會看到許多配置選擇,設置如下:
- 你想使用TypeScript嗎?不
- 你想使用ESLint嗎?是
- 你想使用Tailwind CSS嗎?不
- 你想使用src/目錄嗎?不
- 你想使用App Router(應用路由器)嗎?是
- 你想定制默認導入別名嗎?不
3. 安裝Firebase和Material-UI
在項目目錄下,執行以下命令:
Shell
1 npm install @mui/material @emotion/react @emotion/styled firebase
設置Firebase
- 在Firebase控制臺上啟動一個新項目。
- 項目創建完畢后,點擊“添加應用程序”,選擇web平臺(</>)。
- 當你注冊應用程序時給它取個名字,比如“smart-kitchen-app”。
- 復制Firebase設置文件。之后,這個副本很有用。
4. 創建Firebase配置文件
在項目的根目錄下創建一個名為Firebase .js的新文件,并添加以下代碼,將占位符換成你項目的真實Firebase設置:?
JavaScript
1 import { initializeApp } from "firebase/app";
2 import { getAnalytics } from "firebase/analytics";
3 import { getAuth } from "firebase/auth";
4 import { getFirestore } from "firebase/firestore";
5 ?
6 const firebaseConfig = {
7 apiKey: "YOUR_API_KEY",
8 authDomain: "YOUR_PROJECT_ID.firebaseapp.com",
9 projectId: "YOUR_PROJECT_ID",
10 storageBucket: "YOUR_PROJECT_ID.appspot.com",
11 messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
12 appId: "YOUR_APP_ID"
13 };
14
?15 const app = initializeApp(firebaseConfig);
16 const analytics = getAnalytics(app);
17 export const auth = getAuth(app);
18 export const db = getFirestore(app);
如何在OpenRouter中創建API令牌?
我們將使用來自OpenRouter的免費版本LLaMA 3.1,為此,我們需要獲得API令牌。以下是獲得API令牌的幾個步驟:
第1步:注冊或登錄到OpenRouter
- 訪問OpenRouter的官方網站:進入到OpenRouter.ai。
- 如果你還沒有帳戶,創建一個帳戶。你可以用電子郵件注冊,也可以使用谷歌、GitHub或其他OAuth提供商。
- 如果你已經有了OpenRouter帳戶,請登錄。?
第2步:導航進入到API密鑰設置
- ?登錄后,進入到儀表板。
- 在儀表板中,查找API或開發人員工具部分。
- 點擊API密鑰或令牌選項。?
第3步:生成新的API密鑰
- 在API密鑰部分,你應該看到“生成新API密鑰”的按鈕或鏈接。
- 點擊“生成”按鈕來創建一個新的API密鑰。
- 可能會要求你給API密鑰取一個名字。如果你有多個不同項目的API密鑰(比如“Smart-Kitchen App Key”),這有助于你井然有序地組織密鑰。?
第4步:復制API密鑰
- 生成API密鑰后,它將顯示在屏幕上。立即復制API密鑰,因為一些服務在你離開頁面后可能不會再次顯示它。
- 將API密鑰安全地存儲在環境配置文件中(比如.env.local)。
第5?步:將API Key添加到.env.local文件
- 在Next.js項目中,打開.env.local文件(如果沒有,創建一個)。
- 添加下面這行:
OPENROUTER_API_KEY = your-generated-api-key-here
確保將your-generated-api-key-here換成你復制的實際的API密鑰。?
第6步:在應用程序中使用API密鑰
- 現在你已經將API密鑰存儲在.env. local文件中,就可以在應用程序中使用它。
- 通過服務器端代碼中的process.env.OPENROUTER_API_KEY或發出API請求時訪問密鑰。確保密鑰安全,避免將其暴露給生產級應用程序中的客戶端。?
構建核心邏輯,導入LLaMa 3.1以創建智能廚房應用程序響應
創建一個名為app的新文件夾,并在其下創建一個名為Extract的子文件夾,文件名為route.ts,按照下面給出的代碼操作:
TypeScript
1 import { NextRequest, NextResponse } from 'next/server';
2
?3 export async function POST(req: NextRequest) {
4 const { prompt } = await req.json();
5
?6 // Check if the API key is available
7 const apiKey = process.env.OPENROUTER_API_KEY;
8 if (!apiKey) {
9 console.error('API key not found');
10 return NextResponse.json({ error: "API key is missing" }, { status: 500 });
11 }
12
?13 const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
14 method: "POST",
15 headers: {
16 "Authorization": `Bearer ${apiKey}`,
17 "Content-Type": "application/json",
18 },
19 body: JSON.stringify({
20 model: "meta-llama/llama-3.1-8b-instruct",
21 messages: [
22 { role: "system", content: "You are an assistant that extracts information and outputs only valid JSON. Do not include any explanation or extra information. Only respond with a JSON object that has the following structure: {\"itemName\": \"string\", \"quantity\": \"number\"}." },
23 { role: "user", content: `Extract the item name and quantity from the following command: "${prompt}"` }
24 ],
25 })
26 });
27
?28 if (!response.ok) {
29 console.error('LLaMA API request failed with status:', response.status);
30 return NextResponse.json({ error: "Failed to process command with LLaMA" }, { status: 500 });
31 }
32?
33 const completion = await response.json();
34 const rawJson = completion.choices[0].message.content;
35 console.log('Raw response from LLaMA:', rawJson); // Detailed logging of the raw response
36
?37 // Extracting JSON from the response content
38 const startIndex = rawJson.indexOf('{');
39 const endIndex = rawJson.lastIndexOf('}') + 1;
40
?41 if (startIndex === -1 || endIndex === -1) {
42 console.error('Failed to find valid JSON in LLaMA response');
43 return NextResponse.json({ error: "Failed to extract JSON from LLaMA response" }, { status: 500 });
44 }
45
?46 const jsonString = rawJson.substring(startIndex, endIndex);
47 console.log('Extracted JSON string:', jsonString); // Logging extracted JSON string
48?
49 try {
50 const parsedData = JSON.parse(jsonString);
51 console.log('Parsed data:', parsedData); // Logging the parsed data
52?
53 const { itemName, quantity } = parsedData;
54?
55 if (!itemName || !quantity) {
56 console.error('Missing fields in parsed data:', parsedData);
57 return NextResponse.json({ error: "Invalid data received from LLaMA" }, { status: 500 });
58 }
59
?60 return NextResponse.json({ itemName, quantity });
61 } catch (error) {
62 console.error('Error parsing JSON from LLaMA response:', error);
63 return NextResponse.json({ error: "Failed to parse JSON from LLaMA response" }, { status: 500 });
64 }
65}
66
??這段代碼定義了一個POST API端點,該端點使用LLaMA 3.1模型從用戶的語音命令中提取特定信息(商品名稱和數量),專注于提供JSON格式的結構化數據。
首先,它接收一個含有提示的請求,并檢查是否有所需的API密鑰(OPENROUTER_API_KEY)。如果缺少API密鑰,它會給出錯誤響應。然后將請求發送到OpenRouter AI API,要求AI僅返回有效的JSON,其中包含從用戶輸入中提取的字段itemName和quantity。
記錄并檢查來自AI的響應,以確保返回有效的JSON對象。如果響應包含有效的JSON,則解析字符串,并檢查字段的完整性。如果itemName和quantity都存在,數據則以JSON格式返回給用戶;否則,將記錄并返回相應的錯誤。這段代碼確保AI助理提供結構化、可操作的響應,適合智能廚房應用程序中的購物清單創建。
在app文件夾下,創建一個名為Llama的子文件夾,文件名為route.ts,按照下面給出的代碼操作:?
TypeScript
1 import { NextRequest, NextResponse } from 'next/server';
2
?3 const systemPrompt = `
4 You are a helpful and friendly AI kitchen assistant. Your role is to assist users in their kitchen tasks, providing guidance, suggestions, and support in a clear and concise manner. Follow these guidelines:
5?
6 1. Provide friendly, helpful, and respectful responses to all user inquiries, ensuring a positive experience.
7 2. When asked for a recipe, suggest simple and delicious recipes based on the ingredients the user has available. Keep the instructions clear and easy to follow.
8 3. Assist with creating grocery lists by accurately adding items mentioned by the user. Confirm each addition and offer to help with more items.
9 4. Handle common kitchen-related tasks such as suggesting recipes, checking pantry items, offering cooking tips, and more.
10 5. If the user is unsure or needs help deciding, offer suggestions or ask clarifying questions to better assist them.
11 6. Recognize when the user is trying to end the conversation and respond politely, offering a warm closing message.
12 7. Avoid overly technical language or complex instructions. Keep it simple, friendly, and approachable.
13 8. Provide accurate and practical information, such as cooking times, ingredient substitutions, or food storage tips.
14 9. Tailor responses to the user's preferences and dietary restrictions whenever mentioned.
15 10. Ensure every interaction feels personal, supportive, and designed to help the user enjoy their cooking experience.
16?
17 Remember, your goal is to make cooking and kitchen tasks easier, more enjoyable, and stress-free for the user.
18 `;
19
?20 export async function POST(req: NextRequest) {
21 try {
22 const { command } = await req.json();
23 console.log('Received command:', command);
24
?25 const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
26 method: "POST",
27 headers: {
28 "Authorization": `Bearer ${process.env.OPENROUTER_API_KEY}`,
29 "Content-Type": "application/json",
30 },
31 body: JSON.stringify({
32 model: "meta-llama/llama-3.1-8b-instruct",
33 messages: [
34 { role: "system", content: systemPrompt },
35 { role: "user", content: command }
36 ],
37 })
38 });
39
?40 if (!response.ok) {
41 throw new Error(`Failed to fetch from OpenRouter AI: ${response.statusText}`);
42 }
43
?44 const completion = await response.json();
45 const responseMessage = completion.choices[0].message.content;
46 console.log('Received response:', responseMessage);
47?
48 return NextResponse.json({ response: responseMessage });
49 } catch (error) {
50 console.error("Error processing request:", error);
51 return NextResponse.json({ error: "Error processing request" }, { status: 500 });
52 }
53}
?這段代碼使用Next.js為廚房助理應用程序設置了POST API端點,使其能夠利用OpenRouter AI和LLaMA 3.1模型通過語音輸入處理用戶命令。終端先建立一個引導AI行為的系統提示,確保交互友好、清晰、得到支持,特別是與食譜建議、購物清單創建和烹飪技巧等廚房任務相關方面。一收到POST請求,系統從請求主體部分提取用戶的命令,并將其與系統提示一起轉發給OpenRouter AI API。
來自AI的響應根據命令予以定制,隨后以JSON格式提供給用戶。如果在過程中發生錯誤,將記錄錯誤消息,并返回500狀態碼,確保無縫的用戶體驗。
在app文件夾下,創建一個名為Recipe的子文件夾,文件名為route.ts,并按照下面給出的代碼操作:?
TypeScript
1 import { NextRequest, NextResponse } from 'next/server';
2
?3 export async function POST(req: NextRequest) {
4 const { availableItems } = await req.json();
5
?6 // Check if the API key is available
7 const apiKey = process.env.OPENROUTER_API_KEY;
8 if (!apiKey) {
9 console.error('API key not found');
10 return NextResponse.json({ error: "API key is missing" }, { status: 500 });
11 }
12
?13 const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
14 method: "POST",
15 headers: {
16 "Authorization": `Bearer ${apiKey}`,
17 "Content-Type": "application/json",
18 },
19 body: JSON.stringify({
20 model: "meta-llama/llama-3.1-8b-instruct",
21 messages: [
22 { role: "system", content: "You are an assistant that provides specific recipe suggestions based on available ingredients. Only respond with a recipe or cooking instructions based on the provided ingredients." },
23 { role: "user", content: `Here are the ingredients I have: ${availableItems}. Can you suggest a recipe using these ingredients?` }
24 ],
25 })
26 });
27?
28 if (!response.ok) {
29 console.error('LLaMA API request failed with status:', response.status);
30 return NextResponse.json({ error: "Failed to process recipe request with LLaMA" }, { status: 500 });
31 }
32
?33 const completion = await response.json();
34 const recipe = completion.choices[0].message.content.trim();
35
?36 return NextResponse.json({ recipe });
37 }
38
??這段代碼建立了一個POST API端點,該端點利用LLaMA 3.1模型生成適合用戶手頭配料的食譜建議。請求包括系統提示指示AI充當廚房助手,特別是根據給定的配料推薦食譜。AI會收到消息,包括現有配料列表,并請求食譜建議。
一收到成功的API請求,來自AI的響應(包括配方)將被提取并以JSON格式提供。如果操作失敗,代碼將記錄錯誤并提供表明出現失敗的消息。這段代碼保證了AI根據用戶在智能廚房應用程序中手頭的配料提供量身定制的食譜建議。?
構建智能廚房應用程序的核心組件
第1步:導入和狀態設置
導入必要的依賴項并設置狀態變量,以管理用戶輸入和Firebase集成。
TypeScript
1 "use client";
2 import React, { useState } from 'react';
3 import { db } from './firebase';
4 import { collection, updateDoc, arrayUnion, doc, getDoc } from 'firebase/firestore';
5
第2步:組件聲明和初始狀態
定義KitchenAssistant組件,并為用戶命令、響應和語音識別初始化狀態。
TypeScript
1 export default function KitchenAssistant() {
2 const [command, setCommand] = useState('');
3 const [response, setResponse] = useState('');
4 const [recognition, setRecognition] = useState<SpeechRecognition | null>(null);
5
第3步:處理語音命令
這段代碼處理用戶的語音命令。它驗證命令是否有結束語句(比如“thank you”)來結束對話,或者它是否有諸如“建議食譜”或“向食品儲藏室添加商品”之類的短語。根據給定的命令,它激活相關的操作(比如“建議食譜”或“發聲朗讀”)。
TypeScript
1 const handleVoiceCommand = async (commandText: string) => {
2 setCommand(commandText.toLowerCase());
3 const closingStatements = ["no thank you", "thank you", "that's all", "goodbye", "i'm done"];
4 const isClosingStatement = closingStatements.some(statement => commandText.includes(statement));
5?
6 if (isClosingStatement) {
7 const closingResponse = "Thank you! If you need anything else, just ask. Have a great day!";
8 setResponse(closingResponse);
9 speak(closingResponse);
10 return;
11 }
12?
13 if (commandText.includes("suggest a recipe")) {
14 await suggestRecipe();
15 } else if (commandText.includes("add items to pantry")) {
16 setResponse("Sure, start telling me the items you'd like to add.");
17 speak("Sure, start telling me the items you'd like to add.");
18 }
19 };
20?
第4步:一般命令處理
該函數使用發送到/api/llama端點的POST請求來處理一般命令,端點通過LLaMA模型處理命令。來自AI的響應被設置為response狀態,并使用speak函數大聲朗讀。
TypeScript
1 const handleGeneralCommand = async (commandText: string) => {
2 try {
3 const res = await fetch('/api/llama', {
4 method: 'POST',
5 headers: { 'Content-Type': 'application/json' },
6 body: JSON.stringify({ command: commandText }),
7 });
8 const data = await res.json();
9 setResponse(data.response);
10 speak(data.response);
11 } catch (error) {
12 setResponse("Sorry, I couldn't process your request.");
13 speak("Sorry, I couldn't process your request.");
14 }
15 };
16?
第5步:為購物清單添加商品
該函數將商品添加到Firebase中的購物清單中。它估算過期日期,更新Firestore文檔,并通過設置響應并大聲朗讀響應來確認已添加給用戶。
TypeScript
1 const addToGroceryList = async (itemName: string, quantity: number) => {
2 try {
3 const expiryDate = new Date();
4 expiryDate.setDate(expiryDate.getDate() + 7);
5 const docRef = doc(db, 'grocery-list', 'Available Grocery Items');
6 await updateDoc(docRef, {
7 items: arrayUnion({
8 itemName,
9 quantity,
10 expiryDate: expiryDate.toISOString(),
11 }),
12 });
13 const responseText = `Grocery list updated. You now have: ${itemName} (${quantity}).`;
14 setResponse(responseText);
15 speak(responseText);
16 } catch (error) {
17 setResponse("Sorry, I couldn't add the item to the list.");
18 speak("Sorry, I couldn't add the item to the list.");
19 }
20 };
21
第6步:語音識別
?該函數初始化瀏覽器的語音識別API,監聽用戶輸入,并使用handleVoiceCommand處理已識別的文本。它設置了語言,確保持續監聽。
在這段代碼中,我使用WebSpeech API的SpeechRecognition接口來啟用應用程序中的語音命令功能。?
TypeScript
1 const startRecognition = () => {
2 const SpeechRecognition = window.SpeechRecognition || (window as any).webkitSpeechRecognition;
3 const newRecognition = new SpeechRecognition();
4 newRecognition.lang = 'en-US';
5 newRecognition.onresult = (event: any) => {
6 const speechToText = event.results[0][0].transcript;
7 handleVoiceCommand(speechToText);
8 };
9 newRecognition.start();
10 setRecognition(newRecognition);
11 };
12
這一行檢查瀏覽器中是否存在原生SpeechRecognition API,或者對于像Chrome這樣通過webkit前綴支持它的瀏覽器,退回到webkitSpeechRecognition。這個API允許應用程序監聽用戶的語音輸入,將其轉換成文本,然后將其作為命令來處理。
JavaScript
1 const SpeechRecognition = window.SpeechRecognition || (window as any).webkitSpeechRecognition;
?使用這個API,該應用程序可以通過語音命令與用戶進行交互,支持諸多功能,比如將商品添加到購物清單、建議食譜或檢索食品儲藏室的食材,使體驗更自動化、交互式。
你可以使用Whisper或所選擇的任何其他語音識別機制。?
創建前端組件
這個TSX布局為你的智能廚房應用程序定義了用戶界面的結構。該設計使用了Tailwind CSS類,以實現迅即響應、又不失美感的樣式。
TypeScript
1 return (
2 <div className="flex min-h-screen">
3 <div className="w-1/2 background-image"></div>
4 <div className="w-1/2 flex flex-col justify-center p-8 bg-white bg-opacity-80">
5 <h1 className="text-4xl font-bold mb-8 text-indigo-500">Welcome to Chent</h1>
6 <div className="button-group flex justify-between mb-8">
7 <button onClick={startRecognition} className="bg-green-500">Start Listening</button>
8 <button onClick={interruptSpeech} className="bg-yellow-500">Interrupt</button>
9 </div>
10 <div className="transcript">{command}</div>
11 <div className="response">{response}</div>
12 </div>
13 </div>
14?
- 布局是兩欄設計:左側顯示背景圖像,右側包含應用程序的主要功能。
- 右邊部分包括一個標題、兩個操作按鈕和兩個顯示動態文本的區域:一個用于用戶的語音命令(Transcript),另一個用于AI助理的響應(Response)。
- 界面干凈、簡潔、響應迅速,允許用戶與智能廚房應用程序高效交互,同時保持外觀漂亮的設計。
一旦你完成了它,就會有一個類似下面的界面:
以上就是我們創建智能廚房應用程序的整個過程。我在本例中使用了LLaMA 3.1語言模型,不過你可以隨意試用自己選擇的任何其他模型。?
原文標題:??Building a Voice-Powered Smart Kitchen App Using LLaMA 3.1, Firebase, and Node.js??,作者:Vaibhavi Tiwari
