mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-13 23:45:56 +08:00
### What problem does this PR solve? Feat: Add AgentNode component #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
parent
b6f1cd7809
commit
e47186cc42
@ -152,6 +152,7 @@ export type IIterationNode = BaseNode;
|
|||||||
export type IIterationStartNode = BaseNode;
|
export type IIterationStartNode = BaseNode;
|
||||||
export type IKeywordNode = BaseNode;
|
export type IKeywordNode = BaseNode;
|
||||||
export type ICodeNode = BaseNode<ICodeForm>;
|
export type ICodeNode = BaseNode<ICodeForm>;
|
||||||
|
export type IAgentNode = BaseNode;
|
||||||
|
|
||||||
export type RAGFlowNodeType =
|
export type RAGFlowNodeType =
|
||||||
| IBeginNode
|
| IBeginNode
|
||||||
|
@ -19,6 +19,7 @@ import { useShowDrawer } from '../hooks/use-show-drawer';
|
|||||||
import { ButtonEdge } from './edge';
|
import { ButtonEdge } from './edge';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
import { RagNode } from './node';
|
import { RagNode } from './node';
|
||||||
|
import { AgentNode } from './node/agent-node';
|
||||||
import { BeginNode } from './node/begin-node';
|
import { BeginNode } from './node/begin-node';
|
||||||
import { CategorizeNode } from './node/categorize-node';
|
import { CategorizeNode } from './node/categorize-node';
|
||||||
import { EmailNode } from './node/email-node';
|
import { EmailNode } from './node/email-node';
|
||||||
@ -53,6 +54,7 @@ const nodeTypes: NodeTypes = {
|
|||||||
emailNode: EmailNode,
|
emailNode: EmailNode,
|
||||||
group: IterationNode,
|
group: IterationNode,
|
||||||
iterationStartNode: IterationStartNode,
|
iterationStartNode: IterationStartNode,
|
||||||
|
agentNode: AgentNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
const edgeTypes = {
|
const edgeTypes = {
|
||||||
|
48
web/src/pages/agent/canvas/node/agent-node.tsx
Normal file
48
web/src/pages/agent/canvas/node/agent-node.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { useTheme } from '@/components/theme-provider';
|
||||||
|
import { IAgentNode } from '@/interfaces/database/flow';
|
||||||
|
import { Handle, NodeProps, Position } from '@xyflow/react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||||
|
import styles from './index.less';
|
||||||
|
import NodeHeader from './node-header';
|
||||||
|
|
||||||
|
function InnerAgentNode({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
isConnectable = true,
|
||||||
|
selected,
|
||||||
|
}: NodeProps<IAgentNode>) {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
className={classNames(
|
||||||
|
styles.ragNode,
|
||||||
|
theme === 'dark' ? styles.dark : '',
|
||||||
|
{
|
||||||
|
[styles.selectedNode]: selected,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Handle
|
||||||
|
id="c"
|
||||||
|
type="source"
|
||||||
|
position={Position.Left}
|
||||||
|
isConnectable={isConnectable}
|
||||||
|
className={styles.handle}
|
||||||
|
style={LeftHandleStyle}
|
||||||
|
></Handle>
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
position={Position.Right}
|
||||||
|
isConnectable={isConnectable}
|
||||||
|
className={styles.handle}
|
||||||
|
id="b"
|
||||||
|
style={RightHandleStyle}
|
||||||
|
></Handle>
|
||||||
|
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AgentNode = memo(InnerAgentNode);
|
@ -59,6 +59,7 @@ import {
|
|||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import upperFirst from 'lodash/upperFirst';
|
import upperFirst from 'lodash/upperFirst';
|
||||||
import {
|
import {
|
||||||
|
Box,
|
||||||
CirclePower,
|
CirclePower,
|
||||||
CloudUpload,
|
CloudUpload,
|
||||||
CodeXml,
|
CodeXml,
|
||||||
@ -112,6 +113,7 @@ export enum Operator {
|
|||||||
IterationStart = 'IterationItem',
|
IterationStart = 'IterationItem',
|
||||||
Code = 'Code',
|
Code = 'Code',
|
||||||
WaitingDialogue = 'WaitingDialogue',
|
WaitingDialogue = 'WaitingDialogue',
|
||||||
|
Agent = 'Agent',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CommonOperatorList = Object.values(Operator).filter(
|
export const CommonOperatorList = Object.values(Operator).filter(
|
||||||
@ -132,6 +134,7 @@ export const AgentOperatorList = [
|
|||||||
Operator.Iteration,
|
Operator.Iteration,
|
||||||
Operator.WaitingDialogue,
|
Operator.WaitingDialogue,
|
||||||
Operator.Note,
|
Operator.Note,
|
||||||
|
Operator.Agent,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const operatorIconMap = {
|
export const operatorIconMap = {
|
||||||
@ -173,6 +176,7 @@ export const operatorIconMap = {
|
|||||||
[Operator.IterationStart]: CirclePower,
|
[Operator.IterationStart]: CirclePower,
|
||||||
[Operator.Code]: CodeXml,
|
[Operator.Code]: CodeXml,
|
||||||
[Operator.WaitingDialogue]: MessageSquareMore,
|
[Operator.WaitingDialogue]: MessageSquareMore,
|
||||||
|
[Operator.Agent]: Box,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const operatorMap: Record<
|
export const operatorMap: Record<
|
||||||
@ -313,6 +317,7 @@ export const operatorMap: Record<
|
|||||||
[Operator.IterationStart]: { backgroundColor: '#e6f7ff' },
|
[Operator.IterationStart]: { backgroundColor: '#e6f7ff' },
|
||||||
[Operator.Code]: { backgroundColor: '#4c5458' },
|
[Operator.Code]: { backgroundColor: '#4c5458' },
|
||||||
[Operator.WaitingDialogue]: { backgroundColor: '#a5d65c' },
|
[Operator.WaitingDialogue]: { backgroundColor: '#a5d65c' },
|
||||||
|
[Operator.Agent]: { backgroundColor: '#a5d65c' },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const componentMenuList = [
|
export const componentMenuList = [
|
||||||
@ -356,6 +361,9 @@ export const componentMenuList = [
|
|||||||
{
|
{
|
||||||
name: Operator.WaitingDialogue,
|
name: Operator.WaitingDialogue,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: Operator.Agent,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: Operator.Note,
|
name: Operator.Note,
|
||||||
},
|
},
|
||||||
@ -682,6 +690,14 @@ export const initialCodeValues = {
|
|||||||
|
|
||||||
export const initialWaitingDialogueValues = {};
|
export const initialWaitingDialogueValues = {};
|
||||||
|
|
||||||
|
export const initialAgentValues = {
|
||||||
|
...initialLlmBaseValues,
|
||||||
|
sys_prompt: ``,
|
||||||
|
prompts: [],
|
||||||
|
message_history_window_size: 12,
|
||||||
|
tools: [],
|
||||||
|
};
|
||||||
|
|
||||||
export const CategorizeAnchorPointPositions = [
|
export const CategorizeAnchorPointPositions = [
|
||||||
{ top: 1, right: 34 },
|
{ top: 1, right: 34 },
|
||||||
{ top: 8, right: 18 },
|
{ top: 8, right: 18 },
|
||||||
@ -806,6 +822,7 @@ export const NodeMap = {
|
|||||||
[Operator.IterationStart]: 'iterationStartNode',
|
[Operator.IterationStart]: 'iterationStartNode',
|
||||||
[Operator.Code]: 'ragNode',
|
[Operator.Code]: 'ragNode',
|
||||||
[Operator.WaitingDialogue]: 'ragNode',
|
[Operator.WaitingDialogue]: 'ragNode',
|
||||||
|
[Operator.Agent]: 'agentNode',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LanguageOptions = [
|
export const LanguageOptions = [
|
||||||
|
@ -3,6 +3,7 @@ import { CodeTemplateStrMap, ProgrammingLanguage } from '@/constants/agent';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { Operator } from '../constant';
|
import { Operator } from '../constant';
|
||||||
|
import AgentForm from '../form/agent-form';
|
||||||
import AkShareForm from '../form/akshare-form';
|
import AkShareForm from '../form/akshare-form';
|
||||||
import AnswerForm from '../form/answer-form';
|
import AnswerForm from '../form/answer-form';
|
||||||
import ArXivForm from '../form/arxiv-form';
|
import ArXivForm from '../form/arxiv-form';
|
||||||
@ -192,6 +193,11 @@ export function useFormConfigMap() {
|
|||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
[Operator.Agent]: {
|
||||||
|
component: AgentForm,
|
||||||
|
defaultValues: {},
|
||||||
|
schema: z.object({}),
|
||||||
|
},
|
||||||
[Operator.Baidu]: {
|
[Operator.Baidu]: {
|
||||||
component: BaiduForm,
|
component: BaiduForm,
|
||||||
defaultValues: { top_n: 10 },
|
defaultValues: { top_n: 10 },
|
||||||
|
74
web/src/pages/agent/form/agent-form/index.tsx
Normal file
74
web/src/pages/agent/form/agent-form/index.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { FormContainer } from '@/components/form-container';
|
||||||
|
import { LargeModelFormField } from '@/components/large-model-form-field';
|
||||||
|
import { LlmSettingSchema } from '@/components/llm-setting-items/next';
|
||||||
|
import { PromptEditor } from '@/components/prompt-editor';
|
||||||
|
import { Form, FormControl, FormField, FormItem } from '@/components/ui/form';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { initialAgentValues } from '../../constant';
|
||||||
|
import { useFormValues } from '../../hooks/use-form-values';
|
||||||
|
import { INextOperatorForm } from '../../interface';
|
||||||
|
|
||||||
|
const FormSchema = z.object({
|
||||||
|
sys_prompt: z.string(),
|
||||||
|
prompts: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
role: z.string(),
|
||||||
|
content: z.string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
message_history_window_size: z.coerce.number(),
|
||||||
|
...LlmSettingSchema,
|
||||||
|
tools: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
component_name: z.string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const AgentForm = ({ node }: INextOperatorForm) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const defaultValues = useFormValues(initialAgentValues, node);
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
defaultValues: defaultValues,
|
||||||
|
resolver: zodResolver(FormSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
className="space-y-6 p-4"
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormContainer>
|
||||||
|
<LargeModelFormField></LargeModelFormField>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name={`sys_prompt`}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex-1">
|
||||||
|
<FormControl>
|
||||||
|
<PromptEditor
|
||||||
|
{...field}
|
||||||
|
placeholder={t('flow.messagePlaceholder')}
|
||||||
|
></PromptEditor>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormContainer>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AgentForm;
|
@ -1,6 +1,5 @@
|
|||||||
import { FormContainer } from '@/components/form-container';
|
import { FormContainer } from '@/components/form-container';
|
||||||
import { KnowledgeBaseFormField } from '@/components/knowledge-base-item';
|
import { KnowledgeBaseFormField } from '@/components/knowledge-base-item';
|
||||||
import { LargeModelFormField } from '@/components/large-model-form-field';
|
|
||||||
import { RerankFormFields } from '@/components/rerank';
|
import { RerankFormFields } from '@/components/rerank';
|
||||||
import {
|
import {
|
||||||
initialKeywordsSimilarityWeightValue,
|
initialKeywordsSimilarityWeightValue,
|
||||||
@ -64,7 +63,7 @@ const RetrievalForm = ({ node }: INextOperatorForm) => {
|
|||||||
>
|
>
|
||||||
<FormContainer>
|
<FormContainer>
|
||||||
<QueryVariable></QueryVariable>
|
<QueryVariable></QueryVariable>
|
||||||
<LargeModelFormField></LargeModelFormField>
|
<KnowledgeBaseFormField></KnowledgeBaseFormField>
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
<FormContainer>
|
<FormContainer>
|
||||||
<SimilaritySliderFormField
|
<SimilaritySliderFormField
|
||||||
@ -73,7 +72,7 @@ const RetrievalForm = ({ node }: INextOperatorForm) => {
|
|||||||
></SimilaritySliderFormField>
|
></SimilaritySliderFormField>
|
||||||
<TopNFormField></TopNFormField>
|
<TopNFormField></TopNFormField>
|
||||||
<RerankFormFields></RerankFormFields>
|
<RerankFormFields></RerankFormFields>
|
||||||
<KnowledgeBaseFormField></KnowledgeBaseFormField>
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="empty_response"
|
name="empty_response"
|
||||||
|
@ -33,6 +33,7 @@ import {
|
|||||||
Operator,
|
Operator,
|
||||||
RestrictedUpstreamMap,
|
RestrictedUpstreamMap,
|
||||||
SwitchElseTo,
|
SwitchElseTo,
|
||||||
|
initialAgentValues,
|
||||||
initialAkShareValues,
|
initialAkShareValues,
|
||||||
initialArXivValues,
|
initialArXivValues,
|
||||||
initialBaiduFanyiValues,
|
initialBaiduFanyiValues,
|
||||||
@ -65,6 +66,7 @@ import {
|
|||||||
initialSwitchValues,
|
initialSwitchValues,
|
||||||
initialTemplateValues,
|
initialTemplateValues,
|
||||||
initialTuShareValues,
|
initialTuShareValues,
|
||||||
|
initialWaitingDialogueValues,
|
||||||
initialWenCaiValues,
|
initialWenCaiValues,
|
||||||
initialWikipediaValues,
|
initialWikipediaValues,
|
||||||
initialYahooFinanceValues,
|
initialYahooFinanceValues,
|
||||||
@ -143,6 +145,8 @@ export const useInitializeOperatorParams = () => {
|
|||||||
[Operator.Iteration]: initialIterationValues,
|
[Operator.Iteration]: initialIterationValues,
|
||||||
[Operator.IterationStart]: initialIterationValues,
|
[Operator.IterationStart]: initialIterationValues,
|
||||||
[Operator.Code]: initialCodeValues,
|
[Operator.Code]: initialCodeValues,
|
||||||
|
[Operator.WaitingDialogue]: initialWaitingDialogueValues,
|
||||||
|
[Operator.Agent]: initialAgentValues,
|
||||||
};
|
};
|
||||||
}, [llmId]);
|
}, [llmId]);
|
||||||
|
|
||||||
|
20
web/src/pages/agent/hooks/use-form-values.ts
Normal file
20
web/src/pages/agent/hooks/use-form-values.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export function useFormValues(
|
||||||
|
defaultValues: Record<string, any>,
|
||||||
|
node?: RAGFlowNodeType,
|
||||||
|
) {
|
||||||
|
const values = useMemo(() => {
|
||||||
|
const formData = node?.data?.form;
|
||||||
|
|
||||||
|
if (isEmpty(formData)) {
|
||||||
|
return defaultValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
return formData;
|
||||||
|
}, [defaultValues, node?.data?.form]);
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button, ButtonLoading } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@ -19,6 +19,7 @@ import FlowCanvas from './canvas';
|
|||||||
import { useHandleExportOrImportJsonFile } from './hooks/use-export-json';
|
import { useHandleExportOrImportJsonFile } from './hooks/use-export-json';
|
||||||
import { useFetchDataOnMount } from './hooks/use-fetch-data';
|
import { useFetchDataOnMount } from './hooks/use-fetch-data';
|
||||||
import { useOpenDocument } from './hooks/use-open-document';
|
import { useOpenDocument } from './hooks/use-open-document';
|
||||||
|
import { useSaveGraph } from './hooks/use-save-graph';
|
||||||
import { UploadAgentDialog } from './upload-agent-dialog';
|
import { UploadAgentDialog } from './upload-agent-dialog';
|
||||||
|
|
||||||
function AgentDropdownMenuItem({
|
function AgentDropdownMenuItem({
|
||||||
@ -48,6 +49,7 @@ export default function Agent() {
|
|||||||
onFileUploadOk,
|
onFileUploadOk,
|
||||||
hideFileUploadModal,
|
hideFileUploadModal,
|
||||||
} = useHandleExportOrImportJsonFile();
|
} = useHandleExportOrImportJsonFile();
|
||||||
|
const { saveGraph, loading } = useSaveGraph();
|
||||||
|
|
||||||
const { flowDetail } = useFetchDataOnMount();
|
const { flowDetail } = useFetchDataOnMount();
|
||||||
|
|
||||||
@ -55,6 +57,16 @@ export default function Agent() {
|
|||||||
<section>
|
<section>
|
||||||
<PageHeader back={navigateToAgentList} title={flowDetail.title}>
|
<PageHeader back={navigateToAgentList} title={flowDetail.title}>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<ButtonLoading
|
||||||
|
variant={'outline'}
|
||||||
|
onClick={() => saveGraph()}
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</ButtonLoading>
|
||||||
|
<Button variant={'outline'}>Run app</Button>
|
||||||
|
<Button variant={'outline'}>Publish</Button>
|
||||||
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant={'icon'} size={'icon'}>
|
<Button variant={'icon'} size={'icon'}>
|
||||||
@ -83,17 +95,6 @@ export default function Agent() {
|
|||||||
</AgentDropdownMenuItem>
|
</AgentDropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
||||||
<Button variant={'outline'} size={'sm'}>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
<Button variant={'outline'} size={'sm'}>
|
|
||||||
Run app
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button variant={'tertiary'} size={'sm'}>
|
|
||||||
Publish
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user