WebSearch.svelte 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. <script lang="ts">
  2. import { getRAGConfig, updateRAGConfig } from '$lib/apis/retrieval';
  3. import Switch from '$lib/components/common/Switch.svelte';
  4. import { models } from '$lib/stores';
  5. import { onMount, getContext } from 'svelte';
  6. import { toast } from 'svelte-sonner';
  7. import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
  8. const i18n = getContext('i18n');
  9. export let saveHandler: Function;
  10. let webConfig = null;
  11. let webSearchEngines = [
  12. 'searxng',
  13. 'google_pse',
  14. 'brave',
  15. 'kagi',
  16. 'mojeek',
  17. 'serpstack',
  18. 'serper',
  19. 'serply',
  20. 'searchapi',
  21. 'duckduckgo',
  22. 'tavily',
  23. 'jina',
  24. 'bing',
  25. 'exa'
  26. ];
  27. let youtubeLanguage = 'en';
  28. let youtubeTranslation = null;
  29. let youtubeProxyUrl = '';
  30. const submitHandler = async () => {
  31. // Convert domain filter string to array before sending
  32. if (webConfig?.search?.domain_filter_list) {
  33. webConfig.search.domain_filter_list = webConfig.search.domain_filter_list
  34. .split(',')
  35. .map(domain => domain.trim())
  36. .filter(domain => domain.length > 0);
  37. }
  38. const res = await updateRAGConfig(localStorage.token, {
  39. web: webConfig,
  40. youtube: {
  41. language: youtubeLanguage.split(',').map((lang) => lang.trim()),
  42. translation: youtubeTranslation,
  43. proxy_url: youtubeProxyUrl
  44. }
  45. });
  46. };
  47. onMount(async () => {
  48. const res = await getRAGConfig(localStorage.token);
  49. if (res) {
  50. webConfig = res.web;
  51. // Convert array back to comma-separated string for display
  52. if (webConfig?.search?.domain_filter_list) {
  53. webConfig.search.domain_filter_list = webConfig.search.domain_filter_list.join(', ');
  54. }
  55. youtubeLanguage = res.youtube.language.join(',');
  56. youtubeTranslation = res.youtube.translation;
  57. youtubeProxyUrl = res.youtube.proxy_url;
  58. }
  59. });
  60. </script>
  61. <form
  62. class="flex flex-col h-full justify-between space-y-3 text-sm"
  63. on:submit|preventDefault={async () => {
  64. await submitHandler();
  65. saveHandler();
  66. }}
  67. >
  68. <div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
  69. {#if webConfig}
  70. <div>
  71. <div class=" mb-1 text-sm font-medium">
  72. {$i18n.t('Web Search')}
  73. </div>
  74. <div>
  75. <div class=" py-0.5 flex w-full justify-between">
  76. <div class=" self-center text-xs font-medium">
  77. {$i18n.t('Enable Web Search')}
  78. </div>
  79. <Switch bind:state={webConfig.search.enabled} />
  80. </div>
  81. </div>
  82. <div class=" py-0.5 flex w-full justify-between">
  83. <div class=" self-center text-xs font-medium">{$i18n.t('Web Search Engine')}</div>
  84. <div class="flex items-center relative">
  85. <select
  86. class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
  87. bind:value={webConfig.search.engine}
  88. placeholder={$i18n.t('Select a engine')}
  89. required
  90. >
  91. <option disabled selected value="">{$i18n.t('Select a engine')}</option>
  92. {#each webSearchEngines as engine}
  93. <option value={engine}>{engine}</option>
  94. {/each}
  95. </select>
  96. </div>
  97. </div>
  98. {#if webConfig.search.engine !== ''}
  99. <div class="mt-1.5">
  100. {#if webConfig.search.engine === 'searxng'}
  101. <div>
  102. <div class=" self-center text-xs font-medium mb-1">
  103. {$i18n.t('Searxng Query URL')}
  104. </div>
  105. <div class="flex w-full">
  106. <div class="flex-1">
  107. <input
  108. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
  109. type="text"
  110. placeholder={$i18n.t('Enter Searxng Query URL')}
  111. bind:value={webConfig.search.searxng_query_url}
  112. autocomplete="off"
  113. />
  114. </div>
  115. </div>
  116. </div>
  117. {:else if webConfig.search.engine === 'google_pse'}
  118. <div>
  119. <div class=" self-center text-xs font-medium mb-1">
  120. {$i18n.t('Google PSE API Key')}
  121. </div>
  122. <SensitiveInput
  123. placeholder={$i18n.t('Enter Google PSE API Key')}
  124. bind:value={webConfig.search.google_pse_api_key}
  125. />
  126. </div>
  127. <div class="mt-1.5">
  128. <div class=" self-center text-xs font-medium mb-1">
  129. {$i18n.t('Google PSE Engine Id')}
  130. </div>
  131. <div class="flex w-full">
  132. <div class="flex-1">
  133. <input
  134. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
  135. type="text"
  136. placeholder={$i18n.t('Enter Google PSE Engine Id')}
  137. bind:value={webConfig.search.google_pse_engine_id}
  138. autocomplete="off"
  139. />
  140. </div>
  141. </div>
  142. </div>
  143. {:else if webConfig.search.engine === 'brave'}
  144. <div>
  145. <div class=" self-center text-xs font-medium mb-1">
  146. {$i18n.t('Brave Search API Key')}
  147. </div>
  148. <SensitiveInput
  149. placeholder={$i18n.t('Enter Brave Search API Key')}
  150. bind:value={webConfig.search.brave_search_api_key}
  151. />
  152. </div>
  153. {:else if webConfig.search.engine === 'kagi'}
  154. <div>
  155. <div class=" self-center text-xs font-medium mb-1">
  156. {$i18n.t('Kagi Search API Key')}
  157. </div>
  158. <SensitiveInput
  159. placeholder={$i18n.t('Enter Kagi Search API Key')}
  160. bind:value={webConfig.search.kagi_search_api_key}
  161. />
  162. </div>
  163. {:else if webConfig.search.engine === 'mojeek'}
  164. <div>
  165. <div class=" self-center text-xs font-medium mb-1">
  166. {$i18n.t('Mojeek Search API Key')}
  167. </div>
  168. <SensitiveInput
  169. placeholder={$i18n.t('Enter Mojeek Search API Key')}
  170. bind:value={webConfig.search.mojeek_search_api_key}
  171. />
  172. </div>
  173. {:else if webConfig.search.engine === 'serpstack'}
  174. <div>
  175. <div class=" self-center text-xs font-medium mb-1">
  176. {$i18n.t('Serpstack API Key')}
  177. </div>
  178. <SensitiveInput
  179. placeholder={$i18n.t('Enter Serpstack API Key')}
  180. bind:value={webConfig.search.serpstack_api_key}
  181. />
  182. </div>
  183. {:else if webConfig.search.engine === 'serper'}
  184. <div>
  185. <div class=" self-center text-xs font-medium mb-1">
  186. {$i18n.t('Serper API Key')}
  187. </div>
  188. <SensitiveInput
  189. placeholder={$i18n.t('Enter Serper API Key')}
  190. bind:value={webConfig.search.serper_api_key}
  191. />
  192. </div>
  193. {:else if webConfig.search.engine === 'serply'}
  194. <div>
  195. <div class=" self-center text-xs font-medium mb-1">
  196. {$i18n.t('Serply API Key')}
  197. </div>
  198. <SensitiveInput
  199. placeholder={$i18n.t('Enter Serply API Key')}
  200. bind:value={webConfig.search.serply_api_key}
  201. />
  202. </div>
  203. {:else if webConfig.search.engine === 'searchapi'}
  204. <div>
  205. <div class=" self-center text-xs font-medium mb-1">
  206. {$i18n.t('SearchApi API Key')}
  207. </div>
  208. <SensitiveInput
  209. placeholder={$i18n.t('Enter SearchApi API Key')}
  210. bind:value={webConfig.search.searchapi_api_key}
  211. />
  212. </div>
  213. <div class="mt-1.5">
  214. <div class=" self-center text-xs font-medium mb-1">
  215. {$i18n.t('SearchApi Engine')}
  216. </div>
  217. <div class="flex w-full">
  218. <div class="flex-1">
  219. <input
  220. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
  221. type="text"
  222. placeholder={$i18n.t('Enter SearchApi Engine')}
  223. bind:value={webConfig.search.searchapi_engine}
  224. autocomplete="off"
  225. />
  226. </div>
  227. </div>
  228. </div>
  229. {:else if webConfig.search.engine === 'tavily'}
  230. <div>
  231. <div class=" self-center text-xs font-medium mb-1">
  232. {$i18n.t('Tavily API Key')}
  233. </div>
  234. <SensitiveInput
  235. placeholder={$i18n.t('Enter Tavily API Key')}
  236. bind:value={webConfig.search.tavily_api_key}
  237. />
  238. </div>
  239. {:else if webConfig.search.engine === 'jina'}
  240. <div>
  241. <div class=" self-center text-xs font-medium mb-1">
  242. {$i18n.t('Jina API Key')}
  243. </div>
  244. <SensitiveInput
  245. placeholder={$i18n.t('Enter Jina API Key')}
  246. bind:value={webConfig.search.jina_api_key}
  247. />
  248. </div>
  249. {:else if webConfig.search.engine === 'exa'}
  250. <div>
  251. <div class=" self-center text-xs font-medium mb-1">
  252. {$i18n.t('Exa API Key')}
  253. </div>
  254. <SensitiveInput
  255. placeholder={$i18n.t('Enter Exa API Key')}
  256. bind:value={webConfig.search.exa_api_key}
  257. />
  258. </div>
  259. {:else if webConfig.search.engine === 'bing'}
  260. <div>
  261. <div class=" self-center text-xs font-medium mb-1">
  262. {$i18n.t('Bing Search V7 Endpoint')}
  263. </div>
  264. <div class="flex w-full">
  265. <div class="flex-1">
  266. <input
  267. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
  268. type="text"
  269. placeholder={$i18n.t('Enter Bing Search V7 Endpoint')}
  270. bind:value={webConfig.search.bing_search_v7_endpoint}
  271. autocomplete="off"
  272. />
  273. </div>
  274. </div>
  275. </div>
  276. <div class="mt-2">
  277. <div class=" self-center text-xs font-medium mb-1">
  278. {$i18n.t('Bing Search V7 Subscription Key')}
  279. </div>
  280. <SensitiveInput
  281. placeholder={$i18n.t('Enter Bing Search V7 Subscription Key')}
  282. bind:value={webConfig.search.bing_search_v7_subscription_key}
  283. />
  284. </div>
  285. {/if}
  286. </div>
  287. {/if}
  288. {#if webConfig.search.enabled}
  289. <div class="mt-2 flex gap-2 mb-1">
  290. <div class="w-full">
  291. <div class=" self-center text-xs font-medium mb-1">
  292. {$i18n.t('Search Result Count')}
  293. </div>
  294. <input
  295. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
  296. placeholder={$i18n.t('Search Result Count')}
  297. bind:value={webConfig.search.result_count}
  298. required
  299. />
  300. </div>
  301. <div class="w-full">
  302. <div class=" self-center text-xs font-medium mb-1">
  303. {$i18n.t('Concurrent Requests')}
  304. </div>
  305. <input
  306. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
  307. placeholder={$i18n.t('Concurrent Requests')}
  308. bind:value={webConfig.search.concurrent_requests}
  309. required
  310. />
  311. </div>
  312. </div>
  313. <div class="mt-2">
  314. <div class=" self-center text-xs font-medium mb-1">
  315. {$i18n.t('Domain Filter List')}
  316. </div>
  317. <input
  318. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
  319. placeholder={$i18n.t('Enter domains separated by commas (e.g., example.com,site.org)')}
  320. bind:value={webConfig.search.domain_filter_list}
  321. />
  322. </div>
  323. {/if}
  324. </div>
  325. <hr class=" dark:border-gray-850 my-2" />
  326. <div>
  327. <div class=" mb-1 text-sm font-medium">
  328. {$i18n.t('Web Loader Settings')}
  329. </div>
  330. <div>
  331. <div class=" py-0.5 flex w-full justify-between">
  332. <div class=" self-center text-xs font-medium">
  333. {$i18n.t('Bypass SSL verification for Websites')}
  334. </div>
  335. <button
  336. class="p-1 px-3 text-xs flex rounded transition"
  337. on:click={() => {
  338. webConfig.web_loader_ssl_verification = !webConfig.web_loader_ssl_verification;
  339. submitHandler();
  340. }}
  341. type="button"
  342. >
  343. {#if webConfig.web_loader_ssl_verification === false}
  344. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  345. {:else}
  346. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  347. {/if}
  348. </button>
  349. </div>
  350. </div>
  351. <div class=" mt-2 mb-1 text-sm font-medium">
  352. {$i18n.t('Youtube Loader Settings')}
  353. </div>
  354. <div>
  355. <div class=" py-0.5 flex w-full justify-between">
  356. <div class=" w-20 text-xs font-medium self-center">{$i18n.t('Language')}</div>
  357. <div class=" flex-1 self-center">
  358. <input
  359. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
  360. type="text"
  361. placeholder={$i18n.t('Enter language codes')}
  362. bind:value={youtubeLanguage}
  363. autocomplete="off"
  364. />
  365. </div>
  366. </div>
  367. </div>
  368. <div>
  369. <div class=" py-0.5 flex w-full justify-between">
  370. <div class=" w-20 text-xs font-medium self-center">{$i18n.t('Proxy URL')}</div>
  371. <div class=" flex-1 self-center">
  372. <input
  373. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
  374. type="text"
  375. placeholder={$i18n.t('Enter proxy URL (e.g. https://user:password@host:port)')}
  376. bind:value={youtubeProxyUrl}
  377. autocomplete="off"
  378. />
  379. </div>
  380. </div>
  381. </div>
  382. </div>
  383. {/if}
  384. </div>
  385. <div class="flex justify-end pt-3 text-sm font-medium">
  386. <button
  387. 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"
  388. type="submit"
  389. >
  390. {$i18n.t('Save')}
  391. </button>
  392. </div>
  393. </form>