Citations.svelte 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. <script lang="ts">
  2. import { getContext } from 'svelte';
  3. import CitationsModal from './CitationsModal.svelte';
  4. import Collapsible from '$lib/components/common/Collapsible.svelte';
  5. import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
  6. import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
  7. const i18n = getContext('i18n');
  8. export let citations = [];
  9. let _citations = [];
  10. let showPercentage = false;
  11. let showRelevance = true;
  12. let showCitationModal = false;
  13. let selectedCitation: any = null;
  14. let isCollapsibleOpen = false;
  15. function calculateShowRelevance(citations: any[]) {
  16. const distances = citations.flatMap((citation) => citation.distances ?? []);
  17. const inRange = distances.filter((d) => d !== undefined && d >= -1 && d <= 1).length;
  18. const outOfRange = distances.filter((d) => d !== undefined && (d < -1 || d > 1)).length;
  19. if (distances.length === 0) {
  20. return false;
  21. }
  22. if (
  23. (inRange === distances.length - 1 && outOfRange === 1) ||
  24. (outOfRange === distances.length - 1 && inRange === 1)
  25. ) {
  26. return false;
  27. }
  28. return true;
  29. }
  30. function shouldShowPercentage(citations: any[]) {
  31. const distances = citations.flatMap((citation) => citation.distances ?? []);
  32. return distances.every((d) => d !== undefined && d >= -1 && d <= 1);
  33. }
  34. $: {
  35. _citations = citations.reduce((acc, citation) => {
  36. citation.document.forEach((document, index) => {
  37. const metadata = citation.metadata?.[index];
  38. const distance = citation.distances?.[index];
  39. const id = metadata?.source ?? 'N/A';
  40. let source = citation?.source;
  41. if (metadata?.name) {
  42. source = { ...source, name: metadata.name };
  43. }
  44. if (id.startsWith('http://') || id.startsWith('https://')) {
  45. source = { ...source, name: id, url: id };
  46. }
  47. const existingSource = acc.find((item) => item.id === id);
  48. if (existingSource) {
  49. existingSource.document.push(document);
  50. existingSource.metadata.push(metadata);
  51. if (distance !== undefined) existingSource.distances.push(distance);
  52. } else {
  53. acc.push({
  54. id: id,
  55. source: source,
  56. document: [document],
  57. metadata: metadata ? [metadata] : [],
  58. distances: distance !== undefined ? [distance] : undefined
  59. });
  60. }
  61. });
  62. return acc;
  63. }, []);
  64. showRelevance = calculateShowRelevance(_citations);
  65. showPercentage = shouldShowPercentage(_citations);
  66. }
  67. </script>
  68. <CitationsModal
  69. bind:show={showCitationModal}
  70. citation={selectedCitation}
  71. {showPercentage}
  72. {showRelevance}
  73. />
  74. {#if _citations.length > 0}
  75. <div class=" py-0.5 -mx-0.5 w-full flex gap-1 items-center flex-wrap">
  76. {#if _citations.length <= 3}
  77. <div class="flex text-xs font-medium">
  78. {#each _citations as citation, idx}
  79. <button
  80. id={`source-${citation.source.name}`}
  81. class="no-toggle outline-none flex dark:text-gray-300 p-1 bg-white hover:bg-gray-50 dark:bg-gray-900 dark:hover:bg-gray-850 transition rounded-xl max-w-96"
  82. on:click={() => {
  83. showCitationModal = true;
  84. selectedCitation = citation;
  85. }}
  86. >
  87. {#if _citations.every((c) => c.distances !== undefined)}
  88. <div class="bg-gray-50 dark:bg-gray-800 rounded-full size-4">
  89. {idx + 1}
  90. </div>
  91. {/if}
  92. <div class="flex-1 mx-1 line-clamp-1 truncate">
  93. {citation.source.name}
  94. </div>
  95. </button>
  96. {/each}
  97. </div>
  98. {:else}
  99. <Collapsible bind:open={isCollapsibleOpen} className="w-full">
  100. <div
  101. class="flex items-center gap-2 text-gray-500 hover:text-gray-600 dark:hover:text-gray-400 transition cursor-pointer"
  102. >
  103. <div class="flex-grow flex items-center gap-1 overflow-hidden">
  104. <span class="whitespace-nowrap hidden sm:inline">{$i18n.t('References from')}</span>
  105. <div class="flex items-center">
  106. <div class="flex text-xs font-medium items-center">
  107. {#each _citations.slice(0, 2) as citation, idx}
  108. <button
  109. class="no-toggle outline-none flex dark:text-gray-300 p-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 transition rounded-xl max-w-96"
  110. on:click={() => {
  111. showCitationModal = true;
  112. selectedCitation = citation;
  113. }}
  114. on:pointerup={(e) => {
  115. e.stopPropagation();
  116. }}
  117. >
  118. {#if _citations.every((c) => c.distances !== undefined)}
  119. <div class="bg-gray-50 dark:bg-gray-800 rounded-full size-4">
  120. {idx + 1}
  121. </div>
  122. {/if}
  123. <div class="flex-1 mx-1 line-clamp-1 truncate">
  124. {citation.source.name}
  125. </div>
  126. </button>
  127. {/each}
  128. </div>
  129. </div>
  130. <div class="flex items-center gap-1 whitespace-nowrap">
  131. <span class="hidden sm:inline">{$i18n.t('and')}</span>
  132. {_citations.length - 2}
  133. <span>{$i18n.t('more')}</span>
  134. </div>
  135. </div>
  136. <div class="flex-shrink-0">
  137. {#if isCollapsibleOpen}
  138. <ChevronUp strokeWidth="3.5" className="size-3.5" />
  139. {:else}
  140. <ChevronDown strokeWidth="3.5" className="size-3.5" />
  141. {/if}
  142. </div>
  143. </div>
  144. <div slot="content">
  145. <div class="flex text-xs font-medium">
  146. {#each _citations as citation, idx}
  147. <button
  148. class="no-toggle outline-none flex dark:text-gray-300 p-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 transition rounded-xl max-w-96"
  149. on:click={() => {
  150. showCitationModal = true;
  151. selectedCitation = citation;
  152. }}
  153. >
  154. {#if _citations.every((c) => c.distances !== undefined)}
  155. <div class="bg-gray-50 dark:bg-gray-800 rounded-full size-4">
  156. {idx + 1}
  157. </div>
  158. {/if}
  159. <div class="flex-1 mx-1 line-clamp-1 truncate">
  160. {citation.source.name}
  161. </div>
  162. </button>
  163. {/each}
  164. </div>
  165. </div>
  166. </Collapsible>
  167. {/if}
  168. </div>
  169. {/if}