Completions.svelte 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. <script lang="ts">
  2. import { toast } from 'svelte-sonner';
  3. import { goto } from '$app/navigation';
  4. import { onMount, tick, getContext } from 'svelte';
  5. import { WEBUI_BASE_URL } from '$lib/constants';
  6. import { WEBUI_NAME, config, user, models, settings, showSidebar } from '$lib/stores';
  7. import { chatCompletion } from '$lib/apis/openai';
  8. import { splitStream } from '$lib/utils';
  9. import Selector from '$lib/components/chat/ModelSelector/Selector.svelte';
  10. import MenuLines from '../icons/MenuLines.svelte';
  11. const i18n = getContext('i18n');
  12. let loaded = false;
  13. let text = '';
  14. let selectedModelId = '';
  15. let loading = false;
  16. let stopResponseFlag = false;
  17. let textCompletionAreaElement: HTMLTextAreaElement;
  18. const scrollToBottom = () => {
  19. const element = textCompletionAreaElement;
  20. if (element) {
  21. element.scrollTop = element?.scrollHeight;
  22. }
  23. };
  24. const stopResponse = () => {
  25. stopResponseFlag = true;
  26. console.log('stopResponse');
  27. };
  28. const textCompletionHandler = async () => {
  29. const model = $models.find((model) => model.id === selectedModelId);
  30. const [res, controller] = await chatCompletion(
  31. localStorage.token,
  32. {
  33. model: model.id,
  34. stream: true,
  35. messages: [
  36. {
  37. role: 'assistant',
  38. content: text
  39. }
  40. ]
  41. },
  42. `${WEBUI_BASE_URL}/api`
  43. );
  44. if (res && res.ok) {
  45. const reader = res.body
  46. .pipeThrough(new TextDecoderStream())
  47. .pipeThrough(splitStream('\n'))
  48. .getReader();
  49. while (true) {
  50. const { value, done } = await reader.read();
  51. if (done || stopResponseFlag) {
  52. if (stopResponseFlag) {
  53. controller.abort('User: Stop Response');
  54. }
  55. break;
  56. }
  57. try {
  58. let lines = value.split('\n');
  59. for (const line of lines) {
  60. if (line !== '') {
  61. if (line.includes('[DONE]')) {
  62. console.log('done');
  63. } else {
  64. let data = JSON.parse(line.replace(/^data: /, ''));
  65. console.log(data);
  66. text += data.choices[0].delta.content ?? '';
  67. }
  68. }
  69. }
  70. } catch (error) {
  71. console.log(error);
  72. }
  73. scrollToBottom();
  74. }
  75. }
  76. };
  77. const submitHandler = async () => {
  78. if (selectedModelId) {
  79. loading = true;
  80. await textCompletionHandler();
  81. loading = false;
  82. stopResponseFlag = false;
  83. }
  84. };
  85. onMount(async () => {
  86. if ($user?.role !== 'admin') {
  87. await goto('/');
  88. }
  89. if ($settings?.models) {
  90. selectedModelId = $settings?.models[0];
  91. } else if ($config?.default_models) {
  92. selectedModelId = $config?.default_models.split(',')[0];
  93. } else {
  94. selectedModelId = '';
  95. }
  96. loaded = true;
  97. });
  98. </script>
  99. <div class=" flex flex-col justify-between w-full overflow-y-auto h-full">
  100. <div class="mx-auto w-full md:px-0 h-full">
  101. <div class=" flex flex-col h-full px-4">
  102. <div class="flex flex-col justify-between mb-1 gap-1">
  103. <div class="flex flex-col gap-1 w-full">
  104. <div class="flex w-full">
  105. <div class="overflow-hidden w-full">
  106. <div class="max-w-full">
  107. <Selector
  108. placeholder={$i18n.t('Select a model')}
  109. items={$models.map((model) => ({
  110. value: model.id,
  111. label: model.name,
  112. model: model
  113. }))}
  114. bind:value={selectedModelId}
  115. />
  116. </div>
  117. </div>
  118. </div>
  119. </div>
  120. </div>
  121. <div
  122. class=" pt-0.5 pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0"
  123. id="messages-container"
  124. >
  125. <div class=" h-full w-full flex flex-col">
  126. <div class="flex-1">
  127. <textarea
  128. id="text-completion-textarea"
  129. bind:this={textCompletionAreaElement}
  130. class="w-full h-full p-3 bg-transparent border border-gray-50 dark:border-gray-850 outline-none resize-none rounded-lg text-sm"
  131. bind:value={text}
  132. placeholder={$i18n.t("You're a helpful assistant.")}
  133. />
  134. </div>
  135. </div>
  136. </div>
  137. <div class="pb-3 flex justify-end">
  138. {#if !loading}
  139. <button
  140. class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
  141. on:click={() => {
  142. submitHandler();
  143. }}
  144. >
  145. {$i18n.t('Run')}
  146. </button>
  147. {:else}
  148. <button
  149. class="px-3 py-1.5 text-sm font-medium bg-gray-300 text-black transition rounded-full"
  150. on:click={() => {
  151. stopResponse();
  152. }}
  153. >
  154. {$i18n.t('Cancel')}
  155. </button>
  156. {/if}
  157. </div>
  158. </div>
  159. </div>
  160. </div>
  161. <style>
  162. .scrollbar-hidden::-webkit-scrollbar {
  163. display: none; /* for Chrome, Safari and Opera */
  164. }
  165. .scrollbar-hidden {
  166. -ms-overflow-style: none; /* IE and Edge */
  167. scrollbar-width: none; /* Firefox */
  168. }
  169. </style>