1// ð³ Biographical Relationship Viewer for WikiLive 2// Interactive tree/card view showing all biographical properties from infoboxes 3 4/** 5 * Creates an interactive biographical relationship viewer 6 * Displays above the infoboxes with expandable sections for each entity 7 */ 8function createBiographicalRelationshipViewer(data, chatContainer) { 9 console.log('[BiographicalViewer] ð³ ========== FUNCTION CALLED =========='); 10 console.log('[BiographicalViewer] Data received:', { 11 hasData: !!data, 12 hasContainer: !!chatContainer, 13 dataKeys: data ? Object.keys(data) : [], 14 hasWikipediaData: !!(data?.wikipediaData), 15 wikipediaDataCount: data?.wikipediaData?.length || 0 16 }); 17 18 if (!data || !chatContainer) { 19 console.error('[BiographicalViewer] â Missing required data or container'); 20 return; 21 } 22 23 // Extract all entities with biographical data 24 const entitiesWithBio = extractBiographicalEntities(data); 25 26 console.log('[BiographicalViewer] Entities with bio data:', { 27 count: entitiesWithBio.length, 28 entities: entitiesWithBio.map(e => ({ title: e.title, propCount: Object.keys(e.bioProperties).length })) 29 }); 30 31 if (entitiesWithBio.length === 0) { 32 console.log('[BiographicalViewer] â ï¸ No biographical data found - viewer will not be created'); 33 return; 34 } 35 36 console.log(`[BiographicalViewer] Found ${entitiesWithBio.length} entities with biographical data`); 37 38 // Remove existing viewer if present 39 const existingViewer = chatContainer.querySelector('.biographical-relationship-viewer'); 40 if (existingViewer) { 41 existingViewer.remove(); 42 } 43 44 // Create main container 45 const viewerContainer = document.createElement('div'); 46 viewerContainer.className = 'biographical-relationship-viewer'; 47 viewerContainer.style.cssText = ` 48 margin: 20px 0; 49 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 50 border-radius: 16px; 51 padding: 24px; 52 box-shadow: 0 10px 30px rgba(0,0,0,0.2); 53 animation: slideInFromTop 0.5s ease-out; 54 `; 55 56 // Add animation keyframes 57 if (!document.getElementById('bio-viewer-animations')) { 58 const style = document.createElement('style'); 59 style.id = 'bio-viewer-animations'; 60 style.textContent = ` 61 @keyframes slideInFromTop { 62 from { 63 opacity: 0; 64 transform: translateY(-20px); 65 } 66 to { 67 opacity: 1; 68 transform: translateY(0); 69 } 70 } 71 72 @keyframes expandSection { 73 from { 74 max-height: 0; 75 opacity: 0; 76 } 77 to { 78 max-height: 1000px; 79 opacity: 1; 80 } 81 } 82 83 .bio-property-item:hover { 84 transform: translateX(5px); 85 background: rgba(255,255,255,0.15); 86 } 87 `; 88 document.head.appendChild(style); 89 } 90 91 // Create header 92 const header = document.createElement('div'); 93 header.style.cssText = ` 94 display: flex; 95 justify-content: space-between; 96 align-items: center; 97 margin-bottom: 20px; 98 `; 99 100 const headerTitle = document.createElement('h3'); 101 headerTitle.innerHTML = 'ð³ Biographical Relationships'; 102 headerTitle.style.cssText = ` 103 margin: 0; 104 color: white; 105 font-size: 22px; 106 font-weight: 600; 107 text-shadow: 2px 2px 4px rgba(0,0,0,0.3); 108 `; 109 110 const toggleAllBtn = document.createElement('button'); 111 toggleAllBtn.innerHTML = 'ð Expand All'; 112 toggleAllBtn.style.cssText = ` 113 background: rgba(255,255,255,0.2); 114 border: 1px solid rgba(255,255,255,0.3); 115 color: white; 116 padding: 8px 16px; 117 border-radius: 20px; 118 cursor: pointer; 119 font-size: 14px; 120 font-weight: 500; 121 transition: all 0.3s ease; 122 backdrop-filter: blur(10px); 123 `; 124 125 let allExpanded = false;
126 toggleAllBtn.addEventListener('click', () => { 127 allExpanded = !allExpanded; 128 toggleAllBtn.innerHTML = allExpanded ? 'ð Collapse All' : 'ð Expand All'; 129 130 const allSections = viewerContainer.querySelectorAll('.bio-properties-section'); 131 allSections.forEach(section => { 132 if (allExpanded) { 133 section.style.display = 'block'; 134 section.style.animation = 'expandSection 0.3s ease-out'; 135 } else { 136 section.style.display = 'none'; 137 } 138 }); 139 140 const allIcons = viewerContainer.querySelectorAll('.expand-icon'); 141 allIcons.forEach(icon => { 142 icon.textContent = allExpanded ? 'â¼' : 'â¶'; 143 }); 144 }); 145 146 toggleAllBtn.addEventListener('mouseenter', () => { 147 toggleAllBtn.style.background = 'rgba(255,255,255,0.3)'; 148 toggleAllBtn.style.transform = 'scale(1.05)'; 149 }); 150 151 toggleAllBtn.addEventListener('mouseleave', () => { 152 toggleAllBtn.style.background = 'rgba(255,255,255,0.2)'; 153 toggleAllBtn.style.transform = 'scale(1)'; 154 }); 155 156 header.appendChild(headerTitle); 157 header.appendChild(toggleAllBtn); 158 viewerContainer.appendChild(header); 159 160 // Create stats summary 161 const stats = createStatsSummary(entitiesWithBio); 162 viewerContainer.appendChild(stats); 163 164 // Create entity cards 165 const cardsContainer = document.createElement('div'); 166 cardsContainer.style.cssText = ` 167 display: grid; 168 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 169 gap: 16px; 170 margin-top: 20px; 171 `; 172 173 entitiesWithBio.forEach((entity, index) => { 174 const card = createEntityBioCard(entity, index); 175 cardsContainer.appendChild(card); 176 }); 177 178 viewerContainer.appendChild(cardsContainer); 179 180 // Insert before the fancy UI preview or at the beginning 181 const fancyPreview = chatContainer.querySelector('.fancy-ui-preview'); 182 if (fancyPreview) { 183 chatContainer.insertBefore(viewerContainer, fancyPreview); 184 } else { 185 chatContainer.appendChild(viewerContainer); 186 } 187 188 console.log('[BiographicalViewer] â Viewer created successfully'); 189} 190 191/** 192 * Extract entities with biographical data from the response 193 */ 194function extractBiographicalEntities(data) { 195 console.log('[BiographicalViewer] ð Extracting biographical entities...'); 196 const entities = []; 197 const bioFields = ['spouse', 'spouses', 'children', 'born', 'died', 'birthDate', 'deathDate', 198 'birthPlace', 'deathPlace', 'family', 'parents', 'relatives', 'siblings', 199 'education', 'occupation', 'occupations', 'party', 'nationality', 'citizenship']; 200 201 console.log('[BiographicalViewer] Looking for fields:', bioFields); 202 203 // Check wikipediaData 204 if (data.wikipediaData && Array.isArray(data.wikipediaData)) { 205 console.log('[BiographicalViewer] Checking wikipediaData:', data.wikipediaData.length, 'entities'); 206 data.wikipediaData.forEach((entity, index) => { 207 // Check for both pageData and infobox properties 208 const dataSource = entity.pageData || entity.infobox; 209 210 console.log(`[BiographicalViewer] Entity ${index + 1}:`, { 211 title: entity.pageTitle || entity.title, 212 hasPageData: !!entity.pageData, 213 hasInfobox: !!entity.infobox, 214 dataSourceKeys: dataSource ? Object.keys(dataSource) : [] 215 }); 216 217 if (dataSource) { 218 const bioProps = {}; 219 Object.keys(dataSource).forEach(key => { 220 const lowerKey = key.toLowerCase(); 221 if (bioFields.some(field => lowerKey.includes(field))) { 222 bioProps[key] = dataSource[key]; 223 console.log(`[BiographicalViewer] â Found bio field: ${key} = ${dataSource[key]}`); 224 } 225 }); 226 227 if (Object.keys(bioProps).length > 0) { 228 console.log(`[BiographicalViewer] â Adding entity with ${Object.keys(bioProps).length} bio properties:`, entity.pageTitle || entity.title); 229 entities.push({ 230 title: entity.pageTitle || entity.title, 231 type: entity.type || 'Unknown', 232 bioProperties: bioProps, 233 allProperties: dataSource, 234 imageUrl: entity.imageUrl || entity.main_image_url, 235 summary: entity.summary || entity.extract 236 }); 237 } 238 } 239 }); 240 } 241 242 // Check infoboxes 243 if (data.infoboxes && Array.isArray(data.infoboxes)) {
244 data.infoboxes.forEach(entity => { 245 if (entity.properties) { 246 const bioProps = {}; 247 Object.keys(entity.properties).forEach(key => { 248 const lowerKey = key.toLowerCase(); 249 if (bioFields.some(field => lowerKey.includes(field))) { 250 bioProps[key] = entity.properties[key]; 251 } 252 }); 253 254 if (Object.keys(bioProps).length > 0) { 255 entities.push({ 256 title: entity.title, 257 type: entity.type || 'Unknown', 258 bioProperties: bioProps, 259 allProperties: entity.properties, 260 imageUrl: entity.imageUrl, 261 summary: entity.summary 262 }); 263 } 264 } 265 }); 266 } 267 268 return entities; 269} 270 271/** 272 * Create stats summary bar 273 */ 274function createStatsSummary(entities) { 275 const statsContainer = document.createElement('div'); 276 statsContainer.style.cssText = ` 277 display: flex; 278 gap: 16px; 279 flex-wrap: wrap; 280 padding: 16px; 281 background: rgba(255,255,255,0.1); 282 border-radius: 12px; 283 backdrop-filter: blur(10px); 284 `; 285 286 const totalEntities = entities.length; 287 const totalProperties = entities.reduce((sum, e) => sum + Object.keys(e.bioProperties).length, 0); 288 const uniquePropertyTypes = new Set(); 289 entities.forEach(e => Object.keys(e.bioProperties).forEach(k => uniquePropertyTypes.add(k))); 290 291 const stats = [ 292 { icon: 'ð¥', label: 'Entities', value: totalEntities }, 293 { icon: 'ð', label: 'Relationships', value: totalProperties }, 294 { icon: 'ð', label: 'Property Types', value: uniquePropertyTypes.size } 295 ]; 296
297 stats.forEach(stat => { 298 const statItem = document.createElement('div'); 299 statItem.style.cssText = ` 300 flex: 1; 301 min-width: 120px; 302 text-align: center; 303 padding: 12px; 304 background: rgba(255,255,255,0.05); 305 border-radius: 8px; 306 border: 1px solid rgba(255,255,255,0.1); 307 `; 308 309 statItem.innerHTML = ` 310 <div style="font-size: 24px; margin-bottom: 4px;">${stat.icon}</div> 311 <div style="font-size: 20px; font-weight: 600; color: white; margin-bottom: 2px;">${stat.value}</div> 312 <div style="font-size: 12px; color: rgba(255,255,255,0.7); text-transform: uppercase; letter-spacing: 0.5px;">${stat.label}</div> 313 `; 314 315 statsContainer.appendChild(statItem); 316 }); 317 318 return statsContainer; 319} 320 321/** 322 * Create individual entity biographical card 323 */ 324function createEntityBioCard(entity, index) { 325 const card = document.createElement('div'); 326 card.className = 'bio-entity-card'; 327 card.style.cssText = ` 328 background: rgba(255,255,255,0.95); 329 border-radius: 12px; 330 overflow: hidden; 331 box-shadow: 0 4px 12px rgba(0,0,0,0.15); 332 transition: all 0.3s ease; 333 animation: slideInFromTop 0.5s ease-out ${index * 0.1}s backwards; 334 `; 335 336 card.addEventListener('mouseenter', () => { 337 card.style.transform = 'translateY(-4px)'; 338 card.style.boxShadow = '0 8px 24px rgba(0,0,0,0.2)'; 339 }); 340 341 card.addEventListener('mouseleave', () => { 342 card.style.transform = 'translateY(0)'; 343 card.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'; 344 }); 345 346 // Card header 347 const cardHeader = document.createElement('div'); 348 cardHeader.style.cssText = ` 349 padding: 16px; 350 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 351 color: white; 352 cursor: pointer; 353 display: flex; 354 align-items: center; 355 gap: 12px; 356 `; 357 358 const expandIcon = document.createElement('span'); 359 expandIcon.className = 'expand-icon'; 360 expandIcon.textContent = 'â¶'; 361 expandIcon.style.cssText = ` 362 font-size: 12px; 363 transition: transform 0.3s ease; 364 `; 365 366 const cardTitle = document.createElement('div'); 367 cardTitle.style.cssText = ` 368 flex: 1; 369 `; 370 371 const titleText = document.createElement('h4'); 372 titleText.textContent = entity.title; 373 titleText.style.cssText = ` 374 margin: 0 0 4px 0; 375 font-size: 16px; 376 font-weight: 600; 377 `; 378 379 const propertyCount = document.createElement('div'); 380 propertyCount.textContent = `${Object.keys(entity.bioProperties).length} biographical properties`; 381 propertyCount.style.cssText = ` 382 font-size: 12px; 383 opacity: 0.9; 384 `; 385 386 cardTitle.appendChild(titleText); 387 cardTitle.appendChild(propertyCount); 388 cardHeader.appendChild(expandIcon); 389 cardHeader.appendChild(cardTitle); 390 391 // Properties section (initially hidden) 392 const propertiesSection = document.createElement('div'); 393 propertiesSection.className = 'bio-properties-section'; 394 propertiesSection.style.cssText = ` 395 display: none; 396 padding: 16px; 397 background: white; 398 `; 399 400 // Group properties by category 401 const categorizedProps = categorizeProperties(entity.bioProperties); 402 403 Object.entries(categorizedProps).forEach(([category, props]) => { 404 if (props.length === 0) return; 405 406 const categorySection = document.createElement('div'); 407 categorySection.style.cssText = ` 408 margin-bottom: 16px; 409 `; 410 411 const categoryTitle = document.createElement('div'); 412 categoryTitle.textContent = category; 413 categoryTitle.style.cssText = ` 414 font-size: 13px; 415 font-weight: 600; 416 color: #667eea; 417 text-transform: uppercase; 418 letter-spacing: 0.5px; 419 margin-bottom: 8px; 420 padding-bottom: 4px; 421 border-bottom: 2px solid #667eea; 422 `; 423 424 categorySection.appendChild(categoryTitle); 425 426 props.forEach(([key, value]) => { 427 const propItem = document.createElement('div'); 428 propItem.className = 'bio-property-item'; 429 propItem.style.cssText = `
430 padding: 8px 12px; 431 margin: 4px 0; 432 border-radius: 6px; 433 background: #f8f9fa; 434 transition: all 0.2s ease; 435 border-left: 3px solid #667eea; 436 `; 437 438 const propLabel = document.createElement('div'); 439 propLabel.textContent = formatPropertyLabel(key); 440 propLabel.style.cssText = ` 441 font-size: 12px; 442 font-weight: 600; 443 color: #495057; 444 margin-bottom: 2px; 445 `; 446 447 const propValue = document.createElement('div'); 448 propValue.textContent = value; 449 propValue.style.cssText = ` 450 font-size: 14px; 451 color: #212529; 452 line-height: 1.4; 453 `; 454 455 propItem.appendChild(propLabel); 456 propItem.appendChild(propValue); 457 categorySection.appendChild(propItem); 458 }); 459 460 propertiesSection.appendChild(categorySection); 461 }); 462 463 // Toggle functionality 464 let isExpanded = false; 465 cardHeader.addEventListener('click', () => { 466 isExpanded = !isExpanded; 467 propertiesSection.style.display = isExpanded ? 'block' : 'none'; 468 expandIcon.textContent = isExpanded ? 'â¼' : 'â¶'; 469 expandIcon.style.transform = isExpanded ? 'rotate(0deg)' : 'rotate(0deg)'; 470 471 if (isExpanded) { 472 propertiesSection.style.animation = 'expandSection 0.3s ease-out'; 473 } 474 }); 475 476 card.appendChild(cardHeader); 477 card.appendChild(propertiesSection); 478 479 return card; 480} 481 482/** 483 * Categorize properties into logical groups 484 */ 485function categorizeProperties(properties) { 486 const categories = { 487 'ð¤ Personal': [], 488 'ð¨âð©âð§âð¦ Family': [], 489 'ð Education & Career': [], 490 'ðï¸ Political & Social': [], 491 'ð Location': [], 492 'ð¯ Other': [] 493 }; 494 495 Object.entries(properties).forEach(([key, value]) => { 496 const lowerKey = key.toLowerCase(); 497 498 if (lowerKey.includes('born') || lowerKey.includes('birth') || lowerKey.includes('died') || lowerKey.includes('death') || lowerKey.includes('age')) { 499 categories['ð¤ Personal'].push([key, value]); 500 } else if (lowerKey.includes('spouse') || lowerKey.includes('children') || lowerKey.includes('parent') || lowerKey.includes('family') || lowerKey.includes('relative') || lowerKey.includes('sibling')) { 501 categories['ð¨âð©âð§âð¦ Family'].push([key, value]); 502 } else if (lowerKey.includes('education') || lowerKey.includes('occupation') || lowerKey.includes('career') || lowerKey.includes('field') || lowerKey.includes('work')) { 503 categories['ð Education & Career'].push([key, value]); 504 } else if (lowerKey.includes('party') || lowerKey.includes('political') || lowerKey.includes('president') || lowerKey.includes('senator') || lowerKey.includes('office')) { 505 categories['ðï¸ Political & Social'].push([key, value]); 506 } else if (lowerKey.includes('place') || lowerKey.includes('location') || lowerKey.includes('city') || lowerKey.includes('country') || lowerKey.includes('nationality') || lowerKey.includes('citizenship')) { 507 categories['ð Location'].push([key, value]); 508 } else { 509 categories['ð¯ Other'].push([key, value]); 510 } 511 }); 512 513 return categories; 514} 515 516/** 517 * Format property label for display 518 */ 519function formatPropertyLabel(key) { 520 // Convert camelCase to Title Case with spaces 521 return key 522 .replace(/([A-Z])/g, ' $1') 523 .replace(/^./, str => str.toUpperCase()) 524 .trim(); 525} 526 527// Expose to global scope 528if (typeof window !== 'undefined') { 529 window.createBiographicalRelationshipViewer = createBiographicalRelationshipViewer; 530 console.log('ð³ [Global] createBiographicalRelationshipViewer function exposed to window scope'); 531}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.