import { useEffect, useState } from 'react'; import { getUrlParams } from '../../lib/browser'; import { isLoggedIn } from '../../lib/jwt'; import { showLoginPopup } from '../../lib/popup'; import { generateAiCourseStructure, type AiCourse } from '../../lib/ai'; import { readAICourseStream } from '../../helper/read-stream'; import { AICourseContent } from './AICourseContent'; type GenerateAICourseProps = {}; export function GenerateAICourse(props: GenerateAICourseProps) { const [term, setTerm] = useState(''); const [difficulty, setDifficulty] = useState(''); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(''); const [courseId, setCourseId] = useState(''); const [courseSlug, setCourseSlug] = useState(''); const [course, setCourse] = useState({ title: '', modules: [], difficulty: '', }); useEffect(() => { if (term || difficulty) { return; } const params = getUrlParams(); const paramsTerm = params?.term; const paramsDifficulty = params?.difficulty; if (!paramsTerm || !paramsDifficulty) { return; } setTerm(paramsTerm); setDifficulty(paramsDifficulty); generateCourse({ term: paramsTerm, difficulty: paramsDifficulty }); }, [term, difficulty]); const generateCourse = async (options: { term: string; difficulty: string; }) => { const { term, difficulty } = options; if (!isLoggedIn()) { setIsLoading(false); setError('You must be logged in to generate a course'); showLoginPopup(); return; } setIsLoading(true); setCourse({ title: '', modules: [], difficulty: '', }); setError(''); try { const response = await fetch( `${import.meta.env.PUBLIC_API_URL}/v1-generate-ai-course`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ keyword: term, difficulty, }), credentials: 'include', }, ); if (!response.ok) { const data = await response.json(); console.error( 'Error generating course:', data?.message || 'Something went wrong', ); setIsLoading(false); setError(data?.message || 'Something went wrong'); return; } const reader = response.body?.getReader(); if (!reader) { console.error('Failed to get reader from response'); setError('Something went wrong'); setIsLoading(false); return; } const COURSE_ID_REGEX = new RegExp('@COURSEID:(\\w+)@'); const COURSE_SLUG_REGEX = new RegExp(/@COURSESLUG:([\w-]+)@/); await readAICourseStream(reader, { onStream: (result) => { if (result.includes('@COURSEID') || result.includes('@COURSESLUG')) { const courseIdMatch = result.match(COURSE_ID_REGEX); const courseSlugMatch = result.match(COURSE_SLUG_REGEX); const extractedCourseId = courseIdMatch?.[1] || ''; const extractedCourseSlug = courseSlugMatch?.[1] || ''; if (extractedCourseSlug) { window.history.replaceState( { courseId, courseSlug: extractedCourseSlug, term, difficulty, }, '', `${origin}/ai-tutor/${extractedCourseSlug}`, ); } result = result .replace(COURSE_ID_REGEX, '') .replace(COURSE_SLUG_REGEX, ''); setCourseId(extractedCourseId); } try { const aiCourse = generateAiCourseStructure(result); setCourse({ ...aiCourse, difficulty: difficulty || '', }); } catch (e) { console.error('Error parsing streamed course content:', e); } }, onStreamEnd: (result) => { result = result .replace(COURSE_ID_REGEX, '') .replace(COURSE_SLUG_REGEX, ''); setIsLoading(false); }, }); } catch (error: any) { setError(error?.message || 'Something went wrong'); console.error('Error in course generation:', error); setIsLoading(false); } }; useEffect(() => { const handlePopState = (e: PopStateEvent) => { const { courseId, courseSlug, term, difficulty } = e.state || {}; if (!courseId || !courseSlug) { window.location.reload(); return; } setCourseId(courseId); setCourseSlug(courseSlug); setTerm(term); setDifficulty(difficulty); setIsLoading(true); generateCourse({ term, difficulty }).finally(() => { setIsLoading(false); }); }; window.addEventListener('popstate', handlePopState); return () => { window.removeEventListener('popstate', handlePopState); }; }, []); return ( ); }