aakashbansal commited on
Commit
e64fb74
·
1 Parent(s): 7b19fb1

no, i still get 2 messages. first gives the greeting then a question, th

Browse files
Files changed (1) hide show
  1. src/components/chatbot/chatbot.tsx +58 -42
src/components/chatbot/chatbot.tsx CHANGED
@@ -2,7 +2,7 @@
2
  "use client";
3
 
4
  import type { ChangeEvent } from 'react';
5
- import { useState, useEffect, useRef } from 'react';
6
  import { Button } from '@/components/ui/button';
7
  import { ScrollArea } from '@/components/ui/scroll-area';
8
  import { Skeleton } from '@/components/ui/skeleton';
@@ -31,37 +31,37 @@ export function Chatbot({ imageDataUri, journeyTitle }: ChatbotProps) {
31
  const messageIdCounter = useRef(0);
32
  const isInitialQuestionRef = useRef(true);
33
 
34
- const addMessage = (message: Omit<ChatMessageType, 'id' | 'timestamp'>) => {
35
  messageIdCounter.current += 1;
36
  const newId = `${Date.now()}-${messageIdCounter.current}`;
37
  setMessages(prev => [...prev, { ...message, id: newId, timestamp: new Date() }]);
38
- };
39
 
40
- const fetchNewMCQ = async () => {
41
  if (!imageDataUri) {
42
  addMessage({ sender: 'ai', type: 'error', text: "Image data is not available to generate questions." });
43
  setIsLoading(false);
44
  return;
45
  }
46
  setIsLoading(true);
47
- setIsAwaitingAnswer(false);
48
- setHasAnsweredCorrectly(false);
49
- setCurrentMCQ(null);
50
- setIncorrectAttempts([]);
51
 
52
  try {
53
  const mcqResult = await generateMCQ({ imageDataUri });
54
 
55
  let questionText = mcqResult.mcq;
 
 
56
  if (isInitialQuestionRef.current) {
57
  questionText = `Okay, let's start with ${journeyTitle}! ${mcqResult.mcq}`;
58
- isInitialQuestionRef.current = false;
59
  }
 
60
 
61
- const mcqDataForMessage = { ...mcqResult, mcq: questionText };
62
- setCurrentMCQ(mcqDataForMessage);
63
- addMessage({ sender: 'ai', type: 'mcq', mcq: mcqDataForMessage });
64
- setIsAwaitingAnswer(true);
65
  } catch (error) {
66
  console.error("Error generating MCQ:", error);
67
  const errorMessage = error instanceof Error ? error.message : "An unknown error occurred.";
@@ -74,28 +74,28 @@ export function Chatbot({ imageDataUri, journeyTitle }: ChatbotProps) {
74
  } finally {
75
  setIsLoading(false);
76
  }
77
- };
 
78
 
79
  useEffect(() => {
80
- setMessages([]);
81
- isInitialQuestionRef.current = true;
82
-
 
 
 
 
 
 
83
  if (imageDataUri) {
84
  fetchNewMCQ();
85
  } else {
 
 
86
  addMessage({ sender: 'ai', type: 'text', text: "Preparing your journey..." });
87
  }
88
  // eslint-disable-next-line react-hooks/exhaustive-deps
89
- }, [imageDataUri, journeyTitle]);
90
-
91
- useEffect(() => {
92
- if (imageDataUri && messages.length === 1 && messages[0].type === 'text' && messages[0].text === "Preparing your journey...") {
93
- setMessages([]);
94
- fetchNewMCQ();
95
- }
96
- // eslint-disable-next-line react-hooks/exhaustive-deps
97
- }, [imageDataUri, messages]);
98
-
99
 
100
  useEffect(() => {
101
  if (scrollAreaRef.current) {
@@ -115,7 +115,7 @@ export function Chatbot({ imageDataUri, journeyTitle }: ChatbotProps) {
115
  });
116
 
117
  if (isCorrect) {
118
- setIsLoading(true);
119
  addMessage({ sender: 'ai', type: 'feedback', text: "That's correct! Well done.", isCorrect: true });
120
  try {
121
  const explanationResult = await explainCorrectAnswer({
@@ -131,11 +131,11 @@ export function Chatbot({ imageDataUri, journeyTitle }: ChatbotProps) {
131
  }
132
  setHasAnsweredCorrectly(true);
133
  setIncorrectAttempts([]);
134
- setIsLoading(false);
135
  } else {
136
  const updatedIncorrectAttempts = [...incorrectAttempts, option];
137
  setIncorrectAttempts(updatedIncorrectAttempts);
138
- setIsLoading(true);
139
 
140
  if (updatedIncorrectAttempts.length >= currentMCQ.options.length - 1) {
141
  addMessage({
@@ -156,8 +156,8 @@ export function Chatbot({ imageDataUri, journeyTitle }: ChatbotProps) {
156
  const errorMsg = error instanceof Error ? error.message : "An unknown error occurred";
157
  addMessage({ sender: 'ai', type: 'error', text: `Sorry, I couldn't provide an explanation for that. ${errorMsg}` });
158
  }
159
- setHasAnsweredCorrectly(true);
160
- setIncorrectAttempts([]);
161
  } else {
162
  try {
163
  const explanationResult = await explainIncorrectAnswer({
@@ -167,6 +167,7 @@ export function Chatbot({ imageDataUri, journeyTitle }: ChatbotProps) {
167
  correctAnswer: currentMCQ.correctAnswer,
168
  });
169
  addMessage({ sender: 'ai', type: 'feedback', text: explanationResult.explanation, isCorrect: false });
 
170
  addMessage({ sender: 'ai', type: 'mcq', mcq: currentMCQ });
171
  } catch (error) {
172
  console.error("Error fetching explanation for incorrect answer:", error);
@@ -175,12 +176,21 @@ export function Chatbot({ imageDataUri, journeyTitle }: ChatbotProps) {
175
  addMessage({ sender: 'ai', type: 'feedback', text: "That's not quite right. Try again!", isCorrect: false });
176
  addMessage({ sender: 'ai', type: 'mcq', mcq: currentMCQ });
177
  }
178
- setIsAwaitingAnswer(true);
179
  }
180
- setIsLoading(false);
181
  }
182
  };
183
 
 
 
 
 
 
 
 
 
 
184
  return (
185
  <div className="flex h-full flex-col rounded-lg border bg-card shadow-xl">
186
  <div className="border-b p-4">
@@ -193,9 +203,10 @@ export function Chatbot({ imageDataUri, journeyTitle }: ChatbotProps) {
193
  msg.type === 'mcq' &&
194
  msg.mcq &&
195
  currentMCQ &&
196
- msg.mcq.mcq === currentMCQ.mcq &&
197
  JSON.stringify(msg.mcq.options) === JSON.stringify(currentMCQ.options);
198
 
 
199
  const lastInstanceOfCurrentMCQIndex = messages.findLastIndex(
200
  (m) =>
201
  m.type === 'mcq' &&
@@ -205,9 +216,11 @@ export function Chatbot({ imageDataUri, journeyTitle }: ChatbotProps) {
205
  JSON.stringify(m.mcq.options) === JSON.stringify(currentMCQ.options)
206
  );
207
 
 
 
208
  const shouldThisMessageBeInteractive =
209
  isThisMessageTheCurrentMCQ &&
210
- index === lastInstanceOfCurrentMCQIndex &&
211
  isAwaitingAnswer &&
212
  !hasAnsweredCorrectly;
213
 
@@ -219,10 +232,10 @@ export function Chatbot({ imageDataUri, journeyTitle }: ChatbotProps) {
219
  isAwaitingActiveMCQAnswer={shouldThisMessageBeInteractive}
220
  onOptionSelectActiveMCQ={handleOptionSelect}
221
  incorrectAttemptsForMCQ={
222
- isThisMessageTheCurrentMCQ &&
223
- index === lastInstanceOfCurrentMCQIndex
224
  ? incorrectAttempts
225
- : []
226
  }
227
  />
228
  );
@@ -242,7 +255,7 @@ export function Chatbot({ imageDataUri, journeyTitle }: ChatbotProps) {
242
  {isLoading && <p className="text-center text-sm text-muted-foreground">AI is thinking...</p>}
243
 
244
  {!isLoading && currentMCQ && hasAnsweredCorrectly && (
245
- <Button onClick={fetchNewMCQ} className="w-full" variant="default" disabled={isLoading}>
246
  Next Question <Send className="ml-2 h-4 w-4" />
247
  </Button>
248
  )}
@@ -256,11 +269,14 @@ export function Chatbot({ imageDataUri, journeyTitle }: ChatbotProps) {
256
  {!imageDataUri && !isLoading && (
257
  <p className="text-center text-sm text-muted-foreground">Loading image, please wait...</p>
258
  )}
259
- {!currentMCQ && !isLoading && messages.length > 0 && messages[messages.length -1]?.type !== 'error' && messages[messages.length -1]?.type !== 'text' && (
260
  <p className="text-center text-sm text-muted-foreground">Loading first question...</p>
261
  )}
 
 
 
262
  </div>
263
  </div>
264
  );
265
  }
266
-
 
2
  "use client";
3
 
4
  import type { ChangeEvent } from 'react';
5
+ import { useState, useEffect, useRef, useCallback } from 'react';
6
  import { Button } from '@/components/ui/button';
7
  import { ScrollArea } from '@/components/ui/scroll-area';
8
  import { Skeleton } from '@/components/ui/skeleton';
 
31
  const messageIdCounter = useRef(0);
32
  const isInitialQuestionRef = useRef(true);
33
 
34
+ const addMessage = useCallback((message: Omit<ChatMessageType, 'id' | 'timestamp'>) => {
35
  messageIdCounter.current += 1;
36
  const newId = `${Date.now()}-${messageIdCounter.current}`;
37
  setMessages(prev => [...prev, { ...message, id: newId, timestamp: new Date() }]);
38
+ }, []);
39
 
40
+ const fetchNewMCQ = useCallback(async () => {
41
  if (!imageDataUri) {
42
  addMessage({ sender: 'ai', type: 'error', text: "Image data is not available to generate questions." });
43
  setIsLoading(false);
44
  return;
45
  }
46
  setIsLoading(true);
47
+ // Important: Don't reset currentMCQ or isAwaitingAnswer here if this function is also used for "Next Question"
48
+ // The initial reset is handled by the useEffect. For "Next Question", specific states are managed by handleOptionSelect.
 
 
49
 
50
  try {
51
  const mcqResult = await generateMCQ({ imageDataUri });
52
 
53
  let questionText = mcqResult.mcq;
54
+ let finalMCQData = { ...mcqResult };
55
+
56
  if (isInitialQuestionRef.current) {
57
  questionText = `Okay, let's start with ${journeyTitle}! ${mcqResult.mcq}`;
58
+ isInitialQuestionRef.current = false; // Set to false after using it for the first question
59
  }
60
+ finalMCQData.mcq = questionText;
61
 
62
+ setCurrentMCQ(finalMCQData);
63
+ addMessage({ sender: 'ai', type: 'mcq', mcq: finalMCQData });
64
+ setIsAwaitingAnswer(true); // Always true for a newly fetched question
 
65
  } catch (error) {
66
  console.error("Error generating MCQ:", error);
67
  const errorMessage = error instanceof Error ? error.message : "An unknown error occurred.";
 
74
  } finally {
75
  setIsLoading(false);
76
  }
77
+ // eslint-disable-next-line react-hooks/exhaustive-deps
78
+ }, [imageDataUri, journeyTitle, addMessage]); // isInitialQuestionRef is a ref, not needed in deps if mutated correctly
79
 
80
  useEffect(() => {
81
+ // Full reset for a new journey or when image data changes initially
82
+ setMessages([]);
83
+ setCurrentMCQ(null);
84
+ setIsLoading(false);
85
+ setIsAwaitingAnswer(false);
86
+ setHasAnsweredCorrectly(false);
87
+ setIncorrectAttempts([]);
88
+ isInitialQuestionRef.current = true; // Reset flag for prepending intro
89
+
90
  if (imageDataUri) {
91
  fetchNewMCQ();
92
  } else {
93
+ // If image data is not ready, show a placeholder.
94
+ // The effect will re-run when imageDataUri becomes available because it's in the dependency array.
95
  addMessage({ sender: 'ai', type: 'text', text: "Preparing your journey..." });
96
  }
97
  // eslint-disable-next-line react-hooks/exhaustive-deps
98
+ }, [imageDataUri, journeyTitle, fetchNewMCQ]); // fetchNewMCQ is memoized
 
 
 
 
 
 
 
 
 
99
 
100
  useEffect(() => {
101
  if (scrollAreaRef.current) {
 
115
  });
116
 
117
  if (isCorrect) {
118
+ setIsLoading(true); // Start loading before fetching explanation
119
  addMessage({ sender: 'ai', type: 'feedback', text: "That's correct! Well done.", isCorrect: true });
120
  try {
121
  const explanationResult = await explainCorrectAnswer({
 
131
  }
132
  setHasAnsweredCorrectly(true);
133
  setIncorrectAttempts([]);
134
+ setIsLoading(false); // Stop loading after explanation
135
  } else {
136
  const updatedIncorrectAttempts = [...incorrectAttempts, option];
137
  setIncorrectAttempts(updatedIncorrectAttempts);
138
+ setIsLoading(true); // Start loading before fetching explanation
139
 
140
  if (updatedIncorrectAttempts.length >= currentMCQ.options.length - 1) {
141
  addMessage({
 
156
  const errorMsg = error instanceof Error ? error.message : "An unknown error occurred";
157
  addMessage({ sender: 'ai', type: 'error', text: `Sorry, I couldn't provide an explanation for that. ${errorMsg}` });
158
  }
159
+ setHasAnsweredCorrectly(true); // Mark as correctly handled
160
+ setIncorrectAttempts([]); // Clear attempts for next question
161
  } else {
162
  try {
163
  const explanationResult = await explainIncorrectAnswer({
 
167
  correctAnswer: currentMCQ.correctAnswer,
168
  });
169
  addMessage({ sender: 'ai', type: 'feedback', text: explanationResult.explanation, isCorrect: false });
170
+ // Re-post the same MCQ for another attempt
171
  addMessage({ sender: 'ai', type: 'mcq', mcq: currentMCQ });
172
  } catch (error) {
173
  console.error("Error fetching explanation for incorrect answer:", error);
 
176
  addMessage({ sender: 'ai', type: 'feedback', text: "That's not quite right. Try again!", isCorrect: false });
177
  addMessage({ sender: 'ai', type: 'mcq', mcq: currentMCQ });
178
  }
179
+ setIsAwaitingAnswer(true); // Allow answering the re-posted MCQ
180
  }
181
+ setIsLoading(false); // Stop loading after handling incorrect answer
182
  }
183
  };
184
 
185
+ const handleNextQuestionClick = () => {
186
+ setIsAwaitingAnswer(false);
187
+ setHasAnsweredCorrectly(false);
188
+ setCurrentMCQ(null); // Clear current MCQ before fetching new one
189
+ setIncorrectAttempts([]);
190
+ fetchNewMCQ();
191
+ };
192
+
193
+
194
  return (
195
  <div className="flex h-full flex-col rounded-lg border bg-card shadow-xl">
196
  <div className="border-b p-4">
 
203
  msg.type === 'mcq' &&
204
  msg.mcq &&
205
  currentMCQ &&
206
+ msg.mcq.mcq === currentMCQ.mcq && // Compare by the question text itself
207
  JSON.stringify(msg.mcq.options) === JSON.stringify(currentMCQ.options);
208
 
209
+ // Find the index of the last message in the array that is the current active MCQ
210
  const lastInstanceOfCurrentMCQIndex = messages.findLastIndex(
211
  (m) =>
212
  m.type === 'mcq' &&
 
216
  JSON.stringify(m.mcq.options) === JSON.stringify(currentMCQ.options)
217
  );
218
 
219
+ // Options should be interactive only for the last instance of the current MCQ,
220
+ // and only if we are awaiting an answer and it hasn't been answered correctly yet.
221
  const shouldThisMessageBeInteractive =
222
  isThisMessageTheCurrentMCQ &&
223
+ index === lastInstanceOfCurrentMCQIndex && // Ensure this is the LATEST instance
224
  isAwaitingAnswer &&
225
  !hasAnsweredCorrectly;
226
 
 
232
  isAwaitingActiveMCQAnswer={shouldThisMessageBeInteractive}
233
  onOptionSelectActiveMCQ={handleOptionSelect}
234
  incorrectAttemptsForMCQ={
235
+ // Pass incorrect attempts only to the latest, interactive instance
236
+ shouldThisMessageBeInteractive
237
  ? incorrectAttempts
238
+ : [] // Otherwise, no attempts should be highlighted for past/non-interactive MCQs
239
  }
240
  />
241
  );
 
255
  {isLoading && <p className="text-center text-sm text-muted-foreground">AI is thinking...</p>}
256
 
257
  {!isLoading && currentMCQ && hasAnsweredCorrectly && (
258
+ <Button onClick={handleNextQuestionClick} className="w-full" variant="default" disabled={isLoading}>
259
  Next Question <Send className="ml-2 h-4 w-4" />
260
  </Button>
261
  )}
 
269
  {!imageDataUri && !isLoading && (
270
  <p className="text-center text-sm text-muted-foreground">Loading image, please wait...</p>
271
  )}
272
+ {!currentMCQ && !isLoading && imageDataUri && messages.length === 0 && ( // Condition for when image is loaded but first question not yet.
273
  <p className="text-center text-sm text-muted-foreground">Loading first question...</p>
274
  )}
275
+ {!currentMCQ && !isLoading && imageDataUri && messages.length > 0 && messages[messages.length -1]?.type === 'error' && (
276
+ <p className="text-center text-sm text-destructive-foreground">Could not load question. Try refreshing.</p>
277
+ )}
278
  </div>
279
  </div>
280
  );
281
  }
282
+