context extended
maybe need polishing
This commit is contained in:
1 parent
684481fd2c
commit
28b5b52a8f
11 files changed
+761
-20
No files matched your search
@@ -0,0 +1,88 @@
|
||||
import { getDatabase } from '@/lib/database';
|
||||
import type { FitnessGoal } from '@/lib/database/types';
|
||||
|
||||
// Import types from database package
|
||||
import type {
|
||||
FitnessProfile as DBFitnessProfile,
|
||||
Recommendation as DBRecommendation,
|
||||
} from '@fitai/database';
|
||||
|
||||
export interface AIContext {
|
||||
profile: DBFitnessProfile;
|
||||
activeGoals: FitnessGoal[];
|
||||
completedGoals: FitnessGoal[];
|
||||
recentRecommendations: DBRecommendation[];
|
||||
progressSummary: {
|
||||
goalsCompleted: number;
|
||||
goalsActive: number;
|
||||
averageProgress: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build comprehensive AI context for a user
|
||||
* Aggregates fitness profile, goals, and recommendation history
|
||||
*/
|
||||
export async function buildAIContext(userId: string): Promise<AIContext> {
|
||||
const db = await getDatabase();
|
||||
|
||||
// Fetch fitness profile
|
||||
const profile = await db.getFitnessProfileByUserId(userId);
|
||||
|
||||
if (!profile) {
|
||||
throw new Error(`Fitness profile not found for user ${userId}`);
|
||||
}
|
||||
|
||||
// Fetch all fitness goals
|
||||
const allGoals = await db.getFitnessGoalsByUserId(userId);
|
||||
|
||||
// Separate active and completed goals
|
||||
const activeGoals = allGoals.filter((g) => g.status === 'active');
|
||||
const completedGoals = allGoals.filter((g) => g.status === 'completed');
|
||||
|
||||
// Get all recommendations
|
||||
const allRecommendations = await db.getRecommendationsByUserId(userId);
|
||||
|
||||
// Filter recent recommendations (last 7 days)
|
||||
const sevenDaysAgo = new Date();
|
||||
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
||||
|
||||
const recentRecommendations = allRecommendations
|
||||
.filter((r) => new Date(r.createdAt) > sevenDaysAgo)
|
||||
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
|
||||
.slice(0, 5);
|
||||
|
||||
// Calculate progress summary
|
||||
const progressSummary = {
|
||||
goalsCompleted: completedGoals.length,
|
||||
goalsActive: activeGoals.length,
|
||||
averageProgress:
|
||||
activeGoals.length > 0
|
||||
? activeGoals.reduce((sum, g) => sum + (g.progress || 0), 0) / activeGoals.length
|
||||
: 0,
|
||||
};
|
||||
|
||||
return {
|
||||
profile,
|
||||
activeGoals,
|
||||
completedGoals,
|
||||
recentRecommendations,
|
||||
progressSummary,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Format goal for display in AI prompt
|
||||
*/
|
||||
export function formatGoalForPrompt(goal: FitnessGoal): string {
|
||||
const progress = goal.progress || 0;
|
||||
const current = goal.currentValue || 0;
|
||||
const target = goal.targetValue;
|
||||
const unit = goal.unit || '';
|
||||
|
||||
if (target) {
|
||||
return `${goal.title}: ${current}/${target} ${unit} (${progress.toFixed(1)}% complete)`;
|
||||
}
|
||||
|
||||
return `${goal.title}: ${progress.toFixed(1)}% complete`;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import type { AIContext } from './ai-context';
|
||||
import { formatGoalForPrompt } from './ai-context';
|
||||
|
||||
/**
|
||||
* Build enhanced AI prompt with comprehensive user context
|
||||
* Includes profile, goals, progress, and recommendation history
|
||||
*/
|
||||
export function buildEnhancedPrompt(context: AIContext): string {
|
||||
const { profile, activeGoals, completedGoals, recentRecommendations, progressSummary } = context;
|
||||
|
||||
// Build goals section
|
||||
const activeGoalsText =
|
||||
activeGoals.length > 0
|
||||
? activeGoals.map((g) => `- ${formatGoalForPrompt(g)}`).join('\n')
|
||||
: '- No active goals set';
|
||||
|
||||
const completedGoalsText =
|
||||
completedGoals.length > 0
|
||||
? completedGoals.slice(0, 3).map((g) => g.title).join(', ')
|
||||
: 'None yet';
|
||||
|
||||
// Build context about recent recommendations
|
||||
const recommendationContextText =
|
||||
recentRecommendations.length > 0
|
||||
? `The user has received ${recentRecommendations.length} recommendations in the past week. Consider their progress and avoid repetitive advice.`
|
||||
: 'This is a new user or they haven\'t received recent recommendations. Provide comprehensive guidance.';
|
||||
|
||||
return `You are a professional fitness trainer and nutritionist with access to the user's complete fitness journey.
|
||||
|
||||
## User Profile
|
||||
- Height: ${profile.height || 'Not specified'} cm
|
||||
- Weight: ${profile.weight || 'Not specified'} kg
|
||||
- Age: ${profile.age || 'Not specified'}
|
||||
- Gender: ${profile.gender || 'Not specified'}
|
||||
- Primary Goal: ${profile.fitnessGoal || 'General fitness'}
|
||||
- Activity Level: ${profile.activityLevel || 'Not specified'}
|
||||
- Medical Conditions: ${profile.medicalConditions || 'None'}
|
||||
- Allergies: ${profile.allergies || 'None'}
|
||||
- Injuries: ${profile.injuries || 'None'}
|
||||
|
||||
## Active Goals (${activeGoals.length})
|
||||
${activeGoalsText}
|
||||
|
||||
## Recent Progress
|
||||
- Goals Completed: ${progressSummary.goalsCompleted}
|
||||
- Active Goals: ${progressSummary.goalsActive}
|
||||
- Average Progress on Active Goals: ${progressSummary.averageProgress.toFixed(1)}%
|
||||
${completedGoals.length > 0 ? `- Recently Completed: ${completedGoalsText}` : ''}
|
||||
|
||||
## Recommendation History
|
||||
${recommendationContextText}
|
||||
|
||||
## Task
|
||||
Generate a personalized daily recommendation that:
|
||||
1. Acknowledges their progress on active goals
|
||||
2. Provides specific, actionable steps toward goal completion
|
||||
3. Builds on previous recommendations without being repetitive
|
||||
4. Adapts to their current progress level
|
||||
5. Takes into account their medical conditions, allergies, and injuries
|
||||
|
||||
Respond in the following JSON format ONLY, no other text. Do not use markdown formatting or code blocks:
|
||||
{
|
||||
"recommendationText": "Personalized motivation and progress acknowledgment (2-3 sentences)",
|
||||
"activityPlan": "Specific workout plan aligned with active goals (detailed, 3-5 exercises or activities)",
|
||||
"dietPlan": "Nutrition plan supporting their goals and restrictions (detailed, include meals and portions)"
|
||||
}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build basic prompt for users without complete context
|
||||
* Fallback when goals or other data is missing
|
||||
*/
|
||||
export function buildBasicPrompt(profile: any): string {
|
||||
return `You are a professional fitness trainer and nutritionist.
|
||||
Generate a detailed daily recommendation for a user with the following profile:
|
||||
- Height: ${profile.height} cm
|
||||
- Weight: ${profile.weight} kg
|
||||
- Age: ${profile.age}
|
||||
- Gender: ${profile.gender}
|
||||
- Goal: ${profile.fitnessGoal}
|
||||
- Activity Level: ${profile.activityLevel}
|
||||
- Medical Conditions: ${profile.medicalConditions || "None"}
|
||||
- Injuries: ${profile.injuries || "None"}
|
||||
|
||||
Please provide the response in the following JSON format ONLY, no other text. Do not use markdown formatting or code blocks:
|
||||
{
|
||||
"recommendationText": "General advice and motivation for today.",
|
||||
"activityPlan": "Detailed workout or activity plan for today.",
|
||||
"dietPlan": "Detailed meal plan for today."
|
||||
}`;
|
||||
}
|
||||
@@ -614,6 +614,174 @@ export class SQLiteDatabase implements IDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
// Fitness Goals operations
|
||||
async createFitnessGoal(goalData: Omit<import('./types').FitnessGoal, 'createdAt' | 'updatedAt'>): Promise<import('./types').FitnessGoal> {
|
||||
if (!this.db) throw new Error('Database not connected')
|
||||
|
||||
const now = new Date()
|
||||
const goal: import('./types').FitnessGoal = {
|
||||
...goalData,
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
}
|
||||
|
||||
const stmt = this.db.prepare(
|
||||
`INSERT INTO fitness_goals (
|
||||
id, user_id, fitness_profile_id, goal_type, title, description,
|
||||
target_value, current_value, unit, start_date, target_date,
|
||||
completed_date, status, progress, priority, notes, created_at, updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
)
|
||||
|
||||
stmt.run(
|
||||
goal.id,
|
||||
goal.userId,
|
||||
goal.fitnessProfileId || null,
|
||||
goal.goalType,
|
||||
goal.title,
|
||||
goal.description || null,
|
||||
goal.targetValue || null,
|
||||
goal.currentValue || null,
|
||||
goal.unit || null,
|
||||
goal.startDate.toISOString(),
|
||||
goal.targetDate ? goal.targetDate.toISOString() : null,
|
||||
goal.completedDate ? goal.completedDate.toISOString() : null,
|
||||
goal.status,
|
||||
goal.progress,
|
||||
goal.priority,
|
||||
goal.notes || null,
|
||||
goal.createdAt.toISOString(),
|
||||
goal.updatedAt.toISOString()
|
||||
)
|
||||
|
||||
return goal
|
||||
}
|
||||
|
||||
async getFitnessGoalById(id: string): Promise<import('./types').FitnessGoal | null> {
|
||||
if (!this.db) throw new Error('Database not connected')
|
||||
|
||||
const stmt = this.db.prepare('SELECT * FROM fitness_goals WHERE id = ?')
|
||||
const row = stmt.get(id)
|
||||
|
||||
return row ? this.mapRowToFitnessGoal(row) : null
|
||||
}
|
||||
|
||||
async getFitnessGoalsByUserId(userId: string, status?: string): Promise<import('./types').FitnessGoal[]> {
|
||||
if (!this.db) throw new Error('Database not connected')
|
||||
|
||||
let query = 'SELECT * FROM fitness_goals WHERE user_id = ?'
|
||||
const params: any[] = [userId]
|
||||
|
||||
if (status) {
|
||||
query += ' AND status = ?'
|
||||
params.push(status)
|
||||
}
|
||||
|
||||
query += ' ORDER BY created_at DESC'
|
||||
|
||||
const stmt = this.db.prepare(query)
|
||||
const rows = stmt.all(...params)
|
||||
|
||||
return rows.map(row => this.mapRowToFitnessGoal(row))
|
||||
}
|
||||
|
||||
async updateFitnessGoal(id: string, updates: Partial<import('./types').FitnessGoal>): Promise<import('./types').FitnessGoal | null> {
|
||||
if (!this.db) throw new Error('Database not connected')
|
||||
|
||||
const fields = Object.keys(updates).filter(key => key !== 'id' && key !== 'createdAt')
|
||||
if (fields.length === 0) return this.getFitnessGoalById(id)
|
||||
|
||||
// Map camelCase to snake_case for database columns
|
||||
const columnMap: Record<string, string> = {
|
||||
userId: 'user_id',
|
||||
fitnessProfileId: 'fitness_profile_id',
|
||||
goalType: 'goal_type',
|
||||
targetValue: 'target_value',
|
||||
currentValue: 'current_value',
|
||||
startDate: 'start_date',
|
||||
targetDate: 'target_date',
|
||||
completedDate: 'completed_date',
|
||||
updatedAt: 'updated_at'
|
||||
}
|
||||
|
||||
const setClause = fields.map(field => `${columnMap[field] || field} = ?`).join(', ')
|
||||
const values = fields.map(field => {
|
||||
const val = (updates as any)[field]
|
||||
return val instanceof Date ? val.toISOString() : val
|
||||
})
|
||||
values.push(new Date().toISOString()) // updatedAt
|
||||
values.push(id)
|
||||
|
||||
const stmt = this.db.prepare(`UPDATE fitness_goals SET ${setClause}, updated_at = ? WHERE id = ?`)
|
||||
stmt.run(values)
|
||||
|
||||
return this.getFitnessGoalById(id)
|
||||
}
|
||||
|
||||
async deleteFitnessGoal(id: string): Promise<boolean> {
|
||||
if (!this.db) throw new Error('Database not connected')
|
||||
|
||||
const stmt = this.db.prepare('DELETE FROM fitness_goals WHERE id = ?')
|
||||
const result = stmt.run(id)
|
||||
return (result.changes || 0) > 0
|
||||
}
|
||||
|
||||
async updateGoalProgress(id: string, currentValue: number): Promise<import('./types').FitnessGoal | null> {
|
||||
if (!this.db) throw new Error('Database not connected')
|
||||
|
||||
// Get the goal to calculate progress
|
||||
const goal = await this.getFitnessGoalById(id)
|
||||
if (!goal) return null
|
||||
|
||||
let progress = goal.progress
|
||||
if (goal.targetValue && goal.targetValue > 0) {
|
||||
progress = Math.min(100, (currentValue / goal.targetValue) * 100)
|
||||
}
|
||||
|
||||
const stmt = this.db.prepare(
|
||||
'UPDATE fitness_goals SET current_value = ?, progress = ?, updated_at = ? WHERE id = ?'
|
||||
)
|
||||
stmt.run(currentValue, progress, new Date().toISOString(), id)
|
||||
|
||||
return this.getFitnessGoalById(id)
|
||||
}
|
||||
|
||||
async completeGoal(id: string): Promise<import('./types').FitnessGoal | null> {
|
||||
if (!this.db) throw new Error('Database not connected')
|
||||
|
||||
const now = new Date()
|
||||
const stmt = this.db.prepare(
|
||||
'UPDATE fitness_goals SET status = ?, progress = ?, completed_date = ?, updated_at = ? WHERE id = ?'
|
||||
)
|
||||
stmt.run('completed', 100, now.toISOString(), now.toISOString(), id)
|
||||
|
||||
return this.getFitnessGoalById(id)
|
||||
}
|
||||
|
||||
private mapRowToFitnessGoal(row: any): import('./types').FitnessGoal {
|
||||
return {
|
||||
id: row.id,
|
||||
userId: row.user_id,
|
||||
fitnessProfileId: row.fitness_profile_id,
|
||||
goalType: row.goal_type,
|
||||
title: row.title,
|
||||
description: row.description,
|
||||
targetValue: row.target_value,
|
||||
currentValue: row.current_value,
|
||||
unit: row.unit,
|
||||
startDate: new Date(row.start_date),
|
||||
targetDate: row.target_date ? new Date(row.target_date) : undefined,
|
||||
completedDate: row.completed_date ? new Date(row.completed_date) : undefined,
|
||||
status: row.status,
|
||||
progress: row.progress,
|
||||
priority: row.priority,
|
||||
notes: row.notes,
|
||||
createdAt: new Date(row.created_at),
|
||||
updatedAt: new Date(row.updated_at)
|
||||
}
|
||||
}
|
||||
|
||||
async getDashboardStats(): Promise<{
|
||||
totalUsers: number;
|
||||
activeClients: number;
|
||||
|
||||
@@ -61,6 +61,27 @@ export interface Recommendation {
|
||||
approvedBy?: string;
|
||||
}
|
||||
|
||||
export interface FitnessGoal {
|
||||
id: string;
|
||||
userId: string;
|
||||
fitnessProfileId?: string;
|
||||
goalType: "weight_target" | "strength_milestone" | "endurance_target" | "flexibility_goal" | "habit_building" | "custom";
|
||||
title: string;
|
||||
description?: string;
|
||||
targetValue?: number;
|
||||
currentValue?: number;
|
||||
unit?: string;
|
||||
startDate: Date;
|
||||
targetDate?: Date;
|
||||
completedDate?: Date;
|
||||
status: "active" | "completed" | "abandoned" | "paused";
|
||||
progress: number;
|
||||
priority: "low" | "medium" | "high";
|
||||
notes?: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
// Database Interface - allows us to swap implementations
|
||||
export interface IDatabase {
|
||||
// Connection management
|
||||
@@ -121,6 +142,20 @@ export interface IDatabase {
|
||||
): Promise<Recommendation | null>;
|
||||
deleteRecommendation(id: string): Promise<boolean>;
|
||||
|
||||
// Fitness Goals operations
|
||||
createFitnessGoal(
|
||||
goal: Omit<FitnessGoal, "createdAt" | "updatedAt">
|
||||
): Promise<FitnessGoal>;
|
||||
getFitnessGoalById(id: string): Promise<FitnessGoal | null>;
|
||||
getFitnessGoalsByUserId(userId: string, status?: string): Promise<FitnessGoal[]>;
|
||||
updateFitnessGoal(
|
||||
id: string,
|
||||
updates: Partial<FitnessGoal>
|
||||
): Promise<FitnessGoal | null>;
|
||||
deleteFitnessGoal(id: string): Promise<boolean>;
|
||||
updateGoalProgress(id: string, currentValue: number): Promise<FitnessGoal | null>;
|
||||
completeGoal(id: string): Promise<FitnessGoal | null>;
|
||||
|
||||
// Dashboard operations
|
||||
getDashboardStats(): Promise<{
|
||||
totalUsers: number;
|
||||
|
||||
Reference in new issue
Block a user