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; |