WebSearch.svelte 16 KB

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