mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-26 01:52:16 +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:
@@ -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