|
77 | 77 | text: secondaryText, |
78 | 78 | caption: secondaryCaption, |
79 | 79 | }; |
| 80 | + const MAX_NEIGHBOR_DISPLAY = 10; |
80 | 81 |
|
81 | 82 | function safeText(value) { |
82 | 83 | return typeof value === 'string' ? value.trim() : ''; |
|
524 | 525 | function updateActiveNeighborButton() { |
525 | 526 | const buttons = neighborList.querySelectorAll('.neighbor-button'); |
526 | 527 | buttons.forEach((button) => { |
| 528 | + if (button.dataset.self === 'true') { |
| 529 | + button.dataset.active = 'true'; |
| 530 | + button.setAttribute('aria-pressed', 'true'); |
| 531 | + return; |
| 532 | + } |
527 | 533 | const isActive = button.dataset.neighborId === activeNeighborId; |
528 | 534 | button.dataset.active = isActive ? 'true' : 'false'; |
529 | 535 | button.setAttribute('aria-pressed', isActive ? 'true' : 'false'); |
530 | 536 | }); |
531 | 537 | } |
532 | 538 |
|
| 539 | + function createNeighborListItem(record, datasetData, options = {}) { |
| 540 | + if (!record || !datasetData) { |
| 541 | + return null; |
| 542 | + } |
| 543 | + |
| 544 | + const item = document.createElement('li'); |
| 545 | + const button = document.createElement('button'); |
| 546 | + button.type = 'button'; |
| 547 | + button.className = 'neighbor-button'; |
| 548 | + |
| 549 | + const isSelf = options.isSelf === true; |
| 550 | + const similarity = Number.isFinite(options.similarity) ? options.similarity : 0; |
| 551 | + |
| 552 | + if (isSelf) { |
| 553 | + button.disabled = true; |
| 554 | + button.dataset.self = 'true'; |
| 555 | + button.dataset.active = 'true'; |
| 556 | + button.setAttribute('aria-disabled', 'true'); |
| 557 | + button.setAttribute('aria-pressed', 'true'); |
| 558 | + } else { |
| 559 | + button.dataset.neighborId = record.id; |
| 560 | + const isActiveNeighbor = record.id === activeNeighborId; |
| 561 | + button.dataset.active = isActiveNeighbor ? 'true' : 'false'; |
| 562 | + button.setAttribute('aria-pressed', isActiveNeighbor ? 'true' : 'false'); |
| 563 | + if (activeRecordId) { |
| 564 | + button.setAttribute( |
| 565 | + 'aria-label', |
| 566 | + `Compare ${record.id} with ${activeRecordId} (similarity ${formatSimilarity(similarity)})` |
| 567 | + ); |
| 568 | + } |
| 569 | + } |
| 570 | + |
| 571 | + const header = document.createElement('div'); |
| 572 | + header.className = 'neighbor-button__header'; |
| 573 | + |
| 574 | + const idSpan = document.createElement('span'); |
| 575 | + idSpan.className = 'neighbor-button__id'; |
| 576 | + idSpan.textContent = record.id; |
| 577 | + |
| 578 | + const scoreSpan = document.createElement('span'); |
| 579 | + scoreSpan.className = 'neighbor-button__score'; |
| 580 | + scoreSpan.textContent = formatSimilarity(isSelf ? 1 : similarity); |
| 581 | + |
| 582 | + header.append(idSpan, scoreSpan); |
| 583 | + button.appendChild(header); |
| 584 | + |
| 585 | + if (isSelf) { |
| 586 | + const badge = document.createElement('span'); |
| 587 | + badge.className = 'neighbor-button__self'; |
| 588 | + badge.textContent = typeof options.label === 'string' && options.label ? options.label : 'Selected vector'; |
| 589 | + button.appendChild(badge); |
| 590 | + } |
| 591 | + |
| 592 | + const snippetSpan = document.createElement('span'); |
| 593 | + snippetSpan.className = 'neighbor-button__snippet'; |
| 594 | + const contentEntry = getContentEntry(datasetData.contentKey, record.id); |
| 595 | + const fullPreview = getContentPreview(datasetData.contentKey, contentEntry); |
| 596 | + const preview = truncateText(fullPreview, 120); |
| 597 | + snippetSpan.textContent = preview || 'No source text available.'; |
| 598 | + if (fullPreview && preview !== fullPreview) { |
| 599 | + snippetSpan.setAttribute('title', fullPreview); |
| 600 | + } |
| 601 | + |
| 602 | + button.appendChild(snippetSpan); |
| 603 | + item.appendChild(button); |
| 604 | + return item; |
| 605 | + } |
| 606 | + |
533 | 607 | function renderNeighborList(datasetData) { |
534 | 608 | neighborList.innerHTML = ''; |
535 | 609 |
|
|
551 | 625 | return; |
552 | 626 | } |
553 | 627 |
|
554 | | - neighborStatus.textContent = `Top ${neighborRecords.length} matches for ${activeRecordId}.`; |
555 | | - |
556 | 628 | const fragment = document.createDocumentFragment(); |
| 629 | + const primaryRecord = datasetData.recordMap.get(activeRecordId) || null; |
| 630 | + neighborStatus.textContent = `Selected vector and top ${neighborRecords.length} matches for ${activeRecordId}.`; |
557 | 631 |
|
558 | | - neighborRecords.forEach((entry) => { |
559 | | - const item = document.createElement('li'); |
560 | | - const button = document.createElement('button'); |
561 | | - button.type = 'button'; |
562 | | - button.className = 'neighbor-button'; |
563 | | - button.dataset.neighborId = entry.id; |
564 | | - const isActive = entry.id === activeNeighborId; |
565 | | - button.dataset.active = isActive ? 'true' : 'false'; |
566 | | - button.setAttribute('aria-pressed', isActive ? 'true' : 'false'); |
567 | | - if (activeRecordId) { |
568 | | - button.setAttribute('aria-label', `Compare ${entry.record.id} with ${activeRecordId} (similarity ${formatSimilarity(entry.similarity)})`); |
569 | | - } |
570 | | - |
571 | | - const header = document.createElement('div'); |
572 | | - header.className = 'neighbor-button__header'; |
| 632 | + const selfItem = createNeighborListItem(primaryRecord, datasetData, { |
| 633 | + isSelf: true, |
| 634 | + similarity: 1, |
| 635 | + label: 'Selected vector', |
| 636 | + }); |
| 637 | + if (selfItem) { |
| 638 | + fragment.appendChild(selfItem); |
| 639 | + } |
573 | 640 |
|
574 | | - const idSpan = document.createElement('span'); |
575 | | - idSpan.className = 'neighbor-button__id'; |
576 | | - idSpan.textContent = entry.record.id; |
577 | | - |
578 | | - const scoreSpan = document.createElement('span'); |
579 | | - scoreSpan.className = 'neighbor-button__score'; |
580 | | - scoreSpan.textContent = formatSimilarity(entry.similarity); |
581 | | - |
582 | | - header.append(idSpan, scoreSpan); |
583 | | - |
584 | | - const snippetSpan = document.createElement('span'); |
585 | | - snippetSpan.className = 'neighbor-button__snippet'; |
586 | | - const contentEntry = getContentEntry(datasetData.contentKey, entry.record.id); |
587 | | - const fullPreview = getContentPreview(datasetData.contentKey, contentEntry); |
588 | | - const preview = truncateText(fullPreview, 120); |
589 | | - snippetSpan.textContent = preview || 'No source text available.'; |
590 | | - if (fullPreview && preview !== fullPreview) { |
591 | | - snippetSpan.setAttribute('title', fullPreview); |
| 641 | + neighborRecords.forEach((entry) => { |
| 642 | + const item = createNeighborListItem(entry.record, datasetData, { |
| 643 | + similarity: entry.similarity, |
| 644 | + }); |
| 645 | + if (item) { |
| 646 | + fragment.appendChild(item); |
592 | 647 | } |
593 | | - |
594 | | - button.append(header, snippetSpan); |
595 | | - item.appendChild(button); |
596 | | - fragment.appendChild(item); |
597 | 648 | }); |
598 | 649 |
|
599 | 650 | neighborList.appendChild(fragment); |
|
644 | 695 | }); |
645 | 696 |
|
646 | 697 | neighbors.sort((a, b) => b.similarity - a.similarity); |
647 | | - return neighbors.slice(0, 10); |
| 698 | + return neighbors.slice(0, MAX_NEIGHBOR_DISPLAY); |
648 | 699 | } |
649 | 700 |
|
650 | 701 | function renderPane(pane, record, datasetMeta, source, options = {}) { |
|
0 commit comments