feat: productionize learning engine and classroom

This commit is contained in:
inman committed 2026-08-16 21:44:52 +08:00
1 parent 2d04197f3f
commit ba35adfbfa
124 files changed
+9071 -796

No files matched your search

@@ -0,0 +1,76 @@
import { after, type NextRequest } from 'next/server';
import { authorizeLearningRuntime, runtimeUnauthorized } from '@/lib/makelore-runtime/auth';
import { LEARNING_GENERATION_CONTRACT_VERSION } from '@/lib/makelore-runtime/generation-request';
import {
controlLargeCourse,
LARGE_COURSE_CONTROL_ACTIONS,
largeCourseControlErrorResponse,
type LargeCourseControlAction,
} from '@/lib/makelore-runtime/large-course-control';
import { buildRequestOrigin } from '@/lib/server/classroom-storage';
const MAX_CONTROL_BODY_BYTES = 8 * 1024;
function parseControlBody(raw: string): {
action: LargeCourseControlAction;
moduleIndex?: number;
} | null {
if (new TextEncoder().encode(raw).byteLength > MAX_CONTROL_BODY_BYTES) return null;
try {
const value = JSON.parse(raw) as unknown;
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
const record = value as Record<string, unknown>;
if (Object.keys(record).some((key) => key !== 'action' && key !== 'moduleIndex')) return null;
const action = record.action as LargeCourseControlAction;
if (typeof record.action !== 'string' || !LARGE_COURSE_CONTROL_ACTIONS.includes(action)) {
return null;
}
if (action === 'module-regenerate') {
if (
typeof record.moduleIndex !== 'number'
|| !Number.isInteger(record.moduleIndex)
|| record.moduleIndex < 1
) return null;
return { action, moduleIndex: record.moduleIndex };
}
if (record.moduleIndex !== undefined) return null;
return { action };
} catch {
return null;
}
}
export async function POST(
request: NextRequest,
context: { params: Promise<{ jobId: string }> },
) {
if (!authorizeLearningRuntime(request)) return runtimeUnauthorized();
const ownerPrincipalId = request.headers.get('x-owner-user-id')?.trim() ?? '';
const { jobId } = await context.params;
const body = parseControlBody(await request.text());
if (!ownerPrincipalId || !body) {
return Response.json({ error: 'invalid_request' }, { status: 400 });
}
try {
const result = await controlLargeCourse({
courseId: jobId,
ownerPrincipalId,
action: body.action,
moduleIndex: body.moduleIndex,
baseUrl: buildRequestOrigin(request),
});
if (result.run) after(() => result.run!);
return Response.json({
contractVersion: LEARNING_GENERATION_CONTRACT_VERSION,
jobId,
mode: 'large',
action: result.action,
status: result.status,
done: false,
message: result.message,
...(result.moduleIndex === undefined ? {} : { moduleIndex: result.moduleIndex }),
});
} catch (error) {
return largeCourseControlErrorResponse(error);
}
}