|
|
|
@ -31,6 +31,11 @@ import { RegenerateLesson } from './RegenerateLesson'; |
|
|
|
|
import { TestMyKnowledgeAction } from './TestMyKnowledgeAction'; |
|
|
|
|
import { AICourseLessonChat } from './AICourseLessonChat'; |
|
|
|
|
import { AICourseFooter } from './AICourseFooter'; |
|
|
|
|
import { |
|
|
|
|
ResizableHandle, |
|
|
|
|
ResizablePanel, |
|
|
|
|
ResizablePanelGroup, |
|
|
|
|
} from './Resizeable'; |
|
|
|
|
|
|
|
|
|
type AICourseLessonProps = { |
|
|
|
|
courseSlug: string; |
|
|
|
@ -69,10 +74,11 @@ export function AICourseLesson(props: AICourseLessonProps) { |
|
|
|
|
|
|
|
|
|
onUpgrade, |
|
|
|
|
|
|
|
|
|
isAIChatsOpen, |
|
|
|
|
setIsAIChatsOpen, |
|
|
|
|
isAIChatsOpen: isAIChatsMobileOpen, |
|
|
|
|
setIsAIChatsOpen: setIsAIChatsMobileOpen, |
|
|
|
|
} = props; |
|
|
|
|
|
|
|
|
|
const [isAIChatsOpen, setIsAIChatsOpen] = useState(true); |
|
|
|
|
const [isLoading, setIsLoading] = useState(true); |
|
|
|
|
const [isGenerating, setIsGenerating] = useState(false); |
|
|
|
|
const [error, setError] = useState(''); |
|
|
|
@ -218,217 +224,253 @@ export function AICourseLesson(props: AICourseLessonProps) { |
|
|
|
|
isLoading; |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<div className="grid h-full grid-cols-5"> |
|
|
|
|
<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"> |
|
|
|
|
{(isGenerating || isLoading) && ( |
|
|
|
|
<div className="absolute right-6 top-6 flex items-center justify-center"> |
|
|
|
|
<Loader2Icon |
|
|
|
|
size={18} |
|
|
|
|
strokeWidth={3} |
|
|
|
|
className="animate-spin text-gray-400/70" |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
|
<div className="h-full"> |
|
|
|
|
<ResizablePanelGroup direction="horizontal"> |
|
|
|
|
<ResizablePanel |
|
|
|
|
defaultSize={isAIChatsOpen ? 60 : 100} |
|
|
|
|
minSize={40} |
|
|
|
|
id="course-text-content" |
|
|
|
|
order={1} |
|
|
|
|
> |
|
|
|
|
<div className="relative h-full"> |
|
|
|
|
<div className="absolute inset-0 overflow-y-auto 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"> |
|
|
|
|
<Loader2Icon |
|
|
|
|
size={18} |
|
|
|
|
strokeWidth={3} |
|
|
|
|
className="animate-spin text-gray-400/70" |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
<div className="mb-4 flex items-center justify-between"> |
|
|
|
|
<div className="text-sm text-gray-500"> |
|
|
|
|
Lesson {activeLessonIndex + 1} of {totalLessons} |
|
|
|
|
</div> |
|
|
|
|
<div className="mb-4 flex items-center justify-between"> |
|
|
|
|
<div className="text-sm text-gray-500"> |
|
|
|
|
Lesson {activeLessonIndex + 1} of {totalLessons} |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
{!isGenerating && !isLoading && ( |
|
|
|
|
<div className="absolute right-6 top-6 flex items-center justify-between gap-2"> |
|
|
|
|
<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> |
|
|
|
|
{!isGenerating && !isLoading && ( |
|
|
|
|
<div className="absolute right-6 top-6 flex items-center justify-between gap-2"> |
|
|
|
|
<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> |
|
|
|
|
|
|
|
|
|
<RegenerateLesson |
|
|
|
|
onRegenerateLesson={(prompt) => { |
|
|
|
|
generateAiCourseContent(true, prompt); |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
<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', |
|
|
|
|
isLessonDone |
|
|
|
|
? 'bg-red-500 hover:bg-red-600' |
|
|
|
|
: 'bg-green-500 hover:bg-green-600', |
|
|
|
|
)} |
|
|
|
|
onClick={() => toggleDone()} |
|
|
|
|
> |
|
|
|
|
{isTogglingDone ? ( |
|
|
|
|
<> |
|
|
|
|
<Loader2Icon |
|
|
|
|
size={16} |
|
|
|
|
strokeWidth={3} |
|
|
|
|
className="animate-spin text-white" |
|
|
|
|
/> |
|
|
|
|
Please wait ... |
|
|
|
|
</> |
|
|
|
|
) : ( |
|
|
|
|
<> |
|
|
|
|
{isLessonDone ? ( |
|
|
|
|
<RegenerateLesson |
|
|
|
|
onRegenerateLesson={(prompt) => { |
|
|
|
|
generateAiCourseContent(true, prompt); |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
<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', |
|
|
|
|
isLessonDone |
|
|
|
|
? 'bg-red-500 hover:bg-red-600' |
|
|
|
|
: 'bg-green-500 hover:bg-green-600', |
|
|
|
|
)} |
|
|
|
|
onClick={() => toggleDone()} |
|
|
|
|
> |
|
|
|
|
{isTogglingDone ? ( |
|
|
|
|
<> |
|
|
|
|
<XIcon size={16} /> |
|
|
|
|
Mark as Undone |
|
|
|
|
<Loader2Icon |
|
|
|
|
size={16} |
|
|
|
|
strokeWidth={3} |
|
|
|
|
className="animate-spin text-white" |
|
|
|
|
/> |
|
|
|
|
Please wait ... |
|
|
|
|
</> |
|
|
|
|
) : ( |
|
|
|
|
<> |
|
|
|
|
<CheckIcon size={16} /> |
|
|
|
|
Mark as Done |
|
|
|
|
{isLessonDone ? ( |
|
|
|
|
<> |
|
|
|
|
<XIcon size={16} /> |
|
|
|
|
Mark as Undone |
|
|
|
|
</> |
|
|
|
|
) : ( |
|
|
|
|
<> |
|
|
|
|
<CheckIcon size={16} /> |
|
|
|
|
Mark as Done |
|
|
|
|
</> |
|
|
|
|
)} |
|
|
|
|
</> |
|
|
|
|
)} |
|
|
|
|
</> |
|
|
|
|
)} |
|
|
|
|
</button> |
|
|
|
|
</button> |
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
<h1 className="mb-6 text-balance text-3xl font-semibold 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" |
|
|
|
|
dangerouslySetInnerHTML={{ __html: lessonHtml }} |
|
|
|
|
/> |
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
{error && isLoggedIn() && ( |
|
|
|
|
<div className="mt-8 flex min-h-[300px] items-center justify-center rounded-xl bg-red-50/80"> |
|
|
|
|
{error.includes('reached the limit') ? ( |
|
|
|
|
<div className="flex max-w-sm flex-col items-center text-center"> |
|
|
|
|
<h2 className="text-xl font-semibold text-red-600"> |
|
|
|
|
Limit reached |
|
|
|
|
</h2> |
|
|
|
|
<p className="my-3 text-red-600"> |
|
|
|
|
You have reached the AI usage limit for today. |
|
|
|
|
{!isPaidUser && ( |
|
|
|
|
<>Please upgrade your account to continue.</> |
|
|
|
|
)} |
|
|
|
|
{isPaidUser && ( |
|
|
|
|
<> Please wait until tomorrow to continue.</> |
|
|
|
|
)} |
|
|
|
|
</p> |
|
|
|
|
<h1 className="mb-6 text-balance text-3xl font-semibold max-lg:mb-3 max-lg:text-xl"> |
|
|
|
|
{currentLessonTitle?.replace(/^Lesson\s*?\d+[\.:]\s*/, '')} |
|
|
|
|
</h1> |
|
|
|
|
|
|
|
|
|
{!isPaidUser && ( |
|
|
|
|
<button |
|
|
|
|
onClick={() => { |
|
|
|
|
onUpgrade(); |
|
|
|
|
}} |
|
|
|
|
className="rounded-full bg-red-600 px-4 py-1 text-white hover:bg-red-700" |
|
|
|
|
> |
|
|
|
|
Upgrade Account |
|
|
|
|
</button> |
|
|
|
|
{!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" |
|
|
|
|
dangerouslySetInnerHTML={{ __html: lessonHtml }} |
|
|
|
|
/> |
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
{error && isLoggedIn() && ( |
|
|
|
|
<div className="mt-8 flex min-h-[300px] items-center justify-center rounded-xl bg-red-50/80"> |
|
|
|
|
{error.includes('reached the limit') ? ( |
|
|
|
|
<div className="flex max-w-sm flex-col items-center text-center"> |
|
|
|
|
<h2 className="text-xl font-semibold text-red-600"> |
|
|
|
|
Limit reached |
|
|
|
|
</h2> |
|
|
|
|
<p className="my-3 text-red-600"> |
|
|
|
|
You have reached the AI usage limit for today. |
|
|
|
|
{!isPaidUser && ( |
|
|
|
|
<>Please upgrade your account to continue.</> |
|
|
|
|
)} |
|
|
|
|
{isPaidUser && ( |
|
|
|
|
<> Please wait until tomorrow to continue.</> |
|
|
|
|
)} |
|
|
|
|
</p> |
|
|
|
|
|
|
|
|
|
{!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> |
|
|
|
|
)} |
|
|
|
|
</div> |
|
|
|
|
) : ( |
|
|
|
|
<p className="text-red-600">{error}</p> |
|
|
|
|
)} |
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
{!isLoggedIn() && ( |
|
|
|
|
<div className="mt-8 flex min-h-[152px] flex-col items-center justify-center gap-3 rounded-lg border border-gray-200 p-8"> |
|
|
|
|
<LockIcon className="size-7 stroke-[2] text-gray-400/90" /> |
|
|
|
|
<p className="text-sm text-gray-500"> |
|
|
|
|
Please login to generate course content |
|
|
|
|
</p> |
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
{!isLoading && !isGenerating && !error && ( |
|
|
|
|
<TestMyKnowledgeAction |
|
|
|
|
courseSlug={courseSlug} |
|
|
|
|
activeModuleIndex={activeModuleIndex} |
|
|
|
|
activeLessonIndex={activeLessonIndex} |
|
|
|
|
/> |
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
<div className="mt-8 flex items-center justify-between"> |
|
|
|
|
<button |
|
|
|
|
onClick={onGoToPrevLesson} |
|
|
|
|
disabled={cantGoBack} |
|
|
|
|
className={cn( |
|
|
|
|
'flex items-center rounded-full px-4 py-2 disabled:opacity-50 max-lg:px-3 max-lg:py-1.5 max-lg:text-sm', |
|
|
|
|
cantGoBack |
|
|
|
|
? 'cursor-not-allowed text-gray-400' |
|
|
|
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200', |
|
|
|
|
|
|
|
|
|
{!isLoggedIn() && ( |
|
|
|
|
<div className="mt-8 flex min-h-[152px] flex-col items-center justify-center gap-3 rounded-lg border border-gray-200 p-8"> |
|
|
|
|
<LockIcon className="size-7 stroke-[2] text-gray-400/90" /> |
|
|
|
|
<p className="text-sm text-gray-500"> |
|
|
|
|
Please login to generate course content |
|
|
|
|
</p> |
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
|
> |
|
|
|
|
<ChevronLeft size={16} className="mr-2" /> |
|
|
|
|
Previous <span className="hidden lg:inline"> Lesson</span> |
|
|
|
|
</button> |
|
|
|
|
|
|
|
|
|
<div> |
|
|
|
|
<button |
|
|
|
|
onClick={() => { |
|
|
|
|
if (!isLessonDone) { |
|
|
|
|
toggleDone(undefined, { |
|
|
|
|
onSuccess: () => { |
|
|
|
|
|
|
|
|
|
{!isLoading && !isGenerating && !error && ( |
|
|
|
|
<TestMyKnowledgeAction |
|
|
|
|
courseSlug={courseSlug} |
|
|
|
|
activeModuleIndex={activeModuleIndex} |
|
|
|
|
activeLessonIndex={activeLessonIndex} |
|
|
|
|
/> |
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
<div className="mt-8 flex items-center justify-between"> |
|
|
|
|
<button |
|
|
|
|
onClick={onGoToPrevLesson} |
|
|
|
|
disabled={cantGoBack} |
|
|
|
|
className={cn( |
|
|
|
|
'flex items-center rounded-full px-4 py-2 disabled:opacity-50 max-lg:px-3 max-lg:py-1.5 max-lg:text-sm', |
|
|
|
|
cantGoBack |
|
|
|
|
? 'cursor-not-allowed text-gray-400' |
|
|
|
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200', |
|
|
|
|
)} |
|
|
|
|
> |
|
|
|
|
<ChevronLeft size={16} className="mr-2" /> |
|
|
|
|
Previous{' '} |
|
|
|
|
<span className="hidden lg:inline"> Lesson</span> |
|
|
|
|
</button> |
|
|
|
|
|
|
|
|
|
<div> |
|
|
|
|
<button |
|
|
|
|
onClick={() => { |
|
|
|
|
if (!isLessonDone) { |
|
|
|
|
toggleDone(undefined, { |
|
|
|
|
onSuccess: () => { |
|
|
|
|
onGoToNextLesson(); |
|
|
|
|
}, |
|
|
|
|
}); |
|
|
|
|
} else { |
|
|
|
|
onGoToNextLesson(); |
|
|
|
|
}, |
|
|
|
|
}); |
|
|
|
|
} else { |
|
|
|
|
onGoToNextLesson(); |
|
|
|
|
} |
|
|
|
|
}} |
|
|
|
|
disabled={cantGoForward || isTogglingDone} |
|
|
|
|
className={cn( |
|
|
|
|
'flex items-center rounded-full px-4 py-2 disabled:opacity-50 max-lg:px-3 max-lg:py-1.5 max-lg:text-sm', |
|
|
|
|
cantGoForward |
|
|
|
|
? 'cursor-not-allowed text-gray-400' |
|
|
|
|
: 'bg-gray-800 text-white hover:bg-gray-700', |
|
|
|
|
)} |
|
|
|
|
> |
|
|
|
|
{isTogglingDone ? ( |
|
|
|
|
<> |
|
|
|
|
<Loader2Icon |
|
|
|
|
size={16} |
|
|
|
|
strokeWidth={3} |
|
|
|
|
className="animate-spin text-white" |
|
|
|
|
/> |
|
|
|
|
Please wait ... |
|
|
|
|
</> |
|
|
|
|
) : ( |
|
|
|
|
<> |
|
|
|
|
Next <span className="hidden lg:inline"> Lesson</span> |
|
|
|
|
<ChevronRight size={16} className="ml-2" /> |
|
|
|
|
</> |
|
|
|
|
)} |
|
|
|
|
</button> |
|
|
|
|
} |
|
|
|
|
}} |
|
|
|
|
disabled={cantGoForward || isTogglingDone} |
|
|
|
|
className={cn( |
|
|
|
|
'flex items-center rounded-full px-4 py-2 disabled:opacity-50 max-lg:px-3 max-lg:py-1.5 max-lg:text-sm', |
|
|
|
|
cantGoForward |
|
|
|
|
? 'cursor-not-allowed text-gray-400' |
|
|
|
|
: 'bg-gray-800 text-white hover:bg-gray-700', |
|
|
|
|
)} |
|
|
|
|
> |
|
|
|
|
{isTogglingDone ? ( |
|
|
|
|
<> |
|
|
|
|
<Loader2Icon |
|
|
|
|
size={16} |
|
|
|
|
strokeWidth={3} |
|
|
|
|
className="animate-spin text-white" |
|
|
|
|
/> |
|
|
|
|
Please wait ... |
|
|
|
|
</> |
|
|
|
|
) : ( |
|
|
|
|
<> |
|
|
|
|
Next{' '} |
|
|
|
|
<span className="hidden lg:inline"> Lesson</span> |
|
|
|
|
<ChevronRight size={16} className="ml-2" /> |
|
|
|
|
</> |
|
|
|
|
)} |
|
|
|
|
</button> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
<AICourseFooter /> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</ResizablePanel> |
|
|
|
|
{isAIChatsOpen && ( |
|
|
|
|
<> |
|
|
|
|
<ResizableHandle withHandle={false} className="max-lg:hidden" /> |
|
|
|
|
<ResizablePanel |
|
|
|
|
defaultSize={40} |
|
|
|
|
minSize={20} |
|
|
|
|
id="course-chat-content" |
|
|
|
|
order={2} |
|
|
|
|
className="max-lg:hidden" |
|
|
|
|
> |
|
|
|
|
<AICourseLessonChat |
|
|
|
|
courseSlug={courseSlug} |
|
|
|
|
moduleTitle={currentModuleTitle} |
|
|
|
|
lessonTitle={currentLessonTitle} |
|
|
|
|
onUpgradeClick={onUpgrade} |
|
|
|
|
isDisabled={isGenerating || isLoading || isTogglingDone} |
|
|
|
|
/> |
|
|
|
|
</ResizablePanel> |
|
|
|
|
</> |
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
<AICourseFooter /> |
|
|
|
|
<div |
|
|
|
|
className="fixed inset-0 hidden data-[state=open]:block lg:hidden data-[state=open]:lg:hidden" |
|
|
|
|
data-state={isAIChatsMobileOpen ? 'open' : 'closed'} |
|
|
|
|
> |
|
|
|
|
<div className="absolute inset-0 bg-black/50" /> |
|
|
|
|
<AICourseLessonChat |
|
|
|
|
courseSlug={courseSlug} |
|
|
|
|
moduleTitle={currentModuleTitle} |
|
|
|
|
lessonTitle={currentLessonTitle} |
|
|
|
|
onUpgradeClick={onUpgrade} |
|
|
|
|
isDisabled={isGenerating || isLoading || isTogglingDone} |
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
<button |
|
|
|
|
onClick={() => setIsAIChatsMobileOpen(false)} |
|
|
|
|
className="absolute right-2 top-2 z-20 rounded-full p-1 text-gray-400 hover:text-black" |
|
|
|
|
> |
|
|
|
|
<XIcon className="size-4 stroke-[2.5]" /> |
|
|
|
|
</button> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
<AICourseLessonChat |
|
|
|
|
courseSlug={courseSlug} |
|
|
|
|
moduleTitle={currentModuleTitle} |
|
|
|
|
lessonTitle={currentLessonTitle} |
|
|
|
|
onUpgradeClick={onUpgrade} |
|
|
|
|
isDisabled={isGenerating || isLoading || isTogglingDone} |
|
|
|
|
onClose={() => setIsAIChatsOpen(false)} |
|
|
|
|
isAIChatsOpen={isAIChatsOpen} |
|
|
|
|
setIsAIChatsOpen={setIsAIChatsOpen} |
|
|
|
|
/> |
|
|
|
|
</ResizablePanelGroup> |
|
|
|
|
</div> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|