Models.svelte 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. <script lang="ts">
  2. import Fuse from 'fuse.js';
  3. import { createEventDispatcher, onDestroy, onMount } from 'svelte';
  4. import { tick, getContext } from 'svelte';
  5. import { models } from '$lib/stores';
  6. import { WEBUI_BASE_URL } from '$lib/constants';
  7. const i18n = getContext('i18n');
  8. export let command = '';
  9. export let onSelect = (e) => {};
  10. let selectedIdx = 0;
  11. let filteredItems = [];
  12. let fuse = new Fuse(
  13. $models
  14. .filter((model) => !model?.info?.meta?.hidden)
  15. .map((model) => {
  16. const _item = {
  17. ...model,
  18. modelName: model?.name,
  19. tags: model?.info?.meta?.tags?.map((tag) => tag.name).join(' '),
  20. desc: model?.info?.meta?.description
  21. };
  22. return _item;
  23. }),
  24. {
  25. keys: ['value', 'tags', 'modelName'],
  26. threshold: 0.5
  27. }
  28. );
  29. $: filteredItems = command.slice(1)
  30. ? fuse.search(command.slice(1)).map((e) => {
  31. return e.item;
  32. })
  33. : $models.filter((model) => !model?.info?.meta?.hidden);
  34. $: if (command) {
  35. selectedIdx = 0;
  36. }
  37. export const selectUp = () => {
  38. selectedIdx = Math.max(0, selectedIdx - 1);
  39. };
  40. export const selectDown = () => {
  41. selectedIdx = Math.min(selectedIdx + 1, filteredItems.length - 1);
  42. };
  43. let container;
  44. let adjustHeightDebounce;
  45. const adjustHeight = () => {
  46. if (container) {
  47. if (adjustHeightDebounce) {
  48. clearTimeout(adjustHeightDebounce);
  49. }
  50. adjustHeightDebounce = setTimeout(() => {
  51. if (!container) return;
  52. // Ensure the container is visible before adjusting height
  53. const rect = container.getBoundingClientRect();
  54. container.style.maxHeight = Math.max(Math.min(240, rect.bottom - 100), 100) + 'px';
  55. }, 100);
  56. }
  57. };
  58. const confirmSelect = async (model) => {
  59. onSelect({ type: 'model', data: model });
  60. };
  61. onMount(async () => {
  62. window.addEventListener('resize', adjustHeight);
  63. await tick();
  64. const chatInputElement = document.getElementById('chat-input');
  65. await tick();
  66. chatInputElement?.focus();
  67. await tick();
  68. adjustHeight();
  69. });
  70. onDestroy(() => {
  71. window.removeEventListener('resize', adjustHeight);
  72. });
  73. </script>
  74. {#if filteredItems.length > 0}
  75. <div
  76. id="commands-container"
  77. class="px-2 mb-2 text-left w-full absolute bottom-0 left-0 right-0 z-10"
  78. >
  79. <div class="flex w-full rounded-xl border border-gray-100 dark:border-gray-850">
  80. <div class="flex flex-col w-full rounded-xl bg-white dark:bg-gray-900 dark:text-gray-100">
  81. <div
  82. class="m-1 overflow-y-auto p-1 rounded-r-lg space-y-0.5 scrollbar-hidden max-h-60"
  83. id="command-options-container"
  84. bind:this={container}
  85. >
  86. {#each filteredItems as model, modelIdx}
  87. <button
  88. class="px-3 py-1.5 rounded-xl w-full text-left {modelIdx === selectedIdx
  89. ? 'bg-gray-50 dark:bg-gray-850 selected-command-option-button'
  90. : ''}"
  91. type="button"
  92. on:click={() => {
  93. confirmSelect(model);
  94. }}
  95. on:mousemove={() => {
  96. selectedIdx = modelIdx;
  97. }}
  98. on:focus={() => {}}
  99. >
  100. <div class="flex font-medium text-black dark:text-gray-100 line-clamp-1">
  101. <img
  102. src={model?.info?.meta?.profile_image_url ??
  103. `${WEBUI_BASE_URL}/static/favicon.png`}
  104. alt={model?.name ?? model.id}
  105. class="rounded-full size-6 items-center mr-2"
  106. />
  107. {model.name}
  108. </div>
  109. </button>
  110. {/each}
  111. </div>
  112. </div>
  113. </div>
  114. </div>
  115. {/if}