export const IS_KEY_ONLY_ROADMAP_GENERATION = false; type Lesson = string; type Module = { title: string; lessons: Lesson[]; }; export type AiCourse = { title: string; modules: Module[]; difficulty: string; }; export function generateAiCourseStructure( data: string, ): Omit<AiCourse, 'difficulty'> { const lines = data.split('\n'); let title = ''; const modules: Module[] = []; let currentModule: Module | null = null; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (i === 0 && line.startsWith('#')) { // First line is the title title = line.replace('#', '').trim(); } else if (line.startsWith('## ')) { // New module if (currentModule) { modules.push(currentModule); } currentModule = { title: line.replace('## ', ''), lessons: [], }; // Removed auto-expand code to keep modules collapsed by default } else if (line.startsWith('- ') && currentModule) { // Lesson within current module currentModule.lessons.push(line.replace('- ', '')); } } // Add the last module if it exists if (currentModule) { modules.push(currentModule); } return { title, modules, }; } const NEW_LINE = '\n'.charCodeAt(0); export async function readAIRoadmapStream( reader: ReadableStreamDefaultReader<Uint8Array>, { onStream, onStreamEnd, }: { onStream?: (roadmap: string) => void; onStreamEnd?: (roadmap: string) => void; }, ) { const decoder = new TextDecoder('utf-8'); let result = ''; while (true) { const { value, done } = await reader.read(); if (done) { break; } // We will call the renderRoadmap callback whenever we encounter // a new line with the result until the new line // otherwise, we will keep appending the result to the previous result if (value) { let start = 0; for (let i = 0; i < value.length; i++) { if (value[i] === NEW_LINE) { result += decoder.decode(value.slice(start, i + 1)); onStream?.(result); start = i + 1; } } if (start < value.length) { result += decoder.decode(value.slice(start)); } } } onStream?.(result); onStreamEnd?.(result); reader.releaseLock(); } export async function readAIRoadmapContentStream( reader: ReadableStreamDefaultReader<Uint8Array>, { onStream, onStreamEnd, }: { onStream?: (roadmap: string) => void; onStreamEnd?: (roadmap: string) => void; }, ) { const decoder = new TextDecoder('utf-8'); let result = ''; while (true) { const { value, done } = await reader.read(); if (done) { break; } if (value) { result += decoder.decode(value); onStream?.(result); } } onStream?.(result); onStreamEnd?.(result); reader.releaseLock(); } export async function readStream( reader: ReadableStreamDefaultReader<Uint8Array>, { onStream, onStreamEnd, }: { onStream?: (course: string) => void; onStreamEnd?: (course: string) => void; }, ) { const decoder = new TextDecoder('utf-8'); let result = ''; while (true) { const { value, done } = await reader.read(); if (done) { break; } // Process the stream data as it comes in if (value) { let start = 0; for (let i = 0; i < value.length; i++) { if (value[i] === NEW_LINE) { result += decoder.decode(value.slice(start, i + 1)); onStream?.(result); start = i + 1; } } if (start < value.length) { result += decoder.decode(value.slice(start)); } } } onStream?.(result); onStreamEnd?.(result); reader.releaseLock(); }