113 lines
3.4 KiB
TypeScript
113 lines
3.4 KiB
TypeScript
/**
|
|||
|
|
* 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}`,
|
||
|
|
};
|
||
|
|
}
|