General.svelte 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. <script lang="ts">
  2. import { getDocs } from '$lib/apis/documents';
  3. import {
  4. getRAGConfig,
  5. updateRAGConfig,
  6. getQuerySettings,
  7. scanDocs,
  8. updateQuerySettings,
  9. resetVectorDB,
  10. getEmbeddingModel,
  11. updateEmbeddingModel
  12. } from '$lib/apis/rag';
  13. import { documents } from '$lib/stores';
  14. import { onMount, getContext } from 'svelte';
  15. import { toast } from 'svelte-sonner';
  16. import Tooltip from '$lib/components/common/Tooltip.svelte';
  17. const i18n = getContext('i18n');
  18. export let saveHandler: Function;
  19. let loading = false;
  20. let loading1 = false;
  21. let showResetConfirm = false;
  22. let chunkSize = 0;
  23. let chunkOverlap = 0;
  24. let pdfExtractImages = true;
  25. let querySettings = {
  26. template: '',
  27. k: 4
  28. };
  29. let embeddingModel = {
  30. embedding_model: ''
  31. };
  32. const scanHandler = async () => {
  33. loading = true;
  34. const res = await scanDocs(localStorage.token);
  35. loading = false;
  36. if (res) {
  37. await documents.set(await getDocs(localStorage.token));
  38. toast.success($i18n.t('Scan complete!'));
  39. }
  40. };
  41. const embeddingModelUpdateHandler = async () => {
  42. if ((embeddingModel.embedding_model.split("/").length -1) > 1) {
  43. toast.error($i18n.t('Model filesystem path detected. Model shortname is required for update, cannot continue.', ));
  44. return;
  45. }
  46. console.log('Update embedding model attempt:', embeddingModel.embedding_model);
  47. loading1 = true;
  48. const res = await updateEmbeddingModel(localStorage.token, embeddingModel);
  49. loading1 = false;
  50. if (res) {
  51. console.log('embeddingModelUpdateHandler:', res);
  52. if (res.status === true) {
  53. toast.success(
  54. $i18n.t('Model {{embedding_model}} update complete!', res),
  55. {
  56. duration: 1000 * 10,
  57. }
  58. );
  59. } else {
  60. toast.error(
  61. $i18n.t('Model {{embedding_model}} update failed or not required!', res),
  62. {
  63. duration: 1000 * 10,
  64. }
  65. );
  66. }
  67. }
  68. };
  69. const submitHandler = async () => {
  70. const res = await updateRAGConfig(localStorage.token, {
  71. pdf_extract_images: pdfExtractImages,
  72. chunk: {
  73. chunk_overlap: chunkOverlap,
  74. chunk_size: chunkSize
  75. }
  76. });
  77. querySettings = await updateQuerySettings(localStorage.token, querySettings);
  78. };
  79. onMount(async () => {
  80. const res = await getRAGConfig(localStorage.token);
  81. if (res) {
  82. pdfExtractImages = res.pdf_extract_images;
  83. chunkSize = res.chunk.chunk_size;
  84. chunkOverlap = res.chunk.chunk_overlap;
  85. }
  86. embeddingModel = await getEmbeddingModel(localStorage.token);
  87. querySettings = await getQuerySettings(localStorage.token);
  88. });
  89. </script>
  90. <form
  91. class="flex flex-col h-full justify-between space-y-3 text-sm"
  92. on:submit|preventDefault={() => {
  93. submitHandler();
  94. saveHandler();
  95. }}
  96. >
  97. <div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
  98. <div>
  99. <div class=" mb-2 text-sm font-medium">{$i18n.t('General Settings')}</div>
  100. <div class=" flex w-full justify-between">
  101. <div class=" self-center text-xs font-medium">
  102. {$i18n.t('Scan for documents from {{path}}', { path: '/data/docs' })}
  103. </div>
  104. <button
  105. class=" self-center text-xs p-1 px-3 bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 rounded flex flex-row space-x-1 items-center {loading
  106. ? ' cursor-not-allowed'
  107. : ''}"
  108. on:click={() => {
  109. scanHandler();
  110. console.log('check');
  111. }}
  112. type="button"
  113. disabled={loading}
  114. >
  115. <div class="self-center font-medium">{$i18n.t('Scan')}</div>
  116. <!-- <svg
  117. xmlns="http://www.w3.org/2000/svg"
  118. viewBox="0 0 16 16"
  119. fill="currentColor"
  120. class="w-3 h-3"
  121. >
  122. <path
  123. fill-rule="evenodd"
  124. d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.932.75.75 0 0 1-1.3-.75 6 6 0 0 1 9.44-1.242l.842.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44 1.241l-.84-.84v1.371a.75.75 0 0 1-1.5 0V9.591a.75.75 0 0 1 .75-.75H5.35a.75.75 0 0 1 0 1.5H3.98l.841.841a4.5 4.5 0 0 0 7.08-.932.75.75 0 0 1 1.025-.273Z"
  125. clip-rule="evenodd"
  126. />
  127. </svg> -->
  128. {#if loading}
  129. <div class="ml-3 self-center">
  130. <svg
  131. class=" w-3 h-3"
  132. viewBox="0 0 24 24"
  133. fill="currentColor"
  134. xmlns="http://www.w3.org/2000/svg"
  135. ><style>
  136. .spinner_ajPY {
  137. transform-origin: center;
  138. animation: spinner_AtaB 0.75s infinite linear;
  139. }
  140. @keyframes spinner_AtaB {
  141. 100% {
  142. transform: rotate(360deg);
  143. }
  144. }
  145. </style><path
  146. d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
  147. opacity=".25"
  148. /><path
  149. d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
  150. class="spinner_ajPY"
  151. /></svg
  152. >
  153. </div>
  154. {/if}
  155. </button>
  156. </div>
  157. </div>
  158. <hr class=" dark:border-gray-700" />
  159. <div>
  160. <div class=" flex w-full justify-between">
  161. <Tooltip content={
  162. $i18n.t('Embedding model: {{embedding_model}}', embeddingModel)
  163. }>
  164. <div class=" self-center text-xs font-medium">
  165. {$i18n.t('Update embedding model {{embedding_model}}', { embedding_model: embeddingModel.embedding_model.slice(-40) })}
  166. </div>
  167. </Tooltip>
  168. <Tooltip content={
  169. $i18n.t('Understand that updating or changing your embedding model requires reset of the vector database and re-import of all documents. You have been warned!')
  170. }>
  171. <button
  172. class=" self-center text-xs p-1 px-3 bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 rounded flex flex-row space-x-1 items-center {loading1
  173. ? ' cursor-not-allowed'
  174. : ''}"
  175. on:click={() => {
  176. embeddingModelUpdateHandler(embeddingModel);
  177. }}
  178. type="button"
  179. disabled={loading1}
  180. >
  181. <div class="self-center font-medium">{$i18n.t('Update')}</div>
  182. {#if loading1}
  183. <div class="ml-3 self-center">
  184. <svg
  185. class=" w-3 h-3"
  186. viewBox="0 0 24 24"
  187. fill="currentColor"
  188. xmlns="http://www.w3.org/2000/svg"
  189. ><style>
  190. .spinner_ajPY {
  191. transform-origin: center;
  192. animation: spinner_AtaB 0.75s infinite linear;
  193. }
  194. @keyframes spinner_AtaB {
  195. 100% {
  196. transform: rotate(360deg);
  197. }
  198. }
  199. </style><path
  200. d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
  201. opacity=".25"
  202. /><path
  203. d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
  204. class="spinner_ajPY"
  205. /></svg
  206. >
  207. </div>
  208. {/if}
  209. </button>
  210. </Tooltip>
  211. </div>
  212. </div>
  213. <hr class=" dark:border-gray-700" />
  214. <div class=" ">
  215. <div class=" text-sm font-medium">{$i18n.t('Chunk Params')}</div>
  216. <div class=" flex">
  217. <div class=" flex w-full justify-between">
  218. <div class="self-center text-xs font-medium min-w-fit">{$i18n.t('Chunk Size')}</div>
  219. <div class="self-center p-3">
  220. <input
  221. class=" w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
  222. type="number"
  223. placeholder={$i18n.t('Enter Chunk Size')}
  224. bind:value={chunkSize}
  225. autocomplete="off"
  226. min="0"
  227. />
  228. </div>
  229. </div>
  230. <div class="flex w-full">
  231. <div class=" self-center text-xs font-medium min-w-fit">{$i18n.t('Chunk Overlap')}</div>
  232. <div class="self-center p-3">
  233. <input
  234. class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
  235. type="number"
  236. placeholder={$i18n.t('Enter Chunk Overlap')}
  237. bind:value={chunkOverlap}
  238. autocomplete="off"
  239. min="0"
  240. />
  241. </div>
  242. </div>
  243. </div>
  244. <div>
  245. <div class="flex justify-between items-center text-xs">
  246. <div class=" text-xs font-medium">{$i18n.t('PDF Extract Images (OCR)')}</div>
  247. <button
  248. class=" text-xs font-medium text-gray-500"
  249. type="button"
  250. on:click={() => {
  251. pdfExtractImages = !pdfExtractImages;
  252. }}>{pdfExtractImages ? $i18n.t('On') : $i18n.t('Off')}</button
  253. >
  254. </div>
  255. </div>
  256. </div>
  257. <div>
  258. <div class=" text-sm font-medium">{$i18n.t('Query Params')}</div>
  259. <div class=" flex">
  260. <div class=" flex w-full justify-between">
  261. <div class="self-center text-xs font-medium flex-1">{$i18n.t('Top K')}</div>
  262. <div class="self-center p-3">
  263. <input
  264. class=" w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
  265. type="number"
  266. placeholder={$i18n.t('Enter Top K')}
  267. bind:value={querySettings.k}
  268. autocomplete="off"
  269. min="0"
  270. />
  271. </div>
  272. </div>
  273. <!-- <div class="flex w-full">
  274. <div class=" self-center text-xs font-medium min-w-fit">Chunk Overlap</div>
  275. <div class="self-center p-3">
  276. <input
  277. class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
  278. type="number"
  279. placeholder="Enter Chunk Overlap"
  280. bind:value={chunkOverlap}
  281. autocomplete="off"
  282. min="0"
  283. />
  284. </div>
  285. </div> -->
  286. </div>
  287. <div>
  288. <div class=" mb-2.5 text-sm font-medium">{$i18n.t('RAG Template')}</div>
  289. <textarea
  290. bind:value={querySettings.template}
  291. class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
  292. rows="4"
  293. />
  294. </div>
  295. </div>
  296. <hr class=" dark:border-gray-700" />
  297. {#if showResetConfirm}
  298. <div class="flex justify-between rounded-md items-center py-2 px-3.5 w-full transition">
  299. <div class="flex items-center space-x-3">
  300. <svg
  301. xmlns="http://www.w3.org/2000/svg"
  302. viewBox="0 0 16 16"
  303. fill="currentColor"
  304. class="w-4 h-4"
  305. >
  306. <path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
  307. <path
  308. fill-rule="evenodd"
  309. d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM5.72 7.47a.75.75 0 0 1 1.06 0L8 8.69l1.22-1.22a.75.75 0 1 1 1.06 1.06L9.06 9.75l1.22 1.22a.75.75 0 1 1-1.06 1.06L8 10.81l-1.22 1.22a.75.75 0 0 1-1.06-1.06l1.22-1.22-1.22-1.22a.75.75 0 0 1 0-1.06Z"
  310. clip-rule="evenodd"
  311. />
  312. </svg>
  313. <span>{$i18n.t('Are you sure?')}</span>
  314. </div>
  315. <div class="flex space-x-1.5 items-center">
  316. <button
  317. class="hover:text-white transition"
  318. on:click={() => {
  319. const res = resetVectorDB(localStorage.token).catch((error) => {
  320. toast.error(error);
  321. return null;
  322. });
  323. if (res) {
  324. toast.success($i18n.t('Success'));
  325. }
  326. showResetConfirm = false;
  327. }}
  328. >
  329. <svg
  330. xmlns="http://www.w3.org/2000/svg"
  331. viewBox="0 0 20 20"
  332. fill="currentColor"
  333. class="w-4 h-4"
  334. >
  335. <path
  336. fill-rule="evenodd"
  337. d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
  338. clip-rule="evenodd"
  339. />
  340. </svg>
  341. </button>
  342. <button
  343. class="hover:text-white transition"
  344. on:click={() => {
  345. showResetConfirm = false;
  346. }}
  347. >
  348. <svg
  349. xmlns="http://www.w3.org/2000/svg"
  350. viewBox="0 0 20 20"
  351. fill="currentColor"
  352. class="w-4 h-4"
  353. >
  354. <path
  355. 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"
  356. />
  357. </svg>
  358. </button>
  359. </div>
  360. </div>
  361. {:else}
  362. <button
  363. class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
  364. on:click={() => {
  365. showResetConfirm = true;
  366. }}
  367. >
  368. <div class=" self-center mr-3">
  369. <svg
  370. xmlns="http://www.w3.org/2000/svg"
  371. viewBox="0 0 16 16"
  372. fill="currentColor"
  373. class="w-4 h-4"
  374. >
  375. <path
  376. fill-rule="evenodd"
  377. d="M3.5 2A1.5 1.5 0 0 0 2 3.5v9A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 12.5 4H9.621a1.5 1.5 0 0 1-1.06-.44L7.439 2.44A1.5 1.5 0 0 0 6.38 2H3.5Zm6.75 7.75a.75.75 0 0 0 0-1.5h-4.5a.75.75 0 0 0 0 1.5h4.5Z"
  378. clip-rule="evenodd"
  379. />
  380. </svg>
  381. </div>
  382. <div class=" self-center text-sm font-medium">{$i18n.t('Reset Vector Storage')}</div>
  383. </button>
  384. {/if}
  385. </div>
  386. <div class="flex justify-end pt-3 text-sm font-medium">
  387. <button
  388. class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
  389. type="submit"
  390. >
  391. {$i18n.t('Save')}
  392. </button>
  393. </div>
  394. </form>