Bläddra i källkod

Feat: Create empty agent #3221 (#8054)

### What problem does this PR solve?

Feat: Create empty agent #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
tags/v0.19.1
balibabu 4 månader sedan
förälder
incheckning
c163b799d2
Inget konto är kopplat till bidragsgivarens mejladress

+ 159
- 1
web/src/hooks/use-agent-request.ts Visa fil

@@ -1,9 +1,17 @@
import { IFlow } from '@/interfaces/database/flow';
import { DSL, IFlow, IFlowTemplate } from '@/interfaces/database/flow';
import i18n from '@/locales/config';
import { BeginId } from '@/pages/agent/constant';
import { useGetSharedChatSearchParams } from '@/pages/chat/shared-hooks';
import flowService from '@/services/flow-service';
import { buildMessageListWithUuid } from '@/utils/chat';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useDebounce } from 'ahooks';
import { message } from 'antd';
import { get, set } from 'lodash';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'umi';
import { v4 as uuid } from 'uuid';
import {
useGetPaginationWithRouter,
useHandleSearchChange,
@@ -13,8 +21,77 @@ export const enum AgentApiAction {
FetchAgentList = 'fetchAgentList',
UpdateAgentSetting = 'updateAgentSetting',
DeleteAgent = 'deleteAgent',
FetchAgentDetail = 'fetchAgentDetail',
ResetAgent = 'resetAgent',
SetAgent = 'setAgent',
FetchAgentTemplates = 'fetchAgentTemplates',
}

export const EmptyDsl = {
graph: {
nodes: [
{
id: BeginId,
type: 'beginNode',
position: {
x: 50,
y: 200,
},
data: {
label: 'Begin',
name: 'begin',
},
sourcePosition: 'left',
targetPosition: 'right',
},
],
edges: [],
},
components: {
begin: {
obj: {
component_name: 'Begin',
params: {},
},
downstream: ['Answer:China'], // other edge target is downstream, edge source is current node id
upstream: [], // edge source is upstream, edge target is current node id
},
},
retrieval: [], // reference
history: [],
path: [],
globals: {
'sys.query': '',
'sys.user_id': '',
'sys.conversation_turns': 0,
'sys.files': [],
},
};

export const useFetchAgentTemplates = () => {
const { t } = useTranslation();

const { data } = useQuery<IFlowTemplate[]>({
queryKey: [AgentApiAction.FetchAgentTemplates],
initialData: [],
queryFn: async () => {
const { data } = await flowService.listTemplates();
if (Array.isArray(data?.data)) {
data.data.unshift({
id: uuid(),
title: t('flow.blank'),
description: t('flow.createFromNothing'),
dsl: EmptyDsl,
});
}

return data.data;
},
});

return data;
};

export const useFetchAgentListByPage = () => {
const { searchString, handleInputChange } = useHandleSearchChange();
const { pagination, setPagination } = useGetPaginationWithRouter();
@@ -109,3 +186,84 @@ export const useDeleteAgent = () => {

return { data, loading, deleteAgent: mutateAsync };
};

export const useFetchAgent = (): {
data: IFlow;
loading: boolean;
refetch: () => void;
} => {
const { id } = useParams();
const { sharedId } = useGetSharedChatSearchParams();

const {
data,
isFetching: loading,
refetch,
} = useQuery({
queryKey: [AgentApiAction.FetchAgentDetail],
initialData: {} as IFlow,
refetchOnReconnect: false,
refetchOnMount: false,
refetchOnWindowFocus: false,
gcTime: 0,
queryFn: async () => {
const { data } = await flowService.getCanvas({}, sharedId || id);

const messageList = buildMessageListWithUuid(
get(data, 'data.dsl.messages', []),
);
set(data, 'data.dsl.messages', messageList);

return data?.data ?? {};
},
});

return { data, loading, refetch };
};

export const useResetAgent = () => {
const { id } = useParams();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [AgentApiAction.ResetAgent],
mutationFn: async () => {
const { data } = await flowService.resetCanvas({ id });
return data;
},
});

return { data, loading, resetAgent: mutateAsync };
};

