presentercoach / apps /web /src /lib /dashboard.ts
nathycodes's picture
Deploy Tech Presenter Coach
ec17af3
Raw History Blame Contribute Delete
6.05 kB
import { apiFetch } from "@/lib/api";
export type OverviewStats = {
total_sessions: number;
total_practice_minutes: number;
today_practice_minutes?: number;
current_streak: number;
longest_streak: number;
curriculum_completed_days?: number;
recent_30d: {
session_count: number;
avg_overall_score: number;
avg_wpm: number;
avg_filler_rate: number;
avg_delivery_score: number;
practice_days: number;
};
};
export type TrendPoint = {
date: string;
value: number;
};
export type DashboardSession = {
session_id: number;
recorded_at: string;
duration_seconds: number;
wpm: number;
filler_count: number;
filler_rate: number;
speech_score: number;
delivery_score: number;
overall_score: number;
eye_contact_score: number | null;
vocabulary_density: number;
};
export type HeatmapDay = {
date: string;
count: number;
minutes: number;
};
export type GoalMetric =
| "wpm"
| "filler_rate"
| "overall_score"
| "practice_days_per_week"
| "session_duration_minutes"
| "eye_contact_score"
| "delivery_score";
export type GoalComparison = "gte" | "lte" | "between";
export type UserGoal = {
id: number;
metric_name: GoalMetric;
comparison: GoalComparison;
target_value: number;
target_upper: number | null;
is_active: boolean;
current_value: number | null;
achieved: boolean;
created_at: string;
updated_at: string;
};
export type UserGoalInput = Pick<
UserGoal,
"metric_name" | "comparison" | "target_value" | "target_upper" | "is_active"
>;
export type CoachingFeedback = {
id?: number;
session_id?: number;
status: "not_available" | "pending" | "generating" | "completed" | "failed" | string;
summary?: string | null;
strengths?: Array<{ area: string; detail: string; score_contribution?: string }>;
improvements?: Array<{ area: string; detail: string; priority?: string; example?: string }>;
drills?: Array<{ name: string; description: string; duration_minutes?: number; targets?: string }>;
practice_plan?: {
focus_areas?: string[];
suggested_duration_minutes?: number;
difficulty?: string;
next_session_goal?: string;
};
trend_context?: Record<string, unknown>;
model_used?: string | null;
generation_time_ms?: number | null;
created_at?: string | null;
error_message?: string | null;
};
export type CoachingRetryResponse = {
status: "queued";
job_id: string;
};
export type SessionCompareSummary = {
id: number;
title: string;
mode: string;
created_at: string | null;
duration_ms: number;
video_available: boolean;
curriculum_day: number | null;
transcript_preview: string;
};
export type MetricDelta = {
a: number;
b: number;
delta: number;
};
export type SessionComparisonResponse = {
session_a: SessionCompareSummary;
session_b: SessionCompareSummary;
comparison: Record<string, MetricDelta>;
insights: string[];
};
export type WeeklyBucket = {
session_count: number;
active_days: number;
total_minutes: number;
avg_overall_score: number;
avg_wpm: number;
avg_filler_rate: number;
avg_delivery_score: number;
avg_eye_contact_pct: number;
best_session_id?: number | null;
best_score?: number;
};
export type WeeklyReviewResponse = {
week_start_date: string;
week_end_date: string;
current_week: WeeklyBucket;
previous_week: WeeklyBucket;
deltas: {
overall_score: number;
wpm: number;
filler_rate: number;
eye_contact_pct: number;
minutes: number;
};
top_fillers: Array<{ word: string; count: number }>;
wins: string[];
recommended_focus: string[];
reflection: {
wins_note: string;
focus_next_week: string;
};
};
export const dashboardApi = {
overview: () => apiFetch<OverviewStats>("/dashboard/overview"),
trend: (metric: string, days = 90) => apiFetch<TrendPoint[]>(`/dashboard/trends/${metric}?days=${days}`),
sessions: (limit = 20, offset = 0) =>
apiFetch<DashboardSession[]>(`/dashboard/sessions?limit=${limit}&offset=${offset}`),
heatmap: (days = 365) => apiFetch<HeatmapDay[]>(`/dashboard/heatmap?days=${days}`),
fillers: (lastN = 20) => apiFetch<Record<string, number>>(`/dashboard/fillers?last_n=${lastN}`),
compare: (sessionA: number, sessionB: number) =>
apiFetch<SessionComparisonResponse>(`/dashboard/compare?session_a=${sessionA}&session_b=${sessionB}`),
weeklyReview: (weeksAgo = 0) =>
apiFetch<WeeklyReviewResponse>(`/dashboard/weekly-review?weeks_ago=${weeksAgo}`),
saveWeeklyNote: (payload: { week_start_date: string; wins_note: string; focus_next_week: string }) =>
apiFetch<{ week_start_date: string; wins_note: string; focus_next_week: string; updated_at: string }>(
"/dashboard/weekly-review/note",
{ method: "PUT", body: JSON.stringify(payload) }
),
goals: () => apiFetch<UserGoal[]>("/dashboard/goals"),
createGoal: (goal: UserGoalInput) =>
apiFetch<UserGoal>("/dashboard/goals", { method: "POST", body: JSON.stringify(goal) }),
updateGoal: (goalId: number, goal: Partial<Omit<UserGoalInput, "metric_name">>) =>
apiFetch<UserGoal>(`/dashboard/goals/${goalId}`, { method: "PATCH", body: JSON.stringify(goal) }),
deleteGoal: (goalId: number) =>
apiFetch<{ status: "deleted" }>(`/dashboard/goals/${goalId}`, { method: "DELETE" }),
};
export const coachingApi = {
latest: () => apiFetch<CoachingFeedback>("/coaching/latest"),
session: (sessionId: number, auto = false) =>
apiFetch<CoachingFeedback>(`/coaching/session/${sessionId}${auto ? "?auto=true" : ""}`),
history: (limit = 10) => apiFetch<CoachingFeedback[]>(`/coaching/history?limit=${limit}`),
retry: (sessionId: number) =>
apiFetch<CoachingRetryResponse>(`/coaching/session/${sessionId}/retry`, { method: "POST" }),
};
export const isCoachingAvailable = (feedback: CoachingFeedback | null | undefined) =>
Boolean(feedback && feedback.status !== "not_available");
export const isCoachingPending = (feedback: CoachingFeedback | null | undefined) =>
feedback?.status === "pending" || feedback?.status === "generating";