mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-09-09 08:40:40 +02:00
wip
This commit is contained in:
@@ -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">
|
||||
|
@@ -499,6 +499,7 @@ export function AICourseContent(props: AICourseContentProps) {
|
||||
onForkCourse={() => {
|
||||
setIsForkingCourse(true);
|
||||
}}
|
||||
courseSlug={courseSlug!}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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">
|
||||
|
@@ -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">
|
||||
|
@@ -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);
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user