Sparky Chatbot Workflow (Refined)
This document outlines the refined workflow for how the Sparky chatbot processes user messages, identifies intent, extracts data, interacts with the database, and provides responses, incorporating improvements for robustness, multi-item handling, context, and error management.
Overall Process
- User Input: The user sends a message to the chatbot in natural language, potentially including text and/or an image.
- AI Intent Recognition & Data Extraction: The chatbot sends the user's message to the AI model along with recent conversation history (e.g., the last 5 messages) and a detailed system prompt. The system prompt will instruct the AI to:
- Prioritize the current user message when determining intent and extracting data.
- Use the conversation history primarily for context (e.g., understanding follow-ups like "add another one", clarifying ambiguous terms, or referencing previous items).
- Avoid re-processing requests that appear to have been completed in the recent history.
- Application Logic (SparkyNutritionCoach): Based on the
intent
provided by the AI, the application logic routes the request to the appropriate handler function. - Database Interaction: The handler function interacts with the Supabase database to log data (food entries, exercise entries, measurements) or retrieve information.
- Response Generation: Based on the success or failure of the database operation, or if the intent was conversational, a user-friendly response message is generated.
- User Output: The generated response message is displayed to the user in the chat interface.
Detailed Workflow by Intent
The AI is designed to identify one of the following intents:
- Log Food (
log_food
)- AI Data:
food_name
(string),quantity
(number, inferred or default 1),unit
(string, inferred or default "g"),meal_type
(string, inferred or default "snack"),date
(string, ISO format, inferred or null). - Application Logic:
- Validation: Check if
food_name
is present and is a string. - Search for the extracted
food_name
in thefoods
table (user's custom first, then public). - If Food Found: Log a new entry in the
food_entries
table using the foundfood_id
, extractedquantity
,unit
,meal_type
, and the extracteddate
(defaulting to today if null). Validate the date to prevent logging future entries. - If Food Not Found: Send a secondary request to the AI specifically asking it to generate 3 realistic food options similar to the requested
food_name
, including estimated nutritional details and serving information. Include context like the original input and potentially user preferences (e.g., dietary restrictions) in the prompt. - AI Options Response: AI returns JSON with
food_options
array. - Application Logic (Options): Display the generated food options to the user in a numbered list. Guide the user to reply with a number to select an option OR provide manual nutrition details (e.g., "If none fit, reply with 'manual: 100 cal, 5g protein...'"). Store the options data (including original request details) in the chat message's metadata for handling the user's subsequent selection or manual entry.
- Validation: Check if
- AI Data:
- Log Exercise (
log_exercise
)- AI Data:
exercise_name
(string),duration_minutes
(number, inferred or null),distance
(number, inferred or null),distance_unit
(string, inferred or null),date
(string, ISO format, inferred or null). - Application Logic:
- Validation: Check if
exercise_name
is present and is a string. - Search the
exercises
table for the extractedexercise_name
. - If Exercise Found: Log a new entry in the
exercise_entries
table with extracted details (exercise_id
,duration_minutes
- using AI value or default from DB,calories_burned
- calculated,distance
,distance_unit
, extracteddate
). Validate the date. - If Exercise Not Found: Send a secondary request to the AI specifically asking it to generate 3 exercise options similar to the requested exercise, including estimated
calories_per_hour
. - AI Options Response: AI returns JSON with
exercise_options
array. - Application Logic (Options): Display the generated exercise options to the user in a numbered list. Guide the user on how to provide manual details if needed. Store options data in metadata.
- Validation: Check if
- AI Data:
- Log Body Measurements (
log_measurement
- standard types)- AI Data:
measurements
(array of objects), each withtype
("weight", "neck", "waist", "hips", "steps"),value
(number),unit
(string, inferred or null, except for steps which defaults to "steps"),date
(string, ISO format, inferred or null). - Application Logic:
- Validation: Check if
measurements
is an array and each object hastype
andvalue
. - For each measurement in the
measurements
array:- If
type
is standard: Upsert into thecheck_in_measurements
table for the user and the extracteddate
(defaulting to today). Validate the date.
- If
- Generate a confirmation message listing the measurements that were successfully logged.
- Validation: Check if
- AI Data:
- Log Custom Measurements (
log_measurement
- custom type)- AI Data:
measurements
(array of objects), each withtype
("custom"),name
(string),value
(number),unit
(string, inferred or null),date
(string, ISO format, inferred or null). - Application Logic:
- Validation: Check if
measurements
is an array and each custom object hastype
,name
, andvalue
. - For each custom measurement in the
measurements
array:- Search for a category with the extracted
name
in thecustom_categories
for the user. - If Category Found: Use the found
category_id
. - If Category Not Found: Create a new entry in
custom_categories
with the extractedname
, defaultingfrequency
to 'daily' andmeasurement_type
to 'numeric'. Use the newcategory_id
. - Insert a new entry in the
custom_measurements
with user ID,category_id
, extractedvalue
, and extracteddate
/timestamp (defaulting to now). Validate the date.
- Search for a category with the extracted
- Generate a confirmation message listing the custom measurements that were successfully logged.
- Validation: Check if
- AI Data:
- Log Water Intake (
log_water
)- AI Data:
glasses_consumed
(number, inferred or default 1),date
(string, ISO format, inferred or null). - Application Logic:
- Validation: Check if
glasses_consumed
is a number or null. - Get the current water intake for the user for the extracted
date
(defaulting to today). - Calculate the new total by adding the extracted
glasses_consumed
(or default 1) to the current total. - Upsert the new total into the
water_intake
table for the user and the extracteddate
. Validate the date. - Generate a confirmation message showing the added glasses and the new total for the day.
- Validation: Check if
- AI Data:
- Ask Question (
ask_question
) / General Chat (chat
)- AI Data: Empty
data
object,response
(string containing the AI's conversational reply). - Application Logic:
- Display the
response
provided directly by the AI.
- Display the
- AI Data: Empty
3. Post-Logging Actions & User Interaction
- After successful logging (any type):
- Trigger a refresh of relevant data displays (e.g., food diary, measurements charts).
- Refined Progress Updates: Batch progress updates for multi-item inputs (display one summary after all items are processed). For single item logs, update immediately. The update message should include a summary of today's relevant data (nutrition, exercise, measurements) and a coaching tip. Tailor the summary slightly based on the type of item just logged.
- Handling Option Selection:
- If the user's next message is a number and the previous bot message contained options metadata:
- Validation: Check if the number is a valid index for the options stored in the metadata and if the metadata is still valid/present.
- If Valid Selection: Process the selected option using the stored data (e.g., call
addFoodOption
oraddExerciseOption
). - If Invalid Selection: Return a
CoachResponse
withaction: 'none'
and a message guiding the user (e.g., "Please select a valid option number or tell me what you'd like to do.").
- If the user's next message is not a number but matches the format for manual data entry (e.g., "manual: ..."), process the manual entry.
- If the user's next message is unrelated, process it as a new input using the standard AI workflow.
- If the user's next message is a number and the previous bot message contained options metadata:
Relevant Database Tables
Here are the schemas for the relevant database tables for context:
-- check_in_measurements table
create table public.check_in_measurements (
id uuid not null default gen_random_uuid (),
user_id uuid not null,
entry_date date not null default CURRENT_DATE,
weight numeric null,
neck numeric null,
waist numeric null,
hips numeric null,
steps integer null,
created_at timestamp with time zone not null default now(),
updated_at timestamp with time zone not null default now(),
constraint check_in_measurements_pkey primary key (id),
constraint check_in_measurements_user_date_unique unique (user_id, entry_date)
) TABLESPACE pg_default;
-- custom_categories table
create table public.custom_categories (
id uuid not null default gen_random_uuid (),
user_id uuid not null,
name character varying(50) not null,
measurement_type character varying(50) not null, -- e.g., 'numeric', 'text'
frequency text not null, -- e.g., 'Daily', 'Weekly', 'Monthly', 'All'
created_at timestamp with time zone not null default now(),
updated_at timestamp with time zone not null default now(),
constraint custom_categories_pkey primary key (id),
constraint custom_categories_user_id_fkey foreign KEY (user_id) references auth.users (id),
constraint custom_categories_frequency_check check (
(
frequency = any (array['All'::text, 'Daily'::text, 'Hourly'::text, 'Weekly'::text, 'Monthly'::text]) -- Added Weekly, Monthly based on common use cases
)
)
) TABLESPACE pg_default;
-- custom_measurements table
create table public.custom_measurements (
id uuid not null default gen_random_uuid (),
user_id uuid not null,
category_id uuid not null,
value numeric not null,
entry_date date not null,
entry_hour integer null, -- For hourly frequency
entry_timestamp timestamp with time zone not null default now(),
created_at timestamp with time zone not null default now(),
constraint custom_measurements_pkey primary key (id),
constraint custom_measurements_unique_entry unique (user_id, category_id, entry_date, entry_hour), -- Ensure uniqueness based on frequency
constraint custom_measurements_category_id_fkey foreign KEY (category_id) references custom_categories (id) on delete CASCADE,
constraint custom_measurements_user_id_fkey foreign KEY (user_id) references auth.users (id)
) TABLESPACE pg_default;
-- food_entries table
create table public.food_entries (
id uuid not null default gen_random_uuid (),
user_id uuid not null,
food_id uuid not null,
meal_type character varying(50) not null, -- e.g., 'breakfast', 'lunch', 'dinner', 'snack'
quantity numeric not null,
unit character varying(50) null, -- e.g., 'g', 'oz', 'piece'
entry_date date not null,
created_at timestamp with time zone not null default now(),
variant_id uuid null, -- Link to food_variants if applicable
constraint food_entries_pkey primary key (id),
constraint food_entries_food_id_fkey foreign KEY (food_id) references foods (id),
constraint food_entries_user_id_fkey foreign KEY (user_id) references auth.users (id),
constraint food_entries_variant_id_fkey foreign KEY (variant_id) references food_variants (id)
) TABLESPACE pg_default;
-- foods table
create table public.foods (
id uuid not null default gen_random_uuid (),
name character varying(255) not null,
calories numeric null,
protein numeric null,
carbs numeric null,
fat numeric null,
serving_size numeric null, -- Default serving size in grams or standard unit
serving_unit character varying(50) null, -- e.g., 'g', 'ml', 'piece'
is_custom boolean null default false, -- True if created by a user
user_id uuid null, -- Creator user_id if is_custom is true
created_at timestamp with time zone null default now(),
updated_at timestamp with time zone null default now(),
barcode character varying(255) null, -- For scanning
openfoodfacts_id character varying(255) null, -- Link to external databases
shared_with_public boolean null default false,
saturated_fat numeric null, -- Added for comprehensive nutrition
polyunsaturated_fat numeric null,
monounsaturated_unsaturated numeric null,
trans_fat numeric null,
cholesterol numeric null,
sodium numeric null,
potassium numeric null,
dietary_fiber numeric null,
sugars numeric null,
vitamin_a numeric null,
vitamin_c numeric null,
calcium numeric null,
iron numeric null,
constraint foods_pkey primary key (id),
constraint foods_user_id_fkey foreign KEY (user_id) references auth.users (id)
) TABLESPACE pg_default;
-- exercise_entries table
create table public.exercise_entries (
id uuid not null default gen_random_uuid (),
user_id uuid not null,
exercise_id uuid not null,
duration_minutes integer not null,
calories_burned integer not null,
entry_date date null,
notes text null,
created_at timestamp with time zone null default now(),
constraint exercise_entries_pkey primary key (id),
constraint exercise_entries_exercise_id_fkey foreign KEY (exercise_id) references exercises (id),
constraint exercise_entries_user_id_fkey foreign KEY (user_id) references auth.users (id)
) TABLESPACE pg_default;
-- exercises table
create table public.exercises (
id uuid not null default gen_random_uuid (),
name character varying(255) not null,
category character varying(50) null, -- e.g., 'cardio', 'strength'
calories_per_hour integer null, -- Estimated calories burned per hour
description text null,
is_custom boolean null default false,
user_id uuid null, -- Creator user_id if is_custom is true
created_at timestamp with time zone null default now(),
updated_at timestamp with time zone null default now(),
shared_with_public boolean null default false,
constraint exercises_pkey primary key (id),
constraint exercises_user_id_fkey foreign KEY (user_id) references auth.users (id)
) TABLESPACE pg_default;
-- water_intake table
create table public.water_intake (
id uuid not null default gen_random_uuid (),
user_id uuid not null,
entry_date date not null,
glasses_consumed integer not null,
created_at timestamp with time zone not null default now(),
updated_at timestamp with time zone not null default now(),
constraint water_intake_pkey primary key (id),
constraint water_intake_user_date_unique unique (user_id, entry_date),
constraint water_intake_user_id_fkey foreign KEY (user_id) references auth.users (id)
) TABLESPACE pg_default;
-- ai_service_settings table
create table public.ai_service_settings (
id uuid not null default gen_random_uuid (),
user_id uuid not null,
service_name character varying(255) not null, -- e.g., 'OpenAI', 'Google Gemini'
service_type character varying(50) not null, -- e.g., 'openai', 'google'
api_key text not null,
is_active boolean not null default true,
model_name character varying(255) null, -- Specific model used
system_prompt text null, -- Custom system prompt for the AI
custom_url text null, -- For custom or compatible services
created_at timestamp with time zone not null default now(),
updated_at timestamp with time zone not null default now(),
constraint ai_service_settings_pkey primary key (id),
constraint ai_service_settings_user_id_fkey foreign KEY (user_id) references auth.users (id)
) TABLESPACE pg_default;
-- sparky_chat_history table
create table public.sparky_chat_history (
id uuid not null default gen_random_uuid (),
user_id uuid not null,
session_id uuid not null default gen_random_uuid (), -- To group messages by session
message_type character varying(50) not null, -- 'user' or 'assistant'
content text not null, -- The message content
created_at timestamp with time zone not null default now(),
metadata jsonb null, -- To store additional data like food options, image URLs, etc.
-- Deprecated fields, kept for history but not actively used in new workflow:
message text null,
response text null,
image_url text null,
constraint sparky_chat_history_pkey primary key (id),
constraint sparky_chat_history_user_id_fkey foreign KEY (user_id) references auth.users (id)
) TABLESPACE pg_default;
-- user_preferences table
create table public.user_preferences (
id uuid not null default gen_random_uuid (),
user_id uuid not null,
date_format character varying(50) not null default 'yyyy-MM-dd',
default_weight_unit character varying(50) not null default 'kg',
default_measurement_unit character varying(50) not null default 'cm',
auto_clear_history text null default 'never', -- 'never', 'session', '7days', '30days'
system_prompt text null, -- User-specific override for AI system prompt
created_at timestamp with time zone not null default now(),
updated_at timestamp with time zone not null default now(),
constraint user_preferences_pkey primary key (id),
constraint user_preferences_user_id_fkey foreign KEY (user_id) references auth.users (id) on delete CASCADE,
constraint user_preferences_auto_clear_history_check check (
(
auto_clear_history = any (array['never'::text, 'session'::text, '7days'::text, '30days'::text])
)
)
) TABLESPACE pg_default;
-- user_goals table
create table public.user_goals (
id uuid not null default gen_random_uuid (),
user_id uuid not null,
goal_date date null, -- Null for default goal
calories numeric null,
protein numeric null,
carbs numeric null,
fat numeric null,
water_goal integer null,
created_at timestamp with time zone null default now(),
updated_at timestamp with time zone null default now(),
saturated_fat numeric null, -- Added for comprehensive nutrition goals
polyunsaturated_fat numeric null,
monounsaturated_fat numeric null,
trans_fat numeric null,
cholesterol numeric null,
sodium numeric null,
potassium numeric null,
dietary_fiber numeric null,
sugars numeric null,
vitamin_a numeric null,
vitamin_c numeric null,
calcium numeric null,
iron numeric null,
constraint user_goals_pkey primary key (id),
constraint user_goals_user_id_fkey foreign KEY (user_id) references auth.users (id),
constraint user_goals_unique_user_date unique (user_id, goal_date) -- Ensure only one goal per user per date
) TABLESPACE pg_default;