WebSearch.svelte 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  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. import Tooltip from '$lib/components/common/Tooltip.svelte';
  9. const i18n = getContext('i18n');
  10. export let saveHandler: Function;
  11. let webConfig = null;
  12. let webSearchEngines = [
  13. 'searxng',
  14. 'google_pse',
  15. 'brave',
  16. 'kagi',
  17. 'mojeek',
  18. 'bocha',
  19. 'serpstack',
  20. 'serper',
  21. 'serply',
  22. 'searchapi',
  23. 'serpapi',
  24. 'duckduckgo',
  25. 'tavily',
  26. 'jina',
  27. 'bing',
  28. 'exa',
  29. 'perplexity'
  30. ];
  31. let youtubeLanguage = 'en';
  32. let youtubeTranslation = null;
  33. let youtubeProxyUrl = '';
  34. const submitHandler = async () => {
  35. // Convert domain filter string to array before sending
  36. if (webConfig.search.domain_filter_list) {
  37. webConfig.search.domain_filter_list = webConfig.search.domain_filter_list
  38. .split(',')
  39. .map((domain) => domain.trim())
  40. .filter((domain) => domain.length > 0);
  41. } else {
  42. webConfig.search.domain_filter_list = [];
  43. }
  44. const res = await updateRAGConfig(localStorage.token, {
  45. web: webConfig,
  46. youtube: {
  47. language: youtubeLanguage.split(',').map((lang) => lang.trim()),
  48. translation: youtubeTranslation,
  49. proxy_url: youtubeProxyUrl
  50. }
  51. });
  52. webConfig.search.domain_filter_list = webConfig.search.domain_filter_list.join(', ');
  53. };
  54. onMount(async () => {
  55. const res = await getRAGConfig(localStorage.token);
  56. if (res) {
  57. webConfig = res.web;
  58. // Convert array back to comma-separated string for display
  59. if (webConfig?.search?.domain_filter_list) {
  60. webConfig.search.domain_filter_list = webConfig.search.domain_filter_list.join(', ');
  61. }
  62. youtubeLanguage = res.youtube.language.join(',');
  63. youtubeTranslation = res.youtube.translation;
  64. youtubeProxyUrl = res.youtube.proxy_url;
  65. }
  66. });
  67. </script>
  68. <form
  69. class="flex flex-col h-full justify-between space-y-3 text-sm"
  70. on:submit|preventDefault={async () => {
  71. await submitHandler();
  72. saveHandler();
  73. }}
  74. >
  75. <div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
  76. {#if webConfig}
  77. <div class="">
  78. <div class="mb-3">
  79. <div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
  80. <hr class=" border-gray-100 dark:border-gray-850 my-2" />
  81. <div class=" mb-2.5 flex w-full justify-between">
  82. <div class=" self-center text-xs font-medium">
  83. {$i18n.t('Web Search')}
  84. </div>
  85. <div class="flex items-center relative">
  86. <Switch bind:state={webConfig.search.enabled} />
  87. </div>
  88. </div>
  89. <div class=" mb-2.5 flex w-full justify-between">
  90. <div class=" self-center text-xs font-medium">
  91. {$i18n.t('Web Search Engine')}
  92. </div>
  93. <div class="flex items-center relative">
  94. <select
  95. class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
  96. bind:value={webConfig.search.engine}
  97. placeholder={$i18n.t('Select a engine')}
  98. required
  99. >
  100. <option disabled selected value="">{$i18n.t('Select a engine')}</option>
  101. {#each webSearchEngines as engine}
  102. <option value={engine}>{engine}</option>
  103. {/each}
  104. </select>
  105. </div>
  106. </div>
  107. {#if webConfig.search.engine !== ''}
  108. {#if webConfig.search.engine === 'searxng'}
  109. <div class="mb-2.5 flex w-full flex-col">
  110. <div>
  111. <div class=" self-center text-xs font-medium mb-1">
  112. {$i18n.t('Searxng Query URL')}
  113. </div>
  114. <div class="flex w-full">
  115. <div class="flex-1">
  116. <input
  117. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  118. type="text"
  119. placeholder={$i18n.t('Enter Searxng Query URL')}
  120. bind:value={webConfig.search.searxng_query_url}
  121. autocomplete="off"
  122. />
  123. </div>
  124. </div>
  125. </div>
  126. </div>
  127. {:else if webConfig.search.engine === 'google_pse'}
  128. <div class="mb-2.5 flex w-full flex-col">
  129. <div>
  130. <div class=" self-center text-xs font-medium mb-1">
  131. {$i18n.t('Google PSE API Key')}
  132. </div>
  133. <SensitiveInput
  134. placeholder={$i18n.t('Enter Google PSE API Key')}
  135. bind:value={webConfig.search.google_pse_api_key}
  136. />
  137. </div>
  138. <div class="mt-1.5">
  139. <div class=" self-center text-xs font-medium mb-1">
  140. {$i18n.t('Google PSE Engine Id')}
  141. </div>
  142. <div class="flex w-full">
  143. <div class="flex-1">
  144. <input
  145. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  146. type="text"
  147. placeholder={$i18n.t('Enter Google PSE Engine Id')}
  148. bind:value={webConfig.search.google_pse_engine_id}
  149. autocomplete="off"
  150. />
  151. </div>
  152. </div>
  153. </div>
  154. </div>
  155. {:else if webConfig.search.engine === 'brave'}
  156. <div class="mb-2.5 flex w-full flex-col">
  157. <div>
  158. <div class=" self-center text-xs font-medium mb-1">
  159. {$i18n.t('Brave Search API Key')}
  160. </div>
  161. <SensitiveInput
  162. placeholder={$i18n.t('Enter Brave Search API Key')}
  163. bind:value={webConfig.search.brave_search_api_key}
  164. />
  165. </div>
  166. </div>
  167. {:else if webConfig.search.engine === 'kagi'}
  168. <div class="mb-2.5 flex w-full flex-col">
  169. <div>
  170. <div class=" self-center text-xs font-medium mb-1">
  171. {$i18n.t('Kagi Search API Key')}
  172. </div>
  173. <SensitiveInput
  174. placeholder={$i18n.t('Enter Kagi Search API Key')}
  175. bind:value={webConfig.search.kagi_search_api_key}
  176. />
  177. </div>
  178. .
  179. </div>
  180. {:else if webConfig.search.engine === 'mojeek'}
  181. <div class="mb-2.5 flex w-full flex-col">
  182. <div>
  183. <div class=" self-center text-xs font-medium mb-1">
  184. {$i18n.t('Mojeek Search API Key')}
  185. </div>
  186. <SensitiveInput
  187. placeholder={$i18n.t('Enter Mojeek Search API Key')}
  188. bind:value={webConfig.search.mojeek_search_api_key}
  189. />
  190. </div>
  191. </div>
  192. {:else if webConfig.search.engine === 'bocha'}
  193. <div class="mb-2.5 flex w-full flex-col">
  194. <div>
  195. <div class=" self-center text-xs font-medium mb-1">
  196. {$i18n.t('Bocha Search API Key')}
  197. </div>
  198. <SensitiveInput
  199. placeholder={$i18n.t('Enter Bocha Search API Key')}
  200. bind:value={webConfig.search.bocha_search_api_key}
  201. />
  202. </div>
  203. </div>
  204. {:else if webConfig.search.engine === 'serpstack'}
  205. <div class="mb-2.5 flex w-full flex-col">
  206. <div>
  207. <div class=" self-center text-xs font-medium mb-1">
  208. {$i18n.t('Serpstack API Key')}
  209. </div>
  210. <SensitiveInput
  211. placeholder={$i18n.t('Enter Serpstack API Key')}
  212. bind:value={webConfig.search.serpstack_api_key}
  213. />
  214. </div>
  215. </div>
  216. {:else if webConfig.search.engine === 'serper'}
  217. <div class="mb-2.5 flex w-full flex-col">
  218. <div>
  219. <div class=" self-center text-xs font-medium mb-1">
  220. {$i18n.t('Serper API Key')}
  221. </div>
  222. <SensitiveInput
  223. placeholder={$i18n.t('Enter Serper API Key')}
  224. bind:value={webConfig.search.serper_api_key}
  225. />
  226. </div>
  227. </div>
  228. {:else if webConfig.search.engine === 'serply'}
  229. <div class="mb-2.5 flex w-full flex-col">
  230. <div>
  231. <div class=" self-center text-xs font-medium mb-1">
  232. {$i18n.t('Serply API Key')}
  233. </div>
  234. <SensitiveInput
  235. placeholder={$i18n.t('Enter Serply API Key')}
  236. bind:value={webConfig.search.serply_api_key}
  237. />
  238. </div>
  239. </div>
  240. {:else if webConfig.search.engine === 'searchapi'}
  241. <div class="mb-2.5 flex w-full flex-col">
  242. <div>
  243. <div class=" self-center text-xs font-medium mb-1">
  244. {$i18n.t('SearchApi API Key')}
  245. </div>
  246. <SensitiveInput
  247. placeholder={$i18n.t('Enter SearchApi API Key')}
  248. bind:value={webConfig.search.searchapi_api_key}
  249. />
  250. </div>
  251. <div class="mt-1.5">
  252. <div class=" self-center text-xs font-medium mb-1">
  253. {$i18n.t('SearchApi Engine')}
  254. </div>
  255. <div class="flex w-full">
  256. <div class="flex-1">
  257. <input
  258. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  259. type="text"
  260. placeholder={$i18n.t('Enter SearchApi Engine')}
  261. bind:value={webConfig.search.searchapi_engine}
  262. autocomplete="off"
  263. />
  264. </div>
  265. </div>
  266. </div>
  267. </div>
  268. {:else if webConfig.search.engine === 'serpapi'}
  269. <div class="mb-2.5 flex w-full flex-col">
  270. <div>
  271. <div class=" self-center text-xs font-medium mb-1">
  272. {$i18n.t('SerpApi API Key')}
  273. </div>
  274. <SensitiveInput
  275. placeholder={$i18n.t('Enter SerpApi API Key')}
  276. bind:value={webConfig.search.serpapi_api_key}
  277. />
  278. </div>
  279. <div class="mt-1.5">
  280. <div class=" self-center text-xs font-medium mb-1">
  281. {$i18n.t('SerpApi Engine')}
  282. </div>
  283. <div class="flex w-full">
  284. <div class="flex-1">
  285. <input
  286. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  287. type="text"
  288. placeholder={$i18n.t('Enter SerpApi Engine')}
  289. bind:value={webConfig.search.serpapi_engine}
  290. autocomplete="off"
  291. />
  292. </div>
  293. </div>
  294. </div>
  295. </div>
  296. {:else if webConfig.search.engine === 'tavily'}
  297. <div class="mb-2.5 flex w-full flex-col">
  298. <div>
  299. <div class=" self-center text-xs font-medium mb-1">
  300. {$i18n.t('Tavily API Key')}
  301. </div>
  302. <SensitiveInput
  303. placeholder={$i18n.t('Enter Tavily API Key')}
  304. bind:value={webConfig.search.tavily_api_key}
  305. />
  306. </div>
  307. </div>
  308. {:else if webConfig.search.engine === 'jina'}
  309. <div class="mb-2.5 flex w-full flex-col">
  310. <div>
  311. <div class=" self-center text-xs font-medium mb-1">
  312. {$i18n.t('Jina API Key')}
  313. </div>
  314. <SensitiveInput
  315. placeholder={$i18n.t('Enter Jina API Key')}
  316. bind:value={webConfig.search.jina_api_key}
  317. />
  318. </div>
  319. </div>
  320. {:else if webConfig.search.engine === 'exa'}
  321. <div class="mb-2.5 flex w-full flex-col">
  322. <div>
  323. <div class=" self-center text-xs font-medium mb-1">
  324. {$i18n.t('Exa API Key')}
  325. </div>
  326. <SensitiveInput
  327. placeholder={$i18n.t('Enter Exa API Key')}
  328. bind:value={webConfig.search.exa_api_key}
  329. />
  330. </div>
  331. </div>
  332. {:else if webConfig.search.engine === 'perplexity'}
  333. <div>
  334. <div class=" self-center text-xs font-medium mb-1">
  335. {$i18n.t('Perplexity API Key')}
  336. </div>
  337. <SensitiveInput
  338. placeholder={$i18n.t('Enter Perplexity API Key')}
  339. bind:value={webConfig.search.perplexity_api_key}
  340. />
  341. </div>
  342. {:else if webConfig.search.engine === 'bing'}
  343. <div class="mb-2.5 flex w-full flex-col">
  344. <div>
  345. <div class=" self-center text-xs font-medium mb-1">
  346. {$i18n.t('Bing Search V7 Endpoint')}
  347. </div>
  348. <div class="flex w-full">
  349. <div class="flex-1">
  350. <input
  351. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  352. type="text"
  353. placeholder={$i18n.t('Enter Bing Search V7 Endpoint')}
  354. bind:value={webConfig.search.bing_search_v7_endpoint}
  355. autocomplete="off"
  356. />
  357. </div>
  358. </div>
  359. </div>
  360. <div class="mt-2">
  361. <div class=" self-center text-xs font-medium mb-1">
  362. {$i18n.t('Bing Search V7 Subscription Key')}
  363. </div>
  364. <SensitiveInput
  365. placeholder={$i18n.t('Enter Bing Search V7 Subscription Key')}
  366. bind:value={webConfig.search.bing_search_v7_subscription_key}
  367. />
  368. </div>
  369. </div>
  370. {/if}
  371. {/if}
  372. {#if webConfig.search.enabled}
  373. <div class="mb-2.5 flex w-full flex-col">
  374. <div class="flex gap-2">
  375. <div class="w-full">
  376. <div class=" self-center text-xs font-medium mb-1">
  377. {$i18n.t('Search Result Count')}
  378. </div>
  379. <input
  380. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  381. placeholder={$i18n.t('Search Result Count')}
  382. bind:value={webConfig.search.result_count}
  383. required
  384. />
  385. </div>
  386. <div class="w-full">
  387. <div class=" self-center text-xs font-medium mb-1">
  388. {$i18n.t('Concurrent Requests')}
  389. </div>
  390. <input
  391. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  392. placeholder={$i18n.t('Concurrent Requests')}
  393. bind:value={webConfig.search.concurrent_requests}
  394. required
  395. />
  396. </div>
  397. </div>
  398. </div>
  399. <div class="mb-2.5 flex w-full flex-col">
  400. <div class=" text-xs font-medium mb-1">
  401. {$i18n.t('Domain Filter List')}
  402. </div>
  403. <input
  404. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  405. placeholder={$i18n.t(
  406. 'Enter domains separated by commas (e.g., example.com,site.org)'
  407. )}
  408. bind:value={webConfig.search.domain_filter_list}
  409. />
  410. </div>
  411. {/if}
  412. <div class=" mb-2.5 flex w-full justify-between">
  413. <div class=" self-center text-xs font-medium">
  414. <Tooltip content={$i18n.t('Full Context Mode')} placement="top-start">
  415. {$i18n.t('Bypass Embedding and Retrieval')}
  416. </Tooltip>
  417. </div>
  418. <div class="flex items-center relative">
  419. <Tooltip
  420. content={webConfig.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL
  421. ? $i18n.t(
  422. 'Inject the entire content as context for comprehensive processing, this is recommended for complex queries.'
  423. )
  424. : $i18n.t(
  425. 'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.'
  426. )}
  427. >
  428. <Switch bind:state={webConfig.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL} />
  429. </Tooltip>
  430. </div>
  431. </div>
  432. <div class=" mb-2.5 flex w-full justify-between">
  433. <div class=" self-center text-xs font-medium">
  434. {$i18n.t('Trust Proxy Environment')}
  435. </div>
  436. <div class="flex items-center relative">
  437. <Tooltip
  438. content={webConfig.search.trust_env
  439. ? 'Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents'
  440. : 'Use no proxy to fetch page contents.'}
  441. >
  442. <Switch bind:state={webConfig.search.trust_env} />
  443. </Tooltip>
  444. </div>
  445. </div>
  446. </div>
  447. <div class="mb-3">
  448. <div class=" mb-2.5 text-base font-medium">{$i18n.t('Loader')}</div>
  449. <hr class=" border-gray-100 dark:border-gray-850 my-2" />
  450. <div class=" mb-2.5 flex w-full justify-between">
  451. <div class=" self-center text-xs font-medium">
  452. {$i18n.t('Bypass SSL verification for Websites')}
  453. </div>
  454. <div class="flex items-center relative">
  455. <Switch bind:state={webConfig.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION} />
  456. </div>
  457. </div>
  458. <div class=" mb-2.5 flex w-full justify-between">
  459. <div class=" self-center text-xs font-medium">
  460. {$i18n.t('Youtube Language')}
  461. </div>
  462. <div class="flex items-center relative">
  463. <input
  464. class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
  465. type="text"
  466. placeholder={$i18n.t('Enter language codes')}
  467. bind:value={youtubeLanguage}
  468. autocomplete="off"
  469. />
  470. </div>
  471. </div>
  472. <div class=" mb-2.5 flex flex-col w-full justify-between">
  473. <div class=" mb-1 text-xs font-medium">
  474. {$i18n.t('Youtube Proxy URL')}
  475. </div>
  476. <div class="flex items-center relative">
  477. <input
  478. class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
  479. type="text"
  480. placeholder={$i18n.t('Enter proxy URL (e.g. https://user:password@host:port)')}
  481. bind:value={youtubeProxyUrl}
  482. autocomplete="off"
  483. />
  484. </div>
  485. </div>
  486. </div>
  487. </div>
  488. {/if}
  489. </div>
  490. <div class="flex justify-end pt-3 text-sm font-medium">
  491. <button
  492. 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"
  493. type="submit"
  494. >
  495. {$i18n.t('Save')}
  496. </button>
  497. </div>
  498. </form>