Citations.svelte 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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, 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="mt-1 mb-2 w-full flex gap-1 items-center flex-wrap">
  76. {#if _citations.length <= 3}
  77. {#each _citations as citation, idx}
  78. <div class="flex gap-1 text-xs font-semibold">
  79. <button
  80. class="no-toggle flex dark:text-gray-300 py-1 px-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 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-white dark:bg-gray-700 rounded-full size-4">
  88. {idx + 1}
  89. </div>
  90. {/if}
  91. <div class="flex-1 mx-2 line-clamp-1 truncate">
  92. {citation.source.name}
  93. </div>
  94. </button>
  95. </div>
  96. {/each}
  97. {:else}
  98. <Collapsible bind:open={isCollapsibleOpen} className="w-full">
  99. <div
  100. class="flex items-center gap-1 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. {#if _citations.length > 1 && _citations
  106. .slice(0, 2)
  107. .reduce((acc, citation) => acc + citation.source.name.length, 0) <= 50}
  108. {#each _citations.slice(0, 2) as citation, idx}
  109. <div class="flex items-center">
  110. <button
  111. class="no-toggle flex dark:text-gray-300 py-1 px-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-xl max-w-96 text-xs font-semibold"
  112. on:click={() => {
  113. showCitationModal = true;
  114. selectedCitation = citation;
  115. }}
  116. >
  117. {#if _citations.every((c) => c.distances !== undefined)}
  118. <div class="bg-white dark:bg-gray-700 rounded-full size-4">
  119. {idx + 1}
  120. </div>
  121. {/if}
  122. <div class="flex-1 mx-2 line-clamp-1">
  123. {citation.source.name}
  124. </div>
  125. </button>
  126. {#if idx === 0}<span class="mr-1">,</span>
  127. {/if}
  128. </div>
  129. {/each}
  130. {:else}
  131. {#each _citations.slice(0, 1) as citation, idx}
  132. <div class="flex items-center">
  133. <button
  134. class="no-toggle flex dark:text-gray-300 py-1 px-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-xl max-w-96 text-xs font-semibold"
  135. on:click={() => {
  136. showCitationModal = true;
  137. selectedCitation = citation;
  138. }}
  139. >
  140. {#if _citations.every((c) => c.distances !== undefined)}
  141. <div class="bg-white dark:bg-gray-700 rounded-full size-4">
  142. {idx + 1}
  143. </div>
  144. {/if}
  145. <div class="flex-1 mx-2 line-clamp-1">
  146. {citation.source.name}
  147. </div>
  148. </button>
  149. </div>
  150. {/each}
  151. {/if}
  152. </div>
  153. <div class="flex items-center gap-1 whitespace-nowrap">
  154. <span class="hidden sm:inline">{$i18n.t('and')}</span>
  155. <span class="text-gray-600 dark:text-gray-400">
  156. {_citations.length -
  157. (_citations.length > 1 &&
  158. _citations
  159. .slice(0, 2)
  160. .reduce((acc, citation) => acc + citation.source.name.length, 0) <= 50
  161. ? 2
  162. : 1)}
  163. </span>
  164. <span>{$i18n.t('more')}</span>
  165. </div>
  166. </div>
  167. <div class="flex-shrink-0">
  168. {#if isCollapsibleOpen}
  169. <ChevronUp strokeWidth="3.5" className="size-3.5" />
  170. {:else}
  171. <ChevronDown strokeWidth="3.5" className="size-3.5" />
  172. {/if}
  173. </div>
  174. </div>
  175. <div slot="content" class="mt-2">
  176. <div class="flex flex-wrap gap-2">
  177. {#each _citations as citation, idx}
  178. <div class="flex gap-1 text-xs font-semibold">
  179. <button
  180. class="no-toggle flex dark:text-gray-300 py-1 px-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-xl max-w-96"
  181. on:click={() => {
  182. showCitationModal = true;
  183. selectedCitation = citation;
  184. }}
  185. >
  186. {#if _citations.every((c) => c.distances !== undefined)}
  187. <div class="bg-white dark:bg-gray-700 rounded-full size-4">
  188. {idx + 1}
  189. </div>
  190. {/if}
  191. <div class="flex-1 mx-2 line-clamp-1">
  192. {citation.source.name}
  193. </div>
  194. </button>
  195. </div>
  196. {/each}
  197. </div>
  198. </div>
  199. </Collapsible>
  200. {/if}
  201. </div>
  202. {/if}