aakashbansal commited on
Commit
c24ed27
·
1 Parent(s): 43020c4

I still see a toast message only. not the summary screen. Also, the subt

Browse files
src/app/journey/[journeyId]/page.tsx CHANGED
@@ -50,50 +50,66 @@ export default function JourneyPage() {
50
  setJourney(currentJourney);
51
  setError(null);
52
  } else {
53
- setError("Journey not found. Please select a valid journey.");
54
  setJourney(null);
55
- toast({
56
- title: "Error",
57
- description: "Journey not found.",
58
- variant: "destructive",
59
- });
60
  }
61
  } else {
62
- setError("No journey ID provided.");
63
- toast({
64
- title: "Error",
65
- description: "No journey ID in URL.",
66
- variant: "destructive",
67
- });
68
  }
69
- }, [journeyId, toast]);
70
 
71
  useEffect(() => {
 
72
  if (journey?.imageUrl) {
73
- setIsLoadingJourney(true);
74
  imageToDataUri(journey.imageUrl)
75
  .then(uri => {
76
- setImageDataUri(uri);
77
- if (!uri) {
78
- setError("Failed to load journey image for AI interaction.");
79
- toast({
80
- title: "Image Load Error",
81
- description: "Could not load image data for AI features. Some functionalities might be limited.",
82
- variant: "destructive",
83
- });
 
 
84
  }
85
  })
86
- .finally(() => setIsLoadingJourney(false));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  } else if (journey && !journey.imageUrl) {
88
- setError("Journey image is missing.");
89
  toast({
90
- title: "Missing Image",
91
- description: "The selected journey does not have an image.",
92
  variant: "destructive",
93
  });
94
  setIsLoadingJourney(false);
 
 
95
  }
96
- }, [journey, toast]);
 
 
 
 
 
97
 
98
 
99
  const handleNavigateToSummary = useCallback(() => {
@@ -108,32 +124,47 @@ export default function JourneyPage() {
108
  router.push(`/journey/${journeyId}/summary`);
109
  }, [journeyId, router, toast]);
110
 
111
-
112
- if (error) {
113
- return (
114
  <div className="flex min-h-screen flex-col">
115
  <AppHeader showBackButton backHref="/select-journey" />
116
  <main className="container mx-auto flex flex-1 items-center justify-center p-4">
117
  <Alert variant="destructive" className="max-w-md">
118
  <AlertTriangle className="h-4 w-4" />
119
- <AlertTitle>Error</AlertTitle>
120
- <AlertDescription>{error}</AlertDescription>
121
  </Alert>
122
  </main>
123
  </div>
124
  );
125
  }
126
 
127
- if (!journey && !isLoadingJourney && !error) {
128
- return (
 
129
  <div className="flex min-h-screen flex-col">
130
- <AppHeader showBackButton backHref="/select-journey" />
131
  <main className="container mx-auto flex flex-1 items-center justify-center p-4">
132
  <Loader2 className="h-12 w-12 animate-spin text-primary" />
133
  </main>
134
  </div>
135
  );
136
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
  const journeyTitle = journey?.title || 'Loading Journey...';
139
 
@@ -168,18 +199,20 @@ export default function JourneyPage() {
168
  <div className="flex h-full w-full flex-col items-center justify-center p-4 text-center">
169
  <AlertTriangle className="h-16 w-16 text-destructive mb-4" />
170
  <p className="text-destructive-foreground font-semibold">Image Not Available</p>
171
- <p className="text-sm text-muted-foreground">The image for this journey could not be displayed.</p>
 
 
172
  </div>
173
  )}
174
  </div>
175
 
176
  <div className="h-full overflow-y-auto">
177
- {isLoadingJourney && !imageDataUri && journey?.imageUrl && (
178
  <div className="flex h-full flex-col items-center justify-center rounded-lg border bg-card p-4 shadow-xl">
179
  <Loader2 className="h-12 w-12 animate-spin text-primary mb-4" />
180
  <p className="text-muted-foreground">Preparing AI interactions...</p>
181
  </div>
182
- )}
183
  {!isLoadingJourney && imageDataUri && journey && (
184
  <Chatbot
185
  imageDataUri={imageDataUri}
@@ -187,11 +220,16 @@ export default function JourneyPage() {
187
  journeyId={journeyId}
188
  />
189
  )}
190
- {((!isLoadingJourney && !imageDataUri && journey) || (!journey?.imageUrl && !isLoadingJourney)) && (
 
191
  <div className="flex h-full flex-col items-center justify-center rounded-lg border bg-card p-4 shadow-xl text-center">
192
  <AlertTriangle className="h-12 w-12 text-destructive mb-4" />
193
  <p className="font-semibold text-destructive">AI Features Unavailable</p>
194
- <p className="text-sm text-muted-foreground">Could not load image data required for AI chat and summary.</p>
 
 
 
 
195
  </div>
196
  )}
197
  </div>
 
50
  setJourney(currentJourney);
51
  setError(null);
52
  } else {
53
+ setError("Journey not found. Please select a valid journey from the selection page.");
54
  setJourney(null);
55
+ // No toast here, error will be displayed on page
 
 
 
 
56
  }
57
  } else {
58
+ setError("No journey ID provided in the URL. Please navigate from the selection page.");
59
+ // No toast here, error will be displayed on page
 
 
 
 
60
  }
61
+ }, [journeyId]);
62
 
63
  useEffect(() => {
64
+ let isMounted = true;
65
  if (journey?.imageUrl) {
66
+ setIsLoadingJourney(true);
67
  imageToDataUri(journey.imageUrl)
68
  .then(uri => {
69
+ if (isMounted) {
70
+ setImageDataUri(uri);
71
+ if (!uri) {
72
+ setError(prev => prev || "Failed to load journey image for AI interaction. Some features may be unavailable.");
73
+ toast({
74
+ title: "Image Load Error",
75
+ description: "Could not load image data for AI features. Chat and summary might be limited.",
76
+ variant: "destructive",
77
+ });
78
+ }
79
  }
80
  })
81
+ .catch(() => {
82
+ if (isMounted) {
83
+ setError(prev => prev || "Error processing journey image. Some features may be unavailable.");
84
+ toast({
85
+ title: "Image Processing Error",
86
+ description: "Could not process image data. Chat and summary might be limited.",
87
+ variant: "destructive",
88
+ });
89
+ }
90
+ })
91
+ .finally(() => {
92
+ if (isMounted) {
93
+ setIsLoadingJourney(false);
94
+ }
95
+ });
96
  } else if (journey && !journey.imageUrl) {
97
+ setError(prev => prev || "The selected journey is missing its primary image. AI features cannot be initialized.");
98
  toast({
99
+ title: "Missing Journey Image",
100
+ description: "This journey has no image, so AI chat and summary are unavailable.",
101
  variant: "destructive",
102
  });
103
  setIsLoadingJourney(false);
104
+ } else if (!journey && !journeyId) { // Handles case where journeyId itself is missing from start
105
+ setIsLoadingJourney(false); // Stop loading if no journeyId to begin with
106
  }
107
+
108
+
109
+ return () => {
110
+ isMounted = false;
111
+ };
112
+ }, [journey, toast, journeyId]);
113
 
