Models.svelte 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. <script lang="ts">
  2. import { createEventDispatcher } from 'svelte';
  3. import { generatePrompt } from '$lib/apis/ollama';
  4. import { models } from '$lib/stores';
  5. import { splitStream } from '$lib/utils';
  6. import { tick, getContext } from 'svelte';
  7. import { toast } from 'svelte-sonner';
  8. const i18n = getContext('i18n');
  9. const dispatch = createEventDispatcher();
  10. export let prompt = '';
  11. export let user = null;
  12. export let chatInputPlaceholder = '';
  13. export let messages = [];
  14. let selectedIdx = 0;
  15. let filteredModels = [];
  16. $: filteredModels = $models
  17. .filter((p) =>
  18. p.name.toLowerCase().includes(prompt.toLowerCase().split(' ')?.at(0)?.substring(1) ?? '')
  19. )
  20. .sort((a, b) => a.name.localeCompare(b.name));
  21. $: if (prompt) {
  22. selectedIdx = 0;
  23. }
  24. export const selectUp = () => {
  25. selectedIdx = Math.max(0, selectedIdx - 1);
  26. };
  27. export const selectDown = () => {
  28. selectedIdx = Math.min(selectedIdx + 1, filteredModels.length - 1);
  29. };
  30. const confirmSelect = async (model) => {
  31. prompt = '';
  32. dispatch('select', model);
  33. };
  34. const confirmSelectCollaborativeChat = async (model) => {
  35. // dispatch('select', model);
  36. prompt = '';
  37. user = JSON.parse(JSON.stringify(model.name));
  38. await tick();
  39. chatInputPlaceholder = $i18n.t('{{modelName}} is thinking...', { modelName: model.name });
  40. const chatInputElement = document.getElementById('chat-textarea');
  41. await tick();
  42. chatInputElement?.focus();
  43. await tick();
  44. const convoText = messages.reduce((a, message, i, arr) => {
  45. return `${a}### ${message.role.toUpperCase()}\n${message.content}\n\n`;
  46. }, '');
  47. const res = await generatePrompt(localStorage.token, model.name, convoText);
  48. if (res && res.ok) {
  49. const reader = res.body
  50. .pipeThrough(new TextDecoderStream())
  51. .pipeThrough(splitStream('\n'))
  52. .getReader();
  53. while (true) {
  54. const { value, done } = await reader.read();
  55. if (done) {
  56. break;
  57. }
  58. try {
  59. let lines = value.split('\n');
  60. for (const line of lines) {
  61. if (line !== '') {
  62. console.log(line);
  63. let data = JSON.parse(line);
  64. if ('detail' in data) {
  65. throw data;
  66. }
  67. if ('id' in data) {
  68. console.log(data);
  69. } else {
  70. if (data.done == false) {
  71. if (prompt == '' && data.response == '\n') {
  72. continue;
  73. } else {
  74. prompt += data.response;
  75. console.log(data.response);
  76. chatInputElement.scrollTop = chatInputElement.scrollHeight;
  77. await tick();
  78. }
  79. }
  80. }
  81. }
  82. }
  83. } catch (error) {
  84. console.log(error);
  85. if ('detail' in error) {
  86. toast.error(error.detail);
  87. }
  88. break;
  89. }
  90. }
  91. } else {
  92. if (res !== null) {
  93. const error = await res.json();
  94. console.log(error);
  95. if ('detail' in error) {
  96. toast.error(error.detail);
  97. } else {
  98. toast.error(error.error);
  99. }
  100. } else {
  101. toast.error(
  102. $i18n.t('Uh-oh! There was an issue connecting to {{provider}}.', { provider: 'llama' })
  103. );
  104. }
  105. }
  106. chatInputPlaceholder = '';
  107. console.log(user);
  108. };
  109. </script>
  110. {#if prompt.charAt(0) === '@'}
  111. {#if filteredModels.length > 0}
  112. <div class="pl-1 pr-12 mb-3 text-left w-full absolute bottom-0 left-0 right-0 z-10">
  113. <div class="flex w-full dark:border dark:border-gray-850 rounded-lg">
  114. <div class=" bg-gray-50 dark:bg-gray-850 w-10 rounded-l-lg text-center">
  115. <div class=" text-lg font-semibold mt-2">@</div>
  116. </div>
  117. <div
  118. class="max-h-60 flex flex-col w-full rounded-r-lg bg-white dark:bg-gray-900 dark:text-gray-100"
  119. >
  120. <div class="m-1 overflow-y-auto p-1 rounded-r-lg space-y-0.5 scrollbar-hidden">
  121. {#each filteredModels as model, modelIdx}
  122. <button
  123. class="px-3 py-1.5 rounded-xl w-full text-left {modelIdx === selectedIdx
  124. ? 'bg-gray-50 dark:bg-gray-850 selected-command-option-button'
  125. : ''}"
  126. type="button"
  127. on:click={() => {
  128. confirmSelect(model);
  129. }}
  130. on:mousemove={() => {
  131. selectedIdx = modelIdx;
  132. }}
  133. on:focus={() => {}}
  134. >
  135. <div class="flex font-medium text-black dark:text-gray-100 line-clamp-1">
  136. <img
  137. src={model?.info?.meta?.profile_image_url ?? '/static/favicon.png'}
  138. alt={model?.name ?? model.id}
  139. class="rounded-full size-6 items-center mr-2"
  140. />
  141. {model.name}
  142. </div>
  143. <!-- <div class=" text-xs text-gray-600 line-clamp-1">
  144. {doc.title}
  145. </div> -->
  146. </button>
  147. {/each}
  148. </div>
  149. </div>
  150. </div>
  151. </div>
  152. {/if}
  153. {/if}