feat/ai-tutor-redesign
Arik Chakma 4 days ago
parent 4cf3f052f7
commit 653778b13d
  1. 4
      src/components/GenerateCourse/AICourseContent.tsx
  2. 32
      src/components/GenerateCourse/AICourseLesson.tsx
  3. 17
      src/components/GenerateCourse/ModifyCoursePrompt.tsx
  4. 22
      src/components/GenerateCourse/RegenerateLesson.tsx

@ -472,6 +472,10 @@ export function AICourseContent(props: AICourseContentProps) {
onUpgrade={() => setShowUpgradeModal(true)}
isAIChatsOpen={isAIChatsOpen}
setIsAIChatsOpen={setIsAIChatsOpen}
isForkable={isForkable}
onForkCourse={() => {
setIsForkingCourse(true);
}}
/>
)}

@ -70,6 +70,9 @@ type AICourseLessonProps = {
isAIChatsOpen: boolean;
setIsAIChatsOpen: (isOpen: boolean) => void;
isForkable: boolean;
onForkCourse: () => void;
};
export function AICourseLesson(props: AICourseLessonProps) {
@ -91,6 +94,9 @@ export function AICourseLesson(props: AICourseLessonProps) {
isAIChatsOpen,
setIsAIChatsOpen,
isForkable,
onForkCourse,
} = props;
const [isLoading, setIsLoading] = useState(true);
@ -108,8 +114,7 @@ export function AICourseLesson(props: AICourseLessonProps) {
>([
{
role: 'assistant',
content:
'Hey, I am your AI instructor. How can I help you today? 🤖',
content: 'Hey, I am your AI instructor. How can I help you today? 🤖',
isDefault: true,
},
]);
@ -205,7 +210,7 @@ export function AICourseLesson(props: AICourseLessonProps) {
const questions = getQuestionsFromResult(result);
setDefaultQuestions(questions);
const newResult = result.replace(
/=START_QUESTIONS=.*?=END_QUESTIONS=/,
'',
@ -284,7 +289,7 @@ export function AICourseLesson(props: AICourseLessonProps) {
<div className="relative mx-auto max-w-5xl">
<div className="bg-white p-8 pb-0 max-lg:px-4 max-lg:pt-3">
{(isGenerating || isLoading) && (
<div className="absolute right-6 top-6 flex items-center justify-center">
<div className="absolute top-6 right-6 flex items-center justify-center">
<Loader2Icon
size={18}
strokeWidth={3}
@ -299,7 +304,7 @@ export function AICourseLesson(props: AICourseLessonProps) {
</div>
{!isGenerating && !isLoading && (
<div className="absolute top-2 right-2 lg:right-6 lg:top-6 flex items-center justify-between gap-2">
<div className="absolute top-2 right-2 flex items-center justify-between gap-2 lg:top-6 lg:right-6">
<button
onClick={() => setIsAIChatsOpen(!isAIChatsOpen)}
className="rounded-full p-1 text-gray-400 hover:text-black max-lg:hidden"
@ -315,16 +320,25 @@ export function AICourseLesson(props: AICourseLessonProps) {
onRegenerateLesson={(prompt) => {
generateAiCourseContent(true, prompt);
}}
isForkable={isForkable}
onForkCourse={onForkCourse}
/>
<button
disabled={isLoading || isTogglingDone}
className={cn(
'flex items-center gap-1.5 rounded-full bg-black py-1 pl-2 pr-3 text-sm text-white hover:bg-gray-800 disabled:opacity-50 max-lg:text-xs',
'flex items-center gap-1.5 rounded-full bg-black py-1 pr-3 pl-2 text-sm text-white hover:bg-gray-800 disabled:opacity-50 max-lg:text-xs',
isLessonDone
? 'bg-red-500 hover:bg-red-600'
: 'bg-green-500 hover:bg-green-600',
)}
onClick={() => toggleDone()}
onClick={() => {
if (isForkable) {
onForkCourse();
return;
}
toggleDone();
}}
>
{isTogglingDone ? (
<>
@ -355,13 +369,13 @@ export function AICourseLesson(props: AICourseLessonProps) {
)}
</div>
<h1 className="mb-6 text-balance text-3xl font-semibold max-lg:mb-3 max-lg:text-xl">
<h1 className="mb-6 text-3xl font-semibold text-balance max-lg:mb-3 max-lg:text-xl">
{currentLessonTitle?.replace(/^Lesson\s*?\d+[\.:]\s*/, '')}
</h1>
{!error && isLoggedIn() && (
<div
className="course-content prose prose-lg mt-8 max-w-full text-black prose-headings:mb-3 prose-headings:mt-8 prose-blockquote:font-normal prose-pre:rounded-2xl prose-pre:text-lg prose-li:my-1 prose-thead:border-zinc-800 prose-tr:border-zinc-800 max-lg:mt-4 max-lg:text-base max-lg:prose-h2:mt-3 max-lg:prose-h2:text-lg max-lg:prose-h3:text-base max-lg:prose-pre:px-3 max-lg:prose-pre:text-sm"
className="course-content prose prose-lg prose-headings:mb-3 prose-headings:mt-8 prose-blockquote:font-normal prose-pre:rounded-2xl prose-pre:text-lg prose-li:my-1 prose-thead:border-zinc-800 prose-tr:border-zinc-800 max-lg:prose-h2:mt-3 max-lg:prose-h2:text-lg max-lg:prose-h3:text-base max-lg:prose-pre:px-3 max-lg:prose-pre:text-sm mt-8 max-w-full text-black max-lg:mt-4 max-lg:text-base"
dangerouslySetInnerHTML={{ __html: lessonHtml }}
/>
)}

@ -4,10 +4,17 @@ import { Modal } from '../Modal';
export type ModifyCoursePromptProps = {
onClose: () => void;
onSubmit: (prompt: string) => void;
title?: string;
description?: string;
};
export function ModifyCoursePrompt(props: ModifyCoursePromptProps) {
const { onClose, onSubmit } = props;
const {
onClose,
onSubmit,
title = 'Give AI more context',
description = 'Pass additional information to the AI to generate a course outline.',
} = props;
const [prompt, setPrompt] = useState('');
@ -25,12 +32,8 @@ export function ModifyCoursePrompt(props: ModifyCoursePromptProps) {
>
<div className="flex flex-col gap-4">
<div>
<h2 className="mb-2 text-left text-xl font-semibold">
Give AI more context
</h2>
<p className="text-sm text-gray-500">
Pass additional information to the AI to generate a course outline.
</p>
<h2 className="mb-2 text-left text-xl font-semibold">{title}</h2>
<p className="text-sm text-gray-500">{description}</p>
</div>
<form className="flex flex-col gap-2" onSubmit={handleSubmit}>
<textarea

@ -7,10 +7,12 @@ import { ModifyCoursePrompt } from './ModifyCoursePrompt';
type RegenerateLessonProps = {
onRegenerateLesson: (prompt?: string) => void;
isForkable: boolean;
onForkCourse: () => void;
};
export function RegenerateLesson(props: RegenerateLessonProps) {
const { onRegenerateLesson } = props;
const { onRegenerateLesson, isForkable, onForkCourse } = props;
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
@ -37,6 +39,11 @@ export function RegenerateLesson(props: RegenerateLessonProps) {
onClose={() => setShowPromptModal(false)}
onSubmit={(prompt) => {
setShowPromptModal(false);
if (isForkable) {
onForkCourse();
return;
}
onRegenerateLesson(prompt);
}}
/>
@ -52,9 +59,15 @@ export function RegenerateLesson(props: RegenerateLessonProps) {
<PenSquare className="text-current" size={16} strokeWidth={2.5} />
</button>
{isDropdownVisible && (
<div className="absolute right-0 top-full min-w-[170px] overflow-hidden rounded-md border border-gray-200 bg-white">
<div className="absolute top-full right-0 min-w-[170px] overflow-hidden rounded-md border border-gray-200 bg-white">
<button
onClick={() => {
setIsDropdownVisible(false);
if (isForkable) {
onForkCourse();
return;
}
onRegenerateLesson();
}}
className="flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm text-gray-600 hover:bg-gray-100"
@ -69,6 +82,11 @@ export function RegenerateLesson(props: RegenerateLessonProps) {
<button
onClick={() => {
setIsDropdownVisible(false);
if (isForkable) {
onForkCourse();
return;
}
setShowPromptModal(true);
}}
className="flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm text-gray-600 hover:bg-gray-100"

Loading…
Cancel
Save