1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-09 08:40:40 +02:00
This commit is contained in:
Arik Chakma
2025-06-23 20:13:10 +06:00
parent 71c147a0ef
commit 06db9a98d0
8 changed files with 93 additions and 7 deletions

View File

@@ -6,7 +6,7 @@ import {
} from '../../queries/user-ai-session';
import type { AllowedFormat } from './ContentGenerator';
import { Loader2Icon, RotateCwIcon, SendIcon } from 'lucide-react';
import { useRef, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { cn } from '../../lib/classname';
import { flushSync } from 'react-dom';
import { CheckIcon } from '../ReactIcons/CheckIcon';
@@ -115,6 +115,10 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
setStatus('answering');
};
useEffect(() => {
scrollToBottom();
}, [defaultQuestions]);
return (
<>
<div className="relative h-[420px] w-full overflow-hidden rounded-xl border border-gray-200 bg-white">

View File

@@ -499,6 +499,7 @@ export function AICourseContent(props: AICourseContentProps) {
onForkCourse={() => {
setIsForkingCourse(true);
}}
courseSlug={courseSlug!}
/>
)}

View File

@@ -12,6 +12,7 @@ type AICourseOutlineHeaderProps = {
setViewMode: (mode: AICourseViewMode) => void;
isForkable: boolean;
onForkCourse: () => void;
courseSlug: string;
};
export function AICourseOutlineHeader(props: AICourseOutlineHeaderProps) {
@@ -23,6 +24,7 @@ export function AICourseOutlineHeader(props: AICourseOutlineHeaderProps) {
setViewMode,
isForkable,
onForkCourse,
courseSlug,
} = props;
return (
@@ -36,9 +38,6 @@ export function AICourseOutlineHeader(props: AICourseOutlineHeaderProps) {
<h2 className="mb-1 text-2xl font-bold text-balance max-lg:text-lg max-lg:leading-tight">
{course.title || 'Loading course ..'}
</h2>
<p className="text-sm text-gray-500 capitalize">
{course.title ? course.difficulty : 'Please wait ..'}
</p>
</div>
<div className="absolute top-3 right-3 flex gap-2 max-lg:relative max-lg:top-0 max-lg:right-0 max-lg:w-full max-lg:flex-row-reverse max-lg:justify-between">
@@ -48,6 +47,7 @@ export function AICourseOutlineHeader(props: AICourseOutlineHeaderProps) {
onRegenerateOutline={onRegenerateOutline}
isForkable={isForkable}
onForkCourse={onForkCourse}
courseSlug={courseSlug}
/>
<div className="mr-1 flex rounded-lg border border-gray-200 bg-white p-0.5">
<button

View File

@@ -19,6 +19,7 @@ type AICourseOutlineViewProps = {
viewMode: AICourseViewMode;
isForkable: boolean;
onForkCourse: () => void;
courseSlug: string;
};
export function AICourseOutlineView(props: AICourseOutlineViewProps) {
@@ -34,6 +35,7 @@ export function AICourseOutlineView(props: AICourseOutlineViewProps) {
viewMode,
isForkable,
onForkCourse,
courseSlug,
} = props;
const aiCourseProgress = course.done || [];
@@ -48,6 +50,7 @@ export function AICourseOutlineView(props: AICourseOutlineViewProps) {
setViewMode={setViewMode}
isForkable={isForkable}
onForkCourse={onForkCourse}
courseSlug={courseSlug}
/>
{course.title ? (
<div className="flex flex-col p-6 max-lg:mt-0.5 max-lg:p-4">

View File

@@ -229,6 +229,7 @@ export function AICourseRoadmapView(props: AICourseRoadmapViewProps) {
setViewMode={setViewMode}
isForkable={isForkable}
onForkCourse={onForkCourse}
courseSlug={courseSlug}
/>
{isLoading && (
<div className="absolute inset-0 flex h-full w-full items-center justify-center">

View File

@@ -1,27 +1,71 @@
import { PenSquare, RefreshCcw } from 'lucide-react';
import { PenSquare, RefreshCcw, SettingsIcon } from 'lucide-react';
import { useRef, useState } from 'react';
import { useOutsideClick } from '../../hooks/use-outside-click';
import { cn } from '../../lib/classname';
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
import { ModifyCoursePrompt } from './ModifyCoursePrompt';
import { queryClient } from '../../stores/query-client';
import { getAiCourseOptions } from '../../queries/ai-course';
import { useMutation, useQuery } from '@tanstack/react-query';
import { httpPost } from '../../lib/query-http';
import type { QuestionAnswerChatMessage } from '../ContentGenerator/QuestionAnswerChat';
import { getAiGuideOptions } from '../../queries/ai-guide';
import { UpdatePreferences } from '../GenerateGuide/UpdatePreferences';
type RegenerateOutlineProps = {
onRegenerateOutline: (prompt?: string) => void;
isForkable: boolean;
onForkCourse: () => void;
courseSlug: string;
};
export function RegenerateOutline(props: RegenerateOutlineProps) {
const { onRegenerateOutline, isForkable, onForkCourse } = props;
const { onRegenerateOutline, isForkable, onForkCourse, courseSlug } = props;
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
const [showPromptModal, setShowPromptModal] = useState(false);
const [showUpdatePreferencesModal, setShowUpdatePreferencesModal] =
useState(false);
const ref = useRef<HTMLDivElement>(null);
useOutsideClick(ref, () => setIsDropdownVisible(false));
const { data: aiCourse } = useQuery(
getAiCourseOptions({ aiCourseSlug: courseSlug }),
queryClient,
);
const { mutate: updatePreferences, isPending: isUpdating } = useMutation(
{
mutationFn: (questionAndAnswers: QuestionAnswerChatMessage[]) => {
return httpPost(`/v1-update-ai-course-preferences/${courseSlug}`, {
questionAndAnswers,
});
},
onSuccess: (_, vars) => {
queryClient.setQueryData(
getAiCourseOptions({ aiCourseSlug: courseSlug }).queryKey,
(old) => {
if (!old) {
return old;
}
return {
...old,
questionAndAnswers: vars,
};
},
);
setShowUpdatePreferencesModal(false);
setIsDropdownVisible(false);
onRegenerateOutline();
},
},
queryClient,
);
return (
<>
{showUpgradeModal && (
@@ -46,6 +90,19 @@ export function RegenerateOutline(props: RegenerateOutlineProps) {
/>
)}
{showUpdatePreferencesModal && (
<UpdatePreferences
onClose={() => setShowUpdatePreferencesModal(false)}
questionAndAnswers={aiCourse?.questionAndAnswers}
term={aiCourse?.keyword || ''}
format="course"
onUpdatePreferences={(questionAndAnswers) => {
updatePreferences(questionAndAnswers);
}}
isUpdating={isUpdating}
/>
)}
<div ref={ref} className="relative flex items-stretch">
<button
className={cn('rounded-md px-2.5 text-gray-400 hover:text-black', {
@@ -56,7 +113,22 @@ export function RegenerateOutline(props: RegenerateOutlineProps) {
<PenSquare className="text-current" size={16} strokeWidth={2.5} />
</button>
{isDropdownVisible && (
<div className="absolute top-full right-0 min-w-[170px] translate-y-1 overflow-hidden rounded-md border border-gray-200 bg-white shadow-md">
<div className="absolute top-full right-0 min-w-[190px] translate-y-1 overflow-hidden rounded-md border border-gray-200 bg-white shadow-md">
<button
onClick={() => {
setIsDropdownVisible(false);
setShowUpdatePreferencesModal(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"
>
<SettingsIcon
size={16}
className="text-gray-400"
strokeWidth={2.5}
/>
Update Preferences
</button>
<button
onClick={() => {
setIsDropdownVisible(false);

View File

@@ -1,6 +1,7 @@
import { httpGet } from '../lib/query-http';
import { isLoggedIn } from '../lib/jwt';
import { queryOptions } from '@tanstack/react-query';
import type { QuestionAnswerChatMessage } from '../components/ContentGenerator/QuestionAnswerChat';
export interface AICourseProgressDocument {
_id: string;
@@ -30,6 +31,7 @@ export interface AICourseDocument {
difficulty: string;
modules: AICourseModule[];
viewCount: number;
questionAndAnswers?: QuestionAnswerChatMessage[];
createdAt: Date;
updatedAt: Date;
}
@@ -45,6 +47,7 @@ export function getAiCourseOptions(params: GetAICourseParams) {
);
},
enabled: !!params.aiCourseSlug,
refetchOnMount: false,
};
}

View File

@@ -48,6 +48,7 @@ export function getAiGuideOptions(guideSlug?: string) {
};
},
enabled: !!guideSlug,
refetchOnMount: false,
});
}
@@ -66,6 +67,7 @@ export function aiGuideSuggestionsOptions(guideSlug?: string) {
);
},
enabled: !!guideSlug && !!isLoggedIn(),
refetchOnMount: false,
});
}