Hemang Thakur
deploy
d5c104e
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;