114
 
115
  const handleNavigateToSummary = useCallback(() => {
 
124
  router.push(`/journey/${journeyId}/summary`);
125
  }, [journeyId, router, toast]);
126
 
127
+ if (!journeyId) { // Early exit if journeyId is not in URL
128
+ return (
 
129
  <div className="flex min-h-screen flex-col">
130
  <AppHeader showBackButton backHref="/select-journey" />
131
  <main className="container mx-auto flex flex-1 items-center justify-center p-4">
132
  <Alert variant="destructive" className="max-w-md">
133
  <AlertTriangle className="h-4 w-4" />
134
+ <AlertTitle>Error: Missing Journey</AlertTitle>
135
+ <AlertDescription>No journey ID was provided. Please return to the selection page and choose a journey.</AlertDescription>
136
  </Alert>
137
  </main>
138
  </div>
139
  );
140
  }
141
 
142
+
143
+ if (isLoadingJourney && !journey && !error) { // Initial loading state for journey itself
144
+ return (
145
  <div className="flex min-h-screen flex-col">
146
+ <AppHeader showBackButton backHref="/select-journey" title="Loading Journey..." />
147
  <main className="container mx-auto flex flex-1 items-center justify-center p-4">
148
  <Loader2 className="h-12 w-12 animate-spin text-primary" />
149
  </main>
150
  </div>
151
  );
152
  }
153
+
154
+ if (error && !journey?.imageUrl) { // Display error if journey loading failed or image is missing preventing essential functions
155
+ return (
156
+ <div className="flex min-h-screen flex-col">
157
+ <AppHeader showBackButton backHref="/select-journey" title="Journey Error"/>
158
+ <main className="container mx-auto flex flex-1 items-center justify-center p-4">
159
+ <Alert variant="destructive" className="max-w-lg">
160
+ <AlertTriangle className="h-4 w-4" />
161
+ <AlertTitle>Error Loading Journey</AlertTitle>
162
+ <AlertDescription>{error}</AlertDescription>
163
+ </Alert>
164
+ </main>
165
+ </div>
166
+ );
167
+ }
168
 
169
  const journeyTitle = journey?.title || 'Loading Journey...';
170
 
 
199
  <div className="flex h-full w-full flex-col items-center justify-center p-4 text-center">
200
  <AlertTriangle className="h-16 w-16 text-destructive mb-4" />
201
  <p className="text-destructive-foreground font-semibold">Image Not Available</p>
202
+ <p className="text-sm text-muted-foreground">
203
+ {error && error.includes("Failed to load journey image") ? error : "The image for this journey could not be displayed."}
204
+ </p>
205
  </div>
206
  )}
207
  </div>
208
 
209
  <div className="h-full overflow-y-auto">
210
+ {(isLoadingJourney && !imageDataUri && journey?.imageUrl) ? (
211
  <div className="flex h-full flex-col items-center justify-center rounded-lg border bg-card p-4 shadow-xl">
212
  <Loader2 className="h-12 w-12 animate-spin text-primary mb-4" />
213
  <p className="text-muted-foreground">Preparing AI interactions...</p>
214
  </div>
215
+ ) : null}
216
  {!isLoadingJourney && imageDataUri && journey && (
217
  <Chatbot
218
  imageDataUri={imageDataUri}
 
220
  journeyId={journeyId}
221
  />
222
  )}
223
+ {/* Case: Journey loaded, but image data URI failed or no image URL, and not actively loading image anymore */}
224
+ { !isLoadingJourney && !imageDataUri && journey && (
225
  <div className="flex h-full flex-col items-center justify-center rounded-lg border bg-card p-4 shadow-xl text-center">
226
  <AlertTriangle className="h-12 w-12 text-destructive mb-4" />
227
  <p className="font-semibold text-destructive">AI Features Unavailable</p>
228
+ <p className="text-sm text-muted-foreground">
229
+ {error && (error.includes("Failed to load") || error.includes("missing"))
230
+ ? error
231
+ : "Could not load image data required for AI chat. Please check the journey image."}
232
+ </p>
233
  </div>
234
  )}
235
  </div>
