Girish Jeswani commited on
Commit
bebb492
·
1 Parent(s): 216b537

rag related info

Browse files
phd-advisor-frontend/src/components/MessageBubble.js CHANGED
@@ -1,5 +1,5 @@
1
- import React, { useState } from 'react';
2
- import { Reply, Copy, Check, Maximize2 } from 'lucide-react';
3
  import { advisors, getAdvisorColors } from '../data/advisors';
4
  import { useTheme } from '../contexts/ThemeContext';
5
 
@@ -13,6 +13,8 @@ const MessageBubble = ({
13
  const { isDark } = useTheme();
14
  const [showTooltip, setShowTooltip] = useState(null);
15
  const [copiedStates, setCopiedStates] = useState({});
 
 
16
 
17
  const handleCopy = async (messageId, content) => {
18
  try {
@@ -33,6 +35,10 @@ const MessageBubble = ({
33
  if (onExpand) onExpand(messageId, advisorId);
34
  };
35
 
 
 
 
 
36
  const showTooltipWithDelay = (tooltipType) => {
37
  setTimeout(() => setShowTooltip(tooltipType), 500);
38
  };
@@ -41,6 +47,99 @@ const MessageBubble = ({
41
  setShowTooltip(null);
42
  };
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  if (message.type === 'user') {
45
  return (
46
  <div className="user-message-container">
@@ -75,7 +174,8 @@ const MessageBubble = ({
75
  className="advisor-message-bubble"
76
  style={{
77
  backgroundColor: colors.bgColor,
78
- borderColor: colors.color + '40'
 
79
  }}
80
  >
81
  <div className="advisor-message-header">
@@ -169,9 +269,37 @@ const MessageBubble = ({
169
  <div className="tooltip">Expand on this response</div>
170
  )}
171
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  </div>
173
  </div>
174
  )}
 
 
 
 
 
 
 
 
175
  </div>
176
  </div>
177
  );
 
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { Reply, Copy, Check, Maximize2, Info, FileText, Hash, Target } from 'lucide-react';
3
  import { advisors, getAdvisorColors } from '../data/advisors';
4
  import { useTheme } from '../contexts/ThemeContext';
5
 
 
13
  const { isDark } = useTheme();
14
  const [showTooltip, setShowTooltip] = useState(null);
15
  const [copiedStates, setCopiedStates] = useState({});
16
+ const [showInfoOverlay, setShowInfoOverlay] = useState(false);
17
+ const overlayRef = useRef(null);
18
 
19
  const handleCopy = async (messageId, content) => {
20
  try {
 
35
  if (onExpand) onExpand(messageId, advisorId);
36
  };
37
 
38
+ const handleInfoToggle = () => {
39
+ setShowInfoOverlay(!showInfoOverlay);
40
+ };
41
+
42
  const showTooltipWithDelay = (tooltipType) => {
43
  setTimeout(() => setShowTooltip(tooltipType), 500);
44
  };
 
47
  setShowTooltip(null);
48
  };
49
 
50
+ // Close overlay when clicking outside
51
+ useEffect(() => {
52
+ const handleClickOutside = (event) => {
53
+ if (overlayRef.current && !overlayRef.current.contains(event.target)) {
54
+ setShowInfoOverlay(false);
55
+ }
56
+ };
57
+
58
+ if (showInfoOverlay) {
59
+ document.addEventListener('mousedown', handleClickOutside);
60
+ return () => {
61
+ document.removeEventListener('mousedown', handleClickOutside);
62
+ };
63
+ }
64
+ }, [showInfoOverlay]);
65
+
66
+ // RAG Metadata Component
67
+ const RagInfoOverlay = ({ ragMetadata, colors }) => {
68
+ const hasDocuments = ragMetadata?.usedDocuments || false;
69
+ const chunksUsed = ragMetadata?.chunksUsed || 0;
70
+ const documentChunks = ragMetadata?.documentChunks || [];
71
+
72
+ return (
73
+ <div
74
+ ref={overlayRef}
75
+ className="rag-info-overlay"
76
+ style={{
77
+ borderColor: colors.color + '40',
78
+ backgroundColor: isDark ? '#1f2937' : '#ffffff'
79
+ }}
80
+ >
81
+ <div className="rag-overlay-header" style={{ color: colors.color }}>
82
+ <Info size={14} />
83
+ <span>RAG Information</span>
84
+ </div>
85
+
86
+ <div className="rag-overlay-content">
87
+ {/* Basic Stats */}
88
+ <div className="rag-stat-row">
89
+ <div className="rag-stat-label">Used Documents:</div>
90
+ <div className={`rag-stat-value ${hasDocuments ? 'positive' : 'negative'}`}>
91
+ {hasDocuments ? 'Yes' : 'No'}
92
+ </div>
93
+ </div>
94
+
95
+ <div className="rag-stat-row">
96
+ <div className="rag-stat-label">Document Chunks:</div>
97
+ <div className="rag-stat-value">{chunksUsed}</div>
98
+ </div>
99
+
100
+ {/* Document Details */}
101
+ {hasDocuments && documentChunks.length > 0 && (
102
+ <div className="rag-documents-section">
103
+ <div className="rag-section-title">
104
+ <FileText size={12} />
105
+ Referenced Sources
106
+ </div>
107
+
108
+ {documentChunks.map((chunk, index) => (
109
+ <div key={index} className="rag-document-item">
110
+ <div className="rag-document-header">
111
+ <span className="rag-filename">
112
+ {chunk.metadata?.filename || 'Unknown file'}
113
+ </span>
114
+ <span className="rag-relevance">
115
+ <Target size={10} />
116
+ {Math.round((chunk.relevance_score || 0) * 100)}%
117
+ </span>
118
+ </div>
119
+
120
+ {chunk.text && (
121
+ <div className="rag-chunk-preview">
122
+ {chunk.text.substring(0, 120)}
123
+ {chunk.text.length > 120 && '...'}
124
+ </div>
125
+ )}
126
+ </div>
127
+ ))}
128
+ </div>
129
+ )}
130
+
131
+ {/* No Documents Message */}
132
+ {!hasDocuments && (
133
+ <div className="rag-no-documents">
134
+ <Hash size={12} />
135
+ <span>This response was generated without referencing uploaded documents.</span>
136
+ </div>
137
+ )}
138
+ </div>
139
+ </div>
140
+ );
141
+ };
142
+
143
  if (message.type === 'user') {
144
  return (
145
  <div className="user-message-container">
 
174
  className="advisor-message-bubble"
175
  style={{
176
  backgroundColor: colors.bgColor,
177
+ borderColor: colors.color + '40',
178
+ position: 'relative' // For overlay positioning
179
  }}
180
  >
181
  <div className="advisor-message-header">
 
269
  <div className="tooltip">Expand on this response</div>
270
  )}
271
  </div>
272
+
273
+ {/* NEW: Info Button */}
274
+ <div className="tooltip-container">
275
+ <button
276
+ className="action-button"
277
+ onClick={handleInfoToggle}
278
+ onMouseEnter={() => showTooltipWithDelay('info')}
279
+ onMouseLeave={hideTooltip}
280
+ style={{
281
+ color: showInfoOverlay ? colors.color : colors.color,
282
+ borderColor: showInfoOverlay ? colors.color : colors.color + '40',
283
+ backgroundColor: showInfoOverlay ? colors.color + '20' : 'transparent'
284
+ }}
285
+ >
286
+ <Info size={14} />
287
+ </button>
288
+ {showTooltip === 'info' && (
289
+ <div className="tooltip">RAG Information</div>
290
+ )}
291
+ </div>
292
  </div>
293
  </div>
294
  )}
295
+
296
+ {/* RAG Info Overlay */}
297
+ {showInfoOverlay && (
298
+ <RagInfoOverlay
299
+ ragMetadata={message.ragMetadata}
300
+ colors={colors}
301
+ />
302
+ )}
303
  </div>
304
  </div>
305
  );
phd-advisor-frontend/src/pages/ChatPage.js CHANGED
@@ -138,270 +138,245 @@ const ChatPage = ({ onNavigateToHome }) => {
138
  };
139
 
140
  const handleSendMessage = async (inputMessage) => {
141
- if (replyingTo) {
142
- await handleReplyToAdvisor(inputMessage, replyingTo);
143
- return;
144
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
- const userMessage = {
147
- id: generateMessageId(),
148
- type: 'user',
149
- content: inputMessage,
150
- timestamp: new Date()
151
- };
152
- setMessages(prev => [...prev, userMessage]);
153
-
154
- setIsLoading(true);
155
- setThinkingAdvisors(['system']);
156
 
157
- try {
158
- const response = await fetch('http://localhost:8000/chat-sequential', {
159
- method: 'POST',
160
- headers: {
161
- 'Content-Type': 'application/json',
162
- },
163
- body: JSON.stringify({
164
- user_input: inputMessage
165
- }),
166
- });
167
 
168
- if (!response.ok) {
169
- throw new Error(`HTTP error! status: ${response.status}`);
170
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
- const data = await response.json();
173
- setCollectedInfo(data.collected_info || {});
174
- setThinkingAdvisors([]);
175
 
176
- if (data.type === 'orchestrator_question') {
177
- const orchestratorMessage = {
178
- id: generateMessageId(),
179
- type: 'orchestrator',
180
- content: data.responses[0].response,
181
- timestamp: new Date()
182
- };
183
- setMessages(prev => [...prev, orchestratorMessage]);
184
- } else if (data.type === 'sequential_responses') {
185
- const advisorIds = ['methodist', 'theorist', 'pragmatist'];
186
-
187
- for (let i = 0; i < advisorIds.length; i++) {
188
- const advisorId = advisorIds[i];
189
- const response = data.responses.find(r => r.persona_id === advisorId);
190
-
191
- if (response) {
192
- setThinkingAdvisors([advisorId]);
193
- await new Promise(resolve => setTimeout(resolve, 1000));
194
-
195
- const advisorMessage = {
196
- id: generateMessageId(),
197
- type: 'advisor',
198
- advisorId: advisorId,
199
- content: response.response,
200
- timestamp: new Date()
201
- };
202
-
203
- setMessages(prev => [...prev, advisorMessage]);
204
- setThinkingAdvisors([]);
205
-
206
- if (i < advisorIds.length - 1) {
207
- await new Promise(resolve => setTimeout(resolve, 500));
208
- }
209
- }
210
- }
211
- } else if (data.type === 'error') {
212
- const errorMessage = {
213
  id: generateMessageId(),
214
- type: 'error',
215
- content: data.responses[0].response,
216
  timestamp: new Date()
217
  };
218
- setMessages(prev => [...prev, errorMessage]);
219
  }
220
 
221
- } catch (error) {
222
- console.error('Error sending message:', error);
223
  const errorMessage = {
224
  id: generateMessageId(),
225
  type: 'error',
226
- content: 'Sorry, I encountered an error. Please try again.',
227
  timestamp: new Date()
228
  };
229
  setMessages(prev => [...prev, errorMessage]);
230
- } finally {
231
- setIsLoading(false);
232
- setThinkingAdvisors([]);
233
  }
234
- };
235
-
236
- const handleReplyToAdvisor = async (inputMessage, replyInfo) => {
237
- const userMessage = {
238
- id: generateMessageId(),
239
- type: 'user',
240
- content: inputMessage,
241
- timestamp: new Date(),
242
- replyingTo: replyInfo
243
- };
244
- setMessages(prev => [...prev, userMessage]);
245
-
246
- setIsLoading(true);
247
- setThinkingAdvisors([replyInfo.advisorId]);
248
-
249
- try {
250
- const response = await fetch('http://localhost:8000/reply-to-advisor', {
251
- method: 'POST',
252
- headers: {
253
- 'Content-Type': 'application/json',
254
- },
255
- body: JSON.stringify({
256
- user_input: inputMessage,
257
- advisor_id: replyInfo.advisorId,
258
- original_message_id: replyInfo.messageId
259
- }),
260
- });
261
-
262
- if (!response.ok) {
263
- throw new Error(`HTTP error! status: ${response.status}`);
264
- }
265
-
266
- const data = await response.json();
267
- setThinkingAdvisors([]);
268
 
269
- if (data.type === 'advisor_reply') {
270
- const replyMessage = {
271
- id: generateMessageId(),
272
- type: 'advisor',
273
- advisorId: replyInfo.advisorId,
274
- content: data.response,
275
- timestamp: new Date(),
276
- isReply: true
277
- };
278
- setMessages(prev => [...prev, replyMessage]);
279
- } else if (data.type === 'error') {
280
  const errorMessage = {
281
  id: generateMessageId(),
282
  type: 'error',
283
- content: data.response,
284
  timestamp: new Date()
285
- };
286
- setMessages(prev => [...prev, errorMessage]);
287
- }
288
 
289
- } catch (error) {
290
- console.error('Error sending reply:', error);
291
- const errorMessage = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  id: generateMessageId(),
293
- type: 'error',
294
- content: 'Sorry, I encountered an error. Please try again.',
295
- timestamp: new Date()
 
 
 
 
 
 
 
 
 
296
  };
297
- setMessages(prev => [...prev, errorMessage]);
298
- } finally {
299
- setIsLoading(false);
300
- setThinkingAdvisors([]);
301
- setReplyingTo(null);
302
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  };
304
 
305
  const handleCopyMessage = (messageId, content) => {
306
  // Optional: Show a toast notification or add to message history
307
  console.log(`Copied message ${messageId}: ${content.substring(0, 50)}...`);
308
-
309
- // could add a temporary notification here if desired
310
- // const notificationMessage = {
311
- // id: generateMessageId(),
312
- // type: 'system',
313
- // content: '✅ Response copied to clipboard',
314
- // timestamp: new Date()
315
- // };
316
-
317
- // setMessages(prev => [...prev, notificationMessage]);
318
-
319
- // // Remove the notification after 3 seconds
320
- // setTimeout(() => {
321
- // setMessages(prev => prev.filter(msg => msg.id !== notificationMessage.id));
322
- // }, 3000);
323
  };
324
 
325
- const handleExpandMessage = async (messageId, advisorId) => {
326
- const advisor = advisors[advisorId];
327
-
328
- try {
329
- setIsLoading(true);
330
- setThinkingAdvisors([advisorId]);
331
-
332
- // Find the original message to expand on
333
- const originalMessage = messages.find(msg => msg.id === messageId);
334
-
335
- if (!originalMessage) {
336
- console.error('Original message not found');
337
- return;
338
- }
339
-
340
- // Create advisor-specific expansion prompts
341
- const advisorSpecificPrompts = {
342
- 'methodologist': `Please expand on your previous response with more methodological detail. Include specific research methods, data collection techniques, analytical approaches, and methodological considerations that would be relevant. Provide practical examples and step-by-step guidance where applicable.`,
343
- 'theorist': `Please elaborate on your previous response by exploring the theoretical frameworks and conceptual foundations in greater depth. Include additional theoretical perspectives, scholarly context, and conceptual analysis that would enrich understanding of the topic.`,
344
- 'pragmatist': `Please expand on your previous response with more practical, actionable details. Include specific examples, concrete next steps, real-world applications, and practical strategies that can be immediately implemented.`
345
- };
346
 
347
- // Use advisor-specific prompt or general expansion prompt
348
- const expandPrompt = advisorSpecificPrompts[advisorId] ||
349
- `Please expand and elaborate on your previous response in more detail. Provide additional insights, practical examples, and deeper analysis that would be helpful for understanding and implementing your advice.`;
350
 
351
- // Use the existing /reply-to-advisor endpoint
352
- const response = await fetch('http://localhost:8000/reply-to-advisor', {
353
- method: 'POST',
354
- headers: {
355
- 'Content-Type': 'application/json',
356
- },
357
- body: JSON.stringify({
358
- advisor_id: advisorId,
359
- user_input: expandPrompt,
360
- original_message_id: messageId
361
- }),
362
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363
 
364
- if (!response.ok) {
365
- throw new Error(`HTTP error! status: ${response.status}`);
366
- }
367
 
368
- const data = await response.json();
369
- setThinkingAdvisors([]);
370
 
371
- if (data.type === 'advisor_reply') {
372
- const expandedMessage = {
373
- id: generateMessageId(),
374
- type: 'advisor',
375
- advisorId: advisorId,
376
- content: data.response,
377
- timestamp: new Date(),
378
- isExpansion: true, // Mark this as an expansion
379
- expandedFrom: messageId // Reference to original message
380
- };
381
- setMessages(prev => [...prev, expandedMessage]);
382
- } else if (data.type === 'error') {
383
- const errorMessage = {
384
- id: generateMessageId(),
385
- type: 'error',
386
- content: data.response,
387
- timestamp: new Date()
388
- };
389
- setMessages(prev => [...prev, errorMessage]);
390
- }
391
-
392
- } catch (error) {
393
- console.error('Error expanding message:', error);
394
- const errorMessage = {
395
  id: generateMessageId(),
396
- type: 'error',
397
- content: 'Sorry, I encountered an error while expanding the response. Please try again.',
398
- timestamp: new Date()
 
 
 
 
 
 
 
 
 
 
399
  };
400
- setMessages(prev => [...prev, errorMessage]);
401
- } finally {
402
- setIsLoading(false);
403
- setThinkingAdvisors([]);
404
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
405
  };
406
 
407
  const handleReplyToMessage = (message) => {
 
138
  };
139
 
140
  const handleSendMessage = async (inputMessage) => {
141
+ if (replyingTo) {
142
+ await handleReplyToAdvisor(inputMessage, replyingTo);
143
+ return;
144
+ }
145
+
146
+ const userMessage = {
147
+ id: generateMessageId(),
148
+ type: 'user',
149
+ content: inputMessage,
150
+ timestamp: new Date()
151
+ };
152
+ setMessages(prev => [...prev, userMessage]);
153
+
154
+ setIsLoading(true);
155
+ setThinkingAdvisors(['system']);
156
+
157
+ try {
158
+ const response = await fetch('http://localhost:8000/chat-sequential', {
159
+ method: 'POST',
160
+ headers: {
161
+ 'Content-Type': 'application/json',
162
+ },
163
+ body: JSON.stringify({
164
+ user_input: inputMessage
165
+ }),
166
+ });
167
 
168
+ if (!response.ok) {
169
+ throw new Error(`HTTP error! Status: ${response.status}`);
170
+ }
 
 
 
 
 
 
 
171
 
172
+ const data = await response.json();
173
+ console.log('Backend response:', data); // Debug log
 
 
 
 
 
 
 
 
174
 
175
+ if (data.type === 'persona_responses' && data.responses) {
176
+ // Map each advisor response with RAG metadata
177
+ const advisorMessages = data.responses.map((advisor, index) => ({
178
+ id: generateMessageId(),
179
+ type: 'advisor',
180
+ advisorId: advisor.persona_id,
181
+ advisorName: advisor.persona_name,
182
+ content: advisor.response,
183
+ timestamp: new Date(),
184
+ // NEW: Map RAG metadata to the structure MessageBubble expects
185
+ ragMetadata: {
186
+ usedDocuments: advisor.used_documents || false,
187
+ chunksUsed: advisor.document_chunks_used || 0,
188
+ documentChunks: advisor.retrieved_chunks || []
189
+ }
190
+ }));
191
 
192
+ setMessages(prev => [...prev, ...advisorMessages]);
 
 
193
 
194
+ // Optional: Add RAG summary message if documents were used
195
+ const ragInfo = data.rag_info || {};
196
+ if (ragInfo.personas_using_documents > 0) {
197
+ const ragSummaryMessage = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  id: generateMessageId(),
199
+ type: 'system',
200
+ content: `📚 ${ragInfo.personas_using_documents}/${data.responses.length} advisors referenced your uploaded documents (${ragInfo.total_document_chunks_used} chunks used)`,
201
  timestamp: new Date()
202
  };
203
+ setMessages(prev => [...prev, ragSummaryMessage]);
204
  }
205
 
206
+ } else if (data.type === 'error') {
 
207
  const errorMessage = {
208
  id: generateMessageId(),
209
  type: 'error',
210
+ content: data.message || 'An error occurred. Please try again.',
211
  timestamp: new Date()
212
  };
213
  setMessages(prev => [...prev, errorMessage]);
 
 
 
214
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
+ } catch (error) {
217
+ console.error('Error sending message:', error);
 
 
 
 
 
 
 
 
 
218
  const errorMessage = {
219
  id: generateMessageId(),
220
  type: 'error',
221
+ content: 'Sorry, I encountered an error. Please try again.',
222
  timestamp: new Date()
223
+ };
224
+ setMessages(prev => [...prev, errorMessage]);
225
+ }
226
 
227
+ setIsLoading(false);
228
+ setThinkingAdvisors([]);
229
+ };
230
+
231
+ const handleReplyToAdvisor = async (inputMessage, replyContext) => {
232
+ const replyMessage = {
233
+ id: generateMessageId(),
234
+ type: 'user',
235
+ content: inputMessage,
236
+ replyTo: {
237
+ advisorId: replyContext.advisorId,
238
+ advisorName: replyContext.advisorName,
239
+ messageId: replyContext.messageId
240
+ },
241
+ timestamp: new Date()
242
+ };
243
+ setMessages(prev => [...prev, replyMessage]);
244
+
245
+ setIsLoading(true);
246
+ setThinkingAdvisors([replyContext.advisorId]);
247
+
248
+ try {
249
+ const response = await fetch('http://localhost:8000/reply-to-advisor', {
250
+ method: 'POST',
251
+ headers: {
252
+ 'Content-Type': 'application/json',
253
+ },
254
+ body: JSON.stringify({
255
+ user_input: inputMessage,
256
+ advisor_id: replyContext.advisorId,
257
+ original_message_id: replyContext.messageId
258
+ }),
259
+ });
260
+
261
+ if (!response.ok) {
262
+ throw new Error(`HTTP error! Status: ${response.status}`);
263
+ }
264
+
265
+ const data = await response.json();
266
+
267
+ if (data.type === 'advisor_reply') {
268
+ const replyResponseMessage = {
269
  id: generateMessageId(),
270
+ type: 'advisor',
271
+ advisorId: data.persona_id,
272
+ advisorName: data.persona,
273
+ content: data.response,
274
+ isReply: true,
275
+ timestamp: new Date(),
276
+ // NEW: Map RAG metadata for reply responses too
277
+ ragMetadata: {
278
+ usedDocuments: data.used_documents || false,
279
+ chunksUsed: data.document_chunks_used || 0,
280
+ documentChunks: data.retrieved_chunks || []
281
+ }
282
  };
283
+ setMessages(prev => [...prev, replyResponseMessage]);
 
 
 
 
284
  }
285
+
286
+ } catch (error) {
287
+ console.error('Error replying to advisor:', error);
288
+ const errorMessage = {
289
+ id: generateMessageId(),
290
+ type: 'error',
291
+ content: 'Sorry, I encountered an error with your reply. Please try again.',
292
+ timestamp: new Date()
293
+ };
294
+ setMessages(prev => [...prev, errorMessage]);
295
+ }
296
+
297
+ setIsLoading(false);
298
+ setThinkingAdvisors([]);
299
+ setReplyingTo(null);
300
  };
301
 
302
  const handleCopyMessage = (messageId, content) => {
303
  // Optional: Show a toast notification or add to message history
304
  console.log(`Copied message ${messageId}: ${content.substring(0, 50)}...`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  };
306
 
307
+ const handleExpandMessage = async (messageId, advisorId) => {
308
+ const advisor = advisors[advisorId];
309
+ if (!advisor) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
 
311
+ const originalMessage = messages.find(msg => msg.id === messageId);
312
+ if (!originalMessage) return;
 
313
 
314
+ const expandPrompt = `Please expand on your previous response: "${originalMessage.content.substring(0, 100)}..." Provide more detail and depth.`;
315
+
316
+ const expandMessage = {
317
+ id: generateMessageId(),
318
+ type: 'user',
319
+ content: expandPrompt,
320
+ timestamp: new Date(),
321
+ isExpandRequest: true,
322
+ expandsMessageId: messageId
323
+ };
324
+ setMessages(prev => [...prev, expandMessage]);
325
+
326
+ setIsLoading(true);
327
+ setThinkingAdvisors([advisorId]);
328
+
329
+ try {
330
+ const response = await fetch(`http://localhost:8000/chat/${advisorId}`, {
331
+ method: 'POST',
332
+ headers: {
333
+ 'Content-Type': 'application/json',
334
+ },
335
+ body: JSON.stringify({
336
+ user_input: expandPrompt,
337
+ response_length: 'long'
338
+ }),
339
+ });
340
 
341
+ if (!response.ok) {
342
+ throw new Error(`HTTP error! Status: ${response.status}`);
343
+ }
344
 
345
+ const data = await response.json();
 
346
 
347
+ if (data.type === 'single_persona_response' && data.persona) {
348
+ const expandedMessage = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  id: generateMessageId(),
350
+ type: 'advisor',
351
+ advisorId: advisorId,
352
+ advisorName: advisor.name,
353
+ content: data.persona.response,
354
+ isExpansion: true,
355
+ expandsMessageId: messageId,
356
+ timestamp: new Date(),
357
+ // NEW: Map RAG metadata for expanded responses
358
+ ragMetadata: {
359
+ usedDocuments: data.persona.used_documents || false,
360
+ chunksUsed: data.persona.document_chunks_used || 0,
361
+ documentChunks: data.persona.retrieved_chunks || []
362
+ }
363
  };
364
+ setMessages(prev => [...prev, expandedMessage]);
 
 
 
365
  }
366
+
367
+ } catch (error) {
368
+ console.error('Error expanding message:', error);
369
+ const errorMessage = {
370
+ id: generateMessageId(),
371
+ type: 'error',
372
+ content: 'Sorry, I encountered an error expanding the response. Please try again.',
373
+ timestamp: new Date()
374
+ };
375
+ setMessages(prev => [...prev, errorMessage]);
376
+ }
377
+
378
+ setIsLoading(false);
379
+ setThinkingAdvisors([]);
380
  };
381
 
382
  const handleReplyToMessage = (message) => {
phd-advisor-frontend/src/styles/components.css CHANGED
@@ -830,6 +830,230 @@
830
  display: none;
831
  }
832
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
833
  /* Animations */
834
  @keyframes bounce {
835
  0%, 80%, 100% {
@@ -911,4 +1135,23 @@
911
  padding: 4px 8px;
912
  }
913
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
914
  }
 
830
  display: none;
831
  }
832
 
833
+ .rag-info-overlay {
834
+ position: absolute;
835
+ top: 100%;
836
+ right: 0;
837
+ z-index: 1000;
838
+ width: 320px;
839
+ max-width: 90vw;
840
+ border: 1px solid;
841
+ border-radius: 8px;
842
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
843
+ margin-top: 8px;
844
+ backdrop-filter: blur(10px);
845
+ animation: ragOverlaySlideIn 0.2s ease-out;
846
+ }
847
+
848
+ @keyframes ragOverlaySlideIn {
849
+ from {
850
+ opacity: 0;
851
+ transform: translateY(-10px) scale(0.95);
852
+ }
853
+ to {
854
+ opacity: 1;
855
+ transform: translateY(0) scale(1);
856
+ }
857
+ }
858
+
859
+ .rag-overlay-header {
860
+ display: flex;
861
+ align-items: center;
862
+ gap: 8px;
863
+ padding: 12px 16px;
864
+ font-weight: 600;
865
+ font-size: 14px;
866
+ border-bottom: 1px solid var(--border-color);
867
+ background: var(--bg-secondary);
868
+ border-radius: 8px 8px 0 0;
869
+ }
870
+
871
+ .rag-overlay-content {
872
+ padding: 16px;
873
+ max-height: 400px;
874
+ overflow-y: auto;
875
+ }
876
+
877
+ .rag-stat-row {
878
+ display: flex;
879
+ justify-content: space-between;
880
+ align-items: center;
881
+ margin-bottom: 12px;
882
+ padding: 8px 0;
883
+ border-bottom: 1px solid var(--border-secondary);
884
+ }
885
+
886
+ .rag-stat-row:last-child {
887
+ border-bottom: none;
888
+ margin-bottom: 0;
889
+ }
890
+
891
+ .rag-stat-label {
892
+ font-size: 13px;
893
+ color: var(--text-secondary);
894
+ font-weight: 500;
895
+ }
896
+
897
+ .rag-stat-value {
898
+ font-size: 13px;
899
+ font-weight: 600;
900
+ color: var(--text-primary);
901
+ }
902
+
903
+ .rag-stat-value.positive {
904
+ color: #10B981;
905
+ }
906
+
907
+ .rag-stat-value.negative {
908
+ color: #6B7280;
909
+ }
910
+
911
+ .rag-documents-section {
912
+ margin-top: 16px;
913
+ padding-top: 16px;
914
+ border-top: 1px solid var(--border-secondary);
915
+ }
916
+
917
+ .rag-section-title {
918
+ display: flex;
919
+ align-items: center;
920
+ gap: 6px;
921
+ font-size: 12px;
922
+ font-weight: 600;
923
+ color: var(--text-secondary);
924
+ margin-bottom: 12px;
925
+ text-transform: uppercase;
926
+ letter-spacing: 0.5px;
927
+ }
928
+
929
+ .rag-document-item {
930
+ background: var(--bg-tertiary);
931
+ border: 1px solid var(--border-secondary);
932
+ border-radius: 6px;
933
+ padding: 10px;
934
+ margin-bottom: 8px;
935
+ }
936
+
937
+ .rag-document-item:last-child {
938
+ margin-bottom: 0;
939
+ }
940
+
941
+ .rag-document-header {
942
+ display: flex;
943
+ justify-content: space-between;
944
+ align-items: center;
945
+ margin-bottom: 6px;
946
+ }
947
+
948
+ .rag-filename {
949
+ font-size: 12px;
950
+ font-weight: 600;
951
+ color: var(--text-primary);
952
+ flex: 1;
953
+ margin-right: 8px;
954
+ white-space: nowrap;
955
+ overflow: hidden;
956
+ text-overflow: ellipsis;
957
+ }
958
+
959
+ .rag-relevance {
960
+ display: flex;
961
+ align-items: center;
962
+ gap: 3px;
963
+ font-size: 11px;
964
+ font-weight: 600;
965
+ color: var(--accent-color);
966
+ background: var(--accent-color)15;
967
+ padding: 2px 6px;
968
+ border-radius: 10px;
969
+ white-space: nowrap;
970
+ }
971
+
972
+ .rag-chunk-preview {
973
+ font-size: 11px;
974
+ line-height: 1.4;
975
+ color: var(--text-tertiary);
976
+ background: var(--bg-quaternary);
977
+ padding: 6px 8px;
978
+ border-radius: 4px;
979
+ border-left: 2px solid var(--accent-color);
980
+ margin-top: 6px;
981
+ }
982
+
983
+ .rag-no-documents {
984
+ display: flex;
985
+ align-items: center;
986
+ gap: 8px;
987
+ padding: 12px;
988
+ background: var(--bg-secondary);
989
+ border: 1px dashed var(--border-secondary);
990
+ border-radius: 6px;
991
+ color: var(--text-secondary);
992
+ font-size: 12px;
993
+ font-style: italic;
994
+ margin-top: 8px;
995
+ }
996
+
997
+ /* Accessibility improvements */
998
+ .rag-info-overlay:focus-within {
999
+ outline: 2px solid var(--accent-color);
1000
+ outline-offset: 2px;
1001
+ }
1002
+
1003
+ /* Smooth transitions */
1004
+ .action-button {
1005
+ transition: all 0.2s ease;
1006
+ }
1007
+
1008
+ .action-button:hover {
1009
+ transform: translateY(-1px);
1010
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
1011
+ }
1012
+
1013
+ /* Custom scrollbar for overlay content */
1014
+ .rag-overlay-content::-webkit-scrollbar {
1015
+ width: 4px;
1016
+ }
1017
+
1018
+ .rag-overlay-content::-webkit-scrollbar-track {
1019
+ background: var(--bg-secondary);
1020
+ border-radius: 2px;
1021
+ }
1022
+
1023
+ .rag-overlay-content::-webkit-scrollbar-thumb {
1024
+ background: var(--border-color);
1025
+ border-radius: 2px;
1026
+ }
1027
+
1028
+ .rag-overlay-content::-webkit-scrollbar-thumb:hover {
1029
+ background: var(--text-secondary);
1030
+ }
1031
+
1032
+ /* Dark theme adjustments */
1033
+ [data-theme="dark"] .rag-info-overlay {
1034
+ background: #1f2937;
1035
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4);
1036
+ }
1037
+
1038
+ [data-theme="dark"] .rag-overlay-header {
1039
+ background: #374151;
1040
+ }
1041
+
1042
+ [data-theme="dark"] .rag-document-item {
1043
+ background: #374151;
1044
+ border-color: #4B5563;
1045
+ }
1046
+
1047
+ [data-theme="dark"] .rag-chunk-preview {
1048
+ background: #4B5563;
1049
+ color: #D1D5DB;
1050
+ }
1051
+
1052
+ [data-theme="dark"] .rag-no-documents {
1053
+ background: #374151;
1054
+ border-color: #4B5563;
1055
+ }
1056
+
1057
  /* Animations */
1058
  @keyframes bounce {
1059
  0%, 80%, 100% {
 
1135
  padding: 4px 8px;
1136
  }
1137
 
1138
+ .rag-info-overlay {
1139
+ width: 280px;
1140
+ left: 0;
1141
+ right: auto;
1142
+ }
1143
+
1144
+ .rag-document-header {
1145
+ flex-direction: column;
1146
+ align-items: flex-start;
1147
+ gap: 4px;
1148
+ }
1149
+
1150
+ .rag-filename {
1151
+ margin-right: 0;
1152
+ white-space: normal;
1153
+ overflow: visible;
1154
+ text-overflow: initial;
1155
+ }
1156
+
1157
  }