Files
pwa/src/lib/aiCoach/sessionContext.ts
T

113 lines
3.4 KiB
TypeScript
Raw Normal View History

/**
* AI Coach — Educational Session Context (architecture prep)
*
* Resolves the learner's current session and packages it as context
* for a future AI Coach. Session data is knowledge for the AI, not UI.
*
* Does NOT call any AI provider or send messages.
*/
import {
getSessionByOrder,
PROGRAM_SESSION_COUNT,
type ProgramSession,
type ContentBlock
} from '@/data/program/sessions';
import {
isProgramCompleted,
isSessionCompleted,
isSessionUnlocked,
isWaitingForNextSession,
getProgramProgress,
} from '@/services/programService';
/**
* Structured payload the future AI Coach will receive.
* Built from course data; not rendered in Chat UI.
*/
export interface AiCoachSessionContext {
sessionId: string;
order: number;
title: string;
description: string;
estimatedDurationMinutes: number;
objectivesHint: string;
/** Full session blocks — teaching material for the AI, not for direct display */
blocks: ContentBlock[];
totalSessions: number;
isFinalSession: boolean;
/** ISO timestamp when the next session becomes available (only during cooldown) */
nextSessionUnlocksAt?: string | null;
}
/**
* First unlocked incomplete session (sequential).
* Uses existing unlock/completion helpers — does not skip locked sessions.
*/
export function getCurrentEducationalSession(userId: string): ProgramSession | null {
if (isProgramCompleted(userId)) return null;
for (let order = 1; order <= PROGRAM_SESSION_COUNT; order++) {
if (isSessionCompleted(userId, order)) continue;
if (!isSessionUnlocked(userId, order)) break;
return getSessionByOrder(order) ?? null;
}
return null;
}
/**
* Package a ProgramSession for future AI Coach injection.
* Chat must not render this as a content panel.
*/
export function buildAiCoachSessionContext(
session: ProgramSession,
extras?: { nextSessionUnlocksAt?: string | null },
): AiCoachSessionContext {
return {
sessionId: session.id,
order: session.order,
title: session.title,
description: session.description,
estimatedDurationMinutes: session.estimatedDurationMinutes,
objectivesHint: session.description,
blocks: session.blocks,
totalSessions: PROGRAM_SESSION_COUNT,
isFinalSession: session.order >= PROGRAM_SESSION_COUNT,
nextSessionUnlocksAt: extras?.nextSessionUnlocksAt ?? null,
};
}
/**
* Resolve + package current session context for a user.
* Returns null when the program is finished or no session is available.
*/
export function resolveAiCoachSessionContext(
userId: string
): AiCoachSessionContext | null {
const session = getCurrentEducationalSession(userId);
if (!session) return null;
const progress = getProgramProgress(userId);
return buildAiCoachSessionContext(session, {
nextSessionUnlocksAt: isWaitingForNextSession(userId) ? progress.nextSessionUnlocksAt : null,
});
}
export function getWaitingForNextSession(userId: string): {
sessionOrder: number;
unlocksAt: string;
title: string;
} | null {
const progress = getProgramProgress(userId);
if (!isWaitingForNextSession(userId) || !progress.nextSessionUnlocksAt || !progress.waitingForSessionOrder) {
return null;
}
const session = getSessionByOrder(progress.waitingForSessionOrder);
return {
sessionOrder: progress.waitingForSessionOrder,
unlocksAt: progress.nextSessionUnlocksAt,
title: session?.title ?? `جلسه ${progress.waitingForSessionOrder}`,
};
}