Spaces:
Paused
Paused
| import React from 'react'; | |
| import Card from '@mui/material/Card'; | |
| import CardContent from '@mui/material/CardContent'; | |
| import Typography from '@mui/material/Typography'; | |
| import Link from '@mui/material/Link'; | |
| import './SourcePopup.css'; | |
| // Helper function to extract a friendly domain name from a URL. | |
| const getDomainName = (url) => { | |
| try { | |
| if (!url) return 'Unknown Source'; | |
| const hostname = new URL(url).hostname; | |
| const domain = hostname.startsWith('www.') ? hostname.slice(4) : hostname; | |
| const parts = domain.split('.'); | |
| return parts[0].charAt(0).toUpperCase() + parts[0].slice(1); | |
| } catch (err) { | |
| console.error("Error parsing URL for domain name:", url, err); | |
| return 'Invalid URL'; | |
| } | |
| }; | |
| // Helper function for Levenshtein distance calculation | |
| function levenshtein(a, b) { | |
| if (a.length === 0) return b.length; | |
| if (b.length === 0) return a.length; | |
| const matrix = []; | |
| for (let i = 0; i <= b.length; i++) matrix[i] = [i]; | |
| for (let j = 0; j <= a.length; j++) matrix[0][j] = j; | |
| for (let i = 1; i <= b.length; i++) { | |
| for (let j = 1; j <= a.length; j++) { | |
| if (b.charAt(i - 1) === a.charAt(j - 1)) { | |
| matrix[i][j] = matrix[i - 1][j - 1]; | |
| } else { | |
| matrix[i][j] = Math.min( | |
| matrix[i - 1][j - 1] + 1, | |
| matrix[i][j - 1] + 1, | |
| matrix[i - 1][j] + 1 | |
| ); | |
| } | |
| } | |
| } | |
| return matrix[b.length][a.length]; | |
| } | |
| // SourcePopup component to display source information and excerpts | |
| function SourcePopup({ | |
| sourceData, | |
| excerptsData, | |
| position, | |
| onMouseEnter, | |
| onMouseLeave, | |
| statementText | |
| }) { | |
| if (!sourceData || !position) return null; | |
| const domain = getDomainName(sourceData.link); | |
| let hostname = ''; | |
| try { | |
| hostname = sourceData.link ? new URL(sourceData.link).hostname : ''; | |
| } catch (err) { | |
| hostname = sourceData.link || ''; // Fallback to link if URL parsing fails | |
| } | |
| let displayExcerpt = null; | |
| const sourceIdStr = String(sourceData.id); | |
| // Find the relevant excerpt | |
| if (excerptsData && Array.isArray(excerptsData) && statementText) { | |
| let foundExcerpt = null; | |
| let foundByFuzzy = false; | |
| const norm = s => s.replace(/\s+/g, ' ').trim(); | |
| const lower = s => norm(s).toLowerCase(); | |
| const statementNorm = norm(statementText); | |
| const statementLower = lower(statementText); | |
| console.log(`[SourcePopup] Searching for excerpt for source ID ${sourceIdStr}: ${statementText}`); | |
| // Iterate through the list of statement-to-excerpt mappings | |
| for (const entry of excerptsData) { | |
| const [thisStatement, sourcesMap] = Object.entries(entry)[0]; | |
| const thisNorm = norm(thisStatement); | |
| const thisLower = lower(thisStatement); | |
| console.log(`[SourcePopup] Checking against statement: ${thisStatement}`); | |
| // Normalized exact match | |
| if (thisNorm === statementNorm && sourcesMap && sourceIdStr in sourcesMap) { | |
| foundExcerpt = sourcesMap[sourceIdStr]; | |
| break; | |
| } | |
| // Case-insensitive match | |
| if (thisLower === statementLower && sourcesMap && sourceIdStr in sourcesMap) { | |
| foundExcerpt = sourcesMap[sourceIdStr]; | |
| break; | |
| } | |
| // Substring containment | |
| if ( | |
| (statementNorm && thisNorm && statementNorm.includes(thisNorm)) || | |
| (thisNorm && statementNorm && thisNorm.includes(statementNorm)) | |
| ) { | |
| if (sourcesMap && sourceIdStr in sourcesMap) { | |
| foundExcerpt = sourcesMap[sourceIdStr]; | |
| foundByFuzzy = true; | |
| break; | |
| } | |
| } | |
| // Levenshtein distance | |
| if ( | |
| levenshtein(statementNorm, thisNorm) <= 5 && | |
| sourcesMap && sourceIdStr in sourcesMap | |
| ) { | |
| foundExcerpt = sourcesMap[sourceIdStr]; | |
| foundByFuzzy = true; | |
| break; | |
| } | |
| } | |
| // Set displayExcerpt based on what was found | |
| if (foundExcerpt && foundExcerpt.toLowerCase() !== 'excerpt not found') { | |
| if (foundByFuzzy) { | |
| // Fuzzy match found an excerpt | |
| console.log("[SourcePopup] Fuzzy match found an excerpt:", foundExcerpt); | |
| } else { | |
| // Exact match found an excerpt | |
| console.log("[SourcePopup] Exact match found an excerpt:", foundExcerpt); | |
| } | |
| // Exact match found an excerpt | |
| displayExcerpt = foundExcerpt; | |
| } else if (foundExcerpt) { | |
| // Handle case where LLM explicitly said "Excerpt not found" | |
| displayExcerpt = "Relevant excerpt could not be automatically extracted."; | |
| console.log("[SourcePopup] Excerpt marked as not found or invalid type:", foundExcerpt); | |
| } else { | |
| // Excerpt for this specific source ID wasn't found in the loaded data | |
| displayExcerpt = "Excerpt not found for this citation."; | |
| console.log(`[SourcePopup] Excerpt not found for source ID ${sourceIdStr}: ${statementText}`); | |
| } | |
| } | |
| return ( | |
| <div | |
| className="source-popup" | |
| style={{ | |
| position: 'absolute', // Use absolute positioning | |
| top: `${position.top}px`, | |
| left: `${position.left}px`, | |
| transform: 'translate(-50%, -100%)', // Center above the reference | |
| zIndex: 1100, // Ensure it's above other content | |
| }} | |
| onMouseEnter={onMouseEnter} // Keep popup open when mouse enters it | |
| onMouseLeave={onMouseLeave} // Hide popup when mouse leaves it | |
| > | |
| <Card variant="outlined" className="source-popup-card"> | |
| <CardContent> | |
| <Typography variant="subtitle2" component="div" className="source-popup-title" gutterBottom> | |
| <Link href={sourceData.link} target="_blank" rel="noopener noreferrer" underline="hover" color="inherit"> | |
| {sourceData.title || 'Untitled Source'} | |
| </Link> | |
| </Typography> | |
| <Typography variant="body2" className="source-popup-link-info"> | |
| {hostname && ( | |
| <img | |
| src={`https://www.google.com/s2/favicons?domain=${hostname}&sz=16`} | |
| alt="" | |
| className="source-popup-icon" | |
| /> | |
| )} | |
| <span className="source-popup-domain">{domain}</span> | |
| </Typography> | |
| {displayExcerpt !== null && ( | |
| <Typography variant="caption" className="source-popup-excerpt" display="block" sx={{ mt: 1 }}> | |
| <Link | |
| href={`${sourceData.link}#:~:text=${encodeURIComponent(displayExcerpt)}`} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| underline="none" | |
| color="inherit" | |
| > | |
| {displayExcerpt} | |
| </Link> | |
| </Typography> | |
| )} | |
| </CardContent> | |
| </Card> | |
| </div> | |
| ); | |
| }; | |
| export default SourcePopup; |