aakashbansal commited on
Commit
f8f5a66
·
1 Parent(s): 72c115f

1. for every question, I see there are 4 text options below the question

Browse files
src/components/chatbot/chat-message.tsx CHANGED
@@ -4,9 +4,8 @@
4
  import type { ChatMessage as ChatMessageType } from '@/types';
5
  import { cn } from '@/lib/utils';
6
  import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
7
- import { Bot, User, AlertTriangle, CheckCircle, Info } from 'lucide-react'; // Using Info or AlertTriangle for explanation
8
  import { Card, CardContent } from '@/components/ui/card';
9
- import { Badge } from '@/components/ui/badge';
10
  import { MCQOptions } from './mcq-options';
11
  import type { GenerateMCQOutput } from '@/ai/flows/generate-mcq';
12
 
@@ -15,25 +14,26 @@ interface ChatMessageProps {
15
  activeMCQ?: GenerateMCQOutput | null;
16
  isAwaitingActiveMCQAnswer?: boolean;
17
  onOptionSelectActiveMCQ?: (option: string, isCorrect: boolean) => void;
 
18
  }
19
 
20
  export function ChatMessage({
21
  message,
22
  activeMCQ,
23
  isAwaitingActiveMCQAnswer,
24
- onOptionSelectActiveMCQ
 
25
  }: ChatMessageProps) {
26
  const isUser = message.sender === 'user';
27
  const Icon = isUser ? User : Bot;
28
  const avatarFallback = isUser ? 'U' : 'AI';
29
 
30
- // Determine if this message is the currently active MCQ being interacted with
31
  const isCurrentActiveMCQMessage =
32
  message.type === 'mcq' &&
33
  message.mcq &&
34
  activeMCQ &&
35
- message.mcq.mcq === activeMCQ.mcq && // Compare question text
36
- JSON.stringify(message.mcq.options) === JSON.stringify(activeMCQ.options); // Compare options
37
 
38
  return (
39
  <div
@@ -60,18 +60,14 @@ export function ChatMessage({
60
  {message.type === 'mcq' && message.mcq && (
61
  <div className="space-y-2">
62
  <p className="font-medium">{message.mcq.mcq}</p>
63
- {message.mcq.options.map((option, index) => (
64
- <Badge key={index} variant="secondary" className="mr-2 mt-1 block w-fit py-1 px-2.5">
65
- {String.fromCharCode(65 + index)}. {option}
66
- </Badge>
67
- ))}
68
- {/* Render options if this message is the active MCQ and we are awaiting an answer for it */}
69
  {isCurrentActiveMCQMessage && onOptionSelectActiveMCQ && (
70
  <div className="mt-3">
71
  <MCQOptions
72
  mcq={message.mcq}
73
  onOptionSelect={onOptionSelectActiveMCQ}
74
- disabled={!isAwaitingActiveMCQAnswer} // Controls interactability
 
75
  />
76
  </div>
77
  )}
@@ -79,13 +75,11 @@ export function ChatMessage({
79
  )}
80
  {message.type === 'text' && message.text && <p>{message.text}</p>}
81
  {message.type === 'feedback' && message.text && (
82
- <div className="flex items-start"> {/* items-start for better alignment with multi-line text */}
83
  {message.isCorrect ? (
84
  <CheckCircle className="mr-2 h-5 w-5 shrink-0 text-green-500 mt-0.5" />
85
  ) : (
86
- // Using Info icon for explanations, or AlertTriangle for a slightly stronger visual cue for "incorrect"
87
  <Info className="mr-2 h-5 w-5 shrink-0 text-blue-500 mt-0.5" />
88
- // <AlertTriangle className="mr-2 h-5 w-5 shrink-0 text-orange-500 mt-0.5" />
89
  )}
90
  <p className="flex-1">{message.text}</p>
91
  </div>
 
4
  import type { ChatMessage as ChatMessageType } from '@/types';
5
  import { cn } from '@/lib/utils';
6
  import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
7
+ import { Bot, User, AlertTriangle, CheckCircle, Info } from 'lucide-react';
8
  import { Card, CardContent } from '@/components/ui/card';
 
9
  import { MCQOptions } from './mcq-options';
10
  import type { GenerateMCQOutput } from '@/ai/flows/generate-mcq';
11
 
 
14
  activeMCQ?: GenerateMCQOutput | null;
15
  isAwaitingActiveMCQAnswer?: boolean;
16
  onOptionSelectActiveMCQ?: (option: string, isCorrect: boolean) => void;
17
+ incorrectAttemptsForMCQ?: string[];
18
  }
19
 
20
  export function ChatMessage({
21
  message,
22
  activeMCQ,
23
  isAwaitingActiveMCQAnswer,
24
+ onOptionSelectActiveMCQ,
25
+ incorrectAttemptsForMCQ,
26
  }: ChatMessageProps) {
27
  const isUser = message.sender === 'user';
28
  const Icon = isUser ? User : Bot;
29
  const avatarFallback = isUser ? 'U' : 'AI';
30
 
 
31
  const isCurrentActiveMCQMessage =
32
  message.type === 'mcq' &&
33
  message.mcq &&
34
  activeMCQ &&
35
+ message.mcq.mcq === activeMCQ.mcq &&
36
+ JSON.stringify(message.mcq.options) === JSON.stringify(activeMCQ.options);
37
 
38
  return (
39
  <div
 
60
  {message.type === 'mcq' && message.mcq && (
61
  <div className="space-y-2">
62
  <p className="font-medium">{message.mcq.mcq}</p>
63
+ {/* Removed Badge rendering of options here */}
 
 
 
 
 
64
  {isCurrentActiveMCQMessage && onOptionSelectActiveMCQ && (
65
  <div className="mt-3">
66
  <MCQOptions
67
  mcq={message.mcq}
68
  onOptionSelect={onOptionSelectActiveMCQ}
69
+ disabled={!isAwaitingActiveMCQAnswer}
70
+ incorrectAttempts={incorrectAttemptsForMCQ || []}
71
  />
72
  </div>
73
  )}
 
75
  )}
76
  {message.type === 'text' && message.text && <p>{message.text}</p>}
77
  {message.type === 'feedback' && message.text && (
78
+ <div className="flex items-start">
79
  {message.isCorrect ? (
80
  <CheckCircle className="mr-2 h-5 w-5 shrink-0 text-green-500 mt-0.5" />
81
  ) : (
 
82
  <Info className="mr-2 h-5 w-5 shrink-0 text-blue-500 mt-0.5" />
 
83
  )}
84
  <p className="flex-1">{message.text}</p>
85
  </div>
src/components/chatbot/chatbot.tsx CHANGED
@@ -11,7 +11,7 @@ import { explainIncorrectAnswer } from '@/ai/flows/explain-incorrect-answer-flow
11
  import type { ChatMessage as ChatMessageType } from '@/types';
12
  import { ChatMessage } from './chat-message';
13
  import { useToast } from '@/hooks/use-toast';
14
- import { Send, RotateCcw } from 'lucide-react';
15
 
16
  interface ChatbotProps {
17
  imageDataUri: string | null;
@@ -24,6 +24,7 @@ export function Chatbot({ imageDataUri, journeyTitle }: ChatbotProps) {
24
  const [isLoading, setIsLoading] = useState(false);
25
  const [isAwaitingAnswer, setIsAwaitingAnswer] = useState(false);
26
  const [hasAnsweredCorrectly, setHasAnsweredCorrectly] = useState(false);
 
27
  const scrollAreaRef = useRef<HTMLDivElement>(null);
28
  const { toast } = useToast();
29
 
@@ -40,6 +41,7 @@ export function Chatbot({ imageDataUri, journeyTitle }: ChatbotProps) {
40
  setIsAwaitingAnswer(false);
41
  setHasAnsweredCorrectly(false);
42
  setCurrentMCQ(null);
 
43
 
44
  try {
45
  const mcqData = await generateMCQ({ imageDataUri });
@@ -92,7 +94,7 @@ export function Chatbot({ imageDataUri, journeyTitle }: ChatbotProps) {
92
  const handleOptionSelect = async (option: string, isCorrect: boolean) => {
93
  if (!currentMCQ) return;
94
 
95
- setIsAwaitingAnswer(false); // Disable options while processing
96
 
97
  addMessage({
98
  sender: 'user',
@@ -103,7 +105,9 @@ export function Chatbot({ imageDataUri, journeyTitle }: ChatbotProps) {
103
  if (isCorrect) {
104
  addMessage({ sender: 'ai', type: 'feedback', text: "That's correct! Well done.", isCorrect: true });
105
  setHasAnsweredCorrectly(true);
 
106
  } else {
 
107
  setIsLoading(true);
108
  try {
109
  const explanationResult = await explainIncorrectAnswer({
@@ -117,11 +121,10 @@ export function Chatbot({ imageDataUri, journeyTitle }: ChatbotProps) {
117
  console.error("Error fetching explanation:", error);
118
  const errorMsg = error instanceof Error ? error.message : "An unknown error occurred";
119
  addMessage({ sender: 'ai', type: 'error', text: `Sorry, I couldn't explain that. ${errorMsg}` });
120
- // Fallback to simpler incorrect message if explanation fails
121
  addMessage({ sender: 'ai', type: 'feedback', text: "That's not quite right. Try again!", isCorrect: false });
122
  }
123
- setHasAnsweredCorrectly(false); // Ensure it's false
124
- setIsAwaitingAnswer(true); // Re-enable options for the same MCQ
125
  setIsLoading(false);
126
  }
127
  };
@@ -137,12 +140,25 @@ export function Chatbot({ imageDataUri, journeyTitle }: ChatbotProps) {
137
  <ChatMessage
138
  key={msg.id}
139
  message={msg}
140
- activeMCQ={currentMCQ} // Pass currentMCQ to determine if this message is the active one
141
- isAwaitingActiveMCQAnswer={msg.type === 'mcq' && isAwaitingAnswer && !hasAnsweredCorrectly} // Options active if it's an MCQ, awaiting answer, and not yet correct
 
 
 
 
 
 
142
  onOptionSelectActiveMCQ={handleOptionSelect}
 
 
 
 
 
 
 
143
  />
144
  ))}
145
- {isLoading && !currentMCQ && messages[messages.length -1]?.type !== 'feedback' && ( // Show loading skeleton only if not loading an explanation
146
  <div className="flex items-start space-x-3">
147
  <Skeleton className="h-8 w-8 rounded-full" />
148
  <div className="space-y-2">
 
11
  import type { ChatMessage as ChatMessageType } from '@/types';
12
  import { ChatMessage } from './chat-message';
13
  import { useToast } from '@/hooks/use-toast';
14
+ import { Send } from 'lucide-react';
15
 
16
  interface ChatbotProps {
17
  imageDataUri: string | null;
 
24
  const [isLoading, setIsLoading] = useState(false);
25
  const [isAwaitingAnswer, setIsAwaitingAnswer] = useState(false);
26
  const [hasAnsweredCorrectly, setHasAnsweredCorrectly] = useState(false);
27
+ const [incorrectAttempts, setIncorrectAttempts] = useState<string[]>([]);
28
  const scrollAreaRef = useRef<HTMLDivElement>(null);
29
  const { toast } = useToast();
30
 
 
41
  setIsAwaitingAnswer(false);
42
  setHasAnsweredCorrectly(false);
43
  setCurrentMCQ(null);
44
+ setIncorrectAttempts([]); // Reset incorrect attempts for the new question
45
 
46
  try {
47
  const mcqData = await generateMCQ({ imageDataUri });
 
94
  const handleOptionSelect = async (option: string, isCorrect: boolean) => {
95
  if (!currentMCQ) return;
96
 
97
+ setIsAwaitingAnswer(false);
98
 
99
  addMessage({
100
  sender: 'user',
 
105
  if (isCorrect) {
106
  addMessage({ sender: 'ai', type: 'feedback', text: "That's correct! Well done.", isCorrect: true });
107
  setHasAnsweredCorrectly(true);
108
+ setIncorrectAttempts([]); // Clear attempts for next question
109
  } else {
110
+ setIncorrectAttempts(prev => [...prev, option]); // Add to incorrect attempts
111
  setIsLoading(true);
112
  try {
113
  const explanationResult = await explainIncorrectAnswer({
 
121
  console.error("Error fetching explanation:", error);
122
  const errorMsg = error instanceof Error ? error.message : "An unknown error occurred";
123
  addMessage({ sender: 'ai', type: 'error', text: `Sorry, I couldn't explain that. ${errorMsg}` });
 
124
  addMessage({ sender: 'ai', type: 'feedback', text: "That's not quite right. Try again!", isCorrect: false });
125
  }
126
+ setHasAnsweredCorrectly(false);
127
+ setIsAwaitingAnswer(true);
128
  setIsLoading(false);
129
  }
130
  };
 
140
  <ChatMessage
141
  key={msg.id}
142
  message={msg}
143
+ activeMCQ={currentMCQ}
144
+ isAwaitingActiveMCQAnswer={
145
+ msg.type === 'mcq' &&
146
+ currentMCQ?.mcq === msg.mcq?.mcq && // Check if this message IS the active MCQ
147
+ JSON.stringify(currentMCQ?.options) === JSON.stringify(msg.mcq?.options) &&
148
+ isAwaitingAnswer &&
149
+ !hasAnsweredCorrectly
150
+ }
151
  onOptionSelectActiveMCQ={handleOptionSelect}
152
+ incorrectAttemptsForMCQ={
153
+ msg.type === 'mcq' &&
154
+ currentMCQ?.mcq === msg.mcq?.mcq &&
155
+ JSON.stringify(currentMCQ?.options) === JSON.stringify(msg.mcq?.options)
156
+ ? incorrectAttempts
157
+ : [] // Pass incorrect attempts only for the active MCQ message
158
+ }
159
  />
160
  ))}
161
+ {isLoading && !currentMCQ && messages[messages.length -1]?.type !== 'feedback' && (
162
  <div className="flex items-start space-x-3">
163
  <Skeleton className="h-8 w-8 rounded-full" />
164
  <div className="space-y-2">
src/components/chatbot/mcq-options.tsx CHANGED
@@ -9,34 +9,34 @@ import { useState, useEffect } from 'react';
9
  interface MCQOptionsProps {
10
  mcq: GenerateMCQOutput;
11
  onOptionSelect: (option: string, isCorrect: boolean) => void;
12
- disabled: boolean;
 
13
  }
14
 
15
- export function MCQOptions({ mcq, onOptionSelect, disabled }: MCQOptionsProps) {
16
  const [selectedOption, setSelectedOption] = useState<string | null>(null);
17
 
18
- // Reset selectedOption when the options become enabled again (for retries)
19
  useEffect(() => {
20
  if (!disabled) {
21
- setSelectedOption(null);
22
  }
23
  }, [disabled]);
24
 
25
  const handleSelect = (option: string) => {
26
- // Prevent selection if already disabled (isAwaitingAnswer=false) or an option is already visually selected for this attempt
27
- if (disabled || selectedOption) return;
28
 
29
- setSelectedOption(option); // Visually mark selection for this attempt
30
  const isCorrect = option === mcq.correctAnswer;
31
- onOptionSelect(option, isCorrect); // Parent handles disabling/enabling for next state
32
  };
33
 
34
  return (
35
  <div className="mt-4 space-y-2">
36
  {mcq.options.map((option, index) => {
37
  const isCurrentlySelectedVisual = selectedOption === option;
38
- // Visual feedback for correct/incorrect is handled by AI's response message, not here.
39
- // These buttons are just for selection.
 
40
 
41
  return (
42
  <Button
@@ -44,12 +44,13 @@ export function MCQOptions({ mcq, onOptionSelect, disabled }: MCQOptionsProps) {
44
  variant="outline"
45
  className={cn(
46
  'w-full justify-start text-left h-auto py-2.5 px-4 whitespace-normal',
47
- isCurrentlySelectedVisual && 'font-semibold bg-accent/50', // Highlight current visual selection
48
- disabled && !isCurrentlySelectedVisual && 'opacity-60 cursor-not-allowed', // Dim unselected options if disabled
49
- disabled && isCurrentlySelectedVisual && 'opacity-80 cursor-not-allowed' // Slightly less dim for the one that was just clicked
 
50
  )}
51
  onClick={() => handleSelect(option)}
52
- disabled={disabled || (selectedOption !== null && selectedOption !== option)} // Disable other buttons once one is selected for this attempt
53
  aria-pressed={isCurrentlySelectedVisual}
54
  >
55
  <span className="mr-2 font-medium">{String.fromCharCode(65 + index)}.</span>
 
9
  interface MCQOptionsProps {
10
  mcq: GenerateMCQOutput;
11
  onOptionSelect: (option: string, isCorrect: boolean) => void;
12
+ disabled: boolean; // Overall disabled state (e.g., AI thinking, or question answered correctly)
13
+ incorrectAttempts: string[]; // Options that were tried and are incorrect
14
  }
15
 
16
+ export function MCQOptions({ mcq, onOptionSelect, disabled, incorrectAttempts }: MCQOptionsProps) {
17
  const [selectedOption, setSelectedOption] = useState<string | null>(null);
18
 
 
19
  useEffect(() => {
20
  if (!disabled) {
21
+ setSelectedOption(null); // Reset visual selection if options become active again (e.g., for a retry)
22
  }
23
  }, [disabled]);
24
 
25
  const handleSelect = (option: string) => {
26
+ if (disabled || selectedOption || incorrectAttempts.includes(option)) return;
 
27
 
28
+ setSelectedOption(option);
29
  const isCorrect = option === mcq.correctAnswer;
30
+ onOptionSelect(option, isCorrect);
31
  };
32
 
33
  return (
34
  <div className="mt-4 space-y-2">
35
  {mcq.options.map((option, index) => {
36
  const isCurrentlySelectedVisual = selectedOption === option;
37
+ const isIncorrectlyAttempted = incorrectAttempts.includes(option);
38
+
39
+ constisDisabledForThisOption = disabled || isIncorrectlyAttempted || (selectedOption !== null && selectedOption !== option);
40
 
41
  return (
42
  <Button
 
44
  variant="outline"
45
  className={cn(
46
  'w-full justify-start text-left h-auto py-2.5 px-4 whitespace-normal',
47
+ isCurrentlySelectedVisual && !isIncorrectlyAttempted && 'font-semibold bg-accent/50',
48
+ isIncorrectlyAttempted && 'text-destructive line-through opacity-70 cursor-not-allowed',
49
+ disabled && !isIncorrectlyAttempted && !isCurrentlySelectedVisual && 'opacity-60 cursor-not-allowed',
50
+ disabled && isCurrentlySelectedVisual && !isIncorrectlyAttempted && 'opacity-80 cursor-not-allowed'
51
  )}
52
  onClick={() => handleSelect(option)}
53
+ disabled={isDisabledForThisOption}
54
  aria-pressed={isCurrentlySelectedVisual}
55
  >
56
  <span className="mr-2 font-medium">{String.fromCharCode(65 + index)}.</span>