fix: fixed position

feat/chat
Arik Chakma 3 weeks ago
parent 9baf891b2b
commit 496a77ebbe
  1. 12
      src/components/GenerateCourse/AICourseContent.tsx
  2. 60
      src/components/GenerateCourse/AICourseLesson.tsx
  3. 54
      src/components/GenerateCourse/AICourseLessonChat.tsx

@ -39,7 +39,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(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);
@ -144,6 +144,12 @@ export function AICourseContent(props: AICourseContentProps) {
</> </>
); );
useEffect(() => {
if (window && window?.innerWidth < 1024 && isAIChatsOpen) {
setIsAIChatsOpen(false);
}
}, []);
if (error && !isLoading) { if (error && !isLoading) {
const isLimitReached = error.includes('limit'); const isLimitReached = error.includes('limit');
const isNotFound = error.includes('not exist'); const isNotFound = error.includes('not exist');
@ -239,10 +245,9 @@ export function AICourseContent(props: AICourseContentProps) {
/> />
</div> </div>
{viewMode === 'module' && (
<button <button
onClick={() => setIsAIChatsOpen(!isAIChatsOpen)} onClick={() => setIsAIChatsOpen(!isAIChatsOpen)}
className="ml-1.5 flex items-center justify-center text-gray-400 shadow-sm transition-colors hover:bg-gray-50 hover:text-gray-900 lg:hidden" className="flex items-center justify-center text-gray-400 shadow-sm transition-colors hover:bg-gray-50 hover:text-gray-900 lg:hidden"
> >
{isAIChatsOpen ? ( {isAIChatsOpen ? (
<MessageCircleOffIcon size={17} strokeWidth={3} /> <MessageCircleOffIcon size={17} strokeWidth={3} />
@ -250,7 +255,6 @@ export function AICourseContent(props: AICourseContentProps) {
<MessageCircleIcon size={17} strokeWidth={3} /> <MessageCircleIcon size={17} strokeWidth={3} />
)} )}
</button> </button>
)}
<button <button
onClick={() => setSidebarOpen(!sidebarOpen)} onClick={() => setSidebarOpen(!sidebarOpen)}

@ -29,7 +29,10 @@ import { queryClient } from '../../stores/query-client';
import './AICourseLessonChat.css'; 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,
type AIChatHistoryType,
} from './AICourseLessonChat';
import { AICourseFooter } from './AICourseFooter'; import { AICourseFooter } from './AICourseFooter';
import { import {
ResizableHandle, ResizableHandle,
@ -66,7 +69,7 @@ type AICourseLessonProps = {
onUpgrade: () => void; onUpgrade: () => void;
isAIChatsOpen: boolean; isAIChatsOpen: boolean;
setIsAIChatsOpen: (isAIChatsOpen: boolean) => void; setIsAIChatsOpen: (isOpen: boolean) => void;
}; };
export function AICourseLesson(props: AICourseLessonProps) { export function AICourseLesson(props: AICourseLessonProps) {
@ -86,11 +89,10 @@ export function AICourseLesson(props: AICourseLessonProps) {
onUpgrade, onUpgrade,
isAIChatsOpen: isAIChatsMobileOpen, isAIChatsOpen,
setIsAIChatsOpen: setIsAIChatsMobileOpen, setIsAIChatsOpen,
} = props; } = props;
const [isAIChatsOpen, setIsAIChatsOpen] = useState(true);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [isGenerating, setIsGenerating] = useState(false); const [isGenerating, setIsGenerating] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
@ -101,6 +103,17 @@ export function AICourseLesson(props: AICourseLessonProps) {
const lessonId = `${slugify(String(activeModuleIndex))}-${slugify(String(activeLessonIndex))}`; const lessonId = `${slugify(String(activeModuleIndex))}-${slugify(String(activeLessonIndex))}`;
const isLessonDone = progress?.includes(lessonId); const isLessonDone = progress?.includes(lessonId);
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,
},
]);
const { isPaidUser } = useIsPaidUser(); const { isPaidUser } = useIsPaidUser();
const abortController = useMemo( const abortController = useMemo(
@ -460,48 +473,21 @@ export function AICourseLesson(props: AICourseLessonProps) {
{isAIChatsOpen && ( {isAIChatsOpen && (
<> <>
<ResizableHandle withHandle={false} className="max-lg:hidden" /> <ResizableHandle withHandle={false} className="max-lg:hidden" />
<ResizablePanel
defaultSize={40}
minSize={20}
id="course-chat-content"
order={2}
className="max-lg:hidden"
>
<AICourseLessonChat <AICourseLessonChat
courseSlug={courseSlug} courseSlug={courseSlug}
moduleTitle={currentModuleTitle} moduleTitle={currentModuleTitle}
lessonTitle={currentLessonTitle} lessonTitle={currentLessonTitle}
onUpgradeClick={onUpgrade} onUpgradeClick={onUpgrade}
courseAIChatHistory={courseAIChatHistory}
setCourseAIChatHistory={setCourseAIChatHistory}
isDisabled={isGenerating || isLoading || isTogglingDone} isDisabled={isGenerating || isLoading || isTogglingDone}
defaultQuestions={defaultQuestions} defaultQuestions={defaultQuestions}
onClose={() => setIsAIChatsOpen(false)}
isAIChatsOpen={isAIChatsOpen}
setIsAIChatsOpen={setIsAIChatsOpen}
/> />
</ResizablePanel>
</> </>
)} )}
{isAIChatsMobileOpen && (
<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}
defaultQuestions={defaultQuestions}
/>
<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>
)}
</ResizablePanelGroup> </ResizablePanelGroup>
</div> </div>
); );

@ -6,6 +6,7 @@ import {
HelpCircle, HelpCircle,
LockIcon, LockIcon,
Send, Send,
XIcon,
} from 'lucide-react'; } from 'lucide-react';
import { import {
Fragment, Fragment,
@ -29,6 +30,7 @@ import {
import { getAiCourseLimitOptions } from '../../queries/ai-course'; import { getAiCourseLimitOptions } from '../../queries/ai-course';
import { queryClient } from '../../stores/query-client'; import { queryClient } from '../../stores/query-client';
import { billingDetailsOptions } from '../../queries/billing'; import { billingDetailsOptions } from '../../queries/billing';
import { ResizablePanel } from './Resizeable';
export type AllowedAIChatRole = 'user' | 'assistant'; export type AllowedAIChatRole = 'user' | 'assistant';
export type AIChatHistoryType = { export type AIChatHistoryType = {
@ -46,6 +48,14 @@ type AICourseLessonChatProps = {
isDisabled?: boolean; isDisabled?: boolean;
defaultQuestions?: string[]; defaultQuestions?: string[];
courseAIChatHistory: AIChatHistoryType[];
setCourseAIChatHistory: (history: AIChatHistoryType[]) => void;
onClose: () => void;
isAIChatsOpen: boolean;
setIsAIChatsOpen: (isOpen: boolean) => void;
}; };
export function AICourseLessonChat(props: AICourseLessonChatProps) { export function AICourseLessonChat(props: AICourseLessonChatProps) {
@ -56,23 +66,20 @@ export function AICourseLessonChat(props: AICourseLessonChatProps) {
onUpgradeClick, onUpgradeClick,
isDisabled, isDisabled,
defaultQuestions = [], defaultQuestions = [],
courseAIChatHistory,
setCourseAIChatHistory,
onClose,
isAIChatsOpen,
setIsAIChatsOpen,
} = props; } = props;
const toast = useToast(); const toast = useToast();
const scrollareaRef = useRef<HTMLDivElement | null>(null); const scrollareaRef = useRef<HTMLDivElement | null>(null);
const textareaRef = useRef<HTMLTextAreaElement | null>(null); const textareaRef = useRef<HTMLTextAreaElement | null>(null);
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,
},
]);
const [isStreamingMessage, setIsStreamingMessage] = useState(false); const [isStreamingMessage, setIsStreamingMessage] = useState(false);
const [message, setMessage] = useState(''); const [message, setMessage] = useState('');
const [streamedMessage, setStreamedMessage] = useState(''); const [streamedMessage, setStreamedMessage] = useState('');
@ -203,9 +210,25 @@ export function AICourseLessonChat(props: AICourseLessonChatProps) {
}, []); }, []);
return ( return (
<> <ResizablePanel
<div className="relative h-full"> defaultSize={isAIChatsOpen ? 40 : 0}
<div className="absolute inset-y-0 right-0 z-20 flex w-full flex-col overflow-hidden bg-white"> minSize={20}
id="course-chat-content"
order={2}
className="relative h-full max-lg:fixed max-lg:inset-0 max-lg:data-[chat-state=open]:flex max-lg:data-[chat-state=closed]:hidden"
data-chat-state={isAIChatsOpen ? 'open' : 'closed'}
>
<div
className="absolute inset-y-0 right-0 z-20 flex w-full flex-col overflow-hidden bg-white data-[state=open]:flex data-[state=closed]:hidden"
data-state={isAIChatsOpen ? 'open' : 'closed'}
>
<button
onClick={onClose}
className="absolute right-2 top-2 z-20 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>
@ -327,8 +350,7 @@ export function AICourseLessonChat(props: AICourseLessonChatProps) {
</button> </button>
</form> </form>
</div> </div>
</div> </ResizablePanel>
</>
); );
} }

Loading…
Cancel
Save