File size: 6,713 Bytes
d5c104e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
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; |