1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-02 13:52:46 +02:00
This commit is contained in:
Arik Chakma
2025-07-03 00:20:26 +06:00
parent 70d0f7c82e
commit c370bafc53
6 changed files with 57 additions and 33 deletions

View File

@@ -23,6 +23,8 @@ const DEFAULT_QUESTION_STATE: QuestionState = {
status: 'pending', status: 'pending',
}; };
type QuizStatus = 'answering' | 'submitted' | 'reviewing';
type AIQuizContentProps = { type AIQuizContentProps = {
quizSlug?: string; quizSlug?: string;
questions: QuizQuestion[]; questions: QuizQuestion[];
@@ -38,7 +40,7 @@ export function AIQuizContent(props: AIQuizContentProps) {
const [questionStates, setQuestionStates] = useState< const [questionStates, setQuestionStates] = useState<
Record<number, QuestionState> Record<number, QuestionState>
>({}); >({});
const [isAllQuestionsSubmitted, setIsAllQuestionsSubmitted] = useState(false); const [quizStatus, setQuizStatus] = useState<QuizStatus>('answering');
const activeQuestionState = const activeQuestionState =
questionStates[activeQuestionIndex] ?? DEFAULT_QUESTION_STATE; questionStates[activeQuestionIndex] ?? DEFAULT_QUESTION_STATE;
@@ -59,7 +61,9 @@ export function AIQuizContent(props: AIQuizContentProps) {
return newSelectedOptions; return newSelectedOptions;
}); });
setIsAllQuestionsSubmitted(activeQuestionIndex === questions.length - 1); setQuizStatus(
activeQuestionIndex === questions.length - 1 ? 'submitted' : 'answering',
);
}; };
const handleSetUserAnswer = (userAnswer: string) => { const handleSetUserAnswer = (userAnswer: string) => {
@@ -112,34 +116,54 @@ export function AIQuizContent(props: AIQuizContentProps) {
}); });
}; };
const handleNext = () => {
setActiveQuestionIndex(activeQuestionIndex + 1);
};
const handleRetry = () => { const handleRetry = () => {
setActiveQuestionIndex(0); setActiveQuestionIndex(0);
setQuestionStates({}); setQuestionStates({});
setIsAllQuestionsSubmitted(false); setQuizStatus('answering');
}; };
const hasNextQuestion = activeQuestionIndex < questions.length - 1;
const hasPreviousQuestion = activeQuestionIndex > 0;
const totalQuestions = questions?.length ?? 0; const totalQuestions = questions?.length ?? 0;
const isAllQuestionsSubmitted =
Object.values(questionStates).filter((state) => state.status !== 'pending')
.length === totalQuestions;
const progressPercentage = isLoading const progressPercentage = isLoading
? 0 ? 0
: getPercentage(activeQuestionIndex + 1, totalQuestions); : getPercentage(activeQuestionIndex + 1, totalQuestions);
const shouldShowQuestions =
quizStatus === 'answering' || quizStatus === 'reviewing';
const handleNextQuestion = () => {
if (!hasNextQuestion) {
setQuizStatus(isAllQuestionsSubmitted ? 'submitted' : 'reviewing');
return;
}
setActiveQuestionIndex(activeQuestionIndex + 1);
};
return ( return (
<div className="mx-auto w-full max-w-lg py-10"> <div className="mx-auto w-full max-w-lg py-10">
{!isAllQuestionsSubmitted && ( {shouldShowQuestions && (
<QuizTopNavigation <QuizTopNavigation
activeQuestionIndex={activeQuestionIndex} activeQuestionIndex={activeQuestionIndex}
totalQuestions={totalQuestions} totalQuestions={totalQuestions}
progressPercentage={progressPercentage} progressPercentage={progressPercentage}
onPrevious={() => setActiveQuestionIndex(activeQuestionIndex - 1)} onPrevious={() => {
onNext={() => setActiveQuestionIndex(activeQuestionIndex + 1)} if (!hasPreviousQuestion) {
return;
}
setActiveQuestionIndex(activeQuestionIndex - 1);
}}
onNext={handleNextQuestion}
/> />
)} )}
{isAllQuestionsSubmitted && ( {quizStatus === 'submitted' && (
<AIQuizResults <AIQuizResults
questionStates={questionStates} questionStates={questionStates}
totalQuestions={totalQuestions} totalQuestions={totalQuestions}
@@ -147,10 +171,14 @@ export function AIQuizContent(props: AIQuizContentProps) {
onNewQuiz={() => { onNewQuiz={() => {
window.location.href = '/ai/quiz'; window.location.href = '/ai/quiz';
}} }}
onReview={(questionIndex) => {
setActiveQuestionIndex(questionIndex);
setQuizStatus('reviewing');
}}
/> />
)} )}
{!isAllQuestionsSubmitted && ( {shouldShowQuestions && (
<> <>
{activeQuestion && activeQuestion.type === 'mcq' && ( {activeQuestion && activeQuestion.type === 'mcq' && (
<AIMCQQuestion <AIMCQQuestion
@@ -158,7 +186,7 @@ export function AIQuizContent(props: AIQuizContentProps) {
questionState={activeQuestionState} questionState={activeQuestionState}
setSelectedOptions={handleSelectOptions} setSelectedOptions={handleSelectOptions}
onSubmit={handleSubmit} onSubmit={handleSubmit}
onNext={handleNext} onNext={handleNextQuestion}
/> />
)} )}
@@ -168,7 +196,7 @@ export function AIQuizContent(props: AIQuizContentProps) {
question={activeQuestion} question={activeQuestion}
questionState={activeQuestionState} questionState={activeQuestionState}
onSubmit={handleSubmit} onSubmit={handleSubmit}
onNext={handleNext} onNext={handleNextQuestion}
setUserAnswer={handleSetUserAnswer} setUserAnswer={handleSetUserAnswer}
setCorrectAnswer={handleSetCorrectAnswer} setCorrectAnswer={handleSetCorrectAnswer}
/> />

View File

@@ -218,9 +218,7 @@ export function AIQuizGenerator() {
format={selectedFormatTitle || selectedFormat} format={selectedFormatTitle || selectedFormat}
questionAnswerChatMessages={questionAnswerChatMessages} questionAnswerChatMessages={questionAnswerChatMessages}
setQuestionAnswerChatMessages={setQuestionAnswerChatMessages} setQuestionAnswerChatMessages={setQuestionAnswerChatMessages}
onGenerateNow={() => { from="quiz"
handleSubmit();
}}
/> />
)} )}

View File

@@ -22,10 +22,12 @@ type AIQuizResultsProps = {
totalQuestions: number; totalQuestions: number;
onRetry: () => void; onRetry: () => void;
onNewQuiz: () => void; onNewQuiz: () => void;
onReview?: (questionIndex: number) => void;
}; };
export function AIQuizResults(props: AIQuizResultsProps) { export function AIQuizResults(props: AIQuizResultsProps) {
const { questionStates, totalQuestions, onRetry, onNewQuiz } = props; const { questionStates, totalQuestions, onRetry, onNewQuiz, onReview } =
props;
const states = Object.values(questionStates); const states = Object.values(questionStates);
const correctCount = states.filter( const correctCount = states.filter(
@@ -66,10 +68,11 @@ export function AIQuizResults(props: AIQuizResultsProps) {
const isSkipped = status === 'skipped'; const isSkipped = status === 'skipped';
return ( return (
<div <button
key={quizIndex} key={quizIndex}
onClick={() => onReview?.(quizIndex)}
className={cn( className={cn(
'flex aspect-square flex-col items-center justify-center rounded-xl border border-gray-200 p-2', 'flex aspect-square flex-col items-center justify-center rounded-xl border border-gray-200 p-2 hover:opacity-80',
isCorrect && 'bg-green-700 text-white', isCorrect && 'bg-green-700 text-white',
isIncorrect && 'bg-red-700 text-white', isIncorrect && 'bg-red-700 text-white',
isSkipped && 'bg-gray-700 text-white', isSkipped && 'bg-gray-700 text-white',
@@ -78,7 +81,7 @@ export function AIQuizResults(props: AIQuizResultsProps) {
{isCorrect && <CheckIcon className="h-6 w-6" />} {isCorrect && <CheckIcon className="h-6 w-6" />}
{isIncorrect && <XIcon className="h-6 w-6" />} {isIncorrect && <XIcon className="h-6 w-6" />}
{isSkipped && <SkipForwardIcon className="h-6 w-6" />} {isSkipped && <SkipForwardIcon className="h-6 w-6" />}
</div> </button>
); );
})} })}
</div> </div>

View File

@@ -227,9 +227,6 @@ export function ContentGenerator() {
format={selectedFormat} format={selectedFormat}
questionAnswerChatMessages={questionAnswerChatMessages} questionAnswerChatMessages={questionAnswerChatMessages}
setQuestionAnswerChatMessages={setQuestionAnswerChatMessages} setQuestionAnswerChatMessages={setQuestionAnswerChatMessages}
onGenerateNow={() => {
handleSubmit();
}}
/> />
)} )}

View File

@@ -27,9 +27,11 @@ type QuestionAnswerChatProps = {
setQuestionAnswerChatMessages: ( setQuestionAnswerChatMessages: (
messages: QuestionAnswerChatMessage[], messages: QuestionAnswerChatMessage[],
) => void; ) => void;
onGenerateNow: () => void;
defaultQuestions?: AIQuestionSuggestionsResponse['questions']; defaultQuestions?: AIQuestionSuggestionsResponse['questions'];
type?: 'create' | 'update'; type?: 'create' | 'update';
from?: 'content' | 'quiz';
className?: string; className?: string;
}; };
@@ -40,9 +42,9 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
defaultQuestions, defaultQuestions,
questionAnswerChatMessages, questionAnswerChatMessages,
setQuestionAnswerChatMessages, setQuestionAnswerChatMessages,
onGenerateNow,
type = 'create', type = 'create',
className = '', className = '',
from = 'content',
} = props; } = props;
const [activeMessageIndex, setActiveMessageIndex] = useState( const [activeMessageIndex, setActiveMessageIndex] = useState(
@@ -58,7 +60,7 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
data: aiQuestionSuggestions, data: aiQuestionSuggestions,
isLoading: isLoadingAiQuestionSuggestions, isLoading: isLoadingAiQuestionSuggestions,
} = useQuery( } = useQuery(
aiQuestionSuggestionsOptions({ term, format }, defaultQuestions), aiQuestionSuggestionsOptions({ term, format, from }, defaultQuestions),
queryClient, queryClient,
); );
@@ -113,11 +115,6 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
scrollToBottom(); scrollToBottom();
}; };
const canGenerateNow =
// user can generate after answering 5 questions -> 5 * 2 messages (user and assistant)
!isLoadingAiQuestionSuggestions && questionAnswerChatMessages.length >= 10;
const canReset = questionAnswerChatMessages.length >= 2;
const handleReset = () => { const handleReset = () => {
setQuestionAnswerChatMessages([]); setQuestionAnswerChatMessages([]);
setActiveMessageIndex(0); setActiveMessageIndex(0);

View File

@@ -4,6 +4,7 @@ import { httpGet } from '../lib/query-http';
type AIQuestionSuggestionsQuery = { type AIQuestionSuggestionsQuery = {
term: string; term: string;
format: string; format: string;
from?: 'content' | 'quiz';
}; };
export type AIQuestionSuggestionsResponse = { export type AIQuestionSuggestionsResponse = {
@@ -31,7 +32,7 @@ export function aiQuestionSuggestionsOptions(
query, query,
); );
}, },
enabled: !!query.term && !!query.format, enabled: !!query.term && !!query.format && !!query.from,
refetchOnMount: false, refetchOnMount: false,
}); });
} }