uchill/front_material/api/homework.ts

318 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* API модуль для домашних заданий
*/
import apiClient from '@/lib/api-client';
export interface HomeworkMentor {
id: number;
email: string;
first_name: string;
last_name: string;
}
/** Файл задания/решения (ментор прикрепляет к заданию, ученик видит и скачивает). */
export interface HomeworkFileItem {
id: number;
file_type: 'assignment' | 'submission' | 'feedback';
file: string;
filename: string;
file_size: number;
/** Признак изображения по расширению (с бэкенда) — показывать превью и открывать в модалке. */
is_image?: boolean;
uploaded_by?: { id: number; first_name: string; last_name: string } | null;
created_at: string;
}
export interface Homework {
id: number;
title: string;
description?: string;
mentor: HomeworkMentor;
lesson: number | null;
deadline: string | null;
max_score: number;
passing_score: number;
status: 'draft' | 'published' | 'archived';
/** Черновик «заполнить позже» — создан при завершении урока, нужно дописать задание. */
fill_later?: boolean;
total_submissions: number;
checked_submissions: number;
returned_submissions: number;
average_score: number;
is_overdue: boolean;
created_at: string;
published_at: string | null;
/** Файл задания (один), URL для скачивания. */
attachment?: string | null;
/** Ссылка на материал (внешняя). */
attachment_url?: string | null;
/** Дополнительные файлы задания (ментор прикрепляет несколько). */
files?: HomeworkFileItem[] | null;
students?: { id: number; first_name: string; last_name: string; score: number | null; status: string }[] | null;
student_score?: { score: number | null; max_score: number; status: string } | null;
/** Только для ментора: количество решений с черновиком от ИИ (status=pending, ai_checked_at задано). */
ai_draft_count?: number;
}
export interface HomeworkSubmission {
id: number;
homework: { id: number; title: string; description?: string; max_score: number };
student: { id: number; first_name: string; last_name: string; email: string };
status: string;
content?: string;
/** Основной файл решения (URL для скачивания). */
attachment?: string | null;
attachment_url?: string | null;
/** Доп. файлы решения (студент прикрепляет несколько). */
files?: HomeworkFileItem[] | null;
score?: number | null;
feedback?: string;
/** HTML комментария проверки (markdown → HTML). */
feedback_html?: string;
submitted_at: string;
checked_at?: string | null;
ai_score?: number | null;
ai_feedback?: string;
/** HTML превью черновика ИИ (markdown → HTML). */
ai_feedback_html?: string;
ai_checked_at?: string | null;
/** True, если оценка опубликована автоматически через ИИ. */
graded_by_ai?: boolean;
checked_by?: { id: number; first_name: string; last_name: string } | null;
}
export async function getHomework(params?: {
status?: string;
page_size?: number;
child_id?: string;
}): Promise<{ results: Homework[]; count: number }> {
const q = new URLSearchParams();
if (params?.status) q.append('status', params.status);
if (params?.page_size) q.append('page_size', String(params.page_size || 1000));
if (params?.child_id) q.append('child_id', params.child_id);
const query = q.toString();
const url = `/homework/homeworks/${query ? `?${query}` : ''}`;
const res = await apiClient.get<{ results: Homework[]; count: number } | Homework[]>(url);
const data = res.data;
if (Array.isArray(data)) {
return { results: data, count: data.length };
}
return {
results: data?.results ?? [],
count: data?.count ?? 0,
};
}
export async function getHomeworkById(id: string | number): Promise<Homework> {
const res = await apiClient.get<Homework>(`/homework/homeworks/${id}/`);
return res.data;
}
/** Создать домашнее задание (в т.ч. черновик для «заполнить позже»). По умолчанию макс. балл 5, проходной 1 (не учитывается). */
export async function createHomework(data: {
title: string;
description?: string;
lesson_id?: number;
status?: 'draft' | 'published';
/** Пометить как «заполнить позже» — отображается в колонке «Ожидают заполнения» у ментора. */
fill_later?: boolean;
/** Максимальный балл (15 по умолчанию). По умолчанию 5. */
max_score?: number;
/** Проходной балл (по умолчанию 1, не учитывается). */
passing_score?: number;
}): Promise<Homework> {
const payload = {
...data,
max_score: data.max_score ?? 5,
passing_score: data.passing_score ?? 1,
};
const res = await apiClient.post<Homework>('/homework/homeworks/', payload);
return res.data;
}
/** Опции запроса списка решений (например, отключить кэш для актуальных данных). */
export interface GetHomeworkSubmissionsOptions {
cache?: boolean;
/** Для родителя: user_id ребёнка — вернуть решения этого ребёнка. */
child_id?: string | null;
}
export async function getHomeworkSubmissions(
homeworkId: string | number,
options?: GetHomeworkSubmissionsOptions
): Promise<HomeworkSubmission[]> {
const params = new URLSearchParams({ homework_id: String(homeworkId) });
if (options?.child_id) params.append('child_id', options.child_id);
const res = await apiClient.get<{ results: HomeworkSubmission[] } | HomeworkSubmission[]>(
`/homework/submissions/?${params.toString()}`,
{ cache: options?.cache ?? false }
);
const data = res.data;
if (Array.isArray(data)) return data;
return data?.results ?? [];
}
export async function getMySubmission(
homeworkId: string | number,
options?: GetHomeworkSubmissionsOptions
): Promise<HomeworkSubmission | null> {
const list = await getHomeworkSubmissions(homeworkId, options);
return list.length > 0 ? list[0] : null;
}
/** Получить одно решение по ID (для детального просмотра). */
export async function getHomeworkSubmission(
submissionId: string | number
): Promise<HomeworkSubmission> {
const res = await apiClient.get<HomeworkSubmission>(
`/homework/submissions/${submissionId}/`
);
return res.data;
}
/**
* ДЗ с оценками по предмету для графика прогресса.
* GET /api/homework/submissions/by_subject/
*/
export async function getHomeworkSubmissionsBySubject(params: {
subject: string;
start_date?: string;
end_date?: string;
child_id?: string;
}): Promise<{ count: number; results: HomeworkSubmission[] }> {
const q = new URLSearchParams();
q.append('subject', params.subject);
if (params.start_date) q.append('start_date', params.start_date);
if (params.end_date) q.append('end_date', params.end_date);
if (params.child_id) q.append('child_id', params.child_id);
const res = await apiClient.get<{ count: number; results: HomeworkSubmission[] }>(
`/homework/submissions/by_subject/?${q}`
);
return res.data;
}
export async function gradeSubmission(
submissionId: string | number,
data: { score: number; feedback?: string }
): Promise<unknown> {
const res = await apiClient.post(`/homework/submissions/${submissionId}/grade/`, data);
return res.data;
}
/** Использование токенов за один запрос (если API вернул). */
export interface TokenUsage {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
}
export interface CheckWithAiResponse {
success: boolean;
ai_score: number;
ai_feedback: string;
/** HTML для отображения комментария ИИ (markdown + LaTeX → HTML). */
ai_feedback_html?: string;
ai_checked_at?: string;
message?: string;
/** Токены за эту проверку (потрачено). Остаток лимита — в кабинете Timeweb. */
usage?: TokenUsage;
}
/** Проверить решение через ИИ. Ментор: задание + решение → комментарий и оценка 15. */
export async function checkSubmissionWithAi(
submissionId: string | number
): Promise<CheckWithAiResponse> {
const res = await apiClient.post<CheckWithAiResponse>(
`/homework/submissions/${submissionId}/check_with_ai/`
);
return res.data;
}
export async function returnSubmissionForRevision(
submissionId: string | number,
feedback: string
): Promise<unknown> {
const res = await apiClient.post(`/homework/submissions/${submissionId}/return_for_revision/`, { feedback });
return res.data;
}
/** Удалить своё решение (студент). Задание снова переходит в ожидание загрузки. */
export async function deleteSubmission(submissionId: string | number): Promise<void> {
await apiClient.delete(`/homework/submissions/${submissionId}/`);
}
const MAX_HOMEWORK_FILE_SIZE = 50 * 1024 * 1024; // 50 МБ
const MAX_HOMEWORK_FILES = 10;
export function validateHomeworkFiles(files: File[]): { valid: boolean; error?: string } {
if (files.length === 0) return { valid: true };
if (files.length > MAX_HOMEWORK_FILES) {
return { valid: false, error: `Максимум ${MAX_HOMEWORK_FILES} файлов` };
}
for (const f of files) {
if (f.size > MAX_HOMEWORK_FILE_SIZE) {
return { valid: false, error: `Файл "${f.name}" больше 50 МБ` };
}
}
return { valid: true };
}
/**
* Обновить домашнее задание (для черновиков fill_later).
* PATCH /api/homework/homeworks/{id}/
*/
export async function updateHomework(
homeworkId: string | number,
data: {
title?: string;
description?: string;
deadline?: string | null;
status?: 'draft' | 'published';
fill_later?: boolean;
}
): Promise<Homework> {
const res = await apiClient.patch<Homework>(`/homework/homeworks/${homeworkId}/`, data);
return res.data;
}
/**
* Опубликовать домашнее задание (из черновика в published).
* POST /api/homework/homeworks/{id}/publish/
*/
export async function publishHomework(homeworkId: string | number): Promise<Homework> {
const res = await apiClient.post<Homework>(`/homework/homeworks/${homeworkId}/publish/`);
return res.data;
}
export async function submitHomework(
homeworkId: string | number,
data: { content?: string; text?: string; files?: File[] },
onUploadProgress?: (percent: number) => void
): Promise<unknown> {
const hasFiles = data.files && data.files.length > 0;
if (hasFiles) {
const formData = new FormData();
formData.append('homework_id', String(homeworkId));
if (data.content) formData.append('content', data.content);
if (data.text) formData.append('content', data.text);
data.files!.forEach((f) => formData.append('attachment', f));
const res = await apiClient.post(`/homework/submissions/`, formData, {
onUploadProgress:
onUploadProgress &&
(function (event: { loaded: number; total?: number }) {
if (event.total && event.total > 0) {
const percent = Math.round((event.loaded / event.total) * 100);
onUploadProgress(Math.min(percent, 100));
}
}),
});
return res.data;
}
const res = await apiClient.post(`/homework/submissions/`, {
homework_id: homeworkId,
content: data.content || data.text || '',
});
return res.data;
}