computer-scienceangular-roadmapbackend-roadmapblockchain-roadmapdba-roadmapdeveloper-roadmapdevops-roadmapfrontend-roadmapgo-roadmaphactoberfestjava-roadmapjavascript-roadmapnodejs-roadmappython-roadmapqa-roadmapreact-roadmaproadmapstudy-planvue-roadmapweb3-roadmap
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
184 lines
4.8 KiB
184 lines
4.8 KiB
import { |
|
generateAiCourseStructure, |
|
readStream, |
|
type AiCourse, |
|
} from '../lib/ai'; |
|
import { queryClient } from '../stores/query-client'; |
|
import { getAiCourseLimitOptions } from '../queries/ai-course'; |
|
|
|
type GenerateCourseOptions = { |
|
term: string; |
|
difficulty: string; |
|
slug?: string; |
|
isForce?: boolean; |
|
prompt?: string; |
|
instructions?: string; |
|
goal?: string; |
|
about?: string; |
|
onCourseIdChange?: (courseId: string) => void; |
|
onCourseSlugChange?: (courseSlug: string) => void; |
|
onCourseChange?: (course: AiCourse, rawData: string) => void; |
|
onLoadingChange?: (isLoading: boolean) => void; |
|
onCreatorIdChange?: (creatorId: string) => void; |
|
onError?: (error: string) => void; |
|
src?: string; |
|
}; |
|
|
|
export async function generateCourse(options: GenerateCourseOptions) { |
|
const { |
|
term, |
|
slug, |
|
difficulty, |
|
onCourseIdChange, |
|
onCourseSlugChange, |
|
onCourseChange, |
|
onLoadingChange, |
|
onError, |
|
onCreatorIdChange, |
|
isForce = false, |
|
prompt, |
|
instructions, |
|
goal, |
|
about, |
|
src = 'search', |
|
} = options; |
|
|
|
onLoadingChange?.(true); |
|
onCourseChange?.( |
|
{ |
|
title: '', |
|
modules: [], |
|
difficulty: '', |
|
done: [], |
|
}, |
|
'', |
|
); |
|
onError?.(''); |
|
|
|
try { |
|
let response = null; |
|
|
|
if (slug && isForce) { |
|
response = await fetch( |
|
`${import.meta.env.PUBLIC_API_URL}/v1-regenerate-ai-course/${slug}`, |
|
{ |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json', |
|
}, |
|
credentials: 'include', |
|
body: JSON.stringify({ |
|
isForce, |
|
customPrompt: prompt, |
|
}), |
|
}, |
|
); |
|
} else { |
|
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, |
|
isForce, |
|
customPrompt: prompt, |
|
instructions, |
|
goal, |
|
about, |
|
src, |
|
}), |
|
credentials: 'include', |
|
}, |
|
); |
|
} |
|
|
|
if (!response.ok) { |
|
const data = await response.json(); |
|
console.error( |
|
'Error generating course:', |
|
data?.message || 'Something went wrong', |
|
); |
|
onLoadingChange?.(false); |
|
onError?.(data?.message || 'Something went wrong'); |
|
return; |
|
} |
|
|
|
const reader = response.body?.getReader(); |
|
|
|
if (!reader) { |
|
console.error('Failed to get reader from response'); |
|
onError?.('Something went wrong'); |
|
onLoadingChange?.(false); |
|
return; |
|
} |
|
|
|
const COURSE_ID_REGEX = new RegExp('@COURSEID:(\\w+)@'); |
|
const COURSE_SLUG_REGEX = new RegExp(/@COURSESLUG:([\w-]+)@/); |
|
const CREATOR_ID_REGEX = new RegExp('@CREATORID:(\\w+)@'); |
|
|
|
await readStream(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 creatorIdMatch = result.match(CREATOR_ID_REGEX); |
|
const extractedCourseId = courseIdMatch?.[1] || ''; |
|
const extractedCourseSlug = courseSlugMatch?.[1] || ''; |
|
const extractedCreatorId = creatorIdMatch?.[1] || ''; |
|
|
|
if (extractedCourseSlug) { |
|
window.history.replaceState( |
|
{ |
|
courseId: extractedCourseId, |
|
courseSlug: extractedCourseSlug, |
|
term, |
|
difficulty, |
|
}, |
|
'', |
|
`${origin}/ai/${extractedCourseSlug}`, |
|
); |
|
} |
|
|
|
result = result |
|
.replace(COURSE_ID_REGEX, '') |
|
.replace(COURSE_SLUG_REGEX, '') |
|
.replace(CREATOR_ID_REGEX, ''); |
|
|
|
onCourseIdChange?.(extractedCourseId); |
|
onCourseSlugChange?.(extractedCourseSlug); |
|
onCreatorIdChange?.(extractedCreatorId); |
|
} |
|
|
|
try { |
|
const aiCourse = generateAiCourseStructure(result); |
|
onCourseChange?.( |
|
{ |
|
...aiCourse, |
|
difficulty: difficulty || '', |
|
}, |
|
result, |
|
); |
|
} catch (e) { |
|
console.error('Error parsing streamed course content:', e); |
|
} |
|
}, |
|
onStreamEnd: (result) => { |
|
result = result |
|
.replace(COURSE_ID_REGEX, '') |
|
.replace(COURSE_SLUG_REGEX, '') |
|
.replace(CREATOR_ID_REGEX, ''); |
|
|
|
onLoadingChange?.(false); |
|
queryClient.invalidateQueries(getAiCourseLimitOptions()); |
|
}, |
|
}); |
|
} catch (error: any) { |
|
onError?.(error?.message || 'Something went wrong'); |
|
console.error('Error in course generation:', error); |
|
onLoadingChange?.(false); |
|
} |
|
}
|
|
|