Spaces:
Running
Running
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 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
content: inputMessage,
|
| 150 |
-
timestamp: new Date()
|
| 151 |
-
};
|
| 152 |
-
setMessages(prev => [...prev, userMessage]);
|
| 153 |
-
|
| 154 |
-
setIsLoading(true);
|
| 155 |
-
setThinkingAdvisors(['system']);
|
| 156 |
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
method: 'POST',
|
| 160 |
-
headers: {
|
| 161 |
-
'Content-Type': 'application/json',
|
| 162 |
-
},
|
| 163 |
-
body: JSON.stringify({
|
| 164 |
-
user_input: inputMessage
|
| 165 |
-
}),
|
| 166 |
-
});
|
| 167 |
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
|
| 172 |
-
|
| 173 |
-
setCollectedInfo(data.collected_info || {});
|
| 174 |
-
setThinkingAdvisors([]);
|
| 175 |
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 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: '
|
| 215 |
-
content: data.responses
|
| 216 |
timestamp: new Date()
|
| 217 |
};
|
| 218 |
-
setMessages(prev => [...prev,
|
| 219 |
}
|
| 220 |
|
| 221 |
-
}
|
| 222 |
-
console.error('Error sending message:', error);
|
| 223 |
const errorMessage = {
|
| 224 |
id: generateMessageId(),
|
| 225 |
type: 'error',
|
| 226 |
-
content:
|
| 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 |
-
|
| 270 |
-
|
| 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:
|
| 284 |
timestamp: new Date()
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
id: generateMessageId(),
|
| 293 |
-
type: '
|
| 294 |
-
|
| 295 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
};
|
| 297 |
-
setMessages(prev => [...prev,
|
| 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 |
-
|
| 326 |
-
|
| 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 |
-
|
| 348 |
-
|
| 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 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 363 |
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
|
| 368 |
-
|
| 369 |
-
setThinkingAdvisors([]);
|
| 370 |
|
| 371 |
-
|
| 372 |
-
|
| 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: '
|
| 397 |
-
|
| 398 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
};
|
| 400 |
-
setMessages(prev => [...prev,
|
| 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 |
}
|