AI coach fixed: acquired access to context data

This commit is contained in:
aodulov
2025-11-27 07:51:36 +02:00
parent 4f3a889410
commit 1ef85d7a58
4 changed files with 186 additions and 46 deletions

View File

@@ -1,49 +1,119 @@
import { WorkoutSession } from '../types';
import { WorkoutSession, UserProfile, WorkoutPlan } from '../types';
import { api } from './api';
export const createFitnessChat = (history: WorkoutSession[], lang: 'en' | 'ru' = 'ru'): any => {
// The original returned a Chat object.
// Now we need to return something that behaves like it or refactor the UI.
// The UI likely calls `chat.sendMessage(msg)`.
// So we return an object with `sendMessage`.
interface FitnessChatOptions {
history: WorkoutSession[];
userProfile?: UserProfile;
plans?: WorkoutPlan[];
lang?: 'en' | 'ru';
sessionId?: string;
}
// Summarize data to reduce token count while keeping relevant context
const summary = history.slice(0, 10).map(s => ({
export const createFitnessChat = (
history: WorkoutSession[],
lang: 'en' | 'ru' = 'ru',
userProfile?: UserProfile,
plans?: WorkoutPlan[]
): any => {
// Generate a unique session ID for this chat instance
const sessionId = crypto.randomUUID();
// Summarize workout history
const workoutSummary = history.slice(0, 20).map(s => ({
date: new Date(s.startTime).toLocaleDateString(lang === 'ru' ? 'ru-RU' : 'en-US'),
userWeight: s.userBodyWeight,
exercises: s.sets.map(set => `${set.exerciseName}: ${set.weight ? set.weight + (lang === 'ru' ? 'кг' : 'kg') : ''}${set.reps ? ' x ' + set.reps + (lang === 'ru' ? 'повт' : 'reps') : ''} ${set.distanceMeters ? set.distanceMeters + (lang === 'ru' ? 'м' : 'm') : ''}`).join(', ')
planName: s.planName,
exercises: s.sets.map(set => {
const parts = [];
parts.push(set.exerciseName);
if (set.weight) parts.push(`${set.weight}${lang === 'ru' ? 'кг' : 'kg'}`);
if (set.reps) parts.push(`${set.reps}${lang === 'ru' ? 'повт' : 'reps'}`);
if (set.distanceMeters) parts.push(`${set.distanceMeters}${lang === 'ru' ? 'м' : 'm'}`);
if (set.durationSeconds) parts.push(`${set.durationSeconds}${lang === 'ru' ? 'сек' : 's'}`);
return parts.join(' ');
}).join(', ')
}));
// Calculate personal records
const exerciseRecords = new Map<string, { maxWeight?: number; maxReps?: number; maxDistance?: number }>();
history.forEach(session => {
session.sets.forEach(set => {
const current = exerciseRecords.get(set.exerciseName) || {};
exerciseRecords.set(set.exerciseName, {
maxWeight: Math.max(current.maxWeight || 0, set.weight || 0),
maxReps: Math.max(current.maxReps || 0, set.reps || 0),
maxDistance: Math.max(current.maxDistance || 0, set.distanceMeters || 0)
});
});
});
const personalRecords = Array.from(exerciseRecords.entries()).map(([name, records]) => ({
exercise: name,
...records
}));
// Build comprehensive system instruction
const systemInstruction = lang === 'ru' ? `
Ты — опытный и поддерживающий фитнес-тренер.
Ты — опытный и поддерживающий фитнес-тренер AI Expert.
Твоя задача — анализировать тренировки пользователя и давать краткие, полезные советы на русском языке.
Учитывай вес пользователя (userWeight в json), если он указан, при анализе прогресса в упражнениях с собственным весом.
ДАННЫЕ ПОЛЬЗОВАТЕЛЯ:
${userProfile ? `
- Вес: ${userProfile.weight || 'не указан'} кг
- Рост: ${userProfile.height || 'не указан'} см
- Пол: ${userProfile.gender || 'не указан'}
` : 'Профиль не заполнен'}
Вот последние 10 тренировок пользователя (в формате JSON):
${JSON.stringify(summary)}
Если пользователь спрашивает о прогрессе, используй эти данные.
Отвечай емко, мотивирующе. Избегай длинных лекций, если не просили.
ТРЕНИРОВОЧНЫЕ ПЛАНЫ:
${plans && plans.length > 0 ? JSON.stringify(plans.map(p => ({ name: p.name, exercises: p.steps.map(s => s.exerciseName) }))) : 'Нет активных планов'}
ИСТОРИЯ ТРЕНИРОВОК (последние 20):
${JSON.stringify(workoutSummary, null, 2)}
ЛИЧНЫЕ РЕКОРДЫ:
${JSON.stringify(personalRecords, null, 2)}
ИНСТРУКЦИИ:
- Используй эти данные для анализа прогресса и ответов на вопросы
- Учитывай вес пользователя при анализе упражнений с собственным весом
- Будь конкретным и ссылайся на реальные данные из истории
- Отвечай емко, мотивирующе
- Если данных недостаточно для ответа, честно скажи об этом
` : `
You are an experienced and supportive fitness coach.
You are an experienced and supportive fitness coach called AI Expert.
Your task is to analyze the user's workouts and provide concise, helpful advice in English.
Consider the user's weight (userWeight in json), if provided, when analyzing progress in bodyweight exercises.
USER PROFILE:
${userProfile ? `
- Weight: ${userProfile.weight || 'not specified'} kg
- Height: ${userProfile.height || 'not specified'} cm
- Gender: ${userProfile.gender || 'not specified'}
` : 'Profile not filled'}
Here are the user's last 10 workouts (in JSON format):
${JSON.stringify(summary)}
If the user asks about progress, use this data.
Answer concisely and motivationally. Avoid long lectures unless asked.
ALWAYS answer in the language the user speaks to you, defaulting to English if unsure.
WORKOUT PLANS:
${plans && plans.length > 0 ? JSON.stringify(plans.map(p => ({ name: p.name, exercises: p.steps.map(s => s.exerciseName) }))) : 'No active plans'}
WORKOUT HISTORY (last 20 sessions):
${JSON.stringify(workoutSummary, null, 2)}
PERSONAL RECORDS:
${JSON.stringify(personalRecords, null, 2)}
INSTRUCTIONS:
- Use this data to analyze progress and answer questions
- Consider user's weight when analyzing bodyweight exercises
- Be specific and reference actual data from the history
- Answer concisely and motivationally
- If there's insufficient data to answer, be honest about it
- ALWAYS answer in the language the user speaks to you
`;
return {
sendMessage: async (userMessage: string) => {
const res = await api.post('/ai/chat', {
systemInstruction,
userMessage
userMessage,
sessionId
});
return {
text: res.response,