export const useSetAgent = () => {
const queryClient = useQueryClient();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [AgentApiAction.SetAgent],
mutationFn: async (params: {
id?: string;
title?: string;
dsl?: DSL;
avatar?: string;
}) => {
const { data = {} } = await flowService.setCanvas(params);
if (data.code === 0) {
message.success(
i18n.t(`message.${params?.id ? 'modified' : 'created'}`),
);
queryClient.invalidateQueries({
queryKey: [AgentApiAction.FetchAgentList],
});
}
return data;
},
});

return { data, loading, setAgent: mutateAsync };
};

+ 0
- 5
web/src/pages/agent/debug-content/index.less Visa fil

@@ -1,5 +0,0 @@
.formWrapper {
:global(.ant-form-item-label) {
font-weight: 600 !important;
}
}

+ 46
- 30
web/src/pages/agent/debug-content/index.tsx Visa fil

@@ -23,6 +23,21 @@ import { z } from 'zod';
import { BeginQueryType } from '../constant';
import { BeginQuery } from '../interface';

export const BeginQueryComponentMap = {
[BeginQueryType.Line]: 'string',
[BeginQueryType.Paragraph]: 'string',
[BeginQueryType.Options]: 'string',
[BeginQueryType.File]: 'file',
[BeginQueryType.Integer]: 'number',
[BeginQueryType.Boolean]: 'boolean',
};

const StringFields = [
BeginQueryType.Line,
BeginQueryType.Paragraph,
BeginQueryType.Options,
];

