AddDocModal.svelte 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. <script lang="ts">
  2. import { toast } from 'svelte-sonner';
  3. import dayjs from 'dayjs';
  4. import { onMount, getContext } from 'svelte';
  5. import { createNewDoc, getDocs, tagDocByName, updateDocByName } from '$lib/apis/documents';
  6. import Modal from '../common/Modal.svelte';
  7. import { documents } from '$lib/stores';
  8. import TagInput from '../common/Tags/TagInput.svelte';
  9. import Tags from '../common/Tags.svelte';
  10. import { addTagById } from '$lib/apis/chats';
  11. import { uploadDocToVectorDB } from '$lib/apis/rag';
  12. import { transformFileName } from '$lib/utils';
  13. import { SUPPORTED_FILE_EXTENSIONS, SUPPORTED_FILE_TYPE } from '$lib/constants';
  14. const i18n = getContext('i18n');
  15. export let show = false;
  16. let uploadDocInputElement: HTMLInputElement;
  17. let inputFiles;
  18. let tags = [];
  19. let doc = {
  20. name: '',
  21. title: '',
  22. content: null
  23. };
  24. const uploadDoc = async (file) => {
  25. const res = await uploadDocToVectorDB(localStorage.token, '', file).catch((error) => {
  26. toast.error(error);
  27. return null;
  28. });
  29. if (res) {
  30. await createNewDoc(
  31. localStorage.token,
  32. res.collection_name,
  33. res.filename,
  34. transformFileName(res.filename),
  35. res.filename,
  36. tags.length > 0
  37. ? {
  38. tags: tags
  39. }
  40. : null
  41. ).catch((error) => {
  42. toast.error(error);
  43. return null;
  44. });
  45. await documents.set(await getDocs(localStorage.token));
  46. }
  47. };
  48. const submitHandler = async () => {
  49. if (inputFiles && inputFiles.length > 0) {
  50. for (const file of inputFiles) {
  51. console.log(file, file.name.split('.').at(-1));
  52. if (
  53. SUPPORTED_FILE_TYPE.includes(file['type']) ||
  54. SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
  55. ) {
  56. uploadDoc(file);
  57. } else {
  58. toast.error(
  59. `Unknown File Type '${file['type']}', but accepting and treating as plain text`
  60. );
  61. uploadDoc(file);
  62. }
  63. }
  64. inputFiles = null;
  65. uploadDocInputElement.value = '';
  66. } else {
  67. toast.error($i18n.t(`File not found.`));
  68. }
  69. show = false;
  70. documents.set(await getDocs(localStorage.token));
  71. };
  72. const addTagHandler = async (tagName) => {
  73. if (!tags.find((tag) => tag.name === tagName) && tagName !== '') {
  74. tags = [...tags, { name: tagName }];
  75. } else {
  76. console.log('tag already exists');
  77. }
  78. };
  79. const deleteTagHandler = async (tagName) => {
  80. tags = tags.filter((tag) => tag.name !== tagName);
  81. };
  82. onMount(() => {});
  83. </script>
  84. <Modal size="sm" bind:show>
  85. <div>
  86. <div class=" flex justify-between dark:text-gray-300 px-5 py-4">
  87. <div class=" text-lg font-medium self-center">{$i18n.t('Add Docs')}</div>
  88. <button
  89. class="self-center"
  90. on:click={() => {
  91. show = false;
  92. }}
  93. >
  94. <svg
  95. xmlns="http://www.w3.org/2000/svg"
  96. viewBox="0 0 20 20"
  97. fill="currentColor"
  98. class="w-5 h-5"
  99. >
  100. <path
  101. d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
  102. />
  103. </svg>
  104. </button>
  105. </div>
  106. <hr class=" dark:border-gray-800" />
  107. <div class="flex flex-col md:flex-row w-full px-5 py-4 md:space-x-4 dark:text-gray-200">
  108. <div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
  109. <form
  110. class="flex flex-col w-full"
  111. on:submit|preventDefault={() => {
  112. submitHandler();
  113. }}
  114. >
  115. <div class="mb-3 w-full">
  116. <input
  117. id="upload-doc-input"
  118. bind:this={uploadDocInputElement}
  119. hidden
  120. bind:files={inputFiles}
  121. type="file"
  122. multiple
  123. />
  124. <button
  125. class="w-full text-sm font-medium py-3 bg-gray-100 hover:bg-gray-200 dark:bg-gray-850 dark:hover:bg-gray-800 text-center rounded-xl"
  126. type="button"
  127. on:click={() => {
  128. uploadDocInputElement.click();
  129. }}
  130. >
  131. {#if inputFiles}
  132. {inputFiles.length > 0 ? `${inputFiles.length}` : ''} document(s) selected.
  133. {:else}
  134. {$i18n.t('Click here to select documents.')}
  135. {/if}
  136. </button>
  137. </div>
  138. <div class=" flex flex-col space-y-1.5">
  139. <div class="flex flex-col w-full">
  140. <div class=" mb-1.5 text-xs text-gray-500">{$i18n.t('Tags')}</div>
  141. <Tags {tags} addTag={addTagHandler} deleteTag={deleteTagHandler} />
  142. </div>
  143. </div>
  144. <div class="flex justify-end pt-5 text-sm font-medium">
  145. <button
  146. class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
  147. type="submit"
  148. >
  149. {$i18n.t('Save')}
  150. </button>
  151. </div>
  152. </form>
  153. </div>
  154. </div>
  155. </div>
  156. </Modal>
  157. <style>
  158. input::-webkit-outer-spin-button,
  159. input::-webkit-inner-spin-button {
  160. /* display: none; <- Crashes Chrome on hover */
  161. -webkit-appearance: none;
  162. margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
  163. }
  164. .tabs::-webkit-scrollbar {
  165. display: none; /* for Chrome, Safari and Opera */
  166. }
  167. .tabs {
  168. -ms-overflow-style: none; /* IE and Edge */
  169. scrollbar-width: none; /* Firefox */
  170. }
  171. input[type='number'] {
  172. -moz-appearance: textfield; /* Firefox */
  173. }
  174. </style>