computer-scienceangular-roadmapbackend-roadmapblockchain-roadmapdba-roadmapdeveloper-roadmapdevops-roadmapfrontend-roadmapgo-roadmaphactoberfestjava-roadmapjavascript-roadmapnodejs-roadmappython-roadmapqa-roadmapreact-roadmaproadmapstudy-planvue-roadmapweb3-roadmap
188 lines
5.1 KiB
188 lines
5.1 KiB
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<AiCourse>({ |
|
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 ( |
|
<AICourseContent |
|
courseSlug={courseSlug} |
|
course={course} |
|
isLoading={isLoading} |
|
error={error} |
|
/> |
|
); |
|
}
|
|
|