mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-19 06:01:05 +08:00
Feat(browser control):Add new agent component 'browser' to control browser by AI (#14888)
### What problem does this PR solve? This PR adds a new `Browser` operator to Agent workflows, enabling prompt-driven browser automation in RAGFlow.Technically based ‘Browser-Use’ It includes: - Backend browser component execution with tenant LLM integration - Upload source support (file IDs, URLs, variables, CSV/JSON array) - Downloaded file persistence to RAGFlow storage - Frontend node/operator integration, form config, icon, and i18n updates - Unit tests for upload/download and ID parsing logic - Dependency and Docker updates for browser-use runtime support ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@@ -109,6 +109,7 @@ export enum Operator {
|
||||
StringTransform = 'StringTransform',
|
||||
SearXNG = 'SearXNG',
|
||||
DocGenerator = 'DocGenerator',
|
||||
Browser = 'Browser',
|
||||
Placeholder = 'Placeholder',
|
||||
DataOperations = 'DataOperations',
|
||||
ListOperations = 'ListOperations',
|
||||
|
||||
@@ -1836,6 +1836,20 @@ Best for: Documents with flowing, contextually connected content — such as boo
|
||||
maxRounds: 'Max reflection rounds',
|
||||
delayAfterError: 'Delay after error',
|
||||
maxRetries: 'Max retry rounds',
|
||||
maxSteps: 'Max steps',
|
||||
headless: 'Headless',
|
||||
enableDefaultExtensions: 'Enable default extensions',
|
||||
enableDefaultExtensionsTip:
|
||||
'Enable browser-use default extensions (uBlock, cookie handling, ClearURLs). Disable this to avoid runtime extension downloads.',
|
||||
chromiumSandbox: 'Chromium sandbox',
|
||||
chromiumSandboxTip:
|
||||
'Whether to enable Chromium sandbox. In Docker root environments this is usually disabled; on regular hosts it is recommended to enable.',
|
||||
persistSession: 'Persist session',
|
||||
persistSessionTip:
|
||||
'When enabled, this Browser node reuses its browser session to avoid repeated logins.',
|
||||
uploadSources: 'Upload sources',
|
||||
uploadSourcesTip:
|
||||
'Supports file IDs, file URLs, or variables. You can separate multiple values with commas or use a JSON array format (for example ["id1","https://example.com/a.pdf"]).',
|
||||
advancedSettings: 'Advanced settings',
|
||||
addTools: 'Add tools',
|
||||
sysPromptDefaultValue: `
|
||||
@@ -1938,6 +1952,9 @@ Best for: Documents with flowing, contextually connected content — such as boo
|
||||
'A component that searches via your provided SearXNG instance URL. Specify TopN and the instance URL.',
|
||||
docGenerator: 'Doc Generator',
|
||||
docGeneratorDescription: `Generate a file from Markdown content.`,
|
||||
browser: 'Browser',
|
||||
browserDescription:
|
||||
'Automate browser tasks. Supports model configuration and prompt-driven actions. Upload sources support file IDs and URLs, and downloaded files can be saved to a target folder.',
|
||||
subtitle: 'Subtitle',
|
||||
logoImage: 'Logo Image',
|
||||
logoPosition: 'Logo Position',
|
||||
|
||||
@@ -208,16 +208,19 @@ export default {
|
||||
'Geçersiz beceri: "name" küçük harfli ve URL uyumlu olmalıdır (yalnızca harf, rakam ve tire).',
|
||||
invalid_version:
|
||||
'Geçersiz beceri: "version" geçerli semver olmalıdır (örn. 1.0.0).',
|
||||
invalid_metadata: 'Geçersiz beceri: meta veriler geçersiz alanlar içeriyor.',
|
||||
invalid_metadata:
|
||||
'Geçersiz beceri: meta veriler geçersiz alanlar içeriyor.',
|
||||
invalid_file_type:
|
||||
'Geçersiz beceri: Yalnızca metin tabanlı dosyalara izin verilir.',
|
||||
invalid_path: 'Geçersiz beceri: Dosya yolu geçersiz karakterler içeriyor.',
|
||||
invalid_path:
|
||||
'Geçersiz beceri: Dosya yolu geçersiz karakterler içeriyor.',
|
||||
file_too_large:
|
||||
'Geçersiz beceri: Tekil dosya boyutu 5MB sınırını aşıyor.',
|
||||
total_size_exceeded:
|
||||
'Geçersiz beceri: Toplam paket boyutu 50MB sınırını aşıyor.',
|
||||
no_files: 'Dosya seçilmedi. Lütfen bir beceri klasörü seçin.',
|
||||
noValidFiles: 'Geçerli dosya bulunamadı. Lütfen seçiminizi kontrol edin.',
|
||||
noValidFiles:
|
||||
'Geçerli dosya bulunamadı. Lütfen seçiminizi kontrol edin.',
|
||||
junkFilesFound:
|
||||
'Geçici dosyalar algılandı (örn. .DS_Store). Lütfen yüklemeden önce bunları kaldırın.',
|
||||
read_failed: 'Geçersiz beceri: SKILL.md dosyası okunamadı.',
|
||||
@@ -232,8 +235,8 @@ export default {
|
||||
upload: 'Yükle',
|
||||
importFromGit: "Git'ten içe aktar",
|
||||
gitPlatform: 'Platform',
|
||||
repoUrl: 'Depo URL\'si',
|
||||
repoUrlHelp: 'İsteğe bağlı yol içeren depo URL\'sini destekler',
|
||||
repoUrl: "Depo URL'si",
|
||||
repoUrlHelp: "İsteğe bağlı yol içeren depo URL'sini destekler",
|
||||
accessToken: 'Erişim Tokenı',
|
||||
githubTokenHelp:
|
||||
'Özel depolar veya daha yüksek hız limitleri için (5000 istek/saat)',
|
||||
@@ -250,7 +253,8 @@ export default {
|
||||
},
|
||||
skillSearch: {
|
||||
configTitle: 'Beceri Arama Yapılandırması',
|
||||
configDesc: 'Becerilerin nasıl indeksleneceğini ve aranacağını yapılandırın',
|
||||
configDesc:
|
||||
'Becerilerin nasıl indeksleneceğini ve aranacağını yapılandırın',
|
||||
embeddingModel: 'Embedding Modeli',
|
||||
embeddingModelPlaceholder: 'Bir embedding modeli seçin',
|
||||
vectorSimilarityWeight: 'Vektör Benzerlik Ağırlığı',
|
||||
|
||||
@@ -1529,6 +1529,20 @@ NER:使用 spaCy NER 和基于规则的关键词提取来抽取实体和关系
|
||||
maxRounds: '最大反思轮数',
|
||||
delayAfterError: '错误后延迟',
|
||||
maxRetries: '最大重试轮数',
|
||||
maxSteps: '最大步数',
|
||||
headless: '无头模式',
|
||||
enableDefaultExtensions: '启用默认扩展',
|
||||
enableDefaultExtensionsTip:
|
||||
'启用 browser-use 默认扩展(uBlock、Cookie 处理、ClearURLs)。关闭后可避免运行时下载扩展。',
|
||||
chromiumSandbox: 'Chromium 沙箱',
|
||||
chromiumSandboxTip:
|
||||
'是否启用 Chromium 沙箱。Docker root 环境通常需要关闭,普通宿主机环境建议开启。',
|
||||
persistSession: '持久化登录态',
|
||||
persistSessionTip:
|
||||
'开启后将复用该 Browser 节点的浏览器会话,避免重复登录。',
|
||||
uploadSources: '上传来源',
|
||||
uploadSourcesTip:
|
||||
'支持文件 ID、文件 URL 或变量。可用逗号分隔多个值,也支持 JSON 数组格式(如 ["id1","https://example.com/a.pdf"])。',
|
||||
advancedSettings: '高级设置',
|
||||
addTools: '添加工具',
|
||||
sysPromptDefultValue: `
|
||||
@@ -1643,6 +1657,9 @@ NER:使用 spaCy NER 和基于规则的关键词提取来抽取实体和关系
|
||||
'该组件通过您提供的 SearXNG 实例地址进行搜索。请设置 Top N 和实例 URL。',
|
||||
docGenerator: '文档生成器',
|
||||
docGeneratorDescription: `从 Markdown 内容生成文件。`,
|
||||
browser: 'Browser',
|
||||
browserDescription:
|
||||
'使用 Browser 自动执行浏览器任务。支持模型与提示词配置,上传来源支持文件 ID 与 URL,并可将下载文件保存到指定目录。',
|
||||
subtitle: '副标题',
|
||||
logoImage: '标志图片',
|
||||
logoPosition: '标志位置',
|
||||
|
||||
@@ -123,6 +123,7 @@ export function AccordionOperators({
|
||||
Operator.WenCai,
|
||||
Operator.SearXNG,
|
||||
Operator.DocGenerator,
|
||||
Operator.Browser,
|
||||
]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
|
||||
@@ -698,6 +698,7 @@ export const RestrictedUpstreamMap = {
|
||||
[Operator.LoopStart]: [Operator.Begin],
|
||||
[Operator.ExitLoop]: [Operator.Begin],
|
||||
[Operator.DocGenerator]: [Operator.Begin],
|
||||
[Operator.Browser]: [Operator.Begin],
|
||||
};
|
||||
|
||||
export const NodeMap = {
|
||||
@@ -749,6 +750,7 @@ export const NodeMap = {
|
||||
[Operator.ExitLoop]: 'exitLoopNode',
|
||||
[Operator.ExcelProcessor]: 'ragNode',
|
||||
[Operator.DocGenerator]: 'ragNode',
|
||||
[Operator.Browser]: 'ragNode',
|
||||
};
|
||||
|
||||
export enum BeginQueryType {
|
||||
@@ -980,6 +982,21 @@ export const initialDocGeneratorValues = {
|
||||
},
|
||||
};
|
||||
|
||||
export const initialBrowserValues = {
|
||||
...initialLlmBaseValues,
|
||||
prompts: `{${AgentGlobals.SysQuery}}`,
|
||||
max_steps: 30,
|
||||
headless: true,
|
||||
enable_default_extensions: false,
|
||||
chromium_sandbox: false,
|
||||
persist_session: true,
|
||||
upload_sources: '',
|
||||
outputs: {
|
||||
content: { type: 'string', value: '' },
|
||||
downloaded_files: { type: 'Array<Object>', value: [] },
|
||||
},
|
||||
};
|
||||
|
||||
export enum WebhookMethod {
|
||||
Post = 'POST',
|
||||
Get = 'GET',
|
||||
|
||||
@@ -3,6 +3,7 @@ import AgentForm from '../form/agent-form';
|
||||
import ArXivForm from '../form/arxiv-form';
|
||||
import BeginForm from '../form/begin-form';
|
||||
import BingForm from '../form/bing-form';
|
||||
import BrowserForm from '../form/browser-use-form';
|
||||
import CategorizeForm from '../form/categorize-form';
|
||||
import CodeForm from '../form/code-form';
|
||||
import CrawlerForm from '../form/crawler-form';
|
||||
@@ -114,6 +115,9 @@ export const FormConfigMap = {
|
||||
[Operator.DocGenerator]: {
|
||||
component: DocGeneratorForm,
|
||||
},
|
||||
[Operator.Browser]: {
|
||||
component: BrowserForm,
|
||||
},
|
||||
[Operator.Note]: {
|
||||
component: () => <></>,
|
||||
},
|
||||
|
||||
117
web/src/pages/agent/form/browser-use-form/index.tsx
Normal file
117
web/src/pages/agent/form/browser-use-form/index.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import { NextLLMSelect } from '@/components/llm-select/next';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { NumberInput } from '@/components/ui/input';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { memo } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
import { initialBrowserValues } from '../../constant';
|
||||
import { useFormValues } from '../../hooks/use-form-values';
|
||||
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import { FormWrapper } from '../components/form-wrapper';
|
||||
import { PromptEditor } from '../components/prompt-editor';
|
||||
|
||||
const FormSchema = z.object({
|
||||
llm_id: z.string(),
|
||||
prompts: z.string(),
|
||||
max_steps: z.coerce.number().min(1),
|
||||
headless: z.boolean(),
|
||||
enable_default_extensions: z.boolean(),
|
||||
chromium_sandbox: z.boolean(),
|
||||
persist_session: z.boolean(),
|
||||
upload_sources: z.string().optional(),
|
||||
});
|
||||
|
||||
type FormSchemaType = z.infer<typeof FormSchema>;
|
||||
|
||||
function BrowserForm({ node }: INextOperatorForm) {
|
||||
const { t } = useTranslation();
|
||||
const defaultValues = useFormValues(initialBrowserValues, node);
|
||||
const form = useForm<FormSchemaType>({
|
||||
defaultValues,
|
||||
resolver: zodResolver(FormSchema),
|
||||
mode: 'onChange',
|
||||
});
|
||||
|
||||
useWatchFormChange(node?.id, form);
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<FormWrapper>
|
||||
<RAGFlowFormItem label={t('chat.model')} name="llm_id">
|
||||
<NextLLMSelect></NextLLMSelect>
|
||||
</RAGFlowFormItem>
|
||||
<RAGFlowFormItem label={t('flow.userPrompt')} name="prompts">
|
||||
<PromptEditor showToolbar={true}></PromptEditor>
|
||||
</RAGFlowFormItem>
|
||||
<RAGFlowFormItem label={t('flow.maxSteps')} name="max_steps">
|
||||
{(field) => <NumberInput min={1} {...field}></NumberInput>}
|
||||
</RAGFlowFormItem>
|
||||
<RAGFlowFormItem label={t('flow.headless')} name="headless">
|
||||
{(field) => (
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
></Switch>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
<RAGFlowFormItem
|
||||
label={t('flow.enableDefaultExtensions')}
|
||||
tooltip={t('flow.enableDefaultExtensionsTip')}
|
||||
name="enable_default_extensions"
|
||||
>
|
||||
{(field) => (
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
></Switch>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
<RAGFlowFormItem
|
||||
label={t('flow.chromiumSandbox')}
|
||||
tooltip={t('flow.chromiumSandboxTip')}
|
||||
name="chromium_sandbox"
|
||||
>
|
||||
{(field) => (
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
></Switch>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
<RAGFlowFormItem
|
||||
label={t('flow.persistSession')}
|
||||
tooltip={t('flow.persistSessionTip')}
|
||||
name="persist_session"
|
||||
>
|
||||
{(field) => (
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
></Switch>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
<RAGFlowFormItem
|
||||
label={t('flow.uploadSources')}
|
||||
tooltip={t('flow.uploadSourcesTip')}
|
||||
name="upload_sources"
|
||||
>
|
||||
{(field) => (
|
||||
<PromptEditor
|
||||
{...field}
|
||||
showToolbar
|
||||
multiLine={false}
|
||||
placeholder="file_id,https://example.com/a.pdf,{node@files.0.id}"
|
||||
></PromptEditor>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
</FormWrapper>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(BrowserForm);
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
initialArXivValues,
|
||||
initialBeginValues,
|
||||
initialBingValues,
|
||||
initialBrowserValues,
|
||||
initialCategorizeValues,
|
||||
initialCodeValues,
|
||||
initialCrawlerValues,
|
||||
@@ -181,6 +182,7 @@ export const useInitializeOperatorParams = () => {
|
||||
[Operator.LoopStart]: {},
|
||||
[Operator.ExitLoop]: {},
|
||||
[Operator.DocGenerator]: initialDocGeneratorValues,
|
||||
[Operator.Browser]: { ...initialBrowserValues, llm_id: llmId },
|
||||
[Operator.ExcelProcessor]: {},
|
||||
};
|
||||
}, [llmId]);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { cn } from '@/lib/utils';
|
||||
import {
|
||||
FileCode,
|
||||
FileText,
|
||||
Globe,
|
||||
HousePlus,
|
||||
Infinity as InfinityIcon,
|
||||
LogOut,
|
||||
@@ -57,6 +58,7 @@ export const LucideIconMap = {
|
||||
[Operator.Loop]: InfinityIcon,
|
||||
[Operator.ExitLoop]: LogOut,
|
||||
[Operator.DocGenerator]: FileText,
|
||||
[Operator.Browser]: Globe,
|
||||
};
|
||||
|
||||
const Empty = () => {
|
||||
|
||||
@@ -132,7 +132,11 @@ const ChunkCreatingModal: React.FC<IModalProps<any> & kFProps> = ({
|
||||
<FormItem>
|
||||
<FormLabel>{t('chunk.chunk')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea {...field} autoSize={{ minRows: 4, maxRows: 10 }} resize="vertical" />
|
||||
<Textarea
|
||||
{...field}
|
||||
autoSize={{ minRows: 4, maxRows: 10 }}
|
||||
resize="vertical"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
Reference in New Issue
Block a user