WebSearch.svelte 30 KB

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