mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-09-08 12:30:14 +00:00
feat(web): AWEL flow 2.0 frontend codes (#1898)
Co-authored-by: Fangyin Cheng <staneyffer@gmail.com> Co-authored-by: 谨欣 <echo.cmy@antgroup.com> Co-authored-by: 严志勇 <yanzhiyong@tiansuixiansheng.com> Co-authored-by: yanzhiyong <932374019@qq.com>
This commit is contained in:
@@ -174,7 +174,7 @@ function Database() {
|
||||
onDelete(item);
|
||||
}}
|
||||
>
|
||||
删除
|
||||
{t('Delete_Btn')}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
|
@@ -1,20 +1,48 @@
|
||||
import { addFlow, apiInterceptors, getFlowById, updateFlowById } from '@/client/api';
|
||||
import { apiInterceptors, getFlowById } from '@/client/api';
|
||||
import MuiLoading from '@/components/common/loading';
|
||||
import AddNodes from '@/components/flow/add-nodes';
|
||||
import AddNodesSider from '@/components/flow/add-nodes-sider';
|
||||
import ButtonEdge from '@/components/flow/button-edge';
|
||||
import CanvasNode from '@/components/flow/canvas-node';
|
||||
import { IFlowData, IFlowUpdateParam } from '@/types/flow';
|
||||
import { checkFlowDataRequied, getUniqueNodeId, mapHumpToUnderline, mapUnderlineToHump } from '@/utils/flow';
|
||||
import { FrownOutlined, SaveOutlined } from '@ant-design/icons';
|
||||
import { App, Button, Checkbox, Divider, Form, Input, Modal, Space, message, notification } from 'antd';
|
||||
import {
|
||||
checkFlowDataRequied,
|
||||
getUniqueNodeId,
|
||||
mapUnderlineToHump,
|
||||
} from '@/utils/flow';
|
||||
import {
|
||||
ExportOutlined,
|
||||
FrownOutlined,
|
||||
ImportOutlined,
|
||||
SaveOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Divider, Space, Tooltip, message, notification } from 'antd';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import React, { DragEvent, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import React, {
|
||||
DragEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ReactFlow, { Background, Connection, Controls, ReactFlowProvider, addEdge, useEdgesState, useNodesState, useReactFlow, Node } from 'reactflow';
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
Connection,
|
||||
Controls,
|
||||
ReactFlowProvider,
|
||||
addEdge,
|
||||
useEdgesState,
|
||||
useNodesState,
|
||||
useReactFlow,
|
||||
Node,
|
||||
} from 'reactflow';
|
||||
import 'reactflow/dist/style.css';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const { TextArea } = Input;
|
||||
import {
|
||||
SaveFlowModal,
|
||||
ExportFlowModal,
|
||||
ImportFlowModal,
|
||||
} from '@/components/flow/canvas-modal';
|
||||
|
||||
interface Props {
|
||||
// Define your component props here
|
||||
@@ -24,9 +52,7 @@ const edgeTypes = { buttonedge: ButtonEdge };
|
||||
|
||||
const Canvas: React.FC<Props> = () => {
|
||||
const { t } = useTranslation();
|
||||
const { message } = App.useApp();
|
||||
const { replace } = useRouter();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
const id = searchParams?.get('id') || '';
|
||||
const reactFlow = useReactFlow();
|
||||
@@ -35,8 +61,10 @@ const Canvas: React.FC<Props> = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [flowInfo, setFlowInfo] = useState<IFlowUpdateParam>();
|
||||
const [isSaveFlowModalOpen, setIsSaveFlowModalOpen] = useState(false);
|
||||
const [isExportFlowModalOpen, setIsExportFlowModalOpen] = useState(false);
|
||||
const [isImportModalOpen, setIsImportFlowModalOpen] = useState(false);
|
||||
|
||||
async function getFlowData() {
|
||||
setLoading(true);
|
||||
@@ -81,7 +109,7 @@ const Canvas: React.FC<Props> = () => {
|
||||
};
|
||||
}
|
||||
return node;
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -98,13 +126,18 @@ const Canvas: React.FC<Props> = () => {
|
||||
(event: DragEvent) => {
|
||||
event.preventDefault();
|
||||
const reactFlowBounds = reactFlowWrapper.current!.getBoundingClientRect();
|
||||
const sidebarWidth = (
|
||||
document.getElementsByClassName('ant-layout-sider')?.[0] as HTMLElement
|
||||
)?.offsetWidth; // get sidebar width
|
||||
|
||||
let nodeStr = event.dataTransfer.getData('application/reactflow');
|
||||
if (!nodeStr || typeof nodeStr === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeData = JSON.parse(nodeStr);
|
||||
const position = reactFlow.screenToFlowPosition({
|
||||
x: event.clientX - reactFlowBounds.left,
|
||||
x: event.clientX - reactFlowBounds.left + sidebarWidth,
|
||||
y: event.clientY - reactFlowBounds.top,
|
||||
});
|
||||
const nodeId = getUniqueNodeId(nodeData, reactFlow.getNodes());
|
||||
@@ -129,10 +162,10 @@ const Canvas: React.FC<Props> = () => {
|
||||
};
|
||||
}
|
||||
return node;
|
||||
}),
|
||||
})
|
||||
);
|
||||
},
|
||||
[reactFlow],
|
||||
[reactFlow]
|
||||
);
|
||||
|
||||
const onDragOver = useCallback((event: DragEvent) => {
|
||||
@@ -140,18 +173,7 @@ const Canvas: React.FC<Props> = () => {
|
||||
event.dataTransfer.dropEffect = 'move';
|
||||
}, []);
|
||||
|
||||
function labelChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const label = e.target.value;
|
||||
// replace spaces with underscores, convert uppercase letters to lowercase, remove characters other than digits, letters, _, and -.
|
||||
let result = label
|
||||
.replace(/\s+/g, '_')
|
||||
.replace(/[^a-z0-9_-]/g, '')
|
||||
.toLowerCase();
|
||||
result = result;
|
||||
form.setFieldsValue({ name: result });
|
||||
}
|
||||
|
||||
function clickSave() {
|
||||
function onSave() {
|
||||
const flowData = reactFlow.toObject() as IFlowData;
|
||||
const [check, node, message] = checkFlowDataRequied(flowData);
|
||||
if (!check && message) {
|
||||
@@ -169,139 +191,116 @@ const Canvas: React.FC<Props> = () => {
|
||||
};
|
||||
}
|
||||
return item;
|
||||
}),
|
||||
})
|
||||
);
|
||||
return notification.error({ message: 'Error', description: message, icon: <FrownOutlined className="text-red-600" /> });
|
||||
return notification.error({
|
||||
message: 'Error',
|
||||
description: message,
|
||||
icon: <FrownOutlined className='text-red-600' />,
|
||||
});
|
||||
}
|
||||
setIsModalVisible(true);
|
||||
setIsSaveFlowModalOpen(true);
|
||||
}
|
||||
|
||||
async function handleSaveFlow() {
|
||||
const { name, label, description = '', editable = false, deploy = false } = form.getFieldsValue();
|
||||
const reactFlowObject = mapHumpToUnderline(reactFlow.toObject() as IFlowData);
|
||||
if (id) {
|
||||
const [, , res] = await apiInterceptors(updateFlowById(id, {
|
||||
name,
|
||||
label,
|
||||
description,
|
||||
editable,
|
||||
uid: id,
|
||||
flow_data: reactFlowObject,
|
||||
state: deploy ? 'deployed' : 'developing',
|
||||
}));
|
||||
setIsModalVisible(false);
|
||||
if (res?.success) {
|
||||
message.success('编辑成功');
|
||||
replace('/construct/flow');
|
||||
setIsModalVisible(false);
|
||||
} else if (res?.err_msg) {
|
||||
message.error(res?.err_msg);
|
||||
}
|
||||
} else {
|
||||
const [_, res] = await apiInterceptors(addFlow({ name, label, description, editable, flow_data: reactFlowObject, state: deploy ? 'deployed' : 'developing' }));
|
||||
setIsModalVisible(false);
|
||||
replace('/construct/flow');
|
||||
message.success('创建成功');
|
||||
}
|
||||
function onExport() {
|
||||
setIsExportFlowModalOpen(true);
|
||||
}
|
||||
|
||||
function onImport() {
|
||||
setIsImportFlowModalOpen(true);
|
||||
}
|
||||
|
||||
const getButtonList = () => {
|
||||
const buttonList = [
|
||||
{
|
||||
title: t('Import'),
|
||||
icon: <ImportOutlined className='block text-xl' onClick={onImport} />,
|
||||
},
|
||||
{
|
||||
title: t('save'),
|
||||
icon: <SaveOutlined className='block text-xl' onClick={onSave} />,
|
||||
},
|
||||
];
|
||||
|
||||
if (id !== '') {
|
||||
buttonList.unshift({
|
||||
title: t('Export'),
|
||||
icon: <ExportOutlined className='block text-xl' onClick={onExport} />,
|
||||
});
|
||||
}
|
||||
|
||||
return buttonList;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<MuiLoading visible={loading} />
|
||||
<div className="my-2 mx-4 flex flex-row justify-end items-center">
|
||||
<div className="w-8 h-8 rounded-md bg-stone-300 dark:bg-zinc-700 dark:text-zinc-200 flext justify-center items-center hover:text-blue-500 dark:hover:text-zinc-100">
|
||||
<SaveOutlined className="block text-xl" onClick={clickSave} />
|
||||
<div className='flex flex-row'>
|
||||
<AddNodesSider />
|
||||
|
||||
<div className='flex flex-col flex-1'>
|
||||
<Space className='my-2 mx-4 flex flex-row justify-end'>
|
||||
{getButtonList().map(({ title, icon }) => (
|
||||
<Tooltip
|
||||
key={title}
|
||||
title={title}
|
||||
className='w-8 h-8 rounded-md bg-stone-300 dark:bg-zinc-700 dark:text-zinc-200 hover:text-blue-500 dark:hover:text-zinc-100'
|
||||
>
|
||||
{icon}
|
||||
</Tooltip>
|
||||
))}
|
||||
</Space>
|
||||
|
||||
<Divider className='mt-0 mb-0' />
|
||||
|
||||
<div className='h-[calc(100vh-48px)] w-full' ref={reactFlowWrapper}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeClick={onNodesClick}
|
||||
onConnect={onConnect}
|
||||
onDrop={onDrop}
|
||||
onDragOver={onDragOver}
|
||||
minZoom={0.1}
|
||||
fitView
|
||||
deleteKeyCode={['Backspace', 'Delete']}
|
||||
>
|
||||
<Controls
|
||||
className='flex flex-row items-center'
|
||||
position='bottom-center'
|
||||
/>
|
||||
<Background color='#aaa' gap={16} />
|
||||
{/* <AddNodes /> */}
|
||||
</ReactFlow>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Divider className="mt-0 mb-0" />
|
||||
<div className="h-[calc(100vh-60px)] w-full" ref={reactFlowWrapper}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeClick={onNodesClick}
|
||||
onConnect={onConnect}
|
||||
onDrop={onDrop}
|
||||
onDragOver={onDragOver}
|
||||
minZoom={0.1}
|
||||
fitView
|
||||
deleteKeyCode={['Backspace', 'Delete']}
|
||||
>
|
||||
<Controls className="flex flex-row items-center" position="bottom-center" />
|
||||
<Background color="#aaa" gap={16} />
|
||||
<AddNodes />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
<Modal
|
||||
title={t('flow_modal_title')}
|
||||
open={isModalVisible}
|
||||
onCancel={() => {
|
||||
setIsModalVisible(false);
|
||||
}}
|
||||
cancelButtonProps={{ className: 'hidden' }}
|
||||
okButtonProps={{ className: 'hidden' }}
|
||||
>
|
||||
<Form
|
||||
name="flow_form"
|
||||
form={form}
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
style={{ maxWidth: 600 }}
|
||||
initialValues={{ remember: true }}
|
||||
onFinish={handleSaveFlow}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item label="Title" name="label" initialValue={flowInfo?.label} rules={[{ required: true, message: 'Please input flow title!' }]}>
|
||||
<Input onChange={labelChange} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Name"
|
||||
name="name"
|
||||
initialValue={flowInfo?.name}
|
||||
rules={[
|
||||
{ required: true, message: 'Please input flow name!' },
|
||||
() => ({
|
||||
validator(_, value) {
|
||||
const regex = /^[a-zA-Z0-9_\-]+$/;
|
||||
if (!regex.test(value)) {
|
||||
return Promise.reject('Can only contain numbers, letters, underscores, and dashes');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="Description" initialValue={flowInfo?.description} name="description">
|
||||
<TextArea rows={3} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Editable" name="editable" initialValue={id ? flowInfo?.editable : true} valuePropName="checked">
|
||||
<Checkbox></Checkbox>
|
||||
</Form.Item>
|
||||
<Form.Item label="Deploy" name="deploy" initialValue={id ? (flowInfo?.state === 'deployed' || flowInfo?.state === 'running') : true} valuePropName="checked">
|
||||
<Checkbox></Checkbox>
|
||||
</Form.Item>
|
||||
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
|
||||
<Space>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
setIsModalVisible(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<MuiLoading visible={loading} />
|
||||
|
||||
<SaveFlowModal
|
||||
reactFlow={reactFlow}
|
||||
flowInfo={flowInfo}
|
||||
isSaveFlowModalOpen={isSaveFlowModalOpen}
|
||||
setIsSaveFlowModalOpen={setIsSaveFlowModalOpen}
|
||||
/>
|
||||
|
||||
<ExportFlowModal
|
||||
reactFlow={reactFlow}
|
||||
flowInfo={flowInfo}
|
||||
isExportFlowModalOpen={isExportFlowModalOpen}
|
||||
setIsExportFlowModalOpen={setIsExportFlowModalOpen}
|
||||
/>
|
||||
|
||||
<ImportFlowModal
|
||||
setNodes={setNodes}
|
||||
setEdges={setEdges}
|
||||
isImportModalOpen={isImportModalOpen}
|
||||
setIsImportFlowModalOpen={setIsImportFlowModalOpen}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -1,12 +1,31 @@
|
||||
import BlurredCard, { ChatButton, InnerDropdown } from '@/new-components/common/blurredCard';
|
||||
import BlurredCard, {
|
||||
ChatButton,
|
||||
InnerDropdown,
|
||||
} from '@/new-components/common/blurredCard';
|
||||
import ConstructLayout from '@/new-components/layout/Construct';
|
||||
import { ChatContext } from '@/app/chat-context';
|
||||
import { apiInterceptors, deleteFlowById, getFlows, newDialogue, updateFlowAdmins, addFlow } from '@/client/api';
|
||||
import {
|
||||
apiInterceptors,
|
||||
deleteFlowById,
|
||||
getFlows,
|
||||
newDialogue,
|
||||
addFlow,
|
||||
} from '@/client/api';
|
||||
import MyEmpty from '@/components/common/MyEmpty';
|
||||
import { IFlow, IFlowUpdateParam } from '@/types/flow';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { Button, Modal, Popconfirm, Select, Spin, Tag, message, Form, Input, Checkbox } from 'antd';
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
Popconfirm,
|
||||
Spin,
|
||||
Tag,
|
||||
message,
|
||||
Form,
|
||||
Input,
|
||||
Checkbox,
|
||||
} from 'antd';
|
||||
import { t } from 'i18next';
|
||||
import { concat, debounce } from 'lodash';
|
||||
import moment from 'moment';
|
||||
@@ -19,18 +38,14 @@ function Flow() {
|
||||
const router = useRouter();
|
||||
const { model } = useContext(ChatContext);
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
const [form] = Form.useForm<Pick<IFlow, 'label' | 'name'>>();
|
||||
|
||||
const [flowList, setFlowList] = useState<Array<IFlow>>([]);
|
||||
const [adminOpen, setAdminOpen] = useState<boolean>(false);
|
||||
const [curFlow, setCurFLow] = useState<IFlow>();
|
||||
const [admins, setAdmins] = useState<string[]>([]);
|
||||
const copyFlowTemp = useRef<IFlow>();
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [deploy, setDeploy] = useState(false);
|
||||
const [editable, setEditable] = useState(false);
|
||||
|
||||
const [form] = Form.useForm<Pick<IFlow, 'label' | 'name'>>();
|
||||
|
||||
// 分页信息
|
||||
const totalRef = useRef<{
|
||||
current_page: number;
|
||||
@@ -41,18 +56,14 @@ function Flow() {
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// 获取列表
|
||||
const {
|
||||
run: getFlowListRun,
|
||||
loading,
|
||||
refresh: refreshFlowList,
|
||||
} = useRequest(
|
||||
const { run: getFlowListRun, loading } = useRequest(
|
||||
async (params: any) =>
|
||||
await apiInterceptors(
|
||||
getFlows({
|
||||
page: 1,
|
||||
page_size: 12,
|
||||
...params,
|
||||
}),
|
||||
})
|
||||
),
|
||||
{
|
||||
cacheKey: 'query-flow-list',
|
||||
@@ -66,7 +77,7 @@ function Flow() {
|
||||
};
|
||||
},
|
||||
throttleWait: 300,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const { i18n } = useTranslation();
|
||||
@@ -112,7 +123,9 @@ function Flow() {
|
||||
}, [loading, handleScroll, loadMoreData]);
|
||||
|
||||
const handleChat = async (flow: IFlow) => {
|
||||
const [, res] = await apiInterceptors(newDialogue({ chat_mode: 'chat_agent' }));
|
||||
const [, res] = await apiInterceptors(
|
||||
newDialogue({ chat_mode: 'chat_agent' })
|
||||
);
|
||||
if (res) {
|
||||
const queryStr = qs.stringify({
|
||||
scene: 'chat_flow',
|
||||
@@ -131,30 +144,6 @@ function Flow() {
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (curFlow?.admins?.length) {
|
||||
setAdmins(curFlow?.admins);
|
||||
} else {
|
||||
setAdmins([]);
|
||||
}
|
||||
}, [curFlow]);
|
||||
|
||||
// 更新管理员
|
||||
const { run: updateAdmins, loading: adminLoading } = useRequest(
|
||||
async (value: string[]) => await apiInterceptors(updateFlowAdmins({ uid: curFlow?.uid || '', admins: value })),
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: (data) => {
|
||||
const [error] = data;
|
||||
if (!error) {
|
||||
message.success('更新成功');
|
||||
} else {
|
||||
message.error('更新失败');
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const handleCopy = (flow: IFlow) => {
|
||||
copyFlowTemp.current = flow;
|
||||
form.setFieldValue('label', `${flow.label} Copy`);
|
||||
@@ -166,7 +155,8 @@ function Flow() {
|
||||
|
||||
const onFinish = async (val: { name: string; label: string }) => {
|
||||
if (!copyFlowTemp.current) return;
|
||||
const { source, uid, dag_id, gmt_created, gmt_modified, state, ...params } = copyFlowTemp.current;
|
||||
const { source, uid, dag_id, gmt_created, gmt_modified, state, ...params } =
|
||||
copyFlowTemp.current;
|
||||
const data: IFlowUpdateParam = {
|
||||
...params,
|
||||
editable,
|
||||
@@ -181,18 +171,15 @@ function Flow() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = async (value: string[]) => {
|
||||
setAdmins(value);
|
||||
await updateAdmins(value);
|
||||
await refreshFlowList();
|
||||
};
|
||||
|
||||
return (
|
||||
<ConstructLayout>
|
||||
<Spin spinning={loading}>
|
||||
<div className="relative h-screen w-full p-4 md:p-6 overflow-y-auto" ref={scrollRef}>
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
className='relative h-screen w-full p-4 md:p-6 overflow-y-auto'
|
||||
ref={scrollRef}
|
||||
>
|
||||
<div className='flex justify-between items-center mb-6'>
|
||||
<div className='flex items-center gap-4'>
|
||||
{/* <Input
|
||||
variant="filled"
|
||||
prefix={<SearchOutlined />}
|
||||
@@ -204,9 +191,9 @@ function Flow() {
|
||||
/> */}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className='flex items-center gap-4'>
|
||||
<Button
|
||||
className="border-none text-white bg-button-gradient"
|
||||
className='border-none text-white bg-button-gradient'
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
router.push('/construct/flow/canvas');
|
||||
@@ -216,13 +203,13 @@ function Flow() {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap mx-[-8px] pb-12 justify-start items-stretch">
|
||||
<div className='flex flex-wrap mx-[-8px] pb-12 justify-start items-stretch'>
|
||||
{flowList.map((flow) => (
|
||||
<BlurredCard
|
||||
description={flow.description}
|
||||
name={flow.name}
|
||||
key={flow.uid}
|
||||
logo="/pictures/flow.png"
|
||||
logo='/pictures/flow.png'
|
||||
onClick={() => {
|
||||
router.push('/construct/flow/canvas?id=' + flow.uid);
|
||||
}}
|
||||
@@ -230,19 +217,6 @@ function Flow() {
|
||||
<InnerDropdown
|
||||
menu={{
|
||||
items: [
|
||||
// {
|
||||
// key: 'edit',
|
||||
// label: (
|
||||
// <span
|
||||
// onClick={() => {
|
||||
// setAdminOpen(true);
|
||||
// setCurFLow(flow);
|
||||
// }}
|
||||
// >
|
||||
// 权限管理
|
||||
// </span>
|
||||
// ),
|
||||
// },
|
||||
{
|
||||
key: 'copy',
|
||||
label: (
|
||||
@@ -251,15 +225,20 @@ function Flow() {
|
||||
handleCopy(flow);
|
||||
}}
|
||||
>
|
||||
{t('copy')}
|
||||
{t('Copy_Btn')}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'del',
|
||||
label: (
|
||||
<Popconfirm title="Are you sure to delete this flow?" onConfirm={() => deleteFlow(flow)}>
|
||||
<span className="text-red-400">删除</span>
|
||||
<Popconfirm
|
||||
title='Are you sure to delete this flow?'
|
||||
onConfirm={() => deleteFlow(flow)}
|
||||
>
|
||||
<span className='text-red-400'>
|
||||
{t('Delete_Btn')}
|
||||
</span>
|
||||
</Popconfirm>
|
||||
),
|
||||
},
|
||||
@@ -270,16 +249,36 @@ function Flow() {
|
||||
rightTopHover={false}
|
||||
Tags={
|
||||
<div>
|
||||
<Tag color={flow.source === 'DBGPT-WEB' ? 'green' : 'blue'}>{flow.source}</Tag>
|
||||
<Tag color={flow.editable ? 'green' : 'gray'}>{flow.editable ? 'Editable' : 'Can not Edit'}</Tag>
|
||||
<Tag color={flow.state === 'load_failed' ? 'red' : flow.state === 'running' ? 'green' : 'blue'}>{flow.state}</Tag>
|
||||
<Tag color={flow.source === 'DBGPT-WEB' ? 'green' : 'blue'}>
|
||||
{flow.source}
|
||||
</Tag>
|
||||
<Tag color={flow.editable ? 'green' : 'gray'}>
|
||||
{flow.editable ? 'Editable' : 'Can not Edit'}
|
||||
</Tag>
|
||||
<Tag
|
||||
color={
|
||||
flow.state === 'load_failed'
|
||||
? 'red'
|
||||
: flow.state === 'running'
|
||||
? 'green'
|
||||
: 'blue'
|
||||
}
|
||||
>
|
||||
{flow.state}
|
||||
</Tag>
|
||||
</div>
|
||||
}
|
||||
LeftBottom={
|
||||
<div key={i18n.language + 'flow'} className="flex gap-2">
|
||||
<div key={i18n.language + 'flow'} className='flex gap-2'>
|
||||
<span>{flow?.nick_name}</span>
|
||||
<span>•</span>
|
||||
{flow?.gmt_modified && <span>{moment(flow?.gmt_modified).fromNow() + ' ' + t('update')}</span>}
|
||||
{flow?.gmt_modified && (
|
||||
<span>
|
||||
{moment(flow?.gmt_modified).fromNow() +
|
||||
' ' +
|
||||
t('update')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
RightBottom={
|
||||
@@ -292,40 +291,26 @@ function Flow() {
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{flowList.length === 0 && <MyEmpty description="No flow found" />}
|
||||
{flowList.length === 0 && <MyEmpty description='No flow found' />}
|
||||
</div>
|
||||
</div>
|
||||
</Spin>
|
||||
<Modal title="权限管理" open={adminOpen} onCancel={() => setAdminOpen(false)} footer={null}>
|
||||
<div className="py-4">
|
||||
<div className="mb-1">管理员(工号,去前缀0):</div>
|
||||
<Select
|
||||
mode="tags"
|
||||
value={admins}
|
||||
style={{ width: '100%' }}
|
||||
onChange={handleChange}
|
||||
tokenSeparators={[',']}
|
||||
options={admins?.map((item: string) => ({ label: item, value: item }))}
|
||||
loading={adminLoading}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
<Modal
|
||||
open={showModal}
|
||||
title="Copy AWEL Flow"
|
||||
title='Copy AWEL Flow'
|
||||
onCancel={() => {
|
||||
setShowModal(false);
|
||||
}}
|
||||
footer={false}
|
||||
>
|
||||
<Form form={form} onFinish={onFinish} className="mt-6">
|
||||
<Form.Item name="name" label="Name" rules={[{ required: true }]}>
|
||||
<Form form={form} onFinish={onFinish} className='mt-6'>
|
||||
<Form.Item name='name' label='Name' rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="label" label="Label" rules={[{ required: true }]}>
|
||||
<Form.Item name='label' label='Label' rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="editable">
|
||||
<Form.Item label='editable'>
|
||||
<Checkbox
|
||||
value={editable}
|
||||
checked={editable}
|
||||
@@ -335,7 +320,7 @@ function Flow() {
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="deploy">
|
||||
<Form.Item label='deploy'>
|
||||
<Checkbox
|
||||
value={deploy}
|
||||
checked={deploy}
|
||||
@@ -345,8 +330,8 @@ function Flow() {
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<div className="flex justify-end">
|
||||
<Button type="primary" htmlType="submit">
|
||||
<div className='flex justify-end'>
|
||||
<Button type='primary' htmlType='submit'>
|
||||
{t('Submit')}
|
||||
</Button>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user