mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-09-09 21:08:59 +00:00
Native data AI application framework based on AWEL+AGENT (#1152)
Co-authored-by: Fangyin Cheng <staneyffer@gmail.com> Co-authored-by: lcx01800250 <lcx01800250@alibaba-inc.com> Co-authored-by: licunxing <864255598@qq.com> Co-authored-by: Aralhi <xiaoping0501@gmail.com> Co-authored-by: xuyuan23 <643854343@qq.com> Co-authored-by: aries_ckt <916701291@qq.com> Co-authored-by: hzh97 <2976151305@qq.com>
This commit is contained in:
157
web/components/app/agent-panel.tsx
Normal file
157
web/components/app/agent-panel.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import { apiInterceptors, getAppStrategy, getAppStrategyValues, getResource } from '@/client/api';
|
||||
import { Button, Card, Divider, Input, Select } from 'antd';
|
||||
import { log } from 'console';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import ResourceCard from './resource-card';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface IProps {
|
||||
resourceTypes: any;
|
||||
updateDetailsByAgentKey: (key: string, data: any) => void;
|
||||
detail: any;
|
||||
editResources?: any;
|
||||
}
|
||||
|
||||
export default function AgentPanel(props: IProps) {
|
||||
const { resourceTypes, updateDetailsByAgentKey, detail, editResources } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [resources, setResources] = useState<any>([...(editResources ?? [])]);
|
||||
const [agent, setAgent] = useState<any>({ ...detail, resources: [] });
|
||||
const [strategyOptions, setStrategyOptions] = useState<any>([]);
|
||||
const [strategyValueOptions, setStrategyValueOptions] = useState<any>([]);
|
||||
|
||||
const updateResourcesByIndex = (data: any, index: number) => {
|
||||
setResources((resources: any) => {
|
||||
const tempResources = [...resources];
|
||||
if (!data) {
|
||||
return tempResources.filter((_: any, indey) => index !== indey);
|
||||
}
|
||||
|
||||
return tempResources.map((item: any, indey) => {
|
||||
if (index === indey) {
|
||||
return data;
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getStrategy = async () => {
|
||||
const [_, data] = await apiInterceptors(getAppStrategy());
|
||||
if (data) {
|
||||
setStrategyOptions(data?.map((item) => ({ label: item, value: item })));
|
||||
}
|
||||
};
|
||||
|
||||
const getStrategyValues = async (type: string) => {
|
||||
const [_, data] = await apiInterceptors(getAppStrategyValues(type));
|
||||
if (data) {
|
||||
setStrategyValueOptions(data.map((item) => ({ label: item, value: item })) ?? []);
|
||||
}
|
||||
};
|
||||
|
||||
const formatStrategyValue = (value: string) => {
|
||||
return !value ? [] : value.split(',');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getStrategy();
|
||||
getStrategyValues(detail.llm_strategy);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
updateAgent(resources, 'resources');
|
||||
}, [resources]);
|
||||
|
||||
const updateAgent = (data: any, type: string) => {
|
||||
const tempAgent = { ...agent };
|
||||
tempAgent[type] = data;
|
||||
|
||||
setAgent(tempAgent);
|
||||
|
||||
updateDetailsByAgentKey(detail.key, tempAgent);
|
||||
};
|
||||
|
||||
const handelAdd = () => {
|
||||
setResources([...resources, { name: '', type: '', introduce: '', value: '', is_dynamic: '' }]);
|
||||
};
|
||||
|
||||
const resourceTypeOptions = useMemo(() => {
|
||||
return resourceTypes?.map((item: string) => {
|
||||
return {
|
||||
label: item,
|
||||
value: item,
|
||||
};
|
||||
});
|
||||
}, [resourceTypes]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center mb-6 mt-6">
|
||||
<div className="mr-2 w-16 text-center">Prompt:</div>
|
||||
<Input
|
||||
required
|
||||
className="mr-6 w-1/4"
|
||||
value={agent.prompt_template}
|
||||
onChange={(e) => {
|
||||
updateAgent(e.target.value, 'prompt_template');
|
||||
}}
|
||||
/>
|
||||
<div className="mr-2">LLM Strategy:</div>
|
||||
<Select
|
||||
value={agent.llm_strategy}
|
||||
options={strategyOptions}
|
||||
className="w-1/6 mr-6"
|
||||
onChange={(value) => {
|
||||
updateAgent(value, 'llm_strategy');
|
||||
getStrategyValues(value);
|
||||
}}
|
||||
/>
|
||||
{strategyValueOptions && strategyValueOptions.length > 0 && (
|
||||
<>
|
||||
<div className="mr-2">LLM Strategy Value:</div>
|
||||
<Select
|
||||
value={formatStrategyValue(agent.llm_strategy_value)}
|
||||
className="w-1/4"
|
||||
mode="multiple"
|
||||
options={strategyValueOptions}
|
||||
onChange={(value) => {
|
||||
if (!value || value?.length === 0) {
|
||||
updateAgent(null, 'llm_strategy_value');
|
||||
return null;
|
||||
}
|
||||
|
||||
const curValue = value.reduce((pre: string, cur: string, index: number) => {
|
||||
if (index === 0) {
|
||||
return cur;
|
||||
} else {
|
||||
return `${pre},${cur}`;
|
||||
}
|
||||
}, '');
|
||||
|
||||
updateAgent(curValue, 'llm_strategy_value');
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-3 text-lg font-bold">{t('available_resources')}</div>
|
||||
{resources.map((resource: any, index: number) => {
|
||||
return (
|
||||
<ResourceCard
|
||||
resource={resource}
|
||||
key={index}
|
||||
index={index}
|
||||
updateResourcesByIndex={updateResourcesByIndex}
|
||||
resourceTypeOptions={resourceTypeOptions}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<Button type="primary" className="mt-2" size="middle" onClick={handelAdd}>
|
||||
{t('add_resource')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
103
web/components/app/app-card.tsx
Normal file
103
web/components/app/app-card.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Modal } from 'antd';
|
||||
import { apiInterceptors, collectApp, delApp, newDialogue, unCollectApp } from '@/client/api';
|
||||
import { IApp } from '@/types/app';
|
||||
import { DeleteFilled, MessageFilled, StarFilled, WarningOutlined } from '@ant-design/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { ChatContext } from '@/app/chat-context';
|
||||
import GPTCard from '../common/gpt-card';
|
||||
|
||||
interface IProps {
|
||||
updateApps: (data?: { is_collected: boolean }) => void;
|
||||
app: IApp;
|
||||
handleEdit: (app: any) => void;
|
||||
isCollected: boolean;
|
||||
}
|
||||
|
||||
const { confirm } = Modal;
|
||||
|
||||
const languageMap = {
|
||||
en: '英文',
|
||||
zh: '中文',
|
||||
};
|
||||
|
||||
export default function AppCard(props: IProps) {
|
||||
const { updateApps, app, handleEdit, isCollected } = props;
|
||||
const { model } = useContext(ChatContext);
|
||||
const router = useRouter();
|
||||
|
||||
const [isCollect, setIsCollect] = useState<string>(app.is_collected);
|
||||
const { setAgent: setAgentToChat } = useContext(ChatContext);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const showDeleteConfirm = () => {
|
||||
confirm({
|
||||
title: t('Tips'),
|
||||
icon: <WarningOutlined />,
|
||||
content: `do you want delete the application?`,
|
||||
okText: 'Yes',
|
||||
okType: 'danger',
|
||||
cancelText: 'No',
|
||||
async onOk() {
|
||||
await apiInterceptors(delApp({ app_code: app.app_code }));
|
||||
updateApps(isCollected ? { is_collected: isCollected } : undefined);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setIsCollect(app.is_collected);
|
||||
}, [app]);
|
||||
|
||||
const collect = async () => {
|
||||
const [error] = await apiInterceptors(isCollect === 'true' ? unCollectApp({ app_code: app.app_code }) : collectApp({ app_code: app.app_code }));
|
||||
if (error) return;
|
||||
updateApps(isCollected ? { is_collected: isCollected } : undefined);
|
||||
setIsCollect(isCollect === 'true' ? 'false' : 'true');
|
||||
};
|
||||
|
||||
const handleChat = async () => {
|
||||
setAgentToChat?.(app.app_code);
|
||||
const [, res] = await apiInterceptors(newDialogue({ chat_mode: 'chat_agent' }));
|
||||
if (res) {
|
||||
router.push(`/chat/?scene=chat_agent&id=${res.conv_uid}${model ? `&model=${model}` : ''}`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<GPTCard
|
||||
title={app.app_name}
|
||||
icon={'/icons/node/vis.png'}
|
||||
iconBorder={false}
|
||||
desc={app.app_describe}
|
||||
tags={[
|
||||
{ text: languageMap[app.language], color: 'default' },
|
||||
{ text: app.team_mode, color: 'default' },
|
||||
]}
|
||||
onClick={() => {
|
||||
handleEdit(app);
|
||||
}}
|
||||
operations={[
|
||||
{
|
||||
label: t('Chat'),
|
||||
children: <MessageFilled />,
|
||||
onClick: handleChat,
|
||||
},
|
||||
{
|
||||
label: t('collect'),
|
||||
children: <StarFilled className={app.is_collected === 'false' ? 'text-gray-400' : 'text-yellow-400'} />,
|
||||
onClick: collect,
|
||||
},
|
||||
{
|
||||
label: t('Delete'),
|
||||
children: <DeleteFilled />,
|
||||
onClick: () => {
|
||||
showDeleteConfirm();
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
367
web/components/app/app-modal.tsx
Normal file
367
web/components/app/app-modal.tsx
Normal file
@@ -0,0 +1,367 @@
|
||||
import { AgentParams, IAgent as IAgentParams, IApp, IDetail } from '@/types/app';
|
||||
import { Dropdown, Form, Input, Modal, Select, Space, Spin, Tabs } from 'antd';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import AddIcon from '../icons/add-icon';
|
||||
import AgentPanel from './agent-panel';
|
||||
import { addApp, apiInterceptors, getAgents, getResourceType, getTeamMode, updateApp } from '@/client/api';
|
||||
import type { TabsProps } from 'antd';
|
||||
import DagLayout from './dag-layout';
|
||||
import { IFlow } from '@/types/flow';
|
||||
|
||||
type TargetKey = string;
|
||||
|
||||
type FieldType = {
|
||||
app_name: string;
|
||||
app_describe: string;
|
||||
language: string;
|
||||
team_mode: string;
|
||||
};
|
||||
|
||||
type IAgent = {
|
||||
label: string;
|
||||
children?: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
key: number | string;
|
||||
};
|
||||
|
||||
interface IProps {
|
||||
handleCancel: () => void;
|
||||
open: boolean;
|
||||
updateApps: () => void;
|
||||
type: string;
|
||||
app?: any;
|
||||
}
|
||||
|
||||
const languageOptions = [
|
||||
{ value: 'zh', label: '中文' },
|
||||
{ value: 'en', label: '英文' },
|
||||
];
|
||||
|
||||
type TeamModals = 'awel_layout' | 'singe_agent' | 'auto_plan';
|
||||
|
||||
export default function AppModal(props: IProps) {
|
||||
const { handleCancel, open, updateApps, type, app } = props;
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [spinning, setSpinning] = useState<boolean>(false);
|
||||
const [activeKey, setActiveKey] = useState<string>();
|
||||
const [teamModal, setTeamModal] = useState<{ label: string; value: string }[]>();
|
||||
const [agents, setAgents] = useState<TabsProps['items']>([]);
|
||||
const [dropItems, setDropItems] = useState<IAgent[]>([]);
|
||||
const [details, setDetails] = useState<IDetail[]>([...(app?.details || [])]);
|
||||
const [flow, setFlow] = useState<IFlow>();
|
||||
const [resourceTypes, setResourceTypes] = useState<string[]>();
|
||||
const [curTeamModal, setCurTeamModal] = useState<TeamModals>(app.team_modal || 'auto_plan');
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const onChange = (newActiveKey: string) => {
|
||||
setActiveKey(newActiveKey);
|
||||
};
|
||||
|
||||
const createApp = async (app: IApp) => {
|
||||
await apiInterceptors(type === 'add' ? addApp(app) : updateApp(app));
|
||||
await updateApps();
|
||||
};
|
||||
|
||||
const initApp = async () => {
|
||||
const appDetails = app.details;
|
||||
const [_, resourceType] = await apiInterceptors(getResourceType());
|
||||
|
||||
if (appDetails?.length > 0) {
|
||||
setAgents(
|
||||
appDetails?.map((item: AgentParams) => {
|
||||
return {
|
||||
label: item?.agent_name,
|
||||
children: (
|
||||
<AgentPanel
|
||||
editResources={type === 'edit' && item.resources}
|
||||
detail={{
|
||||
key: item?.agent_name,
|
||||
llm_strategy: item?.llm_strategy,
|
||||
agent_name: item?.agent_name,
|
||||
prompt_template: item?.prompt_template,
|
||||
llm_strategy_value: item?.llm_strategy_value,
|
||||
}}
|
||||
updateDetailsByAgentKey={updateDetailsByAgentKey}
|
||||
resourceTypes={resourceType}
|
||||
/>
|
||||
),
|
||||
key: item?.agent_name,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchTeamModal = async () => {
|
||||
const [_, data] = await apiInterceptors(getTeamMode());
|
||||
if (!data) return null;
|
||||
|
||||
const teamModalOptions = data.map((item) => {
|
||||
return { value: item, label: item };
|
||||
});
|
||||
setTeamModal(teamModalOptions);
|
||||
};
|
||||
|
||||
const fetchAgent = async () => {
|
||||
const [_, data] = await apiInterceptors(getAgents());
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
setDropItems(
|
||||
data
|
||||
.map((agent) => {
|
||||
return {
|
||||
label: agent.name,
|
||||
key: agent.name,
|
||||
onClick: () => {
|
||||
add(agent);
|
||||
},
|
||||
agent,
|
||||
};
|
||||
})
|
||||
.filter((item) => {
|
||||
if (!app.details || app.details?.length === 0) {
|
||||
return item;
|
||||
}
|
||||
return app?.details?.every((detail: AgentParams) => detail.agent_name !== item.label);
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const handleFlowsChange = (data: IFlow) => {
|
||||
setFlow(data);
|
||||
};
|
||||
|
||||
const fetchResourceType = async () => {
|
||||
const [_, data] = await apiInterceptors(getResourceType());
|
||||
if (data) {
|
||||
setResourceTypes(data);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchTeamModal();
|
||||
fetchAgent();
|
||||
fetchResourceType();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
type === 'edit' && initApp();
|
||||
}, [resourceTypes]);
|
||||
|
||||
useEffect(() => {
|
||||
setCurTeamModal(app.team_mode || 'auto_plan');
|
||||
}, [app]);
|
||||
|
||||
const updateDetailsByAgentKey = (key: string, data: IDetail) => {
|
||||
setDetails((details: IDetail[]) => {
|
||||
return details.map((detail: IDetail) => {
|
||||
return key === (detail.agent_name || detail.key) ? data : detail;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const add = async (tabBar: IAgentParams) => {
|
||||
const newActiveKey = tabBar.name;
|
||||
const [_, data] = await apiInterceptors(getResourceType());
|
||||
|
||||
setActiveKey(newActiveKey);
|
||||
|
||||
setDetails((details: IDetail[]) => {
|
||||
return [...details, { key: newActiveKey, name: '', llm_strategy: 'priority' }];
|
||||
});
|
||||
|
||||
setAgents((items: any) => {
|
||||
return [
|
||||
...items,
|
||||
{
|
||||
label: newActiveKey,
|
||||
children: (
|
||||
<AgentPanel
|
||||
detail={{ key: newActiveKey, llm_strategy: 'default', agent_name: newActiveKey, prompt_template: '', llm_strategy_value: null }}
|
||||
updateDetailsByAgentKey={updateDetailsByAgentKey}
|
||||
resourceTypes={data}
|
||||
/>
|
||||
),
|
||||
key: newActiveKey,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
setDropItems((items) => {
|
||||
return items.filter((item) => item.key !== tabBar.name);
|
||||
});
|
||||
};
|
||||
|
||||
const remove = (targetKey: TargetKey) => {
|
||||
let newActiveKey = activeKey;
|
||||
let lastIndex = -1;
|
||||
|
||||
if (!agents) {
|
||||
return null;
|
||||
}
|
||||
|
||||
agents.forEach((item, i) => {
|
||||
if (item.key === targetKey) {
|
||||
lastIndex = i - 1;
|
||||
}
|
||||
});
|
||||
|
||||
const newPanes = agents.filter((item) => item.key !== targetKey);
|
||||
if (newPanes.length && newActiveKey === targetKey) {
|
||||
if (lastIndex >= 0) {
|
||||
newActiveKey = newPanes[lastIndex].key;
|
||||
} else {
|
||||
newActiveKey = newPanes[0].key;
|
||||
}
|
||||
}
|
||||
setDetails((details: IDetail[]) => {
|
||||
return details?.filter((detail: any) => {
|
||||
return (detail.agent_name || detail.key) !== targetKey;
|
||||
});
|
||||
});
|
||||
|
||||
setAgents(newPanes);
|
||||
setActiveKey(newActiveKey);
|
||||
setDropItems((items: any) => {
|
||||
return [
|
||||
...items,
|
||||
{
|
||||
label: targetKey,
|
||||
key: targetKey,
|
||||
onClick: () => {
|
||||
add({ name: targetKey, describe: '', system_message: '' });
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
};
|
||||
|
||||
const onEdit = (targetKey: any, action: 'add' | 'remove') => {
|
||||
if (action === 'add') {
|
||||
// add();
|
||||
} else {
|
||||
remove(targetKey);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const isValidate = await form.validateFields();
|
||||
|
||||
if (!isValidate) {
|
||||
return;
|
||||
}
|
||||
setSpinning(true);
|
||||
|
||||
const data = {
|
||||
...form.getFieldsValue(),
|
||||
};
|
||||
if (type === 'edit') {
|
||||
data.app_code = app.app_code;
|
||||
}
|
||||
|
||||
if (data.team_mode !== 'awel_layout') {
|
||||
data.details = details;
|
||||
} else {
|
||||
const tempFlow = { ...flow };
|
||||
delete tempFlow.flow_data;
|
||||
data.team_context = tempFlow;
|
||||
}
|
||||
|
||||
try {
|
||||
await createApp(data);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSpinning(false);
|
||||
handleCancel();
|
||||
};
|
||||
|
||||
const handleTeamModalChange = (value: TeamModals) => {
|
||||
setCurTeamModal(value);
|
||||
};
|
||||
|
||||
const renderAddIcon = () => {
|
||||
return (
|
||||
<Dropdown menu={{ items: dropItems }} trigger={['click']}>
|
||||
<a className="h-8 flex items-center" onClick={(e) => e.preventDefault()}>
|
||||
<Space>
|
||||
<AddIcon />
|
||||
</Space>
|
||||
</a>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
okText={t('Submit')}
|
||||
title={type === 'edit' ? 'edit application' : 'add application'}
|
||||
open={open}
|
||||
width={'65%'}
|
||||
onCancel={handleCancel}
|
||||
onOk={handleSubmit}
|
||||
destroyOnClose={true}
|
||||
>
|
||||
<Spin spinning={spinning}>
|
||||
<Form
|
||||
form={form}
|
||||
preserve={false}
|
||||
size="large"
|
||||
className="mt-4 max-h-[70vh] overflow-auto h-[90vh]"
|
||||
layout="horizontal"
|
||||
labelAlign="left"
|
||||
labelCol={{ span: 4 }}
|
||||
initialValues={{
|
||||
app_name: app.app_name,
|
||||
app_describe: app.app_describe,
|
||||
language: app.language || languageOptions[0].value,
|
||||
team_mode: app.team_mode || 'auto_plan',
|
||||
}}
|
||||
autoComplete="off"
|
||||
onFinish={handleSubmit}
|
||||
>
|
||||
<Form.Item<FieldType> label={'App Name'} name="app_name" rules={[{ required: true, message: t('Please_input_the_name') }]}>
|
||||
<Input placeholder={t('Please_input_the_name')} />
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType>
|
||||
label={t('Description')}
|
||||
name="app_describe"
|
||||
rules={[{ required: true, message: t('Please_input_the_description') }]}
|
||||
>
|
||||
<Input.TextArea rows={3} placeholder={t('Please_input_the_description')} />
|
||||
</Form.Item>
|
||||
<div className="flex w-full">
|
||||
<Form.Item<FieldType> labelCol={{ span: 7 }} label={t('language')} name="language" className="w-1/2" rules={[{ required: true }]}>
|
||||
<Select className="w-2/3 ml-4" placeholder={t('language_select_tips')} options={languageOptions} />
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType> label={t('team_modal')} name="team_mode" className="w-1/2" labelCol={{ span: 6 }} rules={[{ required: true }]}>
|
||||
<Select
|
||||
defaultValue={app.team_mode || 'auto_plan'}
|
||||
className="ml-4 w-72"
|
||||
onChange={handleTeamModalChange}
|
||||
placeholder={t('Please_input_the_work_modal')}
|
||||
options={teamModal}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
{curTeamModal !== 'awel_layout' ? (
|
||||
<>
|
||||
<div className="mb-5">Agents</div>
|
||||
<Tabs addIcon={renderAddIcon()} type="editable-card" onChange={onChange} activeKey={activeKey} onEdit={onEdit} items={agents} />
|
||||
</>
|
||||
) : (
|
||||
<DagLayout onFlowsChange={handleFlowsChange} teamContext={app.team_context} />
|
||||
)}
|
||||
</Form>
|
||||
</Spin>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
58
web/components/app/dag-layout.tsx
Normal file
58
web/components/app/dag-layout.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PreviewFlow from '../flow/preview-flow';
|
||||
import { apiInterceptors, getFlows } from '@/client/api';
|
||||
import { IFlow } from '@/types/flow';
|
||||
import { Select } from 'antd';
|
||||
import Link from 'next/link';
|
||||
import { t } from 'i18next';
|
||||
|
||||
interface IProps {
|
||||
onFlowsChange: (data: any) => void;
|
||||
teamContext: any;
|
||||
}
|
||||
|
||||
export default function DagLayout(props: IProps) {
|
||||
const { onFlowsChange, teamContext } = props;
|
||||
const [flows, setFlows] = useState<IFlow[]>();
|
||||
const [flowsOptions, setFlowsOptions] = useState<any>();
|
||||
const [curFlow, setCurFlow] = useState<IFlow>();
|
||||
const fetchFlows = async () => {
|
||||
const [_, data] = await apiInterceptors(getFlows());
|
||||
if (data) {
|
||||
setFlowsOptions(data?.items?.map((item: IFlow) => ({ label: item.name, value: item.name })));
|
||||
setFlows(data.items);
|
||||
onFlowsChange(data?.items[0]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFlowsChange = (value: string) => {
|
||||
setCurFlow(flows?.find((item) => value === item.name));
|
||||
onFlowsChange(flows?.find((item) => value === item.name));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchFlows();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setCurFlow(flows?.find((item) => teamContext?.name === item.name) || flows?.[0]);
|
||||
}, [teamContext, flows]);
|
||||
|
||||
return (
|
||||
<div className="w-full h-[300px]">
|
||||
<div className="mr-24 mb-4 mt-2">Flows:</div>
|
||||
<div className="flex items-center mb-6">
|
||||
<Select onChange={handleFlowsChange} value={curFlow?.name || flowsOptions?.[0]?.value} className="w-1/4" options={flowsOptions}></Select>
|
||||
<Link href="/flow/canvas/" className="ml-6">
|
||||
{t('edit_new_applications')}
|
||||
</Link>
|
||||
<div className="text-gray-500 ml-16">{curFlow?.description}</div>
|
||||
</div>
|
||||
{curFlow && (
|
||||
<div className="w-full h-full border-[0.5px] border-dark-gray">
|
||||
<PreviewFlow flowData={curFlow?.flow_data} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
140
web/components/app/resource-card.tsx
Normal file
140
web/components/app/resource-card.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
import { apiInterceptors, getResource } from '@/client/api';
|
||||
import { DeleteFilled } from '@ant-design/icons';
|
||||
import { Button, Card, ConfigProvider, Input, Select, Switch } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface IProps {
|
||||
resourceTypeOptions: any[];
|
||||
updateResourcesByIndex: (data: any, index: number) => void;
|
||||
index: number;
|
||||
resource: any;
|
||||
}
|
||||
|
||||
export default function ResourceCard(props: IProps) {
|
||||
const { resourceTypeOptions, updateResourcesByIndex, index, resource: editResource } = props;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [resourceType, setResourceType] = useState<string>(editResource.type || resourceTypeOptions?.[0].label);
|
||||
const [resourceValueOptions, setResourceValueOptions] = useState<any[]>([]);
|
||||
const [resource, setResource] = useState<any>({
|
||||
name: editResource.name,
|
||||
type: editResource.type,
|
||||
value: editResource.value,
|
||||
is_dynamic: editResource.is_dynamic || false,
|
||||
});
|
||||
|
||||
const fetchResource = async () => {
|
||||
const [_, data] = await apiInterceptors(getResource({ type: resourceType }));
|
||||
|
||||
if (data) {
|
||||
setResourceValueOptions(
|
||||
data?.map((item) => {
|
||||
return { label: item, value: item };
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
setResourceValueOptions([]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (value: string) => {
|
||||
setResourceType(value);
|
||||
};
|
||||
|
||||
const updateResource = (value: any, type: string) => {
|
||||
const tempResource = resource;
|
||||
|
||||
tempResource[type] = value;
|
||||
setResource(tempResource);
|
||||
updateResourcesByIndex(tempResource, index);
|
||||
};
|
||||
|
||||
const handleDeleteResource = () => {
|
||||
updateResourcesByIndex(null, index);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchResource();
|
||||
updateResource(resource.type || resourceType, 'type');
|
||||
}, [resourceType]);
|
||||
|
||||
useEffect(() => {
|
||||
updateResource(resourceValueOptions[0]?.label || editResource.value, 'value');
|
||||
setResource({ ...resource, value: resourceValueOptions[0]?.label || editResource.value });
|
||||
}, [resourceValueOptions]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="mb-3 dark:bg-[#232734] border-gray-200"
|
||||
title={`Resource ${index + 1}`}
|
||||
extra={
|
||||
<DeleteFilled
|
||||
className="text-[#ff1b2e] !text-lg"
|
||||
onClick={() => {
|
||||
handleDeleteResource();
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center mb-6">
|
||||
<div className="font-bold mr-4 w-32 text-center">
|
||||
<span className="text-[#ff4d4f] font-normal">*</span> {t('resource_name')}:
|
||||
</div>
|
||||
<Input
|
||||
className="w-1/3"
|
||||
required
|
||||
value={resource.name}
|
||||
onInput={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateResource(e.target.value, 'name');
|
||||
}}
|
||||
/>
|
||||
<div className="flex items-center">
|
||||
<div className="font-bold w-32 text-center">{t('resource_dynamic')}</div>
|
||||
|
||||
<Switch
|
||||
defaultChecked={editResource.is_dynamic || false}
|
||||
style={{ background: resource.is_dynamic ? '#1677ff' : '#ccc' }}
|
||||
onChange={(value) => {
|
||||
updateResource(value, 'is_dynamic');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex mb-5 items-center">
|
||||
<div className="font-bold mr-4 w-32 text-center">{t('resource_type')}: </div>
|
||||
<Select
|
||||
className="w-1/3"
|
||||
options={resourceTypeOptions}
|
||||
value={resource.type || resourceTypeOptions?.[0]}
|
||||
onChange={(value) => {
|
||||
updateResource(value, 'type');
|
||||
handleChange(value);
|
||||
}}
|
||||
/>
|
||||
<div className="font-bold w-32 text-center">{t('resource_value')}:</div>
|
||||
{resourceValueOptions?.length > 0 ? (
|
||||
<Select
|
||||
value={resource.value}
|
||||
className="flex-1"
|
||||
options={resourceValueOptions}
|
||||
onChange={(value) => {
|
||||
updateResource(value, 'value');
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
className="flex-1"
|
||||
value={resource.value || editResource.value}
|
||||
onInput={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateResource(e.target.value, 'value');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user