src/app/journey/[journeyId]/summary/page.tsx CHANGED
@@ -39,120 +39,152 @@ export default function SummaryPage() {
39
  const [journey, setJourney] = useState<Journey | null>(null);
40
  const [imageDataUri, setImageDataUri] = useState<string | null>(null);
41
  const [summary, setSummary] = useState<string | null>(null);
42
- const [isLoadingJourney, setIsLoadingJourney] = useState(true);
43
- const [isLoadingSummary, setIsLoadingSummary] = useState(false);
44
- const [error, setError] = useState<string | null>(null);
 
 
45
  const { toast } = useToast();
46
 
47
  useEffect(() => {
48
- if (journeyId) {
49
- const currentJourney = getJourneyById(journeyId);
50
- if (currentJourney) {
51
- setJourney(currentJourney);
52
- setError(null);
53
- } else {
54
- setError("Journey not found. Please select a valid journey.");
55
- setJourney(null);
56
- toast({
57
- title: "Error",
58
- description: "Journey not found.",
59
- variant: "destructive",
60
- });
61
- router.replace('/select-journey'); // Redirect if journey is invalid
62
  }
63
- } else {
64
- setError("No journey ID provided.");
65
- toast({
66
- title: "Error",
67
- description: "No journey ID in URL.",
68
- variant: "destructive",
69
- });
70
- router.replace('/select-journey'); // Redirect if no ID
71
  }
72
- }, [journeyId, toast, router]);
73
 
74
- useEffect(() => {
75
- if (journey?.imageUrl) {
76
- setIsLoadingJourney(true);
77
- imageToDataUri(journey.imageUrl)
78
- .then(uri => {
79
- setImageDataUri(uri);
80
- if (!uri) {
81
- setError(prev => prev || "Failed to load journey image for summary."); // Keep existing error if one is already set
82
- toast({
83
- title: "Image Load Error",
84
- description: "Could not load image data for summary. The summary might not be available.",
85
- variant: "destructive",
86
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  }
88
- })
89
- .finally(() => setIsLoadingJourney(false));
90
- } else if (journey && !journey.imageUrl) {
91
- setError(prev => prev || "Journey image is missing. Summary cannot be generated.");
92
- toast({
93
- title: "Missing Image",
94
- description: "The selected journey does not have an image, so a summary cannot be generated.",
95
- variant: "destructive",
 
 
96
  });
97
- setIsLoadingJourney(false);
98
- }
99
- }, [journey, toast]);
100
 
101
  useEffect(() => {
102
- if (imageDataUri && !summary && !isLoadingSummary && !error) { // Fetch summary only if image is available, summary isn't fetched, not already loading, and no prior critical error
 
 
 
 
 
 
 
103
  const fetchSummary = async () => {
 
104
  setIsLoadingSummary(true);
105
  try {
106
- const result = await summarizeImage({ imageDataUri });
107
- setSummary(result.summary);
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  } catch (fetchError) {
109
  console.error('Error generating summary:', fetchError);
110
- const errorMessage = fetchError instanceof Error ? fetchError.message : "An unknown error occurred.";
111
- setSummary(`Failed to generate summary: ${errorMessage}`); // Show error in summary area
112
- toast({
113
- title: "Summary Generation Error",
114
- description: `Could not generate summary. ${errorMessage}`,
115
- variant: "destructive",
116
- });
 
 
117
  } finally {
118
- setIsLoadingSummary(false);
119
  }
120
  };
121
  fetchSummary();
122
  }
123
- }, [imageDataUri, summary, isLoadingSummary, toast, error]);
 
124
 
125
 
126
- if (isLoadingJourney && !journey) { // Initial loading state for journey data
127
- return (
 
128
  <div className="flex min-h-screen flex-col">
129
- <AppHeader showBackButton backHref={journeyId ? `/journey/${journeyId}` : "/select-journey"} title="Loading Summary..." />
130
  <main className="container mx-auto flex flex-1 items-center justify-center p-4">
131
- <Loader2 className="h-12 w-12 animate-spin text-primary" />
 
 
 
 
132
  </main>
133
  </div>
134
  );
135
  }
136
-
137
- if (error && !journey) { // Critical error like journey not found
138
- return (
 
139
  <div className="flex min-h-screen flex-col">
140
- <AppHeader showBackButton backHref="/select-journey" title="Error" />
141
  <main className="container mx-auto flex flex-1 items-center justify-center p-4">
142
- <Alert variant="destructive" className="max-w-md">
143
- <AlertTriangle className="h-4 w-4" />
144
- <AlertTitle>Error</AlertTitle>
145
- <AlertDescription>{error}</AlertDescription>
146
- </Alert>
147
  </main>
148
  </div>
149
  );
150
  }
151
-
152
  const pageTitle = journey ? `Summary: ${journey.title}` : 'Summary Report';
153
  const backNavigation = journeyId ? `/journey/${journeyId}` : '/select-journey';
154
 
155
-
156
  return (
157
  <div className="flex h-screen flex-col">
158
  <AppHeader
@@ -164,14 +196,9 @@ export default function SummaryPage() {
164
  <div className="grid h-full grid-cols-1 gap-2 md:grid-cols-2 md:gap-4">
165
  {/* Left Column: Image */}
166
  <div className="relative h-full w-full overflow-hidden rounded-lg shadow-lg bg-muted">
167
- {isLoadingJourney && journey?.imageUrl ? (
168
- <div className="flex h-full w-full flex-col items-center justify-center">
169
- <Loader2 className="h-16 w-16 animate-spin text-primary mb-4" />
170
- <p className="text-muted-foreground">Loading image for {journey?.title || 'journey'}...</p>
171
- </div>
172
- ) : journey?.imageUrl && imageDataUri ? (
173
  <Image
174
- src={journey.imageUrl}
175
  alt={journey.title}
176
  layout="fill"
177
  objectFit="cover"
@@ -183,7 +210,9 @@ export default function SummaryPage() {
183
  <div className="flex h-full w-full flex-col items-center justify-center p-4 text-center">
184
  <AlertTriangle className="h-16 w-16 text-destructive mb-4" />
185
  <p className="text-destructive-foreground font-semibold">Image Not Available</p>
186
- <p className="text-sm text-muted-foreground">The image for this journey could not be displayed.</p>
 
 
187
  </div>
188
  )}
189
  </div>
@@ -202,35 +231,39 @@ export default function SummaryPage() {
202
  <Skeleton className="h-5 w-full rounded-md" />
203
  </div>
204
  )}
205
- {!isLoadingSummary && summary && (
206
  <div className="prose prose-sm dark:prose-invert max-w-none whitespace-pre-wrap font-body">
207
  {summary}
208
  </div>
209
  )}
210
- {!isLoadingSummary && !summary && imageDataUri && !error && ( // Waiting for summary, image is ready
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  <div className="flex flex-col items-center justify-center py-10 text-muted-foreground">
212
  <Loader2 className="h-8 w-8 animate-spin mb-2 text-primary" />
213
- <p>Generating summary insights...</p>
214
  </div>
215
  )}
216
- {!imageDataUri && !isLoadingJourney && ( // Image data failed to load or no image URL
217
  <div className="py-10 text-center text-muted-foreground">
218
  <AlertTriangle className="h-8 w-8 text-destructive mx-auto mb-2" />
219
  <p className="font-semibold text-destructive">Summary Unavailable</p>
220
- <p>Image data is not available, so a summary cannot be generated.</p>
221
- </div>
222
- )}
223
- {error && !summary && !isLoadingSummary && ( // An error occurred that prevented summary generation
224
- <div className="py-10 text-center">
225
- <Alert variant="destructive">
226
- <AlertTriangle className="h-4 w-4" />
227
- <AlertTitle>Summary Error</AlertTitle>
228
- <AlertDescription>
229
- {error.includes("Journey image is missing") || error.includes("Failed to load journey image")
230
- ? error
231
- : "Could not generate summary at this time."}
232
- </AlertDescription>
233
- </Alert>
234
  </div>
235
  )}
236
  </div>
 
39
  const [journey, setJourney] = useState<Journey | null>(null);
40
  const [imageDataUri, setImageDataUri] = useState<string | null>(null);
41
  const [summary, setSummary] = useState<string | null>(null);
42
+
43
+ const [isLoadingJourneyData, setIsLoadingJourneyData] = useState(true); // For journey object and image data URI
44
+ const [isLoadingSummary, setIsLoadingSummary] = useState(false); // Specifically for AI summary generation
45
+
46
+ const [pageError, setPageError] = useState<string | null>(null); // For critical errors displayed on page
47
  const { toast } = useToast();
48
 
49
  useEffect(() => {
50
+ let isMounted = true;
51
+ setIsLoadingJourneyData(true);
52
+ setPageError(null); // Reset page error on new journeyId
53
+
54
+ if (!journeyId) {
55
+ if (isMounted) {
56
+ setPageError("No journey ID provided. Please select a journey.");
57
+ setIsLoadingJourneyData(false);
58
+ // router.replace('/select-journey'); // Consider redirecting
 
 
 
 
 
59
  }
60
+ return;
 
 
 
 
 
 
 
61
  }
 
62
 
63
+ const currentJourney = getJourneyById(journeyId);
64
+ if (!currentJourney) {
65
+ if (isMounted) {
66
+ setPageError(`Journey with ID "${journeyId}" not found. Please select a valid journey.`);
67
+ setIsLoadingJourneyData(false);
68
+ // router.replace('/select-journey'); // Consider redirecting
69
+ }
70
+ return;
71
+ }
72
+
73
+ if (isMounted) setJourney(currentJourney);
74
+
75
+ if (!currentJourney.imageUrl) {
76
+ if (isMounted) {
77
+ setPageError("The selected journey is missing an image. A summary cannot be generated without an image.");
78
+ setImageDataUri(null); // Ensure imageDataUri is null
79
+ setIsLoadingJourneyData(false);
80
+ }
81
+ return;
82
+ }
83
+
84
+ imageToDataUri(currentJourney.imageUrl)
85
+ .then(uri => {
86
+ if (isMounted) {
87
+ if (uri) {
88
+ setImageDataUri(uri);
89
+ } else {
90
+ setPageError("Failed to load and process the journey image. The summary cannot be generated.");
91
+ setImageDataUri(null);
92
  }
93
+ }
94
+ })
95
+ .catch(() => {
96
+ if (isMounted) {
97
+ setPageError("An error occurred while converting the image. The summary cannot be generated.");
98
+ setImageDataUri(null);
99
+ }
100
+ })
101
+ .finally(() => {
102
+ if (isMounted) setIsLoadingJourneyData(false);
103
  });
104
+
105
+ return () => { isMounted = false; };
106
+ }, [journeyId, router]);
107
 
108
  useEffect(() => {
109
+ let isMounted = true;
110
+ // Fetch summary only if:
111
+ // 1. Journey data and imageDataUri are loaded (isLoadingJourneyData is false)
112
+ // 2. imageDataUri is actually available
113
+ // 3. No critical pageError exists
114
+ // 4. Summary hasn't been fetched yet (summary is null)
115
+ // 5. Not currently loading a summary
116
+ if (!isLoadingJourneyData && imageDataUri && !pageError && !summary && !isLoadingSummary) {
117
  const fetchSummary = async () => {
118
+ if (!isMounted) return;
119
  setIsLoadingSummary(true);
120
  try {
121
+ // imageDataUri is confirmed non-null by the if condition
122
+ const result = await summarizeImage({ imageDataUri: imageDataUri! });
123
+ if (isMounted) {
124
+ if (result && result.summary) {
125
+ setSummary(result.summary);
126
+ } else {
127
+ // This case handles if AI returns unexpected structure
128
+ setSummary("Failed to generate summary: The AI response was not in the expected format.");
129
+ toast({
130
+ title: "Summary Generation Issue",
131
+ description: "The AI did not provide a summary in the expected format. Please try again later.",
132
+ variant: "destructive",
133
+ });
134
+ }
135
+ }
136
  } catch (fetchError) {
137
  console.error('Error generating summary:', fetchError);
138
+ const errorMessage = fetchError instanceof Error ? fetchError.message : "An unknown error occurred during summary generation.";
139
+ if (isMounted) {
140
+ setSummary(`Failed to generate summary: ${errorMessage}`); // Show error in summary area
141
+ toast({
142
+ title: "Summary Generation Error",
143
+ description: `Could not generate summary. ${errorMessage}`,
144
+ variant: "destructive",
145
+ });
146
+ }
147
  } finally {
148
+ if (isMounted) setIsLoadingSummary(false);
149
  }
150
  };
151
  fetchSummary();
152
  }
153
+ return () => { isMounted = false; };
154
+ }, [isLoadingJourneyData, imageDataUri, pageError, summary, isLoadingSummary, toast]);
155
 
156
 
157
+ // Display for critical page errors (journey not found, no ID, image missing/failed to load)
158
+ if (!isLoadingJourneyData && pageError) {
159
+ return (
160
  <div className="flex min-h-screen flex-col">
161
+ <AppHeader showBackButton backHref={journeyId ? `/journey/${journeyId}` : "/select-journey"} title="Summary Error" />
162
  <main className="container mx-auto flex flex-1 items-center justify-center p-4">
163
+ <Alert variant="destructive" className="max-w-md">
164
+ <AlertTriangle className="h-4 w-4" />
165
+ <AlertTitle>Unable to Load Summary</AlertTitle>
166
+ <AlertDescription>{pageError}</AlertDescription>
167
+ </Alert>
168
  </main>
169
  </div>
170
  );
171
  }
172
+
173
+ // Display for initial loading of journey data and image
174
+ if (isLoadingJourneyData) {
175
+ return (
176
  <div className="flex min-h-screen flex-col">
177
+ <AppHeader showBackButton backHref={journeyId ? `/journey/${journeyId}` : "/select-journey"} title="Loading Summary Data..." />
178
  <main className="container mx-auto flex flex-1 items-center justify-center p-4">
179
+ <Loader2 className="h-12 w-12 animate-spin text-primary" />
 
 
 
 
180
  </main>
181
  </div>
182
  );
183
  }
184
+
185
  const pageTitle = journey ? `Summary: ${journey.title}` : 'Summary Report';
186
  const backNavigation = journeyId ? `/journey/${journeyId}` : '/select-journey';
187
 
 
188
  return (
189
  <div className="flex h-screen flex-col">
190
  <AppHeader
 
196
  <div className="grid h-full grid-cols-1 gap-2 md:grid-cols-2 md:gap-4">
197
  {/* Left Column: Image */}
198
  <div className="relative h-full w-full overflow-hidden rounded-lg shadow-lg bg-muted">
199
+ {journey?.imageUrl && imageDataUri ? (
 
 
 
 
 
200
  <Image
201
+ src={journey.imageUrl} /* Use original URL for display if data URI conversion was just for AI */
202
  alt={journey.title}
203
  layout="fill"
204
  objectFit="cover"
 
210
  <div className="flex h-full w-full flex-col items-center justify-center p-4 text-center">
211
  <AlertTriangle className="h-16 w-16 text-destructive mb-4" />
212
  <p className="text-destructive-foreground font-semibold">Image Not Available</p>
213
+ <p className="text-sm text-muted-foreground">
214
+ {pageError || "The image for this journey could not be displayed."}
215
+ </p>
216
  </div>
217
  )}
218
  </div>
 
231
  <Skeleton className="h-5 w-full rounded-md" />
232
  </div>
233
  )}
234
+ {!isLoadingSummary && summary && !summary.startsWith("Failed to generate summary:") && (
235
  <div className="prose prose-sm dark:prose-invert max-w-none whitespace-pre-wrap font-body">
236
  {summary}
237
  </div>
238
  )}
239
+ {/* Displaying error message from summary state if summary generation failed */}
240
+ {!isLoadingSummary && summary && summary.startsWith("Failed to generate summary:") && (
241
+ <Alert variant="destructive">
242
+ <AlertTriangle className="h-4 w-4" />
243
+ <AlertTitle>Summary Generation Failed</AlertTitle>
244
+ <AlertDescription>{summary}</AlertDescription>
245
+ </Alert>
246
+ )}
247
+ {/* Message if summary cannot be generated due to earlier pageError (e.g., no image) */}
248
+ {!isLoadingSummary && !summary && pageError && (
249
+ <Alert variant="destructive">
250
+ <AlertTriangle className="h-4 w-4" />
251
+ <AlertTitle>Summary Unavailable</AlertTitle>
252
+ <AlertDescription>{pageError}</AlertDescription>
253
+ </Alert>
254
+ )}
255
+ {/* Fallback for when summary is null, not loading, and no specific page error for summary generation */}
256
+ {!isLoadingSummary && !summary && !pageError && imageDataUri && (
257
  <div className="flex flex-col items-center justify-center py-10 text-muted-foreground">
258
  <Loader2 className="h-8 w-8 animate-spin mb-2 text-primary" />
259
+ <p>Preparing summary insights...</p> {/* Should transition to loading or result */}
260
  </div>
261
  )}
262
+ {!imageDataUri && !isLoadingJourneyData && !pageError && ( // Case where image is confirmed unavailable, not loading, and no other critical error
263
  <div className="py-10 text-center text-muted-foreground">
264
  <AlertTriangle className="h-8 w-8 text-destructive mx-auto mb-2" />
265
  <p className="font-semibold text-destructive">Summary Unavailable</p>
266
+ <p>Image data is not available, so a summary cannot be generated for this journey.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  </div>
268
  )}
269
  </div>
src/components/chatbot/chatbot.tsx CHANGED
@@ -13,21 +13,21 @@ import { explainCorrectAnswer } from '@/ai/flows/explain-correct-answer-flow';
13
  import type { ChatMessage as ChatMessageType, ActiveMCQ } from '@/types';
14
  import { ChatMessage } from './chat-message';
15
  import { useToast } from '@/hooks/use-toast';
16
- import { Send, FileText } from 'lucide-react';
17
 
18
  const MAX_QUESTIONS = 5;
19
 
20
  interface ChatbotProps {
21
  imageDataUri: string | null;
22
  journeyTitle: string;
23
- journeyId: string; // Added to navigate to summary page
24
  }
25
 
26
  export function Chatbot({ imageDataUri, journeyTitle, journeyId }: ChatbotProps) {
27
  const router = useRouter();
28
  const [messages, setMessages] = useState<ChatMessageType[]>([]);
29
  const [currentMCQ, setCurrentMCQ] = useState<ActiveMCQ | null>(null);
30
- const [isLoading, setIsLoading] = useState(false);
31
  const [isAwaitingAnswer, setIsAwaitingAnswer] = useState(false);
32
  const [hasAnsweredCorrectly, setHasAnsweredCorrectly] = useState(false);
33
  const [incorrectAttempts, setIncorrectAttempts] = useState<string[]>([]);
@@ -58,10 +58,12 @@ export function Chatbot({ imageDataUri, journeyTitle, journeyId }: ChatbotProps)
58
  isInitialQuestionRef.current = true;
59
  messageIdCounter.current = 0;
60
 
61
-
62
  const performInitialFetch = async () => {
63
  if (!imageDataUri) {
64
- if (!ignore && messages.length === 0) {
 
 
 
65
  addMessage({ sender: 'ai', type: 'text', text: "Preparing your journey..." });
66
  }
67
  return;
@@ -70,13 +72,18 @@ export function Chatbot({ imageDataUri, journeyTitle, journeyId }: ChatbotProps)
70
  if (!ignore) setIsLoading(true);
71
 
72
  try {
 
 
 
 
 
73
  const mcqResult = await generateMCQ({ imageDataUri });
74
 
75
  if (ignore) return;
76
 
77
  let activeMcqData: ActiveMCQ = {
78
  ...mcqResult,
79
- originalQuestionTextForFlow: mcqResult.mcq,
80
  };
81
 
82
  if (isInitialQuestionRef.current) {
@@ -105,18 +112,19 @@ export function Chatbot({ imageDataUri, journeyTitle, journeyId }: ChatbotProps)
105
  }
106
  };
107
 
 
 
108
  if (imageDataUri) {
109
- // Clear "Preparing..." message if it exists, then fetch
110
- if (messages.length === 1 && messages[0].text === "Preparing your journey...") {
111
- setMessages([]); // Clear it before fetching
112
- }
113
  performInitialFetch();
114
- } else if (messages.length === 0) { // Only add "Preparing..." if no image and no messages yet
 
 
115
  addMessage({ sender: 'ai', type: 'text', text: "Preparing your journey..." });
116
  }
117
 
 
118
  return () => {
119
- ignore = true;
120
  };
121
  // eslint-disable-next-line react-hooks/exhaustive-deps
122
  }, [imageDataUri, journeyTitle]); // addMessage is memoized
@@ -131,11 +139,11 @@ export function Chatbot({ imageDataUri, journeyTitle, journeyId }: ChatbotProps)
131
  const handleOptionSelect = async (option: string, isCorrect: boolean) => {
132
  if (!currentMCQ) return;
133
 
134
- setIsAwaitingAnswer(false);
135
 
136
  addMessage({
137
  sender: 'user',
138
- type: 'user',
139
  text: option,
140
  });
141
 
@@ -156,12 +164,13 @@ export function Chatbot({ imageDataUri, journeyTitle, journeyId }: ChatbotProps)
156
  const errorMsg = error instanceof Error ? error.message : "An unknown error occurred";
157
  combinedMessage += ` (Sorry, I couldn't provide an explanation for that: ${errorMsg})`;
158
  }
 
159
  addMessage({ sender: 'ai', type: 'feedback', text: combinedMessage, isCorrect: true });
160
  setHasAnsweredCorrectly(true);
161
  setIncorrectAttempts([]);
162
  const newQuestionCount = questionCount + 1;
163
  setQuestionCount(newQuestionCount);
164
- if (newQuestionCount >= MAX_QUESTIONS) {
165
  addMessage({ sender: 'ai', type: 'text', text: "You've completed all 5 questions! Please proceed to the summary report." });
166
  }
167
 
@@ -169,9 +178,11 @@ export function Chatbot({ imageDataUri, journeyTitle, journeyId }: ChatbotProps)
169
  const updatedIncorrectAttempts = [...incorrectAttempts, option];
170
  setIncorrectAttempts(updatedIncorrectAttempts);
171
 
172
- if (updatedIncorrectAttempts.length >= currentMCQ.options.length - 1) { // All incorrect options exhausted
 
173
  let combinedText = "";
174
  try {
 
175
  const incorrectExplanationResult = await explainIncorrectAnswer({
176
  question: questionForAI,
177
  options: currentMCQ.options,
@@ -181,13 +192,14 @@ export function Chatbot({ imageDataUri, journeyTitle, journeyId }: ChatbotProps)
181
  combinedText += `That's not quite right. ${incorrectExplanationResult.explanation} `;
182
  } catch (error) {
183
  console.error("Error fetching explanation for the last incorrect answer:", error);
184
- const errorMsg = error instanceof Error ? error.message : "An unknown error occurred";
185
- combinedText += `That's not quite right. (Failed to get explanation for your choice: ${errorMsg}) `;
186
  }
187
 
188
  combinedText += `The correct answer is "${currentMCQ.correctAnswer}". `;
189
 
190
  try {
 
191
  const correctExplanationResult = await explainCorrectAnswer({
192
  question: questionForAI,
193
  options: currentMCQ.options,
@@ -202,7 +214,7 @@ export function Chatbot({ imageDataUri, journeyTitle, journeyId }: ChatbotProps)
202
 
203
  addMessage({
204
  sender: 'ai',
205
- type: 'feedback',
206
  text: combinedText.trim(),
207
  isCorrect: true, // Mark as 'correct' flow-wise to enable next question/summary
208
  });
@@ -236,7 +248,6 @@ export function Chatbot({ imageDataUri, journeyTitle, journeyId }: ChatbotProps)
236
  } catch (error) {
237
  console.error("Error fetching explanation for incorrect answer:", error);
238
  const errorMsg = error instanceof Error ? error.message : "An unknown error occurred";
239
- // Fallback if explanation fails - still re-prompt
240
  const fallbackRepromptText = `That's not quite right. (Sorry, I couldn't explain that: ${errorMsg}) Please try another option from the question above.`;
241
  const fallbackMCQData: ActiveMCQ = {
242
  ...currentMCQ,
@@ -253,21 +264,20 @@ export function Chatbot({ imageDataUri, journeyTitle, journeyId }: ChatbotProps)
253
 
254
  const handleNextQuestionClick = async () => {
255
  if (questionCount >= MAX_QUESTIONS) {
256
- // This case should ideally be handled by the summary button appearing
257
- // but as a fallback, prevent fetching more questions.
258
  addMessage({ sender: 'ai', type: 'text', text: "You've completed all the questions! Please proceed to the summary." });
259
  setIsLoading(false);
260
- setHasAnsweredCorrectly(true); // Keep UI consistent for summary button
261
  return;
262
  }
263
 
264
- setIsAwaitingAnswer(false); // Reset for the new question
265
  setHasAnsweredCorrectly(false);
266
- setCurrentMCQ(null); // Clear current MCQ to show loading for next
267
- setIncorrectAttempts([]); // Clear attempts for the new question
268
 
269
  if (!imageDataUri) {
270
  addMessage({ sender: 'ai', type: 'error', text: "Image data is not available to generate new questions." });
 
271
  return;
272
  }
273
  setIsLoading(true);
@@ -275,7 +285,7 @@ export function Chatbot({ imageDataUri, journeyTitle, journeyId }: ChatbotProps)
275
  const mcqResult = await generateMCQ({ imageDataUri });
276
  const nextMCQData: ActiveMCQ = {
277
  ...mcqResult,
278
- originalQuestionTextForFlow: mcqResult.mcq, // Store the original question
279
  };
280
  setCurrentMCQ(nextMCQData);
281
  addMessage({ sender: 'ai', type: 'mcq', mcq: nextMCQData });
@@ -289,6 +299,7 @@ export function Chatbot({ imageDataUri, journeyTitle, journeyId }: ChatbotProps)
289
  description: `Could not generate a new question. ${errorMessage}`,
290
  variant: "destructive",
291
  });
 
292
  } finally {
293
  setIsLoading(false);
294
  }
@@ -307,15 +318,11 @@ export function Chatbot({ imageDataUri, journeyTitle, journeyId }: ChatbotProps)
307
  <ScrollArea className="flex-1 p-4" ref={scrollAreaRef}>
308
  <div className="space-y-4">
309
  {messages.map((msg) => {
310
- // Determine if this message is the currently active MCQ
311
  const isLastMessageTheActiveMCQ =
312
  msg.type === 'mcq' &&
313
  currentMCQ &&
314
- // Compare based on the unique ID of the message object
315
  msg.id === messages.findLast(m => m.type === 'mcq' && m.mcq?.mcq === currentMCQ.mcq && JSON.stringify(m.mcq?.options) === JSON.stringify(currentMCQ.options))?.id;
316
 
317
- // Options should be interactive only for the last instance of the currentMCQ
318
- // and if we are awaiting an answer and it hasn't been correctly answered yet.
319
  const shouldThisMessageBeInteractive =
320
  isLastMessageTheActiveMCQ &&
321
  isAwaitingAnswer &&
@@ -323,21 +330,20 @@ export function Chatbot({ imageDataUri, journeyTitle, journeyId }: ChatbotProps)
323
 
324
  return (
325
  <ChatMessage
326
- key={msg.id} // Unique key for React list
327
  message={msg}
328
  activeMCQ={currentMCQ}
329
  isAwaitingActiveMCQAnswer={shouldThisMessageBeInteractive}
330
  onOptionSelectActiveMCQ={handleOptionSelect}
331
  incorrectAttemptsForMCQ={
332
- shouldThisMessageBeInteractive // Pass incorrect attempts only to the interactive MCQ
333
  ? incorrectAttempts
334
- : [] // Otherwise, pass empty array to prevent visual glitches on old messages
335
  }
336
  />
337
  );
338
  })}
339
- {/* Skeleton for initial loading or when fetching next question before currentMCQ is set */}
340
- {isLoading && !currentMCQ && messages[messages.length -1]?.type !== 'feedback' && ( // Avoid skeleton if last msg was feedback for resolved q
341
  <div className="flex items-start space-x-3">
342
  <Skeleton className="h-8 w-8 rounded-full" />
343
  <div className="space-y-2">
@@ -349,7 +355,12 @@ export function Chatbot({ imageDataUri, journeyTitle, journeyId }: ChatbotProps)
349
  </div>
350
  </ScrollArea>
351
  <div className="border-t bg-background/80 p-4">
352
- {isLoading && <p className="text-center text-sm text-muted-foreground">AI is thinking...</p>}
 
 
 
 
 
353
 
354
  {!isLoading && questionCount >= MAX_QUESTIONS && hasAnsweredCorrectly && (
355
  <Button onClick={handleGoToSummary} className="w-full" variant="default">
@@ -362,25 +373,21 @@ export function Chatbot({ imageDataUri, journeyTitle, journeyId }: ChatbotProps)
362
  Next Question <Send className="ml-2 h-4 w-4" />
363
  </Button>
364
  )}
365
-
366
- {/* Message when awaiting answer for a current MCQ */}
367
  {!isLoading && currentMCQ && !hasAnsweredCorrectly && isAwaitingAnswer && (
368
  <p className="text-center text-sm text-muted-foreground">
369
  Please choose an answer from the options above.
370
  </p>
371
  )}
372
 
373
- {/* Message for initial loading of journey/image */}
374
- {!imageDataUri && !isLoading && messages.length > 0 && messages[messages.length -1]?.text === "Preparing your journey..." &&(
375
  <p className="text-center text-sm text-muted-foreground">Loading image, please wait...</p>
376
  )}
377
- {/* Message for initial question load (image ready, no messages yet or error) */}
378
  {!currentMCQ && !isLoading && imageDataUri && messages.length === 0 && (
379
  <p className="text-center text-sm text-muted-foreground">Loading first question...</p>
380
  )}
381
- {/* Message if initial question load failed */}
382
  {!currentMCQ && !isLoading && imageDataUri && messages.length > 0 && messages[messages.length -1]?.type === 'error' && (
383
- <p className="text-center text-sm text-destructive">Could not load question. Try refreshing.</p>
384
  )}
385
  </div>
386
  </div>
 
13
  import type { ChatMessage as ChatMessageType, ActiveMCQ } from '@/types';
14
  import { ChatMessage } from './chat-message';
15
  import { useToast } from '@/hooks/use-toast';
16
+ import { Send, FileText, Loader2 } from 'lucide-react';
17
 
18
  const MAX_QUESTIONS = 5;
19
 
20
  interface ChatbotProps {
21
  imageDataUri: string | null;
22
  journeyTitle: string;
23
+ journeyId: string;
24
  }
25
 
26
  export function Chatbot({ imageDataUri, journeyTitle, journeyId }: ChatbotProps) {
27
  const router = useRouter();
28
  const [messages, setMessages] = useState<ChatMessageType[]>([]);
29
  const [currentMCQ, setCurrentMCQ] = useState<ActiveMCQ | null>(null);
30
+ const [isLoading, setIsLoading] = useState(false); // General loading for AI responses
31
  const [isAwaitingAnswer, setIsAwaitingAnswer] = useState(false);
32
  const [hasAnsweredCorrectly, setHasAnsweredCorrectly] = useState(false);
33
  const [incorrectAttempts, setIncorrectAttempts] = useState<string[]>([]);
 
58
  isInitialQuestionRef.current = true;
59
  messageIdCounter.current = 0;
60
 
 
61
  const performInitialFetch = async () => {
62
  if (!imageDataUri) {
63
+ // This case should ideally be handled by parent not rendering Chatbot,
64
+ // or showing a "waiting for image" state if Chatbot is rendered before URI is ready.
65
+ if (!ignore && messages.length === 0) {
66
+ // Add a temporary "preparing" message if no URI yet, but this will be cleared.
67
  addMessage({ sender: 'ai', type: 'text', text: "Preparing your journey..." });
68
  }
69
  return;
 
72
  if (!ignore) setIsLoading(true);
73
 
74
  try {
75
+ // Clear any "Preparing..." message before fetching the actual question
76
+ if (messages.length === 1 && messages[0].text === "Preparing your journey...") {
77
+ setMessages([]);
78
+ }
79
+
80
  const mcqResult = await generateMCQ({ imageDataUri });
81
 
82
  if (ignore) return;
83
 
84
  let activeMcqData: ActiveMCQ = {
85
  ...mcqResult,
86
+ originalQuestionTextForFlow: mcqResult.mcq,
87
  };
88
 
89
  if (isInitialQuestionRef.current) {
 
112
  }
113
  };
114
 
115
+ // Trigger initial fetch if imageDataUri is present.
116
+ // If imageDataUri is not present initially, this effect will re-run when it becomes available.
117
  if (imageDataUri) {
 
 
 
 
118
  performInitialFetch();
119
+ } else if (messages.length === 0) {
120
+ // If there's no image URI and no messages, show "Preparing..."
121
+ // This typically means the parent component is still loading the image.
122
  addMessage({ sender: 'ai', type: 'text', text: "Preparing your journey..." });
123
  }
124
 
125
+
126
  return () => {
127
+ ignore = true; // Cleanup to prevent state updates on unmounted component
128
  };
129
  // eslint-disable-next-line react-hooks/exhaustive-deps
130
  }, [imageDataUri, journeyTitle]); // addMessage is memoized
 
139
  const handleOptionSelect = async (option: string, isCorrect: boolean) => {
140
  if (!currentMCQ) return;
141
 
142
+ setIsAwaitingAnswer(false); // User has made a selection
143
 
144
  addMessage({
145
  sender: 'user',
146
+ type: 'user', // This type indicates it's the user's selected option text
147
  text: option,
148
  });
149
 
 
164
  const errorMsg = error instanceof Error ? error.message : "An unknown error occurred";
165
  combinedMessage += ` (Sorry, I couldn't provide an explanation for that: ${errorMsg})`;
166
  }
167
+
168
  addMessage({ sender: 'ai', type: 'feedback', text: combinedMessage, isCorrect: true });
169
  setHasAnsweredCorrectly(true);
170
  setIncorrectAttempts([]);
171
  const newQuestionCount = questionCount + 1;
172
  setQuestionCount(newQuestionCount);
173
+ if (newQuestionCount >= MAX_QUESTIONS) {
174
  addMessage({ sender: 'ai', type: 'text', text: "You've completed all 5 questions! Please proceed to the summary report." });
175
  }
176
 
 
178
  const updatedIncorrectAttempts = [...incorrectAttempts, option];
179
  setIncorrectAttempts(updatedIncorrectAttempts);
180
 
181
+ // Check if all incorrect options have been exhausted
182
+ if (updatedIncorrectAttempts.length >= currentMCQ.options.length - 1) {
183
  let combinedText = "";
184
  try {
185
+ // Explain why the *last chosen* option was incorrect
186
  const incorrectExplanationResult = await explainIncorrectAnswer({
187
  question: questionForAI,
188
  options: currentMCQ.options,
 
192
  combinedText += `That's not quite right. ${incorrectExplanationResult.explanation} `;
193
  } catch (error) {
194
  console.error("Error fetching explanation for the last incorrect answer:", error);
195
+ const errorMsg = error instanceof Error ? error.message : "An unknown error occurred";
196
+ combinedText += `That's not quite right. (Failed to get explanation for your choice: ${errorMsg}) `;
197
  }
198
 
199
  combinedText += `The correct answer is "${currentMCQ.correctAnswer}". `;
200
 
201
  try {
202
+ // Explain why the actual correct answer is correct
203
  const correctExplanationResult = await explainCorrectAnswer({
204
  question: questionForAI,
205
  options: currentMCQ.options,
 
214
 
215
  addMessage({
216
  sender: 'ai',
217
+ type: 'feedback', // This will be handled by ChatMessage to not show options
218
  text: combinedText.trim(),
219
  isCorrect: true, // Mark as 'correct' flow-wise to enable next question/summary
220
  });
 
248
  } catch (error) {
249
  console.error("Error fetching explanation for incorrect answer:", error);
250
  const errorMsg = error instanceof Error ? error.message : "An unknown error occurred";
 
251
  const fallbackRepromptText = `That's not quite right. (Sorry, I couldn't explain that: ${errorMsg}) Please try another option from the question above.`;
252
  const fallbackMCQData: ActiveMCQ = {
253
  ...currentMCQ,
 
264
 
265
  const handleNextQuestionClick = async () => {
266
  if (questionCount >= MAX_QUESTIONS) {
 
 
267
  addMessage({ sender: 'ai', type: 'text', text: "You've completed all the questions! Please proceed to the summary." });
268
  setIsLoading(false);
269
+ setHasAnsweredCorrectly(true);
270
  return;
271
  }
272
 
273
+ setIsAwaitingAnswer(false);
274
  setHasAnsweredCorrectly(false);
275
+ setCurrentMCQ(null);
276
+ setIncorrectAttempts([]);
277
 
278
  if (!imageDataUri) {
279
  addMessage({ sender: 'ai', type: 'error', text: "Image data is not available to generate new questions." });
280
+ toast({ title: "Error", description: "Cannot fetch new question: Image data missing.", variant: "destructive" });
281
  return;
282
  }
283
  setIsLoading(true);
 
285
  const mcqResult = await generateMCQ({ imageDataUri });
286
  const nextMCQData: ActiveMCQ = {
287
  ...mcqResult,
288
+ originalQuestionTextForFlow: mcqResult.mcq,
289
  };
290
  setCurrentMCQ(nextMCQData);
291
  addMessage({ sender: 'ai', type: 'mcq', mcq: nextMCQData });
 
299
  description: `Could not generate a new question. ${errorMessage}`,
300
  variant: "destructive",
301
  });
302
+ // Potentially allow user to try again or go to summary if multiple errors occur
303
  } finally {
304
  setIsLoading(false);
305
  }
 
318
  <ScrollArea className="flex-1 p-4" ref={scrollAreaRef}>
319
  <div className="space-y-4">
320
  {messages.map((msg) => {
 
321
  const isLastMessageTheActiveMCQ =
322
  msg.type === 'mcq' &&
323
  currentMCQ &&
 
324
  msg.id === messages.findLast(m => m.type === 'mcq' && m.mcq?.mcq === currentMCQ.mcq && JSON.stringify(m.mcq?.options) === JSON.stringify(currentMCQ.options))?.id;
325
 
 
 
326
  const shouldThisMessageBeInteractive =
327
  isLastMessageTheActiveMCQ &&
328
  isAwaitingAnswer &&
 
330
 
331
  return (
332
  <ChatMessage
333
+ key={msg.id}
334
  message={msg}
335
  activeMCQ={currentMCQ}
336
  isAwaitingActiveMCQAnswer={shouldThisMessageBeInteractive}
337
  onOptionSelectActiveMCQ={handleOptionSelect}
338
  incorrectAttemptsForMCQ={
339
+ shouldThisMessageBeInteractive
340
  ? incorrectAttempts
341
+ : []
342
  }
343
  />
344
  );
345
  })}
346
+ {isLoading && !currentMCQ && messages[messages.length -1]?.type !== 'feedback' && (
 
347
  <div className="flex items-start space-x-3">
348
  <Skeleton className="h-8 w-8 rounded-full" />
349
  <div className="space-y-2">
 
355
  </div>
356
  </ScrollArea>
357
  <div className="border-t bg-background/80 p-4">
358
+ {isLoading && (
359
+ <div className="flex items-center justify-center text-sm text-muted-foreground">
360
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
361
+ AI is thinking...
362
+ </div>
363
+ )}
364
 
365
  {!isLoading && questionCount >= MAX_QUESTIONS && hasAnsweredCorrectly && (
366
  <Button onClick={handleGoToSummary} className="w-full" variant="default">
 
373
  Next Question <Send className="ml-2 h-4 w-4" />
374
  </Button>
375
  )}
376
+
 
377
  {!isLoading && currentMCQ && !hasAnsweredCorrectly && isAwaitingAnswer && (
378
  <p className="text-center text-sm text-muted-foreground">
379
  Please choose an answer from the options above.
380
  </p>
381
  )}
382
 
383
+ {!imageDataUri && !isLoading && messages.length > 0 && messages[messages.length -1]?.text === "Preparing your journey..." &&(
 
384
  <p className="text-center text-sm text-muted-foreground">Loading image, please wait...</p>
385
  )}
 
386
  {!currentMCQ && !isLoading && imageDataUri && messages.length === 0 && (
387
  <p className="text-center text-sm text-muted-foreground">Loading first question...</p>
388
  )}
 
389
  {!currentMCQ && !isLoading && imageDataUri && messages.length > 0 && messages[messages.length -1]?.type === 'error' && (
390
+ <p className="text-center text-sm text-destructive">Could not load question. Try refreshing the page or selecting another journey.</p>
391
  )}
392
  </div>
393
  </div>
src/components/journey/summary-report-dialog.tsx CHANGED
@@ -1,2 +1,4 @@
1
- // This file is no longer needed and will be deleted.
2
- // The summary report is now a dedicated page: /src/app/journey/[journeyId]/summary/page.tsx
 
 
 
1
+
2
+ // This file is no longer needed as the summary report is a dedicated page.
3
+ // This file can be deleted from the project.
4
+ // The build system should prune unreferenced files.