WebSearch.svelte 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
  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 webSearchEngines = [
  12. 'searxng',
  13. 'yacy',
  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. 'sougou',
  31. 'firecrawl',
  32. 'external'
  33. ];
  34. let webLoaderEngines = ['playwright', 'firecrawl', 'tavily', 'external'];
  35. let webConfig = null;
  36. const submitHandler = async () => {
  37. // Convert domain filter string to array before sending
  38. if (webConfig.WEB_SEARCH_DOMAIN_FILTER_LIST) {
  39. webConfig.WEB_SEARCH_DOMAIN_FILTER_LIST = webConfig.WEB_SEARCH_DOMAIN_FILTER_LIST.split(',')
  40. .map((domain) => domain.trim())
  41. .filter((domain) => domain.length > 0);
  42. } else {
  43. webConfig.WEB_SEARCH_DOMAIN_FILTER_LIST = [];
  44. }
  45. // Convert Youtube loader language string to array before sending
  46. if (webConfig.YOUTUBE_LOADER_LANGUAGE) {
  47. webConfig.YOUTUBE_LOADER_LANGUAGE = webConfig.YOUTUBE_LOADER_LANGUAGE.split(',')
  48. .map((lang) => lang.trim())
  49. .filter((lang) => lang.length > 0);
  50. } else {
  51. webConfig.YOUTUBE_LOADER_LANGUAGE = [];
  52. }
  53. const res = await updateRAGConfig(localStorage.token, {
  54. web: webConfig
  55. });
  56. webConfig.WEB_SEARCH_DOMAIN_FILTER_LIST = webConfig.WEB_SEARCH_DOMAIN_FILTER_LIST.join(',');
  57. webConfig.YOUTUBE_LOADER_LANGUAGE = webConfig.YOUTUBE_LOADER_LANGUAGE.join(',');
  58. };
  59. onMount(async () => {
  60. const res = await getRAGConfig(localStorage.token);
  61. if (res) {
  62. webConfig = res.web;
  63. // Convert array back to comma-separated string for display
  64. if (webConfig?.WEB_SEARCH_DOMAIN_FILTER_LIST) {
  65. webConfig.WEB_SEARCH_DOMAIN_FILTER_LIST = webConfig.WEB_SEARCH_DOMAIN_FILTER_LIST.join(',');
  66. }
  67. webConfig.YOUTUBE_LOADER_LANGUAGE = webConfig.YOUTUBE_LOADER_LANGUAGE.join(',');
  68. }
  69. });
  70. </script>
  71. <form
  72. class="flex flex-col h-full justify-between space-y-3 text-sm"
  73. on:submit|preventDefault={async () => {
  74. await submitHandler();
  75. saveHandler();
  76. }}
  77. >
  78. <div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
  79. {#if webConfig}
  80. <div class="">
  81. <div class="mb-3">
  82. <div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
  83. <hr class=" border-gray-100 dark:border-gray-850 my-2" />
  84. <div class=" mb-2.5 flex w-full justify-between">
  85. <div class=" self-center text-xs font-medium">
  86. {$i18n.t('Web Search')}
  87. </div>
  88. <div class="flex items-center relative">
  89. <Switch bind:state={webConfig.ENABLE_WEB_SEARCH} />
  90. </div>
  91. </div>
  92. <div class=" mb-2.5 flex w-full justify-between">
  93. <div class=" self-center text-xs font-medium">
  94. {$i18n.t('Web Search Engine')}
  95. </div>
  96. <div class="flex items-center relative">
  97. <select
  98. class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
  99. bind:value={webConfig.WEB_SEARCH_ENGINE}
  100. placeholder={$i18n.t('Select a engine')}
  101. required
  102. >
  103. <option disabled selected value="">{$i18n.t('Select a engine')}</option>
  104. {#each webSearchEngines as engine}
  105. {#if engine === 'duckduckgo' || engine === 'ddgs'}
  106. <option value={engine}>DDGS</option>
  107. {:else}
  108. <option value={engine}>{engine}</option>
  109. {/if}
  110. {/each}
  111. </select>
  112. </div>
  113. </div>
  114. {#if webConfig.WEB_SEARCH_ENGINE !== ''}
  115. {#if webConfig.WEB_SEARCH_ENGINE === 'searxng'}
  116. <div class="mb-2.5 flex w-full flex-col">
  117. <div>
  118. <div class=" self-center text-xs font-medium mb-1">
  119. {$i18n.t('Searxng Query URL')}
  120. </div>
  121. <div class="flex w-full">
  122. <div class="flex-1">
  123. <input
  124. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  125. type="text"
  126. placeholder={$i18n.t('Enter Searxng Query URL')}
  127. bind:value={webConfig.SEARXNG_QUERY_URL}
  128. autocomplete="off"
  129. required
  130. />
  131. </div>
  132. </div>
  133. </div>
  134. </div>
  135. {:else if webConfig.WEB_SEARCH_ENGINE === 'yacy'}
  136. <div class="mb-2.5 flex w-full flex-col">
  137. <div>
  138. <div class=" self-center text-xs font-medium mb-1">
  139. {$i18n.t('Yacy Instance URL')}
  140. </div>
  141. <div class="flex w-full">
  142. <div class="flex-1">
  143. <input
  144. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  145. type="text"
  146. placeholder={$i18n.t('Enter Yacy URL (e.g. http://yacy.example.com:8090)')}
  147. bind:value={webConfig.YACY_QUERY_URL}
  148. autocomplete="off"
  149. />
  150. </div>
  151. </div>
  152. </div>
  153. </div>
  154. <div class="mb-2.5 flex w-full flex-col">
  155. <div class="flex gap-2">
  156. <div class="w-full">
  157. <div class=" self-center text-xs font-medium mb-1">
  158. {$i18n.t('Yacy Username')}
  159. </div>
  160. <input
  161. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  162. placeholder={$i18n.t('Enter Yacy Username')}
  163. bind:value={webConfig.YACY_USERNAME}
  164. required
  165. />
  166. </div>
  167. <div class="w-full">
  168. <div class=" self-center text-xs font-medium mb-1">
  169. {$i18n.t('Yacy Password')}
  170. </div>
  171. <SensitiveInput
  172. placeholder={$i18n.t('Enter Yacy Password')}
  173. bind:value={webConfig.YACY_PASSWORD}
  174. />
  175. </div>
  176. </div>
  177. </div>
  178. {:else if webConfig.WEB_SEARCH_ENGINE === 'google_pse'}
  179. <div class="mb-2.5 flex w-full flex-col">
  180. <div>
  181. <div class=" self-center text-xs font-medium mb-1">
  182. {$i18n.t('Google PSE API Key')}
  183. </div>
  184. <SensitiveInput
  185. placeholder={$i18n.t('Enter Google PSE API Key')}
  186. bind:value={webConfig.GOOGLE_PSE_API_KEY}
  187. />
  188. </div>
  189. <div class="mt-1.5">
  190. <div class=" self-center text-xs font-medium mb-1">
  191. {$i18n.t('Google PSE Engine Id')}
  192. </div>
  193. <div class="flex w-full">
  194. <div class="flex-1">
  195. <input
  196. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  197. type="text"
  198. placeholder={$i18n.t('Enter Google PSE Engine Id')}
  199. bind:value={webConfig.GOOGLE_PSE_ENGINE_ID}
  200. autocomplete="off"
  201. />
  202. </div>
  203. </div>
  204. </div>
  205. </div>
  206. {:else if webConfig.WEB_SEARCH_ENGINE === 'brave'}
  207. <div class="mb-2.5 flex w-full flex-col">
  208. <div>
  209. <div class=" self-center text-xs font-medium mb-1">
  210. {$i18n.t('Brave Search API Key')}
  211. </div>
  212. <SensitiveInput
  213. placeholder={$i18n.t('Enter Brave Search API Key')}
  214. bind:value={webConfig.BRAVE_SEARCH_API_KEY}
  215. />
  216. </div>
  217. </div>
  218. {:else if webConfig.WEB_SEARCH_ENGINE === 'kagi'}
  219. <div class="mb-2.5 flex w-full flex-col">
  220. <div>
  221. <div class=" self-center text-xs font-medium mb-1">
  222. {$i18n.t('Kagi Search API Key')}
  223. </div>
  224. <SensitiveInput
  225. placeholder={$i18n.t('Enter Kagi Search API Key')}
  226. bind:value={webConfig.KAGI_SEARCH_API_KEY}
  227. />
  228. </div>
  229. </div>
  230. {:else if webConfig.WEB_SEARCH_ENGINE === 'mojeek'}
  231. <div class="mb-2.5 flex w-full flex-col">
  232. <div>
  233. <div class=" self-center text-xs font-medium mb-1">
  234. {$i18n.t('Mojeek Search API Key')}
  235. </div>
  236. <SensitiveInput
  237. placeholder={$i18n.t('Enter Mojeek Search API Key')}
  238. bind:value={webConfig.MOJEEK_SEARCH_API_KEY}
  239. />
  240. </div>
  241. </div>
  242. {:else if webConfig.WEB_SEARCH_ENGINE === 'bocha'}
  243. <div class="mb-2.5 flex w-full flex-col">
  244. <div>
  245. <div class=" self-center text-xs font-medium mb-1">
  246. {$i18n.t('Bocha Search API Key')}
  247. </div>
  248. <SensitiveInput
  249. placeholder={$i18n.t('Enter Bocha Search API Key')}
  250. bind:value={webConfig.BOCHA_SEARCH_API_KEY}
  251. />
  252. </div>
  253. </div>
  254. {:else if webConfig.WEB_SEARCH_ENGINE === 'serpstack'}
  255. <div class="mb-2.5 flex w-full flex-col">
  256. <div>
  257. <div class=" self-center text-xs font-medium mb-1">
  258. {$i18n.t('Serpstack API Key')}
  259. </div>
  260. <SensitiveInput
  261. placeholder={$i18n.t('Enter Serpstack API Key')}
  262. bind:value={webConfig.SERPSTACK_API_KEY}
  263. />
  264. </div>
  265. </div>
  266. {:else if webConfig.WEB_SEARCH_ENGINE === 'serper'}
  267. <div class="mb-2.5 flex w-full flex-col">
  268. <div>
  269. <div class=" self-center text-xs font-medium mb-1">
  270. {$i18n.t('Serper API Key')}
  271. </div>
  272. <SensitiveInput
  273. placeholder={$i18n.t('Enter Serper API Key')}
  274. bind:value={webConfig.SERPER_API_KEY}
  275. />
  276. </div>
  277. </div>
  278. {:else if webConfig.WEB_SEARCH_ENGINE === 'serply'}
  279. <div class="mb-2.5 flex w-full flex-col">
  280. <div>
  281. <div class=" self-center text-xs font-medium mb-1">
  282. {$i18n.t('Serply API Key')}
  283. </div>
  284. <SensitiveInput
  285. placeholder={$i18n.t('Enter Serply API Key')}
  286. bind:value={webConfig.SERPLY_API_KEY}
  287. />
  288. </div>
  289. </div>
  290. {:else if webConfig.WEB_SEARCH_ENGINE === 'tavily'}
  291. <div class="mb-2.5 flex w-full flex-col">
  292. <div>
  293. <div class=" self-center text-xs font-medium mb-1">
  294. {$i18n.t('Tavily API Key')}
  295. </div>
  296. <SensitiveInput
  297. placeholder={$i18n.t('Enter Tavily API Key')}
  298. bind:value={webConfig.TAVILY_API_KEY}
  299. />
  300. </div>
  301. </div>
  302. {:else if webConfig.WEB_SEARCH_ENGINE === 'searchapi'}
  303. <div class="mb-2.5 flex w-full flex-col">
  304. <div>
  305. <div class=" self-center text-xs font-medium mb-1">
  306. {$i18n.t('SearchApi API Key')}
  307. </div>
  308. <SensitiveInput
  309. placeholder={$i18n.t('Enter SearchApi API Key')}
  310. bind:value={webConfig.SEARCHAPI_API_KEY}
  311. />
  312. </div>
  313. <div class="mt-1.5">
  314. <div class=" self-center text-xs font-medium mb-1">
  315. {$i18n.t('SearchApi Engine')}
  316. </div>
  317. <div class="flex w-full">
  318. <div class="flex-1">
  319. <input
  320. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  321. type="text"
  322. placeholder={$i18n.t('Enter SearchApi Engine')}
  323. bind:value={webConfig.SEARCHAPI_ENGINE}
  324. autocomplete="off"
  325. />
  326. </div>
  327. </div>
  328. </div>
  329. </div>
  330. {:else if webConfig.WEB_SEARCH_ENGINE === 'serpapi'}
  331. <div class="mb-2.5 flex w-full flex-col">
  332. <div>
  333. <div class=" self-center text-xs font-medium mb-1">
  334. {$i18n.t('SerpApi API Key')}
  335. </div>
  336. <SensitiveInput
  337. placeholder={$i18n.t('Enter SerpApi API Key')}
  338. bind:value={webConfig.SERPAPI_API_KEY}
  339. />
  340. </div>
  341. <div class="mt-1.5">
  342. <div class=" self-center text-xs font-medium mb-1">
  343. {$i18n.t('SerpApi Engine')}
  344. </div>
  345. <div class="flex w-full">
  346. <div class="flex-1">
  347. <input
  348. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  349. type="text"
  350. placeholder={$i18n.t('Enter SerpApi Engine')}
  351. bind:value={webConfig.SERPAPI_ENGINE}
  352. autocomplete="off"
  353. />
  354. </div>
  355. </div>
  356. </div>
  357. </div>
  358. {:else if webConfig.WEB_SEARCH_ENGINE === 'jina'}
  359. <div class="mb-2.5 flex w-full flex-col">
  360. <div>
  361. <div class=" self-center text-xs font-medium mb-1">
  362. {$i18n.t('Jina API Key')}
  363. </div>
  364. <SensitiveInput
  365. placeholder={$i18n.t('Enter Jina API Key')}
  366. bind:value={webConfig.JINA_API_KEY}
  367. />
  368. </div>
  369. </div>
  370. {:else if webConfig.WEB_SEARCH_ENGINE === 'bing'}
  371. <div class="mb-2.5 flex w-full flex-col">
  372. <div>
  373. <div class=" self-center text-xs font-medium mb-1">
  374. {$i18n.t('Bing Search V7 Endpoint')}
  375. </div>
  376. <div class="flex w-full">
  377. <div class="flex-1">
  378. <input
  379. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  380. type="text"
  381. placeholder={$i18n.t('Enter Bing Search V7 Endpoint')}
  382. bind:value={webConfig.BING_SEARCH_V7_ENDPOINT}
  383. autocomplete="off"
  384. />
  385. </div>
  386. </div>
  387. </div>
  388. <div class="mt-2">
  389. <div class=" self-center text-xs font-medium mb-1">
  390. {$i18n.t('Bing Search V7 Subscription Key')}
  391. </div>
  392. <SensitiveInput
  393. placeholder={$i18n.t('Enter Bing Search V7 Subscription Key')}
  394. bind:value={webConfig.BING_SEARCH_V7_SUBSCRIPTION_KEY}
  395. />
  396. </div>
  397. </div>
  398. {:else if webConfig.WEB_SEARCH_ENGINE === 'exa'}
  399. <div class="mb-2.5 flex w-full flex-col">
  400. <div>
  401. <div class=" self-center text-xs font-medium mb-1">
  402. {$i18n.t('Exa API Key')}
  403. </div>
  404. <SensitiveInput
  405. placeholder={$i18n.t('Enter Exa API Key')}
  406. bind:value={webConfig.EXA_API_KEY}
  407. />
  408. </div>
  409. </div>
  410. {:else if webConfig.WEB_SEARCH_ENGINE === 'perplexity'}
  411. <div class="mb-2.5 flex w-full flex-col">
  412. <div>
  413. <div class=" self-center text-xs font-medium mb-1">
  414. {$i18n.t('Perplexity API Key')}
  415. </div>
  416. <SensitiveInput
  417. placeholder={$i18n.t('Enter Perplexity API Key')}
  418. bind:value={webConfig.PERPLEXITY_API_KEY}
  419. />
  420. </div>
  421. </div>
  422. <div class="mb-2.5 flex w-full flex-col">
  423. <div>
  424. <div class="self-center text-xs font-medium mb-1">
  425. {$i18n.t('Perplexity Model')}
  426. </div>
  427. <input
  428. list="perplexity-model-list"
  429. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  430. bind:value={webConfig.PERPLEXITY_MODEL}
  431. />
  432. <datalist id="perplexity-model-list">
  433. <option value="sonar">{$i18n.t('Sonar')}</option>
  434. <option value="sonar-pro">{$i18n.t('Sonar Pro')}</option>
  435. <option value="sonar-reasoning">{$i18n.t('Sonar Reasoning')}</option>
  436. <option value="sonar-reasoning-pro">{$i18n.t('Sonar Reasoning Pro')}</option>
  437. <option value="sonar-deep-research">{$i18n.t('Sonar Deep Research')}</option>
  438. </datalist>
  439. </div>
  440. </div>
  441. <div class="mb-2.5 flex w-full flex-col">
  442. <div>
  443. <div class=" self-center text-xs font-medium mb-1">
  444. {$i18n.t('Perplexity Search Context Usage')}
  445. </div>
  446. <select
  447. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  448. bind:value={webConfig.PERPLEXITY_SEARCH_CONTEXT_USAGE}
  449. >
  450. <option value="low">{$i18n.t('Low')}</option>
  451. <option value="medium">{$i18n.t('Medium')}</option>
  452. <option value="high">{$i18n.t('High')}</option>
  453. </select>
  454. </div>
  455. </div>
  456. {:else if webConfig.WEB_SEARCH_ENGINE === 'sougou'}
  457. <div class="mb-2.5 flex w-full flex-col">
  458. <div>
  459. <div class=" self-center text-xs font-medium mb-1">
  460. {$i18n.t('Sougou Search API sID')}
  461. </div>
  462. <SensitiveInput
  463. placeholder={$i18n.t('Enter Sougou Search API sID')}
  464. bind:value={webConfig.SOUGOU_API_SID}
  465. />
  466. </div>
  467. </div>
  468. <div class="mb-2.5 flex w-full flex-col">
  469. <div>
  470. <div class=" self-center text-xs font-medium mb-1">
  471. {$i18n.t('Sougou Search API SK')}
  472. </div>
  473. <SensitiveInput
  474. placeholder={$i18n.t('Enter Sougou Search API SK')}
  475. bind:value={webConfig.SOUGOU_API_SK}
  476. />
  477. </div>
  478. </div>
  479. {:else if webConfig.WEB_SEARCH_ENGINE === 'firecrawl'}
  480. <div class="mb-2.5 flex w-full flex-col">
  481. <div>
  482. <div class=" self-center text-xs font-medium mb-1">
  483. {$i18n.t('Firecrawl API Base URL')}
  484. </div>
  485. <div class="flex w-full">
  486. <div class="flex-1">
  487. <input
  488. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  489. type="text"
  490. placeholder={$i18n.t('Enter Firecrawl API Base URL')}
  491. bind:value={webConfig.FIRECRAWL_API_BASE_URL}
  492. autocomplete="off"
  493. />
  494. </div>
  495. </div>
  496. </div>
  497. <div class="mt-2">
  498. <div class=" self-center text-xs font-medium mb-1">
  499. {$i18n.t('Firecrawl API Key')}
  500. </div>
  501. <SensitiveInput
  502. placeholder={$i18n.t('Enter Firecrawl API Key')}
  503. bind:value={webConfig.FIRECRAWL_API_KEY}
  504. />
  505. </div>
  506. </div>
  507. {:else if webConfig.WEB_SEARCH_ENGINE === 'ddgs' || webConfig.WEB_SEARCH_ENGINE === 'duckduckgo'}
  508. <div class="w-full mb-2.5">
  509. <div class=" self-center text-xs font-medium mb-1">
  510. {$i18n.t('Concurrent Requests')}
  511. </div>
  512. <input
  513. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  514. placeholder={$i18n.t('Concurrent Requests')}
  515. bind:value={webConfig.WEB_SEARCH_CONCURRENT_REQUESTS}
  516. required
  517. />
  518. </div>
  519. {:else if webConfig.WEB_SEARCH_ENGINE === 'external'}
  520. <div class="mb-2.5 flex w-full flex-col">
  521. <div>
  522. <div class=" self-center text-xs font-medium mb-1">
  523. {$i18n.t('External Web Search URL')}
  524. </div>
  525. <div class="flex w-full">
  526. <div class="flex-1">
  527. <input
  528. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  529. type="text"
  530. placeholder={$i18n.t('Enter External Web Search URL')}
  531. bind:value={webConfig.EXTERNAL_WEB_SEARCH_URL}
  532. autocomplete="off"
  533. />
  534. </div>
  535. </div>
  536. </div>
  537. <div class="mt-2">
  538. <div class=" self-center text-xs font-medium mb-1">
  539. {$i18n.t('External Web Search API Key')}
  540. </div>
  541. <SensitiveInput
  542. placeholder={$i18n.t('Enter External Web Search API Key')}
  543. bind:value={webConfig.EXTERNAL_WEB_SEARCH_API_KEY}
  544. />
  545. </div>
  546. </div>
  547. {/if}
  548. {/if}
  549. {#if webConfig.ENABLE_WEB_SEARCH}
  550. <div class="mb-2.5 flex w-full flex-col">
  551. <div class="flex gap-2">
  552. <div class="w-full">
  553. <div class=" self-center text-xs font-medium mb-1">
  554. {$i18n.t('Search Result Count')}
  555. </div>
  556. <input
  557. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  558. placeholder={$i18n.t('Search Result Count')}
  559. bind:value={webConfig.WEB_SEARCH_RESULT_COUNT}
  560. required
  561. />
  562. </div>
  563. </div>
  564. </div>
  565. <div class="mb-2.5 flex w-full flex-col">
  566. <div class=" text-xs font-medium mb-1">
  567. {$i18n.t('Domain Filter List')}
  568. </div>
  569. <input
  570. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  571. placeholder={$i18n.t(
  572. 'Enter domains separated by commas (e.g., example.com,site.org)'
  573. )}
  574. bind:value={webConfig.WEB_SEARCH_DOMAIN_FILTER_LIST}
  575. />
  576. </div>
  577. {/if}
  578. <div class=" mb-2.5 flex w-full justify-between">
  579. <div class=" self-center text-xs font-medium">
  580. <Tooltip content={$i18n.t('Full Context Mode')} placement="top-start">
  581. {$i18n.t('Bypass Embedding and Retrieval')}
  582. </Tooltip>
  583. </div>
  584. <div class="flex items-center relative">
  585. <Tooltip
  586. content={webConfig.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL
  587. ? $i18n.t(
  588. 'Inject the entire content as context for comprehensive processing, this is recommended for complex queries.'
  589. )
  590. : $i18n.t(
  591. 'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.'
  592. )}
  593. >
  594. <Switch bind:state={webConfig.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL} />
  595. </Tooltip>
  596. </div>
  597. </div>
  598. <div class=" mb-2.5 flex w-full justify-between">
  599. <div class=" self-center text-xs font-medium">
  600. <Tooltip content={$i18n.t('Bypass Web Loader')} placement="top-start">
  601. {$i18n.t('Bypass Web Loader')}
  602. </Tooltip>
  603. </div>
  604. <div class="flex items-center relative">
  605. <Tooltip content={''}>
  606. <Switch bind:state={webConfig.BYPASS_WEB_SEARCH_WEB_LOADER} />
  607. </Tooltip>
  608. </div>
  609. </div>
  610. <div class=" mb-2.5 flex w-full justify-between">
  611. <div class=" self-center text-xs font-medium">
  612. {$i18n.t('Trust Proxy Environment')}
  613. </div>
  614. <div class="flex items-center relative">
  615. <Tooltip
  616. content={webConfig.WEB_SEARCH_TRUST_ENV
  617. ? $i18n.t(
  618. 'Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.'
  619. )
  620. : $i18n.t('Use no proxy to fetch page contents.')}
  621. >
  622. <Switch bind:state={webConfig.WEB_SEARCH_TRUST_ENV} />
  623. </Tooltip>
  624. </div>
  625. </div>
  626. </div>
  627. <div class="mb-3">
  628. <div class=" mb-2.5 text-base font-medium">{$i18n.t('Loader')}</div>
  629. <hr class=" border-gray-100 dark:border-gray-850 my-2" />
  630. <div class=" mb-2.5 flex w-full justify-between">
  631. <div class=" self-center text-xs font-medium">
  632. {$i18n.t('Web Loader Engine')}
  633. </div>
  634. <div class="flex items-center relative">
  635. <select
  636. class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
  637. bind:value={webConfig.WEB_LOADER_ENGINE}
  638. placeholder={$i18n.t('Select a engine')}
  639. >
  640. <option value="">{$i18n.t('Default')}</option>
  641. {#each webLoaderEngines as engine}
  642. <option value={engine}>{engine}</option>
  643. {/each}
  644. </select>
  645. </div>
  646. </div>
  647. {#if webConfig.WEB_LOADER_ENGINE === '' || webConfig.WEB_LOADER_ENGINE === 'safe_web'}
  648. <div class=" mb-2.5 flex w-full justify-between">
  649. <div class=" self-center text-xs font-medium">
  650. {$i18n.t('Verify SSL Certificate')}
  651. </div>
  652. <div class="flex items-center relative">
  653. <Switch bind:state={webConfig.ENABLE_WEB_LOADER_SSL_VERIFICATION} />
  654. </div>
  655. </div>
  656. {:else if webConfig.WEB_LOADER_ENGINE === 'playwright'}
  657. <div class="mb-2.5 flex w-full flex-col">
  658. <div>
  659. <div class=" self-center text-xs font-medium mb-1">
  660. {$i18n.t('Playwright WebSocket URL')}
  661. </div>
  662. <div class="flex w-full">
  663. <div class="flex-1">
  664. <input
  665. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  666. type="text"
  667. placeholder={$i18n.t('Enter Playwright WebSocket URL')}
  668. bind:value={webConfig.PLAYWRIGHT_WS_URL}
  669. autocomplete="off"
  670. />
  671. </div>
  672. </div>
  673. </div>
  674. <div class="mt-2">
  675. <div class=" self-center text-xs font-medium mb-1">
  676. {$i18n.t('Playwright Timeout (ms)')}
  677. </div>
  678. <div class="flex w-full">
  679. <div class="flex-1">
  680. <input
  681. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  682. placeholder={$i18n.t('Enter Playwright Timeout')}
  683. bind:value={webConfig.PLAYWRIGHT_TIMEOUT}
  684. autocomplete="off"
  685. />
  686. </div>
  687. </div>
  688. </div>
  689. </div>
  690. {:else if webConfig.WEB_LOADER_ENGINE === 'firecrawl' && webConfig.WEB_SEARCH_ENGINE !== 'firecrawl'}
  691. <div class="mb-2.5 flex w-full flex-col">
  692. <div>
  693. <div class=" self-center text-xs font-medium mb-1">
  694. {$i18n.t('Firecrawl API Base URL')}
  695. </div>
  696. <div class="flex w-full">
  697. <div class="flex-1">
  698. <input
  699. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  700. type="text"
  701. placeholder={$i18n.t('Enter Firecrawl API Base URL')}
  702. bind:value={webConfig.FIRECRAWL_API_BASE_URL}
  703. autocomplete="off"
  704. />
  705. </div>
  706. </div>
  707. </div>
  708. <div class="mt-2">
  709. <div class=" self-center text-xs font-medium mb-1">
  710. {$i18n.t('Firecrawl API Key')}
  711. </div>
  712. <SensitiveInput
  713. placeholder={$i18n.t('Enter Firecrawl API Key')}
  714. bind:value={webConfig.FIRECRAWL_API_KEY}
  715. />
  716. </div>
  717. </div>
  718. {:else if webConfig.WEB_LOADER_ENGINE === 'tavily'}
  719. <div class="mb-2.5 flex w-full flex-col">
  720. <div>
  721. <div class=" self-center text-xs font-medium mb-1">
  722. {$i18n.t('Tavily Extract Depth')}
  723. </div>
  724. <div class="flex w-full">
  725. <div class="flex-1">
  726. <input
  727. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  728. type="text"
  729. placeholder={$i18n.t('Enter Tavily Extract Depth')}
  730. bind:value={webConfig.TAVILY_EXTRACT_DEPTH}
  731. autocomplete="off"
  732. />
  733. </div>
  734. </div>
  735. </div>
  736. {#if webConfig.WEB_SEARCH_ENGINE !== 'tavily'}
  737. <div class="mt-2">
  738. <div class=" self-center text-xs font-medium mb-1">
  739. {$i18n.t('Tavily API Key')}
  740. </div>
  741. <SensitiveInput
  742. placeholder={$i18n.t('Enter Tavily API Key')}
  743. bind:value={webConfig.TAVILY_API_KEY}
  744. />
  745. </div>
  746. {/if}
  747. </div>
  748. {:else if webConfig.WEB_LOADER_ENGINE === 'external'}
  749. <div class="mb-2.5 flex w-full flex-col">
  750. <div>
  751. <div class=" self-center text-xs font-medium mb-1">
  752. {$i18n.t('External Web Loader URL')}
  753. </div>
  754. <div class="flex w-full">
  755. <div class="flex-1">
  756. <input
  757. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  758. type="text"
  759. placeholder={$i18n.t('Enter External Web Loader URL')}
  760. bind:value={webConfig.EXTERNAL_WEB_LOADER_URL}
  761. autocomplete="off"
  762. />
  763. </div>
  764. </div>
  765. </div>
  766. <div class="mt-2">
  767. <div class=" self-center text-xs font-medium mb-1">
  768. {$i18n.t('External Web Loader API Key')}
  769. </div>
  770. <SensitiveInput
  771. placeholder={$i18n.t('Enter External Web Loader API Key')}
  772. bind:value={webConfig.EXTERNAL_WEB_LOADER_API_KEY}
  773. />
  774. </div>
  775. </div>
  776. {/if}
  777. <div class="mb-2.5 w-full">
  778. <div class=" self-center text-xs font-medium mb-1">
  779. {$i18n.t('Concurrent Requests')}
  780. </div>
  781. <input
  782. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  783. placeholder={$i18n.t('Concurrent Requests')}
  784. bind:value={webConfig.WEB_LOADER_CONCURRENT_REQUESTS}
  785. required
  786. />
  787. </div>
  788. <div class=" mb-2.5 flex w-full justify-between">
  789. <div class=" self-center text-xs font-medium">
  790. {$i18n.t('Youtube Language')}
  791. </div>
  792. <div class="flex items-center relative">
  793. <input
  794. class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
  795. type="text"
  796. placeholder={$i18n.t('Enter language codes')}
  797. bind:value={webConfig.YOUTUBE_LOADER_LANGUAGE}
  798. autocomplete="off"
  799. />
  800. </div>
  801. </div>
  802. <div class=" mb-2.5 flex flex-col w-full justify-between">
  803. <div class=" mb-1 text-xs font-medium">
  804. {$i18n.t('Youtube Proxy URL')}
  805. </div>
  806. <div class="flex items-center relative">
  807. <input
  808. class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
  809. type="text"
  810. placeholder={$i18n.t('Enter proxy URL (e.g. https://user:password@host:port)')}
  811. bind:value={webConfig.YOUTUBE_LOADER_PROXY_URL}
  812. autocomplete="off"
  813. />
  814. </div>
  815. </div>
  816. </div>
  817. </div>
  818. {/if}
  819. </div>
  820. <div class="flex justify-end pt-3 text-sm font-medium">
  821. <button
  822. 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"
  823. type="submit"
  824. >
  825. {$i18n.t('Save')}
  826. </button>
  827. </div>
  828. </form>