WebSearch.svelte 16 KB

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