ragflow/web/src/pages/agent/operator-icon.tsx
RuyXu 209b731541
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>
2025-08-29 14:15:40 +08:00

87 lines
3 KiB
TypeScript

import { ReactComponent as ArxivIcon } from '@/assets/svg/arxiv.svg';
import { ReactComponent as BingIcon } from '@/assets/svg/bing.svg';
import { ReactComponent as CrawlerIcon } from '@/assets/svg/crawler.svg';
import { ReactComponent as DuckIcon } from '@/assets/svg/duck.svg';
import { ReactComponent as GithubIcon } from '@/assets/svg/github.svg';
import { ReactComponent as GoogleScholarIcon } from '@/assets/svg/google-scholar.svg';
import { ReactComponent as GoogleIcon } from '@/assets/svg/google.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 WenCaiIcon } from '@/assets/svg/wencai.svg';
import { ReactComponent as WikipediaIcon } from '@/assets/svg/wikipedia.svg';
import { ReactComponent as YahooFinanceIcon } from '@/assets/svg/yahoo-finance.svg';
import { IconFont } from '@/components/icon-font';
import { cn } from '@/lib/utils';
import { HousePlus } from 'lucide-react';
import { Operator } from './constant';
interface IProps {
name: Operator;
className?: string;
}
export const OperatorIconMap = {
[Operator.Retrieval]: 'KR',
[Operator.Begin]: 'house-plus',
[Operator.Categorize]: 'a-QuestionClassification',
[Operator.Message]: 'reply',
[Operator.Iteration]: 'loop',
[Operator.Switch]: 'condition',
[Operator.Code]: 'code-set',
[Operator.Agent]: 'agent-ai',
[Operator.UserFillUp]: 'await',
[Operator.StringTransform]: 'a-textprocessing',
[Operator.Note]: 'notebook-pen',
[Operator.ExeSQL]: 'executesql-0',
[Operator.Invoke]: 'httprequest-0',
[Operator.Email]: 'sendemail-0',
};
export const SVGIconMap = {
[Operator.ArXiv]: ArxivIcon,
[Operator.GitHub]: GithubIcon,
[Operator.Bing]: BingIcon,
[Operator.DuckDuckGo]: DuckIcon,
[Operator.Google]: GoogleIcon,
[Operator.GoogleScholar]: GoogleScholarIcon,
[Operator.PubMed]: PubMedIcon,
[Operator.SearXNG]: SearXNGIcon,
[Operator.TavilyExtract]: TavilyIcon,
[Operator.TavilySearch]: TavilyIcon,
[Operator.Wikipedia]: WikipediaIcon,
[Operator.YahooFinance]: YahooFinanceIcon,
[Operator.WenCai]: WenCaiIcon,
[Operator.Crawler]: CrawlerIcon,
};
const Empty = () => {
return <div className="hidden"></div>;
};
const OperatorIcon = ({ name, className }: IProps) => {
const Icon = OperatorIconMap[name as keyof typeof OperatorIconMap] || Empty;
const SvgIcon = SVGIconMap[name as keyof typeof SVGIconMap] || Empty;
if (name === Operator.Begin) {
return (
<div
className={cn(
'inline-block p-1 bg-accent-primary rounded-sm',
className,
)}
>
<HousePlus className="rounded size-3" />
</div>
);
}
return typeof Icon === 'string' ? (
<IconFont name={Icon} className={cn('size-5 ', className)}></IconFont>
) : (
<SvgIcon className={cn('size-5 fill-current', className)}></SvgIcon>
);
};
export default OperatorIcon;