Models.svelte 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. <script lang="ts">
  2. import Fuse from 'fuse.js';
  3. import { createEventDispatcher, 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. const confirmSelect = async (model) => {
  43. command = '';
  44. dispatch('select', model);
  45. };
  46. onMount(async () => {
  47. await tick();
  48. const chatInputElement = document.getElementById('chat-input');
  49. await tick();
  50. chatInputElement?.focus();
  51. await tick();
  52. });
  53. </script>
  54. {#if filteredItems.length > 0}
  55. <div
  56. id="commands-container"
  57. class="px-2 mb-2 text-left w-full absolute bottom-0 left-0 right-0 z-10"
  58. >
  59. <div class="flex w-full rounded-xl border border-gray-100 dark:border-gray-850">
  60. <div
  61. class="max-h-60 flex flex-col w-full rounded-xl bg-white dark:bg-gray-900 dark:text-gray-100"
  62. >
  63. <div class="m-1 overflow-y-auto p-1 rounded-r-lg space-y-0.5 scrollbar-hidden">
  64. {#each filteredItems as model, modelIdx}
  65. <button
  66. class="px-3 py-1.5 rounded-xl w-full text-left {modelIdx === selectedIdx
  67. ? 'bg-gray-50 dark:bg-gray-850 selected-command-option-button'
  68. : ''}"
  69. type="button"
  70. on:click={() => {
  71. confirmSelect(model);
  72. }}
  73. on:mousemove={() => {
  74. selectedIdx = modelIdx;
  75. }}
  76. on:focus={() => {}}
  77. >
  78. <div class="flex font-medium text-black dark:text-gray-100 line-clamp-1">
  79. <img
  80. src={model?.info?.meta?.profile_image_url ?? '/static/favicon.png'}
  81. alt={model?.name ?? model.id}
  82. class="rounded-full size-6 items-center mr-2"
  83. />
  84. {model.name}
  85. </div>
  86. </button>
  87. {/each}
  88. </div>
  89. </div>
  90. </div>
  91. </div>
  92. {/if}