Feat: add SearXNG search tool to Agent (frontend + backend, i18n) (#9699)

### What problem does this PR solve?

This PR integrates SearXNG as a new search tool for Agents. It adds
corresponding form/config UI on the frontend and a new tool
implementation on the backend, enabling aggregated web searches via a
self-hosted SearXNG instance within chats/workflows. It also adds
multilingual copy to support internationalized presentation and
configuration guidance.

### Type of change

- [x] New Feature (non-breaking change which adds functionality)

### What’s Changed
- Frontend: new SearXNG tool configuration, forms, and command wiring
  - Main changes under `web/src/pages/agent/`
- New components and form entries are connected to Agent tool selection
and workflow node configuration
- Backend: new tool implementation
- `agent/tools/searxng.py`: connects to a SearXNG instance and performs
search based on the provided instance URL and query parameters
- i18n updates
- Added/updated keys under `web/src/locales/`: `searXNG` and
`searXNGDescription`
- English reference in
[web/src/locales/en.ts](cci:7://file:///c:/Users/ruy_x/Work/CRSC/2025/Software_Development/2025.8/ragflow-pr/ragflow/web/src/locales/en.ts:0:0-0:0):
    - `searXNG: 'SearXNG'`
- `searXNGDescription: 'A component that searches via your provided
SearXNG instance URL. Specify TopN and the instance URL.'`
- Other languages have `searXNG` and `searXNGDescription` added as well,
but accuracy is only guaranteed for English, Simplified Chinese, and
Traditional Chinese.

---------

Co-authored-by: xurui <xurui@crscd.com.cn>
This commit is contained in:
RuyXu
2025-08-29 14:15:40 +08:00
committed by GitHub
parent c47a38773c
commit 209b731541
24 changed files with 363 additions and 0 deletions

156
agent/tools/searxng.py Normal file
View File

@ -0,0 +1,156 @@
#
# Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import logging
import os
import time
from abc import ABC
import requests
from agent.tools.base import ToolMeta, ToolParamBase, ToolBase
from api.utils.api_utils import timeout
class SearXNGParam(ToolParamBase):
"""
Define the SearXNG component parameters.
"""
def __init__(self):
self.meta: ToolMeta = {
"name": "searxng_search",
"description": "SearXNG is a privacy-focused metasearch engine that aggregates results from multiple search engines without tracking users. It provides comprehensive web search capabilities.",
"parameters": {
"query": {
"type": "string",
"description": "The search keywords to execute with SearXNG. The keywords should be the most important words/terms(includes synonyms) from the original request.",
"default": "{sys.query}",
"required": True
},
"searxng_url": {
"type": "string",
"description": "The base URL of your SearXNG instance (e.g., http://localhost:4000). This is required to connect to your SearXNG server.",
"required": False,
"default": ""
}
}
}
super().__init__()
self.top_n = 10
self.searxng_url = ""
def check(self):
# Keep validation lenient so opening try-run panel won't fail without URL.
# Coerce top_n to int if it comes as string from UI.
try:
if isinstance(self.top_n, str):
self.top_n = int(self.top_n.strip())
except Exception:
pass
self.check_positive_integer(self.top_n, "Top N")
def get_input_form(self) -> dict[str, dict]:
return {
"query": {
"name": "Query",
"type": "line"
},
"searxng_url": {
"name": "SearXNG URL",
"type": "line",
"placeholder": "http://localhost:4000"
}
}
class SearXNG(ToolBase, ABC):
component_name = "SearXNG"
@timeout(os.environ.get("COMPONENT_EXEC_TIMEOUT", 12))
def _invoke(self, **kwargs):
# Gracefully handle try-run without inputs
query = kwargs.get("query")
if not query or not isinstance(query, str) or not query.strip():
self.set_output("formalized_content", "")
return ""
searxng_url = (kwargs.get("searxng_url") or getattr(self._param, "searxng_url", "") or "").strip()
# In try-run, if no URL configured, just return empty instead of raising
if not searxng_url:
self.set_output("formalized_content", "")
return ""
last_e = ""
for _ in range(self._param.max_retries+1):
try:
# 构建搜索参数
search_params = {
'q': query,
'format': 'json',
'categories': 'general',
'language': 'auto',
'safesearch': 1,
'pageno': 1
}
# 发送搜索请求
response = requests.get(
f"{searxng_url}/search",
params=search_params,
timeout=10
)
response.raise_for_status()
data = response.json()
# 验证响应数据
if not data or not isinstance(data, dict):
raise ValueError("Invalid response from SearXNG")
results = data.get("results", [])
if not isinstance(results, list):
raise ValueError("Invalid results format from SearXNG")
# 限制结果数量
results = results[:self._param.top_n]
# 处理搜索结果
self._retrieve_chunks(results,
get_title=lambda r: r.get("title", ""),
get_url=lambda r: r.get("url", ""),
get_content=lambda r: r.get("content", ""))
self.set_output("json", results)
return self.output("formalized_content")
except requests.RequestException as e:
last_e = f"Network error: {e}"
logging.exception(f"SearXNG network error: {e}")
time.sleep(self._param.delay_after_error)
except Exception as e:
last_e = str(e)
logging.exception(f"SearXNG error: {e}")
time.sleep(self._param.delay_after_error)
if last_e:
self.set_output("_ERROR", last_e)
return f"SearXNG error: {last_e}"
assert False, self.output()
def thoughts(self) -> str:
return """
Keywords: {}
Searching with SearXNG for relevant results...
""".format(self.get_input().get("query", "-_-!"))

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
<circle cx="9.5" cy="9.5" r="2.5" fill="currentColor" opacity="0.6"/>
<path d="M12 2l1.5 3h3L15 7l1.5 3L15 8.5 12 10 9 8.5 7.5 10 9 7 7.5 5h3L12 2z" opacity="0.4"/>
</svg>

After

Width:  |  Height:  |  Size: 506 B

View File

@ -868,6 +868,9 @@ export default {
duckDuckGo: 'DuckDuckGo', duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription: duckDuckGoDescription:
'Eine Komponente, die auf duckduckgo.com sucht und Ihnen ermöglicht, die Anzahl der Suchergebnisse mit TopN anzugeben. Sie ergänzt die vorhandenen Wissensdatenbanken.', 'Eine Komponente, die auf duckduckgo.com sucht und Ihnen ermöglicht, die Anzahl der Suchergebnisse mit TopN anzugeben. Sie ergänzt die vorhandenen Wissensdatenbanken.',
searXNG: 'SearXNG',
searXNGDescription:
'Eine Komponente, die auf https://searxng.org/ sucht und Ihnen ermöglicht, die Anzahl der Suchergebnisse mit TopN anzugeben. Sie ergänzt die vorhandenen Wissensdatenbanken.',
channel: 'Kanal', channel: 'Kanal',
channelTip: channelTip:
'Führt eine Textsuche oder Nachrichtensuche für die Eingabe der Komponente durch', 'Führt eine Textsuche oder Nachrichtensuche für die Eingabe der Komponente durch',

View File

@ -1005,6 +1005,9 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
duckDuckGo: 'DuckDuckGo', duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription: duckDuckGoDescription:
'A component that searches from duckduckgo.com, allowing you to specify the number of search results using TopN. It supplements the existing knowledge bases.', 'A component that searches from duckduckgo.com, allowing you to specify the number of search results using TopN. It supplements the existing knowledge bases.',
searXNG: 'SearXNG',
searXNGDescription:
'A component that searches via your provided SearXNG instance URL. Specify TopN and the instance URL.',
channel: 'Channel', channel: 'Channel',
channelTip: `Perform text search or news search on the component's input`, channelTip: `Perform text search or news search on the component's input`,
text: 'Text', text: 'Text',

View File

@ -571,6 +571,9 @@ export default {
duckDuckGo: 'DuckDuckGo', duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription: duckDuckGoDescription:
'Un componente que recupera resultados de búsqueda de duckduckgo.com, con TopN especificando el número de resultados de búsqueda. Complementa las bases de conocimiento existentes.', 'Un componente que recupera resultados de búsqueda de duckduckgo.com, con TopN especificando el número de resultados de búsqueda. Complementa las bases de conocimiento existentes.',
searXNG: 'SearXNG',
searXNGDescription:
'Un componente que realiza búsquedas mediante la URL de la instancia de SearXNG que usted proporcione. Especifique TopN y la URL de la instancia.',
channel: 'Canal', channel: 'Canal',
channelTip: channelTip:
'Realizar búsqueda de texto o búsqueda de noticias en la entrada del componente.', 'Realizar búsqueda de texto o búsqueda de noticias en la entrada del componente.',

View File

@ -781,6 +781,9 @@ export default {
duckDuckGo: 'DuckDuckGo', duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription: duckDuckGoDescription:
'Un composant qui recherche sur duckduckgo.com, vous permettant de spécifier le nombre de résultats avec TopN. Il complète les bases de connaissances existantes.', 'Un composant qui recherche sur duckduckgo.com, vous permettant de spécifier le nombre de résultats avec TopN. Il complète les bases de connaissances existantes.',
searXNG: 'SearXNG',
searXNGDescription:
'Un composant qui effectue des recherches via la URL de l\'instance de SearXNG que vous fournissez. Spécifiez TopN et l\'URL de l\'instance.',
channel: 'Canal', channel: 'Canal',
channelTip: channelTip:
"Effectuer une recherche de texte ou d'actualités sur l'entrée du composant", "Effectuer une recherche de texte ou d'actualités sur l'entrée du composant",

View File

@ -759,6 +759,9 @@ export default {
duckDuckGo: 'DuckDuckGo', duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription: duckDuckGoDescription:
'Komponen yang mengambil hasil pencarian dari duckduckgo.com, dengan TopN menentukan jumlah hasil pencarian. Ini melengkapi basis pengetahuan yang ada.', 'Komponen yang mengambil hasil pencarian dari duckduckgo.com, dengan TopN menentukan jumlah hasil pencarian. Ini melengkapi basis pengetahuan yang ada.',
searXNG: 'SearXNG',
searXNGDescription:
'Komponen yang melakukan pencarian menggunakan URL instance SearXNG yang Anda berikan. Spesifikasikan TopN dan URL instance.',
channel: 'Saluran', channel: 'Saluran',
channelTip: `Lakukan pencarian teks atau pencarian berita pada input komponen`, channelTip: `Lakukan pencarian teks atau pencarian berita pada input komponen`,
text: 'Teks', text: 'Teks',

View File

@ -739,6 +739,9 @@ export default {
duckDuckGo: 'DuckDuckGo', duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription: duckDuckGoDescription:
'duckduckgo.comから検索を行うコンポーネントで、TopNを使用して検索結果の数を指定します。既存のナレッジベースを補完します。', 'duckduckgo.comから検索を行うコンポーネントで、TopNを使用して検索結果の数を指定します。既存のナレッジベースを補完します。',
searXNG: 'SearXNG',
searXNGDescription:
'SearXNGのインスタンスURLを提供して検索を行うコンポーネント。TopNとインスタンスURLを指定してください。',
channel: 'チャンネル', channel: 'チャンネル',
channelTip: `コンポーネントの入力に対してテキスト検索またはニュース検索を実行します`, channelTip: `コンポーネントの入力に対してテキスト検索またはニュース検索を実行します`,
text: 'テキスト', text: 'テキスト',

View File

@ -726,6 +726,9 @@ export default {
duckDuckGo: 'DuckDuckGo', duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription: duckDuckGoDescription:
'Um componente que realiza buscas no duckduckgo.com, permitindo especificar o número de resultados de pesquisa usando TopN. Ele complementa as bases de conhecimento existentes.', 'Um componente que realiza buscas no duckduckgo.com, permitindo especificar o número de resultados de pesquisa usando TopN. Ele complementa as bases de conhecimento existentes.',
searXNG: 'SearXNG',
searXNGDescription:
'Um componente que realiza buscas via URL da instância SearXNG que você fornece. Especifique TopN e URL da instância.',
channel: 'Canal', channel: 'Canal',
channelTip: `Realize uma busca por texto ou por notícias na entrada do componente`, channelTip: `Realize uma busca por texto ou por notícias na entrada do componente`,
text: 'Texto', text: 'Texto',

View File

@ -859,6 +859,9 @@ export default {
baiduDescription: `Ищет на baidu.com.`, baiduDescription: `Ищет на baidu.com.`,
duckDuckGo: 'DuckDuckGo', duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription: 'Ищет на duckduckgo.com.', duckDuckGoDescription: 'Ищет на duckduckgo.com.',
searXNG: 'SearXNG',
searXNGDescription:
'Компонент, который выполняет поиск по указанному вами URL-адресу экземпляра SearXNG. Укажите TopN и URL-адрес экземпляра.',
channel: 'Канал', channel: 'Канал',
channelTip: `Текстовый или новостной поиск`, channelTip: `Текстовый или новостной поиск`,
text: 'Текст', text: 'Текст',

View File

@ -818,6 +818,9 @@ export default {
duckDuckGo: 'DuckDuckGo', duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription: duckDuckGoDescription:
'Một thành phần truy xuất kết quả tìm kiếm từ duckduckgo.com, với TopN xác định số lượng kết quả tìm kiếm. Nó bổ sung cho các cơ sở kiến thức hiện có.', 'Một thành phần truy xuất kết quả tìm kiếm từ duckduckgo.com, với TopN xác định số lượng kết quả tìm kiếm. Nó bổ sung cho các cơ sở kiến thức hiện có.',
searXNG: 'SearXNG',
searXNGDescription:
'Một thành phần truy xuất kết quả tìm kiếm từ searxng.com, với TopN xác định số lượng kết quả tìm kiếm. Nó bổ sung cho các cơ sở kiến thức hiện có.',
channel: 'Kênh', channel: 'Kênh',
channelTip: `Thực hiện tìm kiếm văn bản hoặc tìm kiếm tin tức trên đầu vào của thành phần`, channelTip: `Thực hiện tìm kiếm văn bản hoặc tìm kiếm tin tức trên đầu vào của thành phần`,
text: 'Văn bản', text: 'Văn bản',

View File

@ -845,6 +845,9 @@ export default {
duckDuckGo: 'DuckDuckGo', duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription: duckDuckGoDescription:
'此元件用於從 www.duckduckgo.com 取得搜尋結果。通常,它作為知識庫的補充。 Top N 指定您需要採用的搜尋結果數。', '此元件用於從 www.duckduckgo.com 取得搜尋結果。通常,它作為知識庫的補充。 Top N 指定您需要採用的搜尋結果數。',
searXNG: 'SearXNG',
searXNGDescription:
'該組件通過您提供的 SearXNG 實例地址進行搜索。請設置 Top N 和實例 URL。',
channel: '頻道', channel: '頻道',
channelTip: '針對該組件的輸入進行文字搜尋或新聞搜索', channelTip: '針對該組件的輸入進行文字搜尋或新聞搜索',
text: '文字', text: '文字',

View File

@ -971,6 +971,9 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
duckDuckGo: 'DuckDuckGo', duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription: duckDuckGoDescription:
'此元件用於從 www.duckduckgo.com 取得搜尋結果。通常,它作為知識庫的補充。 Top N 指定您需要調整的搜尋結果數。', '此元件用於從 www.duckduckgo.com 取得搜尋結果。通常,它作為知識庫的補充。 Top N 指定您需要調整的搜尋結果數。',
searXNG: 'SearXNG',
searXNGDescription:
'该组件通过您提供的 SearXNG 实例地址进行搜索。请设置 Top N 和实例 URL。',
channel: '频道', channel: '频道',
channelTip: '针对该组件的输入进行文本搜索或新闻搜索', channelTip: '针对该组件的输入进行文本搜索或新闻搜索',
text: '文本', text: '文本',

View File

@ -201,6 +201,7 @@ function AccordionOperators({
Operator.GitHub, Operator.GitHub,
Operator.Invoke, Operator.Invoke,
Operator.WenCai, Operator.WenCai,
Operator.SearXNG,
]} ]}
isCustomDropdown={isCustomDropdown} isCustomDropdown={isCustomDropdown}
mousePosition={mousePosition} mousePosition={mousePosition}

View File

@ -88,6 +88,7 @@ export enum Operator {
TavilyExtract = 'TavilyExtract', TavilyExtract = 'TavilyExtract',
UserFillUp = 'UserFillUp', UserFillUp = 'UserFillUp',
StringTransform = 'StringTransform', StringTransform = 'StringTransform',
SearXNG = 'SearXNG',
} }
export const SwitchLogicOperatorOptions = ['and', 'or']; export const SwitchLogicOperatorOptions = ['and', 'or'];
@ -211,6 +212,9 @@ export const componentMenuList = [
{ {
name: Operator.Email, name: Operator.Email,
}, },
{
name: Operator.SearXNG,
},
]; ];
export const SwitchOperatorOptions = [ export const SwitchOperatorOptions = [
@ -340,6 +344,22 @@ export const initialDuckValues = {
}, },
}; };
export const initialSearXNGValues = {
top_n: '10',
searxng_url: '',
query: AgentGlobals.SysQuery,
outputs: {
formalized_content: {
value: '',
type: 'string',
},
json: {
value: [],
type: 'Array<Object>',
},
},
};
export const initialBaiduValues = { export const initialBaiduValues = {
top_n: 10, top_n: 10,
...initialQueryBaseValues, ...initialQueryBaseValues,
@ -807,6 +827,7 @@ export const RestrictedUpstreamMap = {
[Operator.GitHub]: [Operator.Begin, Operator.Retrieval], [Operator.GitHub]: [Operator.Begin, Operator.Retrieval],
[Operator.BaiduFanyi]: [Operator.Begin, Operator.Retrieval], [Operator.BaiduFanyi]: [Operator.Begin, Operator.Retrieval],
[Operator.QWeather]: [Operator.Begin, Operator.Retrieval], [Operator.QWeather]: [Operator.Begin, Operator.Retrieval],
[Operator.SearXNG]: [Operator.Begin, Operator.Retrieval],
[Operator.ExeSQL]: [Operator.Begin], [Operator.ExeSQL]: [Operator.Begin],
[Operator.Switch]: [Operator.Begin], [Operator.Switch]: [Operator.Begin],
[Operator.WenCai]: [Operator.Begin], [Operator.WenCai]: [Operator.Begin],
@ -851,6 +872,7 @@ export const NodeMap = {
[Operator.GitHub]: 'ragNode', [Operator.GitHub]: 'ragNode',
[Operator.BaiduFanyi]: 'ragNode', [Operator.BaiduFanyi]: 'ragNode',
[Operator.QWeather]: 'ragNode', [Operator.QWeather]: 'ragNode',
[Operator.SearXNG]: 'ragNode',
[Operator.ExeSQL]: 'ragNode', [Operator.ExeSQL]: 'ragNode',
[Operator.Switch]: 'switchNode', [Operator.Switch]: 'switchNode',
[Operator.Concentrator]: 'logicNode', [Operator.Concentrator]: 'logicNode',

View File

@ -27,6 +27,7 @@ import QWeatherForm from '../form/qweather-form';
import RelevantForm from '../form/relevant-form'; import RelevantForm from '../form/relevant-form';
import RetrievalForm from '../form/retrieval-form/next'; import RetrievalForm from '../form/retrieval-form/next';
import RewriteQuestionForm from '../form/rewrite-question-form'; import RewriteQuestionForm from '../form/rewrite-question-form';
import SearXNGForm from '../form/searxng-form';
import StringTransformForm from '../form/string-transform-form'; import StringTransformForm from '../form/string-transform-form';
import SwitchForm from '../form/switch-form'; import SwitchForm from '../form/switch-form';
import TavilyExtractForm from '../form/tavily-extract-form'; import TavilyExtractForm from '../form/tavily-extract-form';
@ -132,6 +133,9 @@ export const FormConfigMap = {
[Operator.Invoke]: { [Operator.Invoke]: {
component: InvokeForm, component: InvokeForm,
}, },
[Operator.SearXNG]: {
component: SearXNGForm,
},
[Operator.Concentrator]: { [Operator.Concentrator]: {
component: () => <></>, component: () => <></>,
}, },

View File

@ -27,6 +27,7 @@ const Menus = [
// Operator.Bing, // Operator.Bing,
Operator.DuckDuckGo, Operator.DuckDuckGo,
Operator.Wikipedia, Operator.Wikipedia,
Operator.SearXNG,
Operator.YahooFinance, Operator.YahooFinance,
Operator.PubMed, Operator.PubMed,
Operator.GoogleScholar, Operator.GoogleScholar,

View File

@ -0,0 +1,73 @@
import { FormContainer } from '@/components/form-container';
import { TopNFormField } from '@/components/top-n-item';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { useTranslate } from '@/hooks/common-hooks';
import { zodResolver } from '@hookform/resolvers/zod';
import { memo } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { initialSearXNGValues } from '../../constant';
import { useFormValues } from '../../hooks/use-form-values';
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
import { INextOperatorForm } from '../../interface';
import { buildOutputList } from '../../utils/build-output-list';
import { FormWrapper } from '../components/form-wrapper';
import { Output } from '../components/output';
import { QueryVariable } from '../components/query-variable';
const FormSchema = z.object({
query: z.string(),
searxng_url: z.string().min(1),
top_n: z.string(),
});
const outputList = buildOutputList(initialSearXNGValues.outputs);
function SearXNGForm({ node }: INextOperatorForm) {
const { t } = useTranslate('flow');
const defaultValues = useFormValues(initialSearXNGValues, node);
const form = useForm<z.infer<typeof FormSchema>>({
defaultValues,
resolver: zodResolver(FormSchema),
});
useWatchFormChange(node?.id, form);
return (
<Form {...form}>
<FormWrapper>
<FormContainer>
<QueryVariable></QueryVariable>
<TopNFormField></TopNFormField>
<FormField
control={form.control}
name="searxng_url"
render={({ field }) => (
<FormItem>
<FormLabel>SearXNG URL</FormLabel>
<FormControl>
<Input {...field} placeholder="http://localhost:4000" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</FormContainer>
</FormWrapper>
<div className="p-5">
<Output list={outputList}></Output>
</div>
</Form>
);
}
export default memo(SearXNGForm);

View File

@ -12,6 +12,7 @@ import GoogleForm from './google-form';
import GoogleScholarForm from './google-scholar-form'; import GoogleScholarForm from './google-scholar-form';
import PubMedForm from './pubmed-form'; import PubMedForm from './pubmed-form';
import RetrievalForm from './retrieval-form'; import RetrievalForm from './retrieval-form';
import SearXNGForm from './searxng-form';
import TavilyForm from './tavily-form'; import TavilyForm from './tavily-form';
import WenCaiForm from './wencai-form'; import WenCaiForm from './wencai-form';
import WikipediaForm from './wikipedia-form'; import WikipediaForm from './wikipedia-form';
@ -37,4 +38,5 @@ export const ToolFormConfigMap = {
[Operator.TavilySearch]: TavilyForm, [Operator.TavilySearch]: TavilyForm,
[Operator.TavilyExtract]: TavilyForm, [Operator.TavilyExtract]: TavilyForm,
[Operator.WenCai]: WenCaiForm, [Operator.WenCai]: WenCaiForm,
[Operator.SearXNG]: SearXNGForm,
}; };

View File

@ -0,0 +1,58 @@
import { FormContainer } from '@/components/form-container';
import { TopNFormField } from '@/components/top-n-item';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { useTranslate } from '@/hooks/common-hooks';
import { zodResolver } from '@hookform/resolvers/zod';
import { memo } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { useValues } from '../use-values';
import { useWatchFormChange } from '../use-watch-change';
const FormSchema = z.object({
searxng_url: z.string().min(1),
top_n: z.string(),
});
function SearXNGForm() {
const { t } = useTranslate('flow');
const values = useValues();
const form = useForm<z.infer<typeof FormSchema>>({
defaultValues: values as any,
resolver: zodResolver(FormSchema),
});
useWatchFormChange(form);
return (
<Form {...form}>
<FormContainer>
<TopNFormField></TopNFormField>
<FormField
control={form.control}
name="searxng_url"
render={({ field }) => (
<FormItem>
<FormLabel>SearXNG URL</FormLabel>
<FormControl>
<Input {...field} placeholder="http://localhost:4000" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</FormContainer>
</Form>
);
}
export default memo(SearXNGForm);

View File

@ -45,6 +45,7 @@ import {
initialRelevantValues, initialRelevantValues,
initialRetrievalValues, initialRetrievalValues,
initialRewriteQuestionValues, initialRewriteQuestionValues,
initialSearXNGValues,
initialStringTransformValues, initialStringTransformValues,
initialSwitchValues, initialSwitchValues,
initialTavilyExtractValues, initialTavilyExtractValues,
@ -113,6 +114,7 @@ export const useInitializeOperatorParams = () => {
[Operator.Bing]: initialBingValues, [Operator.Bing]: initialBingValues,
[Operator.GoogleScholar]: initialGoogleScholarValues, [Operator.GoogleScholar]: initialGoogleScholarValues,
[Operator.DeepL]: initialDeepLValues, [Operator.DeepL]: initialDeepLValues,
[Operator.SearXNG]: initialSearXNGValues,
[Operator.GitHub]: initialGithubValues, [Operator.GitHub]: initialGithubValues,
[Operator.BaiduFanyi]: initialBaiduFanyiValues, [Operator.BaiduFanyi]: initialBaiduFanyiValues,
[Operator.QWeather]: initialQWeatherValues, [Operator.QWeather]: initialQWeatherValues,

View File

@ -39,6 +39,7 @@ import {
initialRelevantValues, initialRelevantValues,
initialRetrievalValues, initialRetrievalValues,
initialRewriteQuestionValues, initialRewriteQuestionValues,
initialSearXNGValues,
initialStringTransformValues, initialStringTransformValues,
initialSwitchValues, initialSwitchValues,
initialTavilyExtractValues, initialTavilyExtractValues,
@ -89,6 +90,7 @@ export const useInitializeOperatorParams = () => {
[Operator.Bing]: initialBingValues, [Operator.Bing]: initialBingValues,
[Operator.GoogleScholar]: initialGoogleScholarValues, [Operator.GoogleScholar]: initialGoogleScholarValues,
[Operator.DeepL]: initialDeepLValues, [Operator.DeepL]: initialDeepLValues,
[Operator.SearXNG]: initialSearXNGValues,
[Operator.GitHub]: initialGithubValues, [Operator.GitHub]: initialGithubValues,
[Operator.BaiduFanyi]: initialBaiduFanyiValues, [Operator.BaiduFanyi]: initialBaiduFanyiValues,
[Operator.QWeather]: initialQWeatherValues, [Operator.QWeather]: initialQWeatherValues,

View File

@ -56,6 +56,8 @@ export function useAgentToolInitialValues() {
return pick(initialValues, 'top_n', 'query_type'); return pick(initialValues, 'top_n', 'query_type');
case Operator.Code: case Operator.Code:
return {}; return {};
case Operator.SearXNG:
return pick(initialValues, 'searxng_url', 'top_n');
default: default:
return initialValues; return initialValues;

View File

@ -6,6 +6,7 @@ import { ReactComponent as GithubIcon } from '@/assets/svg/github.svg';
import { ReactComponent as GoogleScholarIcon } from '@/assets/svg/google-scholar.svg'; import { ReactComponent as GoogleScholarIcon } from '@/assets/svg/google-scholar.svg';
import { ReactComponent as GoogleIcon } from '@/assets/svg/google.svg'; import { ReactComponent as GoogleIcon } from '@/assets/svg/google.svg';
import { ReactComponent as PubMedIcon } from '@/assets/svg/pubmed.svg'; import { ReactComponent as PubMedIcon } from '@/assets/svg/pubmed.svg';
import { ReactComponent as SearXNGIcon } from '@/assets/svg/searxng.svg';
import { ReactComponent as TavilyIcon } from '@/assets/svg/tavily.svg'; import { ReactComponent as TavilyIcon } from '@/assets/svg/tavily.svg';
import { ReactComponent as WenCaiIcon } from '@/assets/svg/wencai.svg'; import { ReactComponent as WenCaiIcon } from '@/assets/svg/wencai.svg';
import { ReactComponent as WikipediaIcon } from '@/assets/svg/wikipedia.svg'; import { ReactComponent as WikipediaIcon } from '@/assets/svg/wikipedia.svg';
@ -46,6 +47,7 @@ export const SVGIconMap = {
[Operator.Google]: GoogleIcon, [Operator.Google]: GoogleIcon,
[Operator.GoogleScholar]: GoogleScholarIcon, [Operator.GoogleScholar]: GoogleScholarIcon,
[Operator.PubMed]: PubMedIcon, [Operator.PubMed]: PubMedIcon,
[Operator.SearXNG]: SearXNGIcon,
[Operator.TavilyExtract]: TavilyIcon, [Operator.TavilyExtract]: TavilyIcon,
[Operator.TavilySearch]: TavilyIcon, [Operator.TavilySearch]: TavilyIcon,
[Operator.Wikipedia]: WikipediaIcon, [Operator.Wikipedia]: WikipediaIcon,