interface IProps {
parameters: BeginQuery[];
ok(parameters: any[]): void;
@@ -44,9 +59,27 @@ const DebugContent = ({

const FormSchema = useMemo(() => {
const obj = parameters.reduce((pre, cur, idx) => {
pre[idx] = z.string().optional();
const type = cur.type;
let fieldSchema;
if (StringFields.some((x) => x === type)) {
fieldSchema = z.string();
} else if (type === BeginQueryType.Boolean) {
fieldSchema = z.boolean();
} else if (type === BeginQueryType.Integer) {
fieldSchema = z.coerce.number();
} else {
fieldSchema = z.instanceof(File);
}

if (cur.optional) {
fieldSchema.optional();
}

pre[idx.toString()] = fieldSchema;

return pre;
}, {});

return z.object(obj);
}, [parameters]);

@@ -61,6 +94,7 @@ const DebugContent = ({
switchVisible,
showModal: showPopover,
} = useSetModalState();

const { setRecord, currentRecord } = useSetSelectedRecord<number>();
// const { submittable } = useHandleSubmittable(form);
const submittable = true;
@@ -226,29 +260,10 @@ const DebugContent = ({
[form, t],
);

const onOk = useCallback(async () => {
// const values = await form.validateFields();
const nextValues = Object.entries(values).map(([key, value]) => {
const item = parameters[Number(key)];
let nextValue = value;
if (Array.isArray(value)) {
nextValue = ``;

value.forEach((x) => {
nextValue +=
x?.originFileObj instanceof File
? `${x.name}\n${x.response?.data}\n----\n`
: `${x.url}\n${x.result}\n----\n`;
});
}
return { ...item, value: nextValue };
});

ok(nextValues);
}, [ok, parameters]);

const onSubmit = useCallback(
(values: z.infer<typeof FormSchema>) => {
console.log('🚀 ~ values:', values);
return values;
const nextValues = Object.entries(values).map(([key, value]) => {
const item = parameters[Number(key)];
let nextValue = value;
@@ -274,20 +289,21 @@ const DebugContent = ({
<>
<section>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
{parameters.map((x, idx) => {
return <div key={idx}>{renderWidget(x, idx.toString())}</div>;
})}
<ButtonLoading
type="submit"
loading={loading}
disabled={!submittable || isUploading || submitButtonDisabled}
className="w-full"
>
{t(isNext ? 'common.next' : 'flow.run')}
</ButtonLoading>
</form>
</Form>
</section>
<ButtonLoading
onClick={onOk}
loading={loading}
disabled={!submittable || isUploading || submitButtonDisabled}
>
{t(isNext ? 'common.next' : 'flow.run')}
</ButtonLoading>
</>
);
};

+ 0
- 1
web/src/pages/agent/form/begin-form/index.tsx Visa fil

@@ -191,7 +191,6 @@ const BeginForm = ({ node }: INextOperatorForm) => {

{visible && (
<ParameterDialog
visible={visible}
hideModal={hideModal}
initialValue={currentRecord}
onOk={ok}

+ 9
- 4
web/src/pages/agent/form/begin-form/parameter-dialog.tsx Visa fil

@@ -19,6 +19,7 @@ import { RAGFlowSelect, RAGFlowSelectOptionType } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { IModalProps } from '@/interfaces/common';
import { zodResolver } from '@hookform/resolvers/zod';
import { isEmpty } from 'lodash';
import { useEffect, useMemo } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
@@ -60,11 +61,13 @@ function ParameterForm({

const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
mode: 'onChange',
defaultValues: {
type: BeginQueryType.Line,
optional: false,
key: '',
name: '',
options: [],
},
});

@@ -98,10 +101,12 @@ function ParameterForm({
});

useEffect(() => {
form.reset({
...initialValue,
options: initialValue.options?.map((x) => ({ value: x })),
});
if (!isEmpty(initialValue)) {
form.reset({
...initialValue,
options: initialValue.options?.map((x) => ({ value: x })),
});
}
}, [form, initialValue]);

function onSubmit(data: z.infer<typeof FormSchema>) {

+ 13
- 9
web/src/pages/agent/hooks/use-save-graph.ts Visa fil

@@ -1,4 +1,8 @@
import { useFetchFlow, useResetFlow, useSetFlow } from '@/hooks/flow-hooks';
import {
useFetchAgent,
useResetAgent,
useSetAgent,
} from '@/hooks/use-agent-request';
import { RAGFlowNodeType } from '@/interfaces/database/flow';
import { useDebounceEffect } from 'ahooks';
import dayjs from 'dayjs';
@@ -8,20 +12,20 @@ import useGraphStore from '../store';
import { useBuildDslData } from './use-build-dsl';

export const useSaveGraph = () => {
const { data } = useFetchFlow();
const { setFlow, loading } = useSetFlow();
const { data } = useFetchAgent();
const { setAgent, loading } = useSetAgent();
const { id } = useParams();
const { buildDslData } = useBuildDslData();

const saveGraph = useCallback(
async (currentNodes?: RAGFlowNodeType[]) => {
return setFlow({
return setAgent({
id,
title: data.title,
dsl: buildDslData(currentNodes),
});
},
[setFlow, id, data.title, buildDslData],
[setAgent, id, data.title, buildDslData],
);

return { saveGraph, loading };
@@ -29,21 +33,21 @@ export const useSaveGraph = () => {

export const useSaveGraphBeforeOpeningDebugDrawer = (show: () => void) => {
const { saveGraph, loading } = useSaveGraph();
const { resetFlow } = useResetFlow();
const { resetAgent } = useResetAgent();

const handleRun = useCallback(
async (nextNodes?: RAGFlowNodeType[]) => {
const saveRet = await saveGraph(nextNodes);
if (saveRet?.code === 0) {
// Call the reset api before opening the run drawer each time
const resetRet = await resetFlow();
const resetRet = await resetAgent();
// After resetting, all previous messages will be cleared.
if (resetRet?.code === 0) {
show();
}
}
},
[saveGraph, resetFlow, show],
[saveGraph, resetAgent, show],
);

return { handleRun, loading };
@@ -54,7 +58,7 @@ export const useWatchAgentChange = (chatDrawerVisible: boolean) => {
const nodes = useGraphStore((state) => state.nodes);
const edges = useGraphStore((state) => state.edges);
const { saveGraph } = useSaveGraph();
const { data: flowDetail } = useFetchFlow();
const { data: flowDetail } = useFetchAgent();

const setSaveTime = useCallback((updateTime: number) => {
setTime(dayjs(updateTime).format('YYYY-MM-DD HH:mm:ss'));

+ 42
- 8
web/src/pages/agents/agent-templates.tsx Visa fil

@@ -1,8 +1,9 @@
import { PageHeader } from '@/components/page-header';
import { useSetModalState } from '@/hooks/common-hooks';
import { useFetchFlowTemplates } from '@/hooks/flow-hooks';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { useCallback } from 'react';
import { useFetchAgentTemplates, useSetAgent } from '@/hooks/use-agent-request';
import { IFlowTemplate } from '@/interfaces/database/flow';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { CreateAgentDialog } from './create-agent-dialog';
import { TemplateCard } from './template-card';
@@ -10,16 +11,49 @@ import { TemplateCard } from './template-card';
export default function AgentTemplates() {
const { navigateToAgentList } = useNavigatePage();
const { t } = useTranslation();
const { data: list } = useFetchFlowTemplates();
const list = useFetchAgentTemplates();
const { loading, setAgent } = useSetAgent();

const {
visible: creatingVisible,
hideModal: hideCreatingModal,
showModal: showCreatingModal,
} = useSetModalState();

const handleOk = useCallback(async () => {
// return onOk(name, checkedId);
}, []);
const [template, setTemplate] = useState<IFlowTemplate>();

const showModal = useCallback(
(record: IFlowTemplate) => {
setTemplate(record);
showCreatingModal();
},
[showCreatingModal],
);

const { navigateToAgent } = useNavigatePage();

const handleOk = useCallback(
async (payload: any) => {
let dsl = template?.dsl;
const ret = await setAgent({
title: payload.name,
dsl,
avatar: template?.avatar,
});

if (ret?.code === 0) {
hideCreatingModal();
navigateToAgent(ret.data.id)();
}
},
[
hideCreatingModal,
navigateToAgent,
setAgent,
template?.avatar,
template?.dsl,
],
);

return (
<section>
@@ -33,14 +67,14 @@ export default function AgentTemplates() {
<TemplateCard
key={x.id}
data={x}
showModal={showCreatingModal}
showModal={showModal}
></TemplateCard>
);
})}
</div>
{creatingVisible && (
<CreateAgentDialog
loading={false}
loading={loading}
visible={creatingVisible}
hideModal={hideCreatingModal}
onOk={handleOk}

+ 3
- 3
web/src/pages/agents/create-agent-dialog.tsx Visa fil

@@ -1,3 +1,4 @@
import { ButtonLoading } from '@/components/ui/button';
import {
Dialog,
DialogContent,
@@ -5,7 +6,6 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { LoadingButton } from '@/components/ui/loading-button';
import { IModalProps } from '@/interfaces/common';
import { TagRenameId } from '@/pages/add-knowledge/constant';
import { useTranslation } from 'react-i18next';
@@ -26,9 +26,9 @@ export function CreateAgentDialog({
</DialogHeader>
<CreateAgentForm hideModal={hideModal} onOk={onOk}></CreateAgentForm>
<DialogFooter>
<LoadingButton type="submit" form={TagRenameId} loading={loading}>
<ButtonLoading type="submit" form={TagRenameId} loading={loading}>
{t('common.save')}
</LoadingButton>
</ButtonLoading>
</DialogFooter>
</DialogContent>
</Dialog>

+ 9
- 6
web/src/pages/agents/template-card.tsx Visa fil

@@ -1,19 +1,22 @@
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { useSetModalState } from '@/hooks/common-hooks';
import { IFlowTemplate } from '@/interfaces/database/flow';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';

interface IProps {
data: IFlowTemplate;
showModal(record: IFlowTemplate): void;
}

export function TemplateCard({
data,
showModal,
}: IProps & Pick<ReturnType<typeof useSetModalState>, 'showModal'>) {
export function TemplateCard({ data, showModal }: IProps) {
const { t } = useTranslation();

const handleClick = useCallback(() => {
showModal(data);
}, [data, showModal]);

return (
<Card className="bg-colors-background-inverse-weak border-colors-outline-neutral-standard group relative">
<CardContent className="p-4 ">
@@ -35,7 +38,7 @@ export function TemplateCard({
<Button
variant="tertiary"
className="absolute bottom-4 right-4 left-4 hidden justify-end group-hover:block text-center"
onClick={showModal}
onClick={handleClick}
>
{t('flow.useTemplate')}
</Button>

Laddar…
Avbryt
Spara