feat/chat
Arik Chakma 2 months ago
parent 8cbf0737b7
commit 1323975b28
  1. 13
      src/components/GenerateCourse/AICourseContent.tsx
  2. 19
      src/components/GenerateCourse/AICourseFooter.tsx
  3. 48
      src/components/GenerateCourse/AICourseLesson.tsx
  4. 33
      src/components/GenerateCourse/AICourseLessonChat.tsx

@ -19,6 +19,7 @@ import { AICourseSidebarModuleList } from './AICourseSidebarModuleList';
import { AILimitsPopup } from './AILimitsPopup'; import { AILimitsPopup } from './AILimitsPopup';
import { AICourseOutlineView } from './AICourseOutlineView'; import { AICourseOutlineView } from './AICourseOutlineView';
import { AICourseRoadmapView } from './AICourseRoadmapView'; import { AICourseRoadmapView } from './AICourseRoadmapView';
import { AICourseFooter } from './AICourseFooter';
type AICourseContentProps = { type AICourseContentProps = {
courseSlug?: string; courseSlug?: string;
@ -35,6 +36,7 @@ export function AICourseContent(props: AICourseContentProps) {
const [showUpgradeModal, setShowUpgradeModal] = useState(false); const [showUpgradeModal, setShowUpgradeModal] = useState(false);
const [showAILimitsPopup, setShowAILimitsPopup] = useState(false); const [showAILimitsPopup, setShowAILimitsPopup] = useState(false);
const [isAIChatsOpen, setIsAIChatsOpen] = useState(true);
const [activeModuleIndex, setActiveModuleIndex] = useState(0); const [activeModuleIndex, setActiveModuleIndex] = useState(0);
const [activeLessonIndex, setActiveLessonIndex] = useState(0); const [activeLessonIndex, setActiveLessonIndex] = useState(0);
@ -412,6 +414,8 @@ export function AICourseContent(props: AICourseContentProps) {
onGoToNextLesson={goToNextLesson} onGoToNextLesson={goToNextLesson}
key={`${courseSlug}-${activeModuleIndex}-${activeLessonIndex}`} key={`${courseSlug}-${activeModuleIndex}-${activeLessonIndex}`}
onUpgrade={() => setShowUpgradeModal(true)} onUpgrade={() => setShowUpgradeModal(true)}
isAIChatsOpen={isAIChatsOpen}
setIsAIChatsOpen={setIsAIChatsOpen}
/> />
)} )}
@ -445,14 +449,7 @@ export function AICourseContent(props: AICourseContentProps) {
/> />
)} )}
<div <AICourseFooter className={viewMode === 'module' ? 'hidden' : ''} />
className={cn(
'mx-auto mb-10 mt-5 text-center text-sm text-gray-400',
viewMode === 'module' ? 'hidden' : '',
)}
>
AI can make mistakes, check important info.
</div>
</main> </main>
</div> </div>

@ -0,0 +1,19 @@
import { cn } from '../../lib/classname';
type AICourseFooterProps = {
className?: string;
};
export function AICourseFooter(props: AICourseFooterProps) {
const { className } = props;
return (
<div
className={cn(
'mx-auto mb-10 mt-5 text-center text-sm text-gray-400',
className,
)}
>
AI can make mistakes, check important info.
</div>
);
}

