Models.svelte 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  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. const i18n = getContext('i18n');
  7. const dispatch = createEventDispatcher();
  8. export let command = '';
  9. let selectedIdx = 0;
  10. let filteredItems = [];
  11. let fuse = new Fuse(
  12. $models
  13. .filter((model) => !model?.info?.meta?.hidden)
  14. .map((model) => {
  15. const _item = {
  16. ...model,
  17. modelName: model?.name,
  18. tags: model?.info?.meta?.tags?.map((tag) => tag.name).join(' '),
  19. desc: model?.info?.meta?.description
  20. };
  21. return _item;
  22. }),
  23. {
  24. keys: ['value', 'tags', 'modelName'],
  25. threshold: 0.3
  26. }
  27. );
  28. $: filteredItems = command.slice(1)
  29. ? fuse.search(command).map((e) => {
  30. return e.item;
  31. })
  32. : $models.filter((model) => !model?.info?.meta?.hidden);
  33. $: if (command) {
  34. selectedIdx = 0;
  35. }
  36. export const selectUp = () => {
  37. selectedIdx = Math.max(0, selectedIdx - 1);
  38. };
  39. export const selectDown = () => {
  40. selectedIdx = Math.min(selectedIdx + 1, filteredItems.length - 1);
  41. };
  42. let container;
  43. let adjustHeightDebounce;
  44. const adjustHeight = () => {
  45. if (container) {
  46. if (adjustHeightDebounce) {
  47. clearTimeout(adjustHeightDebounce);
  48. }
  49. adjustHeightDebounce = setTimeout(() => {
  50. if (!container) return;
  51. // Ensure the container is visible before adjusting height
  52. const rect = container.getBoundingClientRect();
  53. container.style.maxHeight = Math.max(Math.min(240, rect.bottom - 100), 100) + 'px';
  54. }, 100);
  55. }
  56. };
  57. const confirmSelect = async (model) => {
  58. command = '';
  59. dispatch('select', model);
  60. };
  61. onMount(async () => {
  62. window.addEventListener('resize', adjustHeight);
  63. adjustHeight();
  64. await tick();
  65. const chatInputElement = document.getElementById('chat-input');
  66. await tick();
  67. chatInputElement?.focus();
  68. await tick();
  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 ?? '/static/favicon.png'}
  103. alt={model?.name ?? model.id}
  104. class="rounded-full size-6 items-center mr-2"
  105. />
  106. {model.name}
  107. </div>
  108. </button>
  109. {/each}
  110. </div>
  111. </div>
  112. </div>
  113. </div>
  114. {/if}