From e807cf9d064981b3f3afd130d8f9c285d76ce747 Mon Sep 17 00:00:00 2001 From: Jay Kerkar Date: Sat, 5 Apr 2025 06:13:47 +0000 Subject: [PATCH 1/9] chore: remove unused GET function from gemini API route --- src/routes/api/gemini/+server.ts | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/routes/api/gemini/+server.ts diff --git a/src/routes/api/gemini/+server.ts b/src/routes/api/gemini/+server.ts deleted file mode 100644 index 6fc76b2..0000000 --- a/src/routes/api/gemini/+server.ts +++ /dev/null @@ -1 +0,0 @@ -export async function GET() {} From 5f1474d84d565eee9beb783d1bc1ab565434d864 Mon Sep 17 00:00:00 2001 From: Jay Kerkar Date: Sat, 5 Apr 2025 06:15:44 +0000 Subject: [PATCH 2/9] refactor: clean up imports and update AI model version in workout plan API --- src/routes/api/workout-plan/+server.ts | 31 +++++++++++++------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/routes/api/workout-plan/+server.ts b/src/routes/api/workout-plan/+server.ts index caa94ca..9bfc202 100644 --- a/src/routes/api/workout-plan/+server.ts +++ b/src/routes/api/workout-plan/+server.ts @@ -1,10 +1,10 @@ +import { GoogleGenAI } from '@google/genai'; import { GEMINI_API_KEY } from '$env/static/private'; import { createWorkoutPlanFunctionDeclaration } from '$lib/toolspecs/fitness-planner'; -import { GoogleGenAI } from '@google/genai'; export async function POST({ request }) { const userData = await request.json(); - + const config = { tools: [ { @@ -13,15 +13,13 @@ export async function POST({ request }) { ] }; - // Configure the client const ai = new GoogleGenAI({ apiKey: GEMINI_API_KEY }); - // Define a simple user prompt const contents = [ { role: 'user', parts: [ - { + { text: `Create a workout routine that fits my profile: I'm a ${userData.fitness_level || 'intermediate'} level fitness enthusiast, ${userData.age || 35} years old, @@ -36,23 +34,24 @@ export async function POST({ request }) { } ]; - // Send request with function declarations const response = await ai.models.generateContent({ - model: 'gemini-2.0-flash', + model: 'gemini-2.5-pro-exp-03-25', contents: contents, config: config }); - // Get the structured workout plan from the function call const workoutPlan = response.functionCalls?.at(0)?.args; - return new Response(JSON.stringify({ - workoutPlan, - fullResponse: response - }), { - status: 200, - headers: { - 'Content-Type': 'application/json' + return new Response( + JSON.stringify({ + workoutPlan, + fullResponse: response + }), + { + status: 200, + headers: { + 'Content-Type': 'application/json' + } } - }); + ); } From f22013f0fe589f2a5bc8bddf1d3689c11f4b147a Mon Sep 17 00:00:00 2001 From: Jay Kerkar Date: Sat, 5 Apr 2025 13:32:19 +0000 Subject: [PATCH 3/9] feat: integrate chat agent component and enhance nutritional OCR page - Added ChatAgent component to the app layout for user interaction. - Improved nutritional OCR page with enhanced UI components and functionality. - Removed unnecessary console logs from onboarding and macros API. - Created ProfileCard component to display user fitness profile details. - Updated onboarding logic to fetch and display user profile information. - Enhanced chat functionality with better message handling and user experience. --- src/lib/components/ProfileCard.svelte | 134 ++++ .../(app)/(components)/chat-agent.svelte | 706 ++++++++++++++++++ src/routes/(app)/+layout.svelte | 6 +- src/routes/(app)/dashboard/+page.svelte | 1 - src/routes/(app)/nutritional-ocr/+page.svelte | 272 ++++++- src/routes/(app)/onboarding/+page.svelte | 1 - src/routes/api/macros/+server.ts | 1 - src/routes/api/onboarding/+server.ts | 2 - 8 files changed, 1078 insertions(+), 45 deletions(-) create mode 100644 src/lib/components/ProfileCard.svelte create mode 100644 src/routes/(app)/(components)/chat-agent.svelte diff --git a/src/lib/components/ProfileCard.svelte b/src/lib/components/ProfileCard.svelte new file mode 100644 index 0000000..6b49c78 --- /dev/null +++ b/src/lib/components/ProfileCard.svelte @@ -0,0 +1,134 @@ + + + + + + + Your Fitness Profile + + Verify your information + + +
+
+
+ +
+
+

Level

+

{capitalize(profile.fitnessLevel)}

+
+
+
+
+ +
+
+

Goal

+

{capitalize(profile.fitnessGoal)}

+
+
+
+
+ +
+
+

Weight

+

{profile.weight.value}{profile.weight.unit}

+
+
+
+
+ +
+
+

Height

+

{profile.height.value}{profile.height.unit}

+
+
+
+ +
+
+ +

Training {profile.daysPerWeek} days/week

+
+
+ +
+ {#each profile.availableEquipment as equipment} + {capitalize(equipment)} + {/each} +
+
+
+

Age & Gender

+

{profile.age} years old, {capitalize(profile.gender)}

+
+
+
+ + + + +
diff --git a/src/routes/(app)/(components)/chat-agent.svelte b/src/routes/(app)/(components)/chat-agent.svelte new file mode 100644 index 0000000..157183b --- /dev/null +++ b/src/routes/(app)/(components)/chat-agent.svelte @@ -0,0 +1,706 @@ + + + +
+ +
+ + +
+ + +
+
+
+ + + AI + + +
+

Fitness Assistant

+

+ + + Online + +

+
+
+ +
+ + +
+ {#if isCheckingOnboarding} +
+
+ +

Loading your profile...

+
+
+ {:else if !hasCompletedOnboarding} +
+ + + Onboarding Required + + To get personalized fitness assistance, we need some information about you and + your fitness goals. + + + +
+ {:else} +
+ {#each messages as message (message.id)} +
+ {#if message.sender === 'bot'} + + + AI + + + {/if} + +
+ {#if message.sender === 'bot'} + {renderMessage(message)} + {:else} +

{message.content}

+ {/if} + + {formatTime(message.timestamp)} + +
+ + {#if message.sender === 'user'} + + + You + + + {/if} +
+ {/each} + + {#if isLoading} +
+ + + AI + + +
+
+ + + +
+
+
+ {/if} +
+ {/if} +
+
+ +
+
+ + +
+
+
+
+
+
+ + +{#if isOpen && !isMinimized} + +{:else if isMinimized} + +{/if} \ No newline at end of file diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index c273f2a..7037a90 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -1,4 +1,5 @@ stream?.getTracks().forEach((t) => t.stop())} /> -
-

Scan Nutrition Label

- - - - - - - {#if error} -

Error: {error}

- {/if} - - {#if parsedNutrition.calories} -

Extracted Nutrition Info

-
    -
  • Calories: {parsedNutrition.calories} / 100g
  • -
  • Fats: {parsedNutrition.fat}g / 100g
  • -
  • Carbohydrates: {parsedNutrition.carbs}g / 100g
  • -
  • Protein: {parsedNutrition.protein}g / 100g
  • -
- {/if} +
+ + + + + Nutrition Label Scanner + + + Point your camera at a nutrition label to extract nutritional information + + + + + {#if !cameraPermissionGranted} +
+ +
+

Camera Access Required

+

+ Allow camera access to scan nutrition labels +

+
+ +
+ {:else} +
+ {#if loading} +
+
+
+

Processing label...

+
+
+ {/if} + + + + +
+
+
+ Center the nutrition label +
+
+
+
+ +
+ + + +
+ + {#if error} +
+ + {error} +
+ {/if} + {/if} +
+
+ + + +
+ +
+ + + Scanned Nutrition Info + +
+ + +
+
+
+ +
+
+
+

Nutritional Information

+ Per 100g +
+ +
+
+ {parsedNutrition.calories || '0'} + CALORIES +
+
+ {parsedNutrition.protein || '0'}g + PROTEIN +
+
+ {parsedNutrition.carbs || '0'}g + CARBS +
+
+ {parsedNutrition.fat || '0'}g + FAT +
+
+
+
+
+
+
diff --git a/src/routes/(app)/onboarding/+page.svelte b/src/routes/(app)/onboarding/+page.svelte index 3345605..a152157 100644 --- a/src/routes/(app)/onboarding/+page.svelte +++ b/src/routes/(app)/onboarding/+page.svelte @@ -156,7 +156,6 @@ throw new Error('Network response was not ok'); } const data = await response.text(); - console.log(data); isSuccess = true; toast.success('Your fitness profile has been created!'); } catch (error) { diff --git a/src/routes/api/macros/+server.ts b/src/routes/api/macros/+server.ts index 5d4e54a..78a4aaf 100644 --- a/src/routes/api/macros/+server.ts +++ b/src/routes/api/macros/+server.ts @@ -35,7 +35,6 @@ export async function POST({ request }) { const { macros_data } = await request.json(); - console.log(macros_data); try { await prisma.macros.create({ data: { diff --git a/src/routes/api/onboarding/+server.ts b/src/routes/api/onboarding/+server.ts index 4f7c073..8d73afd 100644 --- a/src/routes/api/onboarding/+server.ts +++ b/src/routes/api/onboarding/+server.ts @@ -31,6 +31,4 @@ export async function POST({ request }) { } catch (e) { return new Response('Error saving onboarding data', { status: 500 }); } - - console.log(onboarding); } From 2a9b80630c710cb73b591c4082119861a1d4a4ca Mon Sep 17 00:00:00 2001 From: Jay Kerkar Date: Sat, 5 Apr 2025 14:36:15 +0000 Subject: [PATCH 4/9] refactor: simplify ProfileCard component and improve chat agent message rendering --- src/lib/components/ProfileCard.svelte | 134 +----- .../(app)/(components)/chat-agent.svelte | 400 ++++++------------ 2 files changed, 148 insertions(+), 386 deletions(-) diff --git a/src/lib/components/ProfileCard.svelte b/src/lib/components/ProfileCard.svelte index 6b49c78..a8f4723 100644 --- a/src/lib/components/ProfileCard.svelte +++ b/src/lib/components/ProfileCard.svelte @@ -1,134 +1,18 @@ - - - - - - Your Fitness Profile - - Verify your information - - -
-
-
- -
-
-

Level

-

{capitalize(profile.fitnessLevel)}

-
-
-
-
- -
-
-

Goal

-

{capitalize(profile.fitnessGoal)}

-
-
-
-
- -
-
-

Weight

-

{profile.weight.value}{profile.weight.unit}

-
-
-
-
- -
-
-

Height

-

{profile.height.value}{profile.height.unit}

-
-
-
- -
-
- -

Training {profile.daysPerWeek} days/week

-
-
- -
- {#each profile.availableEquipment as equipment} - {capitalize(equipment)} - {/each} -
-
-
-

Age & Gender

-

{profile.age} years old, {capitalize(profile.gender)}

-
-
-
- - - - -
diff --git a/src/routes/(app)/(components)/chat-agent.svelte b/src/routes/(app)/(components)/chat-agent.svelte index 157183b..a4f13d0 100644 --- a/src/routes/(app)/(components)/chat-agent.svelte +++ b/src/routes/(app)/(components)/chat-agent.svelte @@ -11,19 +11,19 @@ import { Button } from '$lib/components/ui/button'; import { ScrollArea } from '$lib/components/ui/scroll-area'; import { Separator } from '$lib/components/ui/separator'; - import { - Send, - X, - Minimize2, - Loader2, - AlertTriangle, - Bot, - User, - Trophy, - Target, - Weight, - Ruler, - Calendar, + import { + Send, + X, + Minimize2, + Loader2, + AlertTriangle, + Bot, + User, + Trophy, + Target, + Weight, + Ruler, + Calendar, Dumbbell, CheckCircle2, XCircle @@ -123,107 +123,127 @@ // Function to handle the message that contains profile info function renderMessage(message: Message) { if (message.content.includes('Please confirm your fitness profile:') && userOnboardingData) { - return ( + return `
-

{message.content}

- +

${message.content}

+ ${ProfileCard(formatOnboardingDataToProfile(userOnboardingData))}
- ); + `; } - return

{message.content}

; + return `

${message.content}

`; } - function ProfileCard({ profile }: { profile: UserProfile }) { - function handleConfirm() { - sendResponseMessage('yes this is correct'); - } - - function handleUpdateInfo() { - sendResponseMessage('no not correct'); - } - - return ( - - - - - Your Fitness Profile - - Please verify your information - - + function ProfileCard(profile: UserProfile) { + return ` +
+
+
+
+ + + +
+
+

Your Fitness Profile

+

Current Information

+
+
+
+ +
-
-
- +
+
+ + +
-
-

Level

-

{capitalize(profile.fitnessLevel)}

+
+

Fitness Level

+

${capitalize(profile.fitnessLevel)}

-
-
- +
+
+ + + + +
-
-

Goal

-

{capitalize(profile.fitnessGoal)}

+
+

Fitness Goal

+

${capitalize(profile.fitnessGoal)}

-
-
- +
+
+ + +
-
-

Weight

-

{profile.weight.value}{profile.weight.unit}

+
+

Weight

+

${profile.weight.value} ${profile.weight.unit}

-
-
- +
+
+ + +
-
-

Height

-

{profile.height.value}{profile.height.unit}

+
+

Height

+

${profile.height.value} ${profile.height.unit}

- - -
-
- - Training {profile.daysPerWeek} days/week +
+
+
+ + + + + + +
+
+

Training Schedule

+

${profile.daysPerWeek} days per week

+
-
- -
- {#each profile.availableEquipment as equipment} - {capitalize(equipment)} - {/each} +
+
+ + + + +
+
+

Available Equipment

+
+ ${profile.availableEquipment + .map( + equipment => ` + + ${capitalize(equipment)} + ` + ) + .join('')} +
- - - - - - - ); +
+
+ `; } async function fetchOnboardingStatus() { @@ -403,161 +423,18 @@
- -
- - -
-
-
- - - AI - - -
-

Fitness Assistant

-

- - - Online - -

-
-
- -
- - -
- {#if isCheckingOnboarding} -
-
- -

Loading your profile...

-
-
- {:else if !hasCompletedOnboarding} -
- - - Onboarding Required - - To get personalized fitness assistance, we need some information about you and - your fitness goals. - - - -
- {:else} -
- {#each messages as message (message.id)} -
- {#if message.sender === 'bot'} - - - AI - - - {/if} - -
- {#if message.sender === 'bot'} - {renderMessage(message)} - {:else} -

{message.content}

- {/if} - - {formatTime(message.timestamp)} - -
- - {#if message.sender === 'user'} - - - You - - - {/if} -
- {/each} - - {#if isLoading} -
- - - AI - - -
-
- - - -
-
-
- {/if} -
- {/if} -
-
- -
-
- - -
-
-
-
-
-
- {#if isOpen && !isMinimized}