@ -5,6 +5,8 @@ import {
ChevronRight, ChevronRight,
Loader2Icon, Loader2Icon,
LockIcon, LockIcon,
MessageCircleIcon,
MessageCircleOffIcon,
XIcon, XIcon,
} from 'lucide-react'; } from 'lucide-react';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
@ -28,6 +30,7 @@ import './AICourseLessonChat.css';
import { RegenerateLesson } from './RegenerateLesson'; import { RegenerateLesson } from './RegenerateLesson';
import { TestMyKnowledgeAction } from './TestMyKnowledgeAction'; import { TestMyKnowledgeAction } from './TestMyKnowledgeAction';
import { AICourseLessonChat } from './AICourseLessonChat'; import { AICourseLessonChat } from './AICourseLessonChat';
import { AICourseFooter } from './AICourseFooter';
type AICourseLessonProps = { type AICourseLessonProps = {
courseSlug: string; courseSlug: string;
@ -44,6 +47,9 @@ type AICourseLessonProps = {
onGoToNextLesson: () => void; onGoToNextLesson: () => void;
onUpgrade: () => void; onUpgrade: () => void;
isAIChatsOpen: boolean;
setIsAIChatsOpen: (isAIChatsOpen: boolean) => void;
}; };
export function AICourseLesson(props: AICourseLessonProps) { export function AICourseLesson(props: AICourseLessonProps) {
@ -62,6 +68,9 @@ export function AICourseLesson(props: AICourseLessonProps) {
onGoToNextLesson, onGoToNextLesson,
onUpgrade, onUpgrade,
isAIChatsOpen,
setIsAIChatsOpen,
} = props; } = props;
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
@ -210,7 +219,12 @@ export function AICourseLesson(props: AICourseLessonProps) {
return ( return (
<div className="grid h-full grid-cols-5"> <div className="grid h-full grid-cols-5">
<div className="relative col-span-3"> <div
className={cn(
'relative',
isAIChatsOpen ? 'col-span-3 max-lg:col-span-5' : 'col-span-5',
)}
>
<div className="absolute inset-0 overflow-y-auto bg-white p-8 pb-0 max-lg:px-4 max-lg:pt-3"> <div className="absolute inset-0 overflow-y-auto bg-white p-8 pb-0 max-lg:px-4 max-lg:pt-3">
{(isGenerating || isLoading) && ( {(isGenerating || isLoading) && (
<div className="absolute right-6 top-6 flex items-center justify-center"> <div className="absolute right-6 top-6 flex items-center justify-center">
@ -269,6 +283,17 @@ export function AICourseLesson(props: AICourseLessonProps) {
</> </>
)} )}
</button> </button>
<button
onClick={() => setIsAIChatsOpen(!isAIChatsOpen)}
className="rounded-full p-1 text-gray-400 hover:text-black max-lg:hidden"
>
{!isAIChatsOpen ? (
<MessageCircleIcon className="size-4 stroke-[2.5]" />
) : (
<MessageCircleOffIcon className="size-4 stroke-[2.5]" />
)}
</button>
</div> </div>
)} )}
</div> </div>
@ -390,19 +415,20 @@ export function AICourseLesson(props: AICourseLessonProps) {
</div> </div>
</div> </div>
<div className="mx-auto mb-10 mt-5 text-center text-sm text-gray-400"> <AICourseFooter />
AI can make mistakes, check important info.
</div>
</div> </div>
</div> </div>
<AICourseLessonChat {isAIChatsOpen && (
courseSlug={courseSlug} <AICourseLessonChat
moduleTitle={currentModuleTitle} courseSlug={courseSlug}
lessonTitle={currentLessonTitle} moduleTitle={currentModuleTitle}
onUpgradeClick={onUpgrade} lessonTitle={currentLessonTitle}
isDisabled={isGenerating || isLoading || isTogglingDone} onUpgradeClick={onUpgrade}
/> isDisabled={isGenerating || isLoading || isTogglingDone}
onClose={() => setIsAIChatsOpen(false)}
/>
)}
</div> </div>
); );
} }

@ -6,6 +6,7 @@ import {
HelpCircle, HelpCircle,
LockIcon, LockIcon,
Send, Send,
XIcon,
} from 'lucide-react'; } from 'lucide-react';
import { import {
useCallback, useCallback,
@ -44,13 +45,22 @@ type AICourseLessonChatProps = {
lessonTitle: string; lessonTitle: string;
onUpgradeClick: () => void; onUpgradeClick: () => void;
isDisabled?: boolean; isDisabled?: boolean;
onClose: () => void;
}; };
export function AICourseLessonChat(props: AICourseLessonChatProps) { export function AICourseLessonChat(props: AICourseLessonChatProps) {
const { courseSlug, moduleTitle, lessonTitle, onUpgradeClick, isDisabled } = const {
props; courseSlug,
moduleTitle,
lessonTitle,
onUpgradeClick,
isDisabled,
onClose,
} = props;
const toast = useToast(); const toast = useToast();
const containerRef = useRef<HTMLDivElement | null>(null);
const scrollareaRef = useRef<HTMLDivElement | null>(null); const scrollareaRef = useRef<HTMLDivElement | null>(null);
const [courseAIChatHistory, setCourseAIChatHistory] = useState< const [courseAIChatHistory, setCourseAIChatHistory] = useState<
@ -193,9 +203,24 @@ export function AICourseLessonChat(props: AICourseLessonChatProps) {
scrollToBottom(); scrollToBottom();
}, []); }, []);
useOutsideClick(containerRef, () => {
onClose();
});
return ( return (
<div className="relative col-span-2 h-full border-l border-gray-200"> <div className="relative col-span-2 h-full border-l border-gray-200 max-lg:fixed max-lg:inset-y-0 max-lg:right-0 max-lg:z-10 max-lg:w-[420px] max-lg:border-none">
<div className="absolute inset-0 flex w-full flex-col overflow-hidden bg-white"> <div className="fixed inset-0 z-10 bg-black/50 lg:hidden" />
<div
className="absolute inset-0 z-20 flex w-full flex-col overflow-hidden bg-white"
ref={containerRef}
>
<button
onClick={onClose}
className="absolute right-2 top-2 hidden rounded-full p-1 text-gray-400 hover:text-black max-lg:block"
>
<XIcon className="size-4 stroke-[2.5]" />
</button>
<div className="flex items-center justify-between gap-2 border-b border-gray-200 px-4 py-2 text-sm"> <div className="flex items-center justify-between gap-2 border-b border-gray-200 px-4 py-2 text-sm">
<h4 className="text-base font-medium">Course AI</h4> <h4 className="text-base font-medium">Course AI</h4>
</div> </div>

Loading…
Cancel
Save