|
|
|
@ -25,6 +25,7 @@ import { |
|
|
|
|
import { queryClient } from '../../stores/query-client'; |
|
|
|
|
import { AICourseFollowUp } from './AICourseFollowUp'; |
|
|
|
|
import './AICourseFollowUp.css'; |
|
|
|
|
import { useIsPaidUser } from '../../queries/billing'; |
|
|
|
|
|
|
|
|
|
type AICourseModuleViewProps = { |
|
|
|
|
courseSlug: string; |
|
|
|
@ -72,6 +73,8 @@ export function AICourseModuleView(props: AICourseModuleViewProps) { |
|
|
|
|
const lessonId = `${slugify(currentModuleTitle)}__${slugify(currentLessonTitle)}`; |
|
|
|
|
const isLessonDone = aiCourseProgress?.done.includes(lessonId); |
|
|
|
|
|
|
|
|
|
const { isPaidUser } = useIsPaidUser(); |
|
|
|
|
|
|
|
|
|
const abortController = useMemo( |
|
|
|
|
() => new AbortController(), |
|
|
|
|
[activeModuleIndex, activeLessonIndex], |
|
|
|
@ -124,36 +127,41 @@ export function AICourseModuleView(props: AICourseModuleViewProps) { |
|
|
|
|
removeAuthToken(); |
|
|
|
|
window.location.reload(); |
|
|
|
|
} |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const reader = response.body?.getReader(); |
|
|
|
|
|
|
|
|
|
if (!reader) { |
|
|
|
|
if (!response.body) { |
|
|
|
|
setIsLoading(false); |
|
|
|
|
setError('Something went wrong'); |
|
|
|
|
setError('No response body received'); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
setIsLoading(false); |
|
|
|
|
setIsGenerating(true); |
|
|
|
|
await readStream(reader, { |
|
|
|
|
onStream: async (result) => { |
|
|
|
|
if (abortController.signal.aborted) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
setLessonHtml(markdownToHtml(result, false)); |
|
|
|
|
}, |
|
|
|
|
onStreamEnd: async (result) => { |
|
|
|
|
if (abortController.signal.aborted) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
setLessonHtml(await markdownToHtmlWithHighlighting(result)); |
|
|
|
|
queryClient.invalidateQueries(getAiCourseLimitOptions()); |
|
|
|
|
setIsGenerating(false); |
|
|
|
|
}, |
|
|
|
|
}); |
|
|
|
|
try { |
|
|
|
|
const reader = response.body.getReader(); |
|
|
|
|
setIsLoading(false); |
|
|
|
|
setIsGenerating(true); |
|
|
|
|
await readStream(reader, { |
|
|
|
|
onStream: async (result) => { |
|
|
|
|
if (abortController.signal.aborted) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
setLessonHtml(markdownToHtml(result, false)); |
|
|
|
|
}, |
|
|
|
|
onStreamEnd: async (result) => { |
|
|
|
|
if (abortController.signal.aborted) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
setLessonHtml(await markdownToHtmlWithHighlighting(result)); |
|
|
|
|
queryClient.invalidateQueries(getAiCourseLimitOptions()); |
|
|
|
|
setIsGenerating(false); |
|
|
|
|
}, |
|
|
|
|
}); |
|
|
|
|
} catch (e) { |
|
|
|
|
setError(e instanceof Error ? e.message : 'Something went wrong'); |
|
|
|
|
setIsLoading(false); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const { mutate: toggleDone, isPending: isTogglingDone } = useMutation( |
|
|
|
@ -273,18 +281,21 @@ export function AICourseModuleView(props: AICourseModuleViewProps) { |
|
|
|
|
Limit reached |
|
|
|
|
</h2> |
|
|
|
|
<p className="my-3 text-red-600"> |
|
|
|
|
You have reached the AI usage limit for today. Please upgrade |
|
|
|
|
your account to continue. |
|
|
|
|
You have reached the AI usage limit for today. |
|
|
|
|
{!isPaidUser && <>Please upgrade your account to continue.</>} |
|
|
|
|
{isPaidUser && <>Please wait until tomorrow to continue.</>} |
|
|
|
|
</p> |
|
|
|
|
|
|
|
|
|
<button |
|
|
|
|
onClick={() => { |
|
|
|
|
onUpgrade(); |
|
|
|
|
}} |
|
|
|
|
className="rounded-full bg-red-600 px-4 py-1 text-white hover:bg-red-700" |
|
|
|
|
> |
|
|
|
|
Upgrade Account |
|
|
|
|
</button> |
|
|
|
|
{!isPaidUser && ( |
|
|
|
|
<button |
|
|
|
|
onClick={() => { |
|
|
|
|
onUpgrade(); |
|
|
|
|
}} |
|
|
|
|
className="rounded-full bg-red-600 px-4 py-1 text-white hover:bg-red-700" |
|
|
|
|
> |
|
|
|
|
Upgrade Account |
|
|
|
|
</button> |
|
|
|
|
)} |
|
|
|
|
</div> |
|
|
|
|
) : ( |
|
|
|
|
<p className="text-red-600">{error}</p> |
|
|
|
|