diff --git a/app/actions/recipe_action.ts b/app/actions/recipe_action.ts index 76ef63f..bb0df46 100644 --- a/app/actions/recipe_action.ts +++ b/app/actions/recipe_action.ts @@ -15,11 +15,15 @@ interface RecipeResponse { export default async function RecipeAction(props: RecipeActionProps): Promise { try { + console.log("Initiating recipe generation with props:", props); const { data } = await axios.post( "/api/recipe", props ); + console.log("try to save recipe to DB with convoID:", data.id); + ensureRecipeSaved(data.id!); + return data; } catch (err) { @@ -36,4 +40,39 @@ export default async function RecipeAction(props: RecipeActionProps): Promise convo.id === id); if (!convoHistories.length) { + // return the local recipe if it exists, otherwise return an error + const localRecipe = await getCookie(id); + if (localRecipe) { + return { + success: true, + data: { + ...localRecipe?.recipe, + imgSrc: localRecipe?.imgSrc || null, + } as HistoryItemData + } + } + return { success: false, - error: "No conversation found with that ID", + error: "No recipe found with that ID", } } @@ -33,5 +47,4 @@ export default async function ConvoDetails( {id}: {id: string} ) { success: true, data: convoHistories[0].data, } - } \ No newline at end of file diff --git a/app/api/recipe/route.ts b/app/api/recipe/route.ts index c2c3776..0392606 100644 --- a/app/api/recipe/route.ts +++ b/app/api/recipe/route.ts @@ -4,7 +4,7 @@ import { cleanJSON, getCachedImage, getUserID } from "@/lib/helpers"; import { randomUUID } from "crypto"; import { addSeshHistory } from "@/lib/session"; import { imageURLFromID } from "@/lib/utils"; -import { waitUntil } from '@vercel/functions'; +import { deleteCookie, getCookie, saveAsCookie } from "@/app/lib/cookies"; export async function POST(req: NextRequest) { @@ -15,30 +15,30 @@ export async function POST(req: NextRequest) { const imgSrc = imgID ? imageURLFromID(imgID) : null; const prevGenerated = { mealName, ingredients }; + console.log(`Received request for ${convoID} with meal: ${mealName} and image: ${imgSrc}`); - waitUntil( - (async () => { - try { - const [userID, chatHistory] = await Promise.all([ - getUserID(), - convoHistory(imgSrc, JSON.stringify(prevGenerated)) - ]); + try { + const chatHistory = await convoHistory(imgSrc, JSON.stringify(prevGenerated)); + console.log("got convo history") - // Call AI - const ai_response = await getRecipe(details, chatHistory); - const recipe = cleanJSON(ai_response); + // Call AI + const ai_response = await getRecipe(details, chatHistory); + const recipe = cleanJSON(ai_response); + console.log("got recipe from AI") - // Save to Database - await addSeshHistory(userID, convoID, recipe, imgSrc); + // Save temporarily + await saveAsCookie( + convoID, { + recipe, + imgSrc, + }); - console.log(`✅ Successfully generated and saved recipe for ${convoID}`); + console.log(`✅ Successfully generated and saved recipe for ${convoID}`); - } catch (bgError) { - // Wont reach FE - console.error(`❌ Background processing failed for ${convoID}:`, bgError); - } - })() - ); + } catch (bgError) { + // Wont reach FE + console.error(`❌ Background processing failed for ${convoID}:`, bgError); + } return NextResponse.json({ success: true, @@ -47,6 +47,33 @@ export async function POST(req: NextRequest) { } +// USE A PUT to save the session to DB +export async function PUT(req: NextRequest) { + const body = await req.json(); + const { convoID } = body; + + try { + const [userID, storedData] = await Promise.all([ + getUserID(), + getCookie(convoID) + ]); + + if (!storedData) { // it has already been saved / issue, return moved + return NextResponse.json({ success: false, error: "No session data found" }, { status: 302 }); + } + + await addSeshHistory(userID, convoID, storedData.recipe, storedData.imgSrc); + console.log(`✅ Successfully added ${convoID} to DB`); + + deleteCookie(convoID); // clean up the cookie after saving to DB + + return NextResponse.json({ success: true }, { status: 200 }); + + } catch (err) { + console.error(`❌ Failed to add ${convoID} to DB:`, err); + return NextResponse.json({ success: false, error: "Failed to save session" }, { status: 500 }); + } +} diff --git a/app/lib/cookies.ts b/app/lib/cookies.ts index ed3a565..038f1d1 100644 --- a/app/lib/cookies.ts +++ b/app/lib/cookies.ts @@ -3,20 +3,43 @@ import { UUID } from "crypto"; import { cookies } from "next/headers"; + +export async function getCookie(key: string) { + const cookieJar = await cookies(); + const cookie = cookieJar.get(key); + + return cookie? JSON.parse(cookie.value) : null +} + + export async function setCookies(data: {userID: UUID}) { const cookieJar = await cookies(); cookieJar.set("session_data", JSON.stringify(data), { httpOnly: true, secure: process.env.NODE_ENV === "production", - maxAge: 60 * 60 * 24, // 1 day + maxAge: 60 * 60 * 24 * 7, // 1 week path: "/", }); } -export async function getCookies(){ - const cookieJar = await cookies(); - const cookie = cookieJar.get("session_data"); +export async function getSessionCookies(){ + const sessionData = await getCookie("session_data"); + return sessionData || {}; +} - return cookie? JSON.parse(cookie.value) : {} +export async function saveAsCookie(key: string, data: Record) { + const cookieJar = await cookies(); + cookieJar.set(key, JSON.stringify(data), { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + maxAge: 60 * 60 * 24, // 1 day + path: "/", + }); } + + +export async function deleteCookie(key: string) { + const cookieJar = await cookies(); + cookieJar.delete(key); +} \ No newline at end of file diff --git a/app/lib/helpers.ts b/app/lib/helpers.ts index 9a4a76d..81fe1b9 100644 --- a/app/lib/helpers.ts +++ b/app/lib/helpers.ts @@ -1,5 +1,5 @@ import { randomUUID } from "crypto"; -import { getCookies, setCookies } from "./cookies"; +import { getSessionCookies, setCookies } from "./cookies"; import { cache } from "react"; @@ -24,7 +24,7 @@ export function cleanJSON(content: string) { export async function getUserID(){ // Retrieve or set session cookie - const cookies = await getCookies(); + const cookies = await getSessionCookies(); let userID; if (cookies.userID) { diff --git a/app/lib/recipe.js b/app/lib/recipe.js index 1c62a65..50c11cf 100644 --- a/app/lib/recipe.js +++ b/app/lib/recipe.js @@ -8,7 +8,7 @@ const ai = new GoogleGenAI({ }); export async function getRecipe(description, history) { - const model = 'gemini-2.5-pro'; + const model = 'gemini-3.1-pro-preview'; // Create the chat session const chat = ai.chats.create({ @@ -24,8 +24,7 @@ export async function getRecipe(description, history) { type Step: {description, duration, price?} # price only when necessary (e.g., if a budget was mentioned) Requirements: - 1. Use Google Search to find special ingredients and current, accurate prices. - 2. Respond strictly in the format: { "mealName": "name", "ingredients": Array, "recipe": Array }`; + . Respond strictly in the format: { "mealName": "name", "ingredients": Array, "recipe": Array }`; // Using stream for better UX, or you can use chat.sendMessage for a single block const result = await chat.sendMessage({ message: prompt });