feat: ai course chat (#8426)
* feat: ai course chat * wip: remove old code * wip * feat: responsiveness of ai chat * fix: key warning * feat: make chat resizeable * wip * wip: default questions * wip * fix: fixed position * fix: hide button * Fix scroll issue * Improve questions UI * Refactor UI * Add close icon * Update UI for course chat * Close AI chat question --------- Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>pull/8446/head
parent
981af58fa9
commit
dfd54b35b0
9 changed files with 561 additions and 354 deletions
@ -1,74 +0,0 @@ |
||||
import { ArrowRightIcon, BotIcon } from 'lucide-react'; |
||||
import { useState } from 'react'; |
||||
import { |
||||
AICourseFollowUpPopover, |
||||
type AIChatHistoryType, |
||||
} from './AICourseFollowUpPopover'; |
||||
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal'; |
||||
|
||||
type AICourseFollowUpProps = { |
||||
courseSlug: string; |
||||
moduleTitle: string; |
||||
lessonTitle: string; |
||||
}; |
||||
|
||||
export function AICourseFollowUp(props: AICourseFollowUpProps) { |
||||
const { courseSlug, moduleTitle, lessonTitle } = props; |
||||
|
||||
const [isOpen, setIsOpen] = useState(false); |
||||
const [showUpgradeModal, setShowUpgradeModal] = useState(false); |
||||
|
||||
const [courseAIChatHistory, setCourseAIChatHistory] = useState< |
||||
AIChatHistoryType[] |
||||
>([ |
||||
{ |
||||
role: 'assistant', |
||||
content: |
||||
'Hey, I am your AI instructor. Here are some examples of what you can ask me about 🤖', |
||||
isDefault: true, |
||||
}, |
||||
]); |
||||
|
||||
return ( |
||||
<div className="relative"> |
||||
<button |
||||
className="mt-4 flex w-full items-center gap-2 rounded-lg border border-yellow-300 bg-yellow-100 p-4 hover:bg-yellow-200 max-lg:mt-3 max-lg:text-sm" |
||||
onClick={() => setIsOpen(true)} |
||||
> |
||||
<BotIcon className="h-4 w-4" /> |
||||
<span>Ask AI some follow up questions</span> |
||||
|
||||
<ArrowRightIcon className="ml-auto h-4 w-4 max-sm:hidden" /> |
||||
</button> |
||||
|
||||
{showUpgradeModal && ( |
||||
<UpgradeAccountModal onClose={() => setShowUpgradeModal(false)} /> |
||||
)} |
||||
|
||||
{isOpen && ( |
||||
<AICourseFollowUpPopover |
||||
courseSlug={courseSlug} |
||||
moduleTitle={moduleTitle} |
||||
lessonTitle={lessonTitle} |
||||
courseAIChatHistory={courseAIChatHistory} |
||||
setCourseAIChatHistory={setCourseAIChatHistory} |
||||
onUpgradeClick={() => { |
||||
setIsOpen(false); |
||||
setShowUpgradeModal(true); |
||||
}} |
||||
onOutsideClick={() => { |
||||
if (!isOpen) { |
||||
return; |
||||
} |
||||
|
||||
setIsOpen(false); |
||||
}} |
||||
/> |
||||
)} |
||||
|
||||
{isOpen && ( |
||||
<div className="pointer-events-none fixed inset-0 z-50 bg-black/50" /> |
||||
)} |
||||
</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> |
||||
); |
||||
} |
@ -0,0 +1,42 @@ |
||||
import { GripVertical } from 'lucide-react'; |
||||
import * as ResizablePrimitive from 'react-resizable-panels'; |
||||
import { cn } from '../../lib/classname'; |
||||
|
||||
const ResizablePanelGroup = ({ |
||||
className, |
||||
...props |
||||
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => ( |
||||
<ResizablePrimitive.PanelGroup |
||||
className={cn( |
||||
'flex h-full w-full data-[panel-group-direction=vertical]:flex-col', |
||||
className, |
||||
)} |
||||
{...props} |
||||
/> |
||||
); |
||||
|
||||
const ResizablePanel = ResizablePrimitive.Panel; |
||||
|
||||
const ResizableHandle = ({ |
||||
withHandle, |
||||
className, |
||||
...props |
||||
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & { |
||||
withHandle?: boolean; |
||||
}) => ( |
||||
<ResizablePrimitive.PanelResizeHandle |
||||
className={cn( |
||||
'relative flex w-px items-center justify-center bg-gray-200 after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90', |
||||
className, |
||||
)} |
||||
{...props} |
||||
> |
||||
{withHandle && ( |
||||
<div className="z-10 flex h-[30px] w-3 items-center justify-center rounded-sm bg-gray-200 text-black hover:bg-gray-300"> |
||||
<GripVertical className="size-5" /> |
||||
</div> |
||||
)} |
||||
</ResizablePrimitive.PanelResizeHandle> |
||||
); |
||||
|
||||
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }; |
Loading…
Reference in new issue