presentercoach / apps /web /src /lib /presenter.ts
nathycodes's picture
Deploy Tech Presenter Coach
ec17af3
Raw History Blame Contribute Delete
5.41 kB
import type { StudioLaunchContext } from "@video/shared";
import { apiFetch } from "@/lib/api";
export type PresenterProfile = {
display_name: string;
primary_role: string;
focus_track: string;
experience_level: string;
target_wpm_min: number;
target_wpm_max: number;
daily_minutes_goal: number;
weaknesses: string[];
};
export type CurriculumDay = {
day: number;
week: number;
week_title: string;
title: string;
focus_skill: string;
duration_min: number;
mode: string;
warmup: string;
camera_challenge: string;
prompt: string;
teleprompter_script: string;
bullet_cues: string[];
target_keywords: string[];
success_metric: string;
personalized_tip?: string | null;
completed: boolean;
completed_session_id: number | null;
best_score: number | null;
completed_at: string | null;
reflection_notes: string;
};
export type CurriculumPlanResponse = {
profile: PresenterProfile;
summary: {
completed_days: number;
total_days: number;
completion_pct: number;
recommended_day: number;
current_week: number;
};
weeks: Array<{
week: number;
title: string;
theme: string;
completed_count: number;
total_count: number;
}>;
today_mission: CurriculumDay;
days: CurriculumDay[];
};
export type WarmupExercise = {
id: string;
title: string;
category: string;
duration_seconds: number;
instructions: string;
script: string;
};
export type CameraChallenge = {
id: string;
title: string;
mode: string;
duration_seconds: number;
difficulty: string;
focus: string;
prompt: string;
teleprompter_script: string;
bullet_cues: string[];
target_keywords: string[];
};
export type TargetedExercise = {
id: string;
title: string;
priority: "high" | "medium" | "low" | string;
target_metric: string;
why_recommended: string;
mode: string;
duration_seconds: number;
prompt: string;
teleprompter_script: string;
bullet_cues: string[];
target_keywords: string[];
};
export type ExercisesResponse = {
recent_averages: {
session_count: number;
avg_wpm: number;
avg_filler_rate: number;
avg_eye_contact_pct: number;
avg_delivery_score: number;
avg_overall_score: number;
};
targeted_exercises: TargetedExercise[];
daily_warmups: WarmupExercise[];
camera_challenges: CameraChallenge[];
};
export type TechNewsItem = {
id: string;
headline: string;
summary: string;
source: string;
category: string;
url: string;
published_at: string;
why_it_matters?: string;
talking_points?: string[];
key_terms?: string[];
is_live: boolean;
};
export type StoryFrameworkOption = {
id: string;
label: string;
target_seconds: number;
target_words: number;
description: string;
};
export type TechNewsFeedResponse = {
live_connected: boolean;
live_count: number;
categories: string[];
frameworks: StoryFrameworkOption[];
stories: TechNewsItem[];
};
export type StoryboardSection = {
beat: string;
timing: string;
guidance: string;
script: string;
};
export type StoryboardResponse = {
headline: string;
category: string;
framework: string;
framework_label: string;
target_seconds: number;
target_words: number;
sections: StoryboardSection[];
teleprompter_script: string;
bullet_cues: string[];
target_keywords: string[];
camera_tips: string[];
};
export const curriculumApi = {
getProfile: () => apiFetch<PresenterProfile>("/curriculum/profile"),
updateProfile: (payload: PresenterProfile) =>
apiFetch<PresenterProfile>("/curriculum/profile", {
method: "PUT",
body: JSON.stringify(payload),
}),
getPlan: () => apiFetch<CurriculumPlanResponse>("/curriculum/plan"),
completeDay: (
dayNumber: number,
payload: { session_id?: number; score?: number; reflection_notes?: string }
) =>
apiFetch(`/curriculum/days/${dayNumber}/complete`, {
method: "POST",
body: JSON.stringify(payload),
}),
getExercises: () => apiFetch<ExercisesResponse>("/curriculum/exercises"),
};
export const newsApi = {
getFeed: (category?: string, refresh = false) => {
const params = new URLSearchParams();
if (category && category !== "All") params.set("category", category);
if (refresh) params.set("refresh", "true");
const qs = params.toString();
return apiFetch<TechNewsFeedResponse>(`/news/feed${qs ? `?${qs}` : ""}`);
},
generateStoryboard: (payload: {
headline: string;
summary?: string;
source?: string;
url?: string;
category?: string;
framework: string;
personal_angle?: string;
}) =>
apiFetch<StoryboardResponse>("/news/storyboard", {
method: "POST",
body: JSON.stringify(payload),
}),
};
const STUDIO_LAUNCH_KEY = "sm_studio_launch_context";
export const saveStudioLaunchContext = (ctx: StudioLaunchContext) => {
if (typeof window === "undefined") return;
try {
window.sessionStorage.setItem(STUDIO_LAUNCH_KEY, JSON.stringify(ctx));
} catch {
// Ignore storage errors
}
};
export const consumeStudioLaunchContext = (): StudioLaunchContext | null => {
if (typeof window === "undefined") return null;
try {
const raw = window.sessionStorage.getItem(STUDIO_LAUNCH_KEY);
if (!raw) return null;
window.sessionStorage.removeItem(STUDIO_LAUNCH_KEY);
return JSON.parse(raw) as StudioLaunchContext;
} catch {
return null;
}
};