Suggestions.svelte 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. <script lang="ts">
  2. import Fuse from 'fuse.js';
  3. import Bolt from '$lib/components/icons/Bolt.svelte';
  4. import { onMount, getContext, createEventDispatcher } from 'svelte';
  5. import { WEBUI_NAME } from '$lib/stores';
  6. import { WEBUI_VERSION } from '$lib/constants';
  7. const i18n = getContext('i18n');
  8. const dispatch = createEventDispatcher();
  9. export let suggestionPrompts = [];
  10. export let className = '';
  11. export let inputValue = '';
  12. let sortedPrompts = [];
  13. const fuseOptions = {
  14. keys: ['content', 'title'],
  15. threshold: 0.5
  16. };
  17. let fuse;
  18. let filteredPrompts = [];
  19. // Initialize Fuse
  20. $: fuse = new Fuse(sortedPrompts, fuseOptions);
  21. // Update the filteredPrompts if inputValue changes
  22. // Only increase version if something wirklich geändert hat
  23. $: getFilteredPrompts(inputValue);
  24. // Helper function to check if arrays are the same
  25. // (based on unique IDs oder content)
  26. function arraysEqual(a, b) {
  27. if (a.length !== b.length) return false;
  28. for (let i = 0; i < a.length; i++) {
  29. if ((a[i].id ?? a[i].content) !== (b[i].id ?? b[i].content)) {
  30. return false;
  31. }
  32. }
  33. return true;
  34. }
  35. const getFilteredPrompts = (inputValue) => {
  36. const newFilteredPrompts = inputValue.trim()
  37. ? fuse.search(inputValue.trim()).map((result) => result.item)
  38. : sortedPrompts;
  39. // Compare with the oldFilteredPrompts
  40. // If there's a difference, update array + version
  41. if (!arraysEqual(filteredPrompts, newFilteredPrompts)) {
  42. filteredPrompts = newFilteredPrompts;
  43. }
  44. };
  45. $: if (suggestionPrompts) {
  46. sortedPrompts = [...(suggestionPrompts ?? [])].sort(() => Math.random() - 0.5);
  47. getFilteredPrompts(inputValue);
  48. }
  49. </script>
  50. <div class="mb-1 flex gap-1 text-xs font-medium items-center text-gray-400 dark:text-gray-600">
  51. {#if filteredPrompts.length > 0}
  52. <Bolt />
  53. {$i18n.t('Suggested')}
  54. {:else}
  55. <!-- Keine Vorschläge -->
  56. <div
  57. class="flex w-full text-center items-center justify-center self-start text-gray-400 dark:text-gray-600"
  58. >
  59. {$WEBUI_NAME} ‧ v{WEBUI_VERSION}
  60. </div>
  61. {/if}
  62. </div>
  63. <div class="h-40 overflow-auto scrollbar-none {className} items-start">
  64. {#if filteredPrompts.length > 0}
  65. {#each filteredPrompts as prompt, idx (prompt.id || prompt.content)}
  66. <button
  67. class="waterfall flex flex-col flex-1 shrink-0 w-full justify-between
  68. px-3 py-2 rounded-xl bg-transparent hover:bg-black/5
  69. dark:hover:bg-white/5 transition group"
  70. style="animation-delay: {idx * 60}ms"
  71. on:click={() => dispatch('select', prompt.content)}
  72. >
  73. <div class="flex flex-col text-left">
  74. {#if prompt.title && prompt.title[0] !== ''}
  75. <div
  76. class="font-medium dark:text-gray-300 dark:group-hover:text-gray-200 transition line-clamp-1"
  77. >
  78. {prompt.title[0]}
  79. </div>
  80. <div class="text-xs text-gray-500 font-normal line-clamp-1">
  81. {prompt.title[1]}
  82. </div>
  83. {:else}
  84. <div
  85. class="font-medium dark:text-gray-300 dark:group-hover:text-gray-200 transition line-clamp-1"
  86. >
  87. {prompt.content}
  88. </div>
  89. <div class="text-xs text-gray-500 font-normal line-clamp-1">{$i18n.t('Prompt')}</div>
  90. {/if}
  91. </div>
  92. </button>
  93. {/each}
  94. {/if}
  95. </div>
  96. <style>
  97. /* Waterfall animation for the suggestions */
  98. @keyframes fadeInUp {
  99. 0% {
  100. opacity: 0;
  101. transform: translateY(20px);
  102. }
  103. 100% {
  104. opacity: 1;
  105. transform: translateY(0);
  106. }
  107. }
  108. .waterfall {
  109. opacity: 0;
  110. animation-name: fadeInUp;
  111. animation-duration: 200ms;
  112. animation-fill-mode: forwards;
  113. animation-timing-function: ease;
  114. }
  115. </style>