feat: AI client and prompt templates
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
import OpenAI from 'openai';
|
||||
import { config } from '../config';
|
||||
|
||||
export function createAiClient(): OpenAI {
|
||||
return new OpenAI({
|
||||
apiKey: config.openrouterApiKey,
|
||||
baseURL: 'https://openrouter.ai/api/v1',
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import type { AIContext } from './context';
|
||||
import { formatDuration } from '../utils/duration';
|
||||
|
||||
function sessionLines(ctx: AIContext): string {
|
||||
if (ctx.recentSessions.length === 0) return 'No sessions recorded yet.';
|
||||
return ctx.recentSessions
|
||||
.map(s => ` ${s.date}: ${formatDuration(s.duration_min)}${s.notes ? ` (${s.notes})` : ''}`)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
function scheduleLines(ctx: AIContext): string {
|
||||
if (ctx.upcomingSchedule.length === 0) return 'No upcoming schedule.';
|
||||
return ctx.upcomingSchedule
|
||||
.map(e => ` ${e.date}: ${formatDuration(e.planned_min)} [${e.status}]`)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
function skipLines(ctx: AIContext): string {
|
||||
if (ctx.recentSkips.length === 0) return 'None.';
|
||||
return ctx.recentSkips
|
||||
.map(s => ` ${s.date}${s.reason ? `: ${s.reason}` : ''}`)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
function systemBase(ctx: AIContext): string {
|
||||
return `You are a supportive personal training coach. You help the user track and improve their anal training practice.
|
||||
|
||||
Today: ${ctx.today}
|
||||
Training goal: ${ctx.goal}
|
||||
Current streak: ${ctx.currentStreak} sessions
|
||||
|
||||
Recent sessions (newest first):
|
||||
${sessionLines(ctx)}
|
||||
|
||||
Upcoming schedule:
|
||||
${scheduleLines(ctx)}
|
||||
|
||||
Recent skips:
|
||||
${skipLines(ctx)}
|
||||
|
||||
Be encouraging but honest. Always explain your reasoning. Flag if you think the pace is too fast or too slow.`;
|
||||
}
|
||||
|
||||
export function progressPrompt(ctx: AIContext): { system: string; user: string } {
|
||||
return {
|
||||
system: systemBase(ctx),
|
||||
user: 'Analyse my recent training history. Give me a summary of my progress, highlight any patterns (positive or negative), and tell me how I\'m tracking toward my goal.',
|
||||
};
|
||||
}
|
||||
|
||||
export function nextStepPrompt(ctx: AIContext): { system: string; user: string } {
|
||||
return {
|
||||
system: systemBase(ctx),
|
||||
user: 'Based on my history, what should my next training step be? Should I increase duration, maintain, or take a rest? Explain your reasoning.',
|
||||
};
|
||||
}
|
||||
|
||||
export function reschedulePrompt(ctx: AIContext, note: string | null): { system: string; user: string } {
|
||||
const extra = note ? `\n\nAdditional context from the user: "${note}"` : '';
|
||||
return {
|
||||
system: systemBase(ctx),
|
||||
user: `Generate a training schedule for the next 14 days starting from ${ctx.today}.${extra}
|
||||
|
||||
Respond with ONLY a JSON array — no prose, no markdown explanation — in this exact format:
|
||||
[
|
||||
{ "date": "YYYY-MM-DD", "planned_min": 60, "rationale": "reason" }
|
||||
]
|
||||
|
||||
Only include days with planned sessions (rest days are omitted). Space sessions appropriately.`,
|
||||
};
|
||||
}
|
||||
|
||||
export function setupPrompt(
|
||||
currentLevel: string, availability: string, goal: string
|
||||
): { system: string; user: string } {
|
||||
return {
|
||||
system: 'You are a supportive personal training coach helping a new user set up their anal training plan. Be warm, practical, and non-judgmental.',
|
||||
user: `New user setup:
|
||||
- Current level: ${currentLevel}
|
||||
- Typical availability: ${availability}
|
||||
- Goal: ${goal}
|
||||
|
||||
Generate a 14-day starter schedule. Respond with ONLY a JSON array in this exact format:
|
||||
[
|
||||
{ "date": "YYYY-MM-DD", "planned_min": 30, "rationale": "Starting gently" }
|
||||
]`,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user