AddDocModal.svelte 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  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 pt-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. <div class="flex flex-col md:flex-row w-full px-5 py-4 md:space-x-4 dark:text-gray-200">
  107. <div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
  108. <form
  109. class="flex flex-col w-full"
  110. on:submit|preventDefault={() => {
  111. submitHandler();
  112. }}
  113. >
  114. <div class="mb-3 w-full">
  115. <input
  116. id="upload-doc-input"
  117. bind:this={uploadDocInputElement}
  118. hidden
  119. bind:files={inputFiles}
  120. type="file"
  121. multiple
  122. />
  123. <button
  124. 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"
  125. type="button"
  126. on:click={() => {
  127. uploadDocInputElement.click();
  128. }}
  129. >
  130. {#if inputFiles}
  131. {inputFiles.length > 0 ? `${inputFiles.length}` : ''} document(s) selected.
  132. {:else}
  133. {$i18n.t('Click here to select documents.')}
  134. {/if}
  135. </button>
  136. </div>
  137. <div class=" flex flex-col space-y-1.5">
  138. <div class="flex flex-col w-full">
  139. <div class=" mb-1.5 text-xs text-gray-500">{$i18n.t('Tags')}</div>
  140. <Tags {tags} addTag={addTagHandler} deleteTag={deleteTagHandler} />
  141. </div>
  142. </div>
  143. <div class="flex justify-end pt-5 text-sm font-medium">
  144. <button
  145. class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
  146. type="submit"
  147. >
  148. {$i18n.t('Save')}
  149. </button>
  150. </div>
  151. </form>
  152. </div>
  153. </div>
  154. </div>
  155. </Modal>
  156. <style>
  157. input::-webkit-outer-spin-button,
  158. input::-webkit-inner-spin-button {
  159. /* display: none; <- Crashes Chrome on hover */
  160. -webkit-appearance: none;
  161. margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
  162. }
  163. .tabs::-webkit-scrollbar {
  164. display: none; /* for Chrome, Safari and Opera */
  165. }
  166. .tabs {
  167. -ms-overflow-style: none; /* IE and Edge */
  168. scrollbar-width: none; /* Firefox */
  169. }
  170. input[type='number'] {
  171. -moz-appearance: textfield; /* Firefox */
  172. }
  173. </style>