Citations.svelte 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  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. 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"
  81. on:click={() => {
  82. showCitationModal = true;
  83. selectedCitation = citation;
  84. }}
  85. >
  86. {#if _citations.every((c) => c.distances !== undefined)}
  87. <div class="bg-gray-50 dark:bg-gray-800 rounded-full size-4">
  88. {idx + 1}
  89. </div>
  90. {/if}
  91. <div class="flex-1 mx-1 line-clamp-1 truncate">
  92. {citation.source.name}
  93. </div>
  94. </button>
  95. {/each}
  96. </div>
  97. {:else}
  98. <Collapsible bind:open={isCollapsibleOpen} className="w-full">
  99. <div
  100. class="flex items-center gap-2 text-gray-500 hover:text-gray-600 dark:hover:text-gray-400 transition cursor-pointer"
  101. >
  102. <div class="flex-grow flex items-center gap-1 overflow-hidden">
  103. <span class="whitespace-nowrap hidden sm:inline">{$i18n.t('References from')}</span>
  104. <div class="flex items-center">
  105. <div class="flex text-xs font-medium items-center">
  106. {#each _citations.slice(0, 2) as citation, idx}
  107. <button
  108. 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"
  109. on:click={() => {
  110. showCitationModal = true;
  111. selectedCitation = citation;
  112. }}
  113. on:pointerup={(e) => {
  114. e.stopPropagation();
  115. }}
  116. >
  117. {#if _citations.every((c) => c.distances !== undefined)}
  118. <div class="bg-gray-50 dark:bg-gray-800 rounded-full size-4">
  119. {idx + 1}
  120. </div>
  121. {/if}
  122. <div class="flex-1 mx-1 line-clamp-1 truncate">
  123. {citation.source.name}
  124. </div>
  125. </button>
  126. {/each}
  127. </div>
  128. </div>
  129. <div class="flex items-center gap-1 whitespace-nowrap">
  130. <span class="hidden sm:inline">{$i18n.t('and')}</span>
  131. {_citations.length - 2}
  132. <span>{$i18n.t('more')}</span>
  133. </div>
  134. </div>
  135. <div class="flex-shrink-0">
  136. {#if isCollapsibleOpen}
  137. <ChevronUp strokeWidth="3.5" className="size-3.5" />
  138. {:else}
  139. <ChevronDown strokeWidth="3.5" className="size-3.5" />
  140. {/if}
  141. </div>
  142. </div>
  143. <div slot="content">
  144. <div class="flex text-xs font-medium">
  145. {#each _citations as citation, idx}
  146. <button
  147. 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"
  148. on:click={() => {
  149. showCitationModal = true;
  150. selectedCitation = citation;
  151. }}
  152. >
  153. {#if _citations.every((c) => c.distances !== undefined)}
  154. <div class="bg-gray-50 dark:bg-gray-800 rounded-full size-4">
  155. {idx + 1}
  156. </div>
  157. {/if}
  158. <div class="flex-1 mx-1 line-clamp-1 truncate">
  159. {citation.source.name}
  160. </div>
  161. </button>
  162. {/each}
  163. </div>
  164. </div>
  165. </Collapsible>
  166. {/if}
  167. </div>
  168. {/if}