Selector.svelte 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. <script lang="ts">
  2. import Fuse from 'fuse.js';
  3. import { DropdownMenu } from 'bits-ui';
  4. import { onMount, getContext, createEventDispatcher } from 'svelte';
  5. import { flyAndScale } from '$lib/utils/transitions';
  6. import { knowledge } from '$lib/stores';
  7. import Dropdown from '$lib/components/common/Dropdown.svelte';
  8. const i18n = getContext('i18n');
  9. const dispatch = createEventDispatcher();
  10. export let onClose: Function = () => {};
  11. let query = '';
  12. let items = [];
  13. let filteredItems = [];
  14. let fuse = null;
  15. $: if (fuse) {
  16. filteredItems = query
  17. ? fuse.search(query).map((e) => {
  18. return e.item;
  19. })
  20. : items;
  21. }
  22. onMount(() => {
  23. let legacy_documents = $knowledge.filter((item) => item?.meta?.document);
  24. let legacy_collections =
  25. legacy_documents.length > 0
  26. ? [
  27. {
  28. name: 'All Documents',
  29. legacy: true,
  30. type: 'collection',
  31. description: 'Deprecated (legacy collection), please create a new knowledge base.',
  32. title: $i18n.t('All Documents'),
  33. collection_names: legacy_documents.map((item) => item.id)
  34. },
  35. ...legacy_documents
  36. .reduce((a, item) => {
  37. return [...new Set([...a, ...(item?.meta?.tags ?? []).map((tag) => tag.name)])];
  38. }, [])
  39. .map((tag) => ({
  40. name: tag,
  41. legacy: true,
  42. type: 'collection',
  43. description: 'Deprecated (legacy collection), please create a new knowledge base.',
  44. collection_names: legacy_documents
  45. .filter((item) => (item?.meta?.tags ?? []).map((tag) => tag.name).includes(tag))
  46. .map((item) => item.id)
  47. }))
  48. ]
  49. : [];
  50. items = [...$knowledge, ...legacy_collections].map((item) => {
  51. return {
  52. ...item,
  53. ...(item?.legacy || item?.meta?.legacy || item?.meta?.document ? { legacy: true } : {}),
  54. type: item?.meta?.document ? 'document' : 'collection'
  55. };
  56. });
  57. fuse = new Fuse(items, {
  58. keys: ['name', 'description']
  59. });
  60. });
  61. </script>
  62. <Dropdown
  63. on:change={(e) => {
  64. if (e.detail === false) {
  65. onClose();
  66. query = '';
  67. }
  68. }}
  69. >
  70. <slot />
  71. <div slot="content">
  72. <DropdownMenu.Content
  73. class="w-full max-w-80 rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
  74. sideOffset={8}
  75. side="bottom"
  76. align="start"
  77. transition={flyAndScale}
  78. >
  79. <div class=" flex w-full space-x-2 py-0.5 px-2">
  80. <div class="flex flex-1">
  81. <div class=" self-center ml-1 mr-3">
  82. <svg
  83. xmlns="http://www.w3.org/2000/svg"
  84. viewBox="0 0 20 20"
  85. fill="currentColor"
  86. class="w-4 h-4"
  87. >
  88. <path
  89. fill-rule="evenodd"
  90. d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
  91. clip-rule="evenodd"
  92. />
  93. </svg>
  94. </div>
  95. <input
  96. class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
  97. bind:value={query}
  98. placeholder={$i18n.t('Search Knowledge')}
  99. />
  100. </div>
  101. </div>
  102. <hr class=" border-gray-50 dark:border-gray-700 my-1.5" />
  103. <div class="max-h-48 overflow-y-scroll">
  104. {#if filteredItems.length === 0}
  105. <div class="text-center text-sm text-gray-500 dark:text-gray-400">
  106. {$i18n.t('No knowledge found')}
  107. </div>
  108. {:else}
  109. {#each filteredItems as item}
  110. <DropdownMenu.Item
  111. class="flex gap-2.5 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
  112. on:click={() => {
  113. dispatch('select', item);
  114. }}
  115. >
  116. <div class="flex items-center">
  117. <div class="flex flex-col">
  118. <div class=" w-fit mb-0.5">
  119. {#if item.legacy}
  120. <div
  121. class="bg-gray-500/20 text-gray-700 dark:text-gray-200 rounded uppercase text-xs font-bold px-1"
  122. >
  123. Legacy
  124. </div>
  125. {:else if item?.meta?.document}
  126. <div
  127. class="bg-gray-500/20 text-gray-700 dark:text-gray-200 rounded uppercase text-xs font-bold px-1"
  128. >
  129. Document
  130. </div>
  131. {:else}
  132. <div
  133. class="bg-green-500/20 text-green-700 dark:text-green-200 rounded uppercase text-xs font-bold px-1"
  134. >
  135. Collection
  136. </div>
  137. {/if}
  138. </div>
  139. <div class="line-clamp-1 font-medium pr-0.5">
  140. {item.name}
  141. </div>
  142. </div>
  143. </div>
  144. </DropdownMenu.Item>
  145. {/each}
  146. {/if}
  147. </div>
  148. </DropdownMenu.Content>
  149. </div>
  150. </Dropdown>