index.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. import { ChatContext } from '@/app/chat-context';
  2. import { addFlow, apiInterceptors, deleteFlowById, getFlows, newDialogue } from '@/client/api';
  3. import MyEmpty from '@/components/common/MyEmpty';
  4. import BlurredCard, { ChatButton, InnerDropdown } from '@/new-components/common/blurredCard';
  5. import ConstructLayout from '@/new-components/layout/Construct';
  6. import { IFlow, IFlowUpdateParam } from '@/types/flow';
  7. import { PlusOutlined, SearchOutlined } from '@ant-design/icons';
  8. import { useRequest } from 'ahooks';
  9. import { Avatar, Button, Checkbox, Form, Input, Modal, Pagination, Popconfirm, Spin, Tag, message } from 'antd';
  10. import { t } from 'i18next';
  11. import moment from 'moment';
  12. import { useRouter } from 'next/router';
  13. import qs from 'querystring';
  14. import { useContext, useRef, useState } from 'react';
  15. import { useTranslation } from 'react-i18next';
  16. function Flow() {
  17. const router = useRouter();
  18. const { model } = useContext(ChatContext);
  19. const [messageApi] = message.useMessage();
  20. const [form] = Form.useForm<Pick<IFlow, 'label' | 'name'>>();
  21. const [flowList, setFlowList] = useState<Array<IFlow>>([]);
  22. const copyFlowTemp = useRef<IFlow>();
  23. const [showModal, setShowModal] = useState(false);
  24. const [deploy, setDeploy] = useState(false);
  25. const [editable, setEditable] = useState(false);
  26. const totalRef = useRef<{
  27. current_page: number;
  28. total_count: number;
  29. total_page: number;
  30. }>();
  31. const scrollRef = useRef<HTMLDivElement>(null);
  32. // get flow list
  33. const { run: getFlowListRun, loading } = useRequest(
  34. async (params: any) =>
  35. await apiInterceptors(
  36. getFlows({
  37. page: 1,
  38. page_size: 12,
  39. ...params,
  40. }),
  41. ),
  42. {
  43. cacheKey: 'query-flow-list',
  44. onSuccess: data => {
  45. const [, res] = data;
  46. // setFlowList((prev) => concat([...prev], res?.items || []));
  47. setFlowList(res?.items || []);
  48. totalRef.current = {
  49. current_page: res?.page || 1,
  50. total_count: res?.total_count || 0,
  51. total_page: res?.total_pages || 0,
  52. };
  53. },
  54. throttleWait: 300,
  55. },
  56. );
  57. const { i18n } = useTranslation();
  58. // 触底加载更多
  59. // const loadMoreData = useCallback(() => {
  60. // const current = totalRef.current;
  61. // if (!current) {
  62. // return;
  63. // }
  64. // if (current.current_page < current.total_page) {
  65. // getFlowListRun({
  66. // page: current.current_page + 1,
  67. // });
  68. // current.current_page = current.current_page + 1;
  69. // }
  70. // }, [getFlowListRun]);
  71. // // 滚动事件
  72. // const handleScroll = debounce((e: Event) => {
  73. // const target = e.target as HTMLDivElement;
  74. // if (target.scrollHeight - target.scrollTop <= target.clientHeight + 200) {
  75. // loadMoreData();
  76. // }
  77. // }, 200);
  78. // useEffect(() => {
  79. // if (loading) {
  80. // return;
  81. // }
  82. // const currentScrollRef = scrollRef.current;
  83. // if (currentScrollRef) {
  84. // currentScrollRef?.addEventListener('scroll', handleScroll);
  85. // if (currentScrollRef.scrollHeight === currentScrollRef.clientHeight) {
  86. // loadMoreData();
  87. // }
  88. // }
  89. // return () => {
  90. // if (currentScrollRef) {
  91. // currentScrollRef?.removeEventListener('scroll', handleScroll);
  92. // }
  93. // };
  94. // }, [loading, handleScroll, loadMoreData]);
  95. const handleChat = async (flow: IFlow) => {
  96. const [, res] = await apiInterceptors(newDialogue({ chat_mode: 'chat_agent' }));
  97. if (res) {
  98. const queryStr = qs.stringify({
  99. scene: 'chat_flow',
  100. id: res.conv_uid,
  101. model: model,
  102. select_param: flow.uid,
  103. });
  104. router.push(`/chat?${queryStr}`);
  105. }
  106. };
  107. async function deleteFlow(flow: IFlow) {
  108. const [, , res] = await apiInterceptors(deleteFlowById(flow.uid));
  109. if (res?.success) {
  110. setFlowList(flows => flows.filter(_flow => _flow.uid !== flow.uid));
  111. }
  112. }
  113. const handleCopy = (flow: IFlow) => {
  114. copyFlowTemp.current = flow;
  115. form.setFieldValue('label', `${flow.label} Copy`);
  116. form.setFieldValue('name', `${flow.name}_copy`);
  117. setEditable(true);
  118. setShowModal(true);
  119. };
  120. const onFinish = async (val: { name: string; label: string }) => {
  121. if (!copyFlowTemp.current) return;
  122. const { source, uid, dag_id, gmt_created, gmt_modified, state, ...params } = copyFlowTemp.current;
  123. const data: IFlowUpdateParam = {
  124. ...params,
  125. editable,
  126. state: deploy ? 'deployed' : 'developing',
  127. ...val,
  128. };
  129. const [err] = await apiInterceptors(addFlow(data));
  130. if (!err) {
  131. messageApi.success(t('save_flow_success'));
  132. setShowModal(false);
  133. getFlowListRun({});
  134. }
  135. };
  136. return (
  137. <ConstructLayout>
  138. <Spin spinning={loading}>
  139. <div className='relative h-screen bg-[#fff] pt-0 w-full p-4 overflow-y-auto' ref={scrollRef} >
  140. <div className='mt-2 rounded-[10px] flex h-16 justify-between items-center '>
  141. <Input
  142. variant='filled'
  143. prefix={<SearchOutlined />}
  144. placeholder={t('please_enter_the_keywords')}
  145. allowClear
  146. className='w-[400px] h-[40px]
  147. border-1 border-[#f1f1f1]
  148. backdrop-filter
  149. backdrop-blur-lg
  150. dark:border-[#6f7f95]
  151. dark:bg-[#6f7f95]
  152. dark:bg-opacity-60'
  153. />
  154. <span className='flex gap-2 items-center'>
  155. <Avatar className='bg-gradient-to-tr from-[#31afff] to-[#1677ff] cursor-pointer'>
  156. </Avatar>
  157. <span
  158. >
  159. admin
  160. </span>
  161. </span>
  162. </div>
  163. <div className='flex items-center gap-4 justify-between'>
  164. <span></span>
  165. <Button
  166. className='border-none text-white bg-button-gradient'
  167. onClick={() => {
  168. router.push('/construct/flow/canvas');
  169. }}
  170. >
  171. 创建工作流
  172. </Button>
  173. </div>
  174. <div className='rounded-[10px] flex flex-col h-full mt-4 relative bg-slate-200 p-4 border-1 mb-2'>
  175. {flowList.map(flow => (
  176. <BlurredCard
  177. description={flow.description}
  178. name={flow.name}
  179. key={flow.uid}
  180. logo='/pictures/flow.png'
  181. onClick={() => {
  182. router.push('/construct/flow/canvas?id=' + flow.uid);
  183. }}
  184. RightTop={
  185. <InnerDropdown
  186. menu={{
  187. items: [
  188. {
  189. key: 'copy',
  190. label: (
  191. <span
  192. onClick={() => {
  193. handleCopy(flow);
  194. }}
  195. >
  196. {t('Copy_Btn')}
  197. </span>
  198. ),
  199. },
  200. {
  201. key: 'del',
  202. label: (
  203. <Popconfirm title='Are you sure to delete this flow?' onConfirm={() => deleteFlow(flow)}>
  204. <span className='text-red-400'>{t('Delete_Btn')}</span>
  205. </Popconfirm>
  206. ),
  207. },
  208. ],
  209. }}
  210. />
  211. }
  212. rightTopHover={false}
  213. Tags={
  214. <div>
  215. <Tag color={flow.source === 'DBGPT-WEB' ? 'green' : 'blue'}>{flow.source}</Tag>
  216. <Tag color={flow.editable ? 'green' : 'gray'}>{flow.editable ? 'Editable' : 'Can not Edit'}</Tag>
  217. <Tag color={flow.state === 'load_failed' ? 'red' : flow.state === 'running' ? 'green' : 'blue'}>
  218. {flow.state}
  219. </Tag>
  220. </div>
  221. }
  222. LeftBottom={
  223. <div key={i18n.language + 'flow'} className='flex gap-2'>
  224. <span>{flow?.nick_name}</span>
  225. <span>•</span>
  226. {flow?.gmt_modified && <span>{moment(flow?.gmt_modified).fromNow() + ' ' + t('update')}</span>}
  227. </div>
  228. }
  229. RightBottom={
  230. <ChatButton
  231. onClick={() => {
  232. handleChat(flow);
  233. }}
  234. text={t('start_chat')}
  235. />
  236. }
  237. />
  238. ))}
  239. {flowList.length === 0 && <MyEmpty description='No flow found' />}
  240. <div className='w-full flex justify-end shrink-0 pb-12'>
  241. <Pagination
  242. total={totalRef.current?.total_count || 0}
  243. pageSize={12}
  244. current={totalRef.current?.current_page}
  245. onChange={async (page, page_size) => {
  246. await getFlowListRun({ page, page_size });
  247. }}
  248. />
  249. </div>
  250. </div>
  251. </div>
  252. </Spin>
  253. <Modal
  254. open={showModal}
  255. destroyOnClose
  256. title='Copy AWEL Flow'
  257. onCancel={() => {
  258. setShowModal(false);
  259. }}
  260. footer={false}
  261. >
  262. <Form form={form} onFinish={onFinish} className='mt-6'>
  263. <Form.Item name='name' label='Name' rules={[{ required: true }]}>
  264. <Input />
  265. </Form.Item>
  266. <Form.Item name='label' label='Label' rules={[{ required: true }]}>
  267. <Input />
  268. </Form.Item>
  269. <Form.Item label='editable'>
  270. <Checkbox
  271. value={editable}
  272. checked={editable}
  273. onChange={e => {
  274. const val = e.target.checked;
  275. setEditable(val);
  276. }}
  277. />
  278. </Form.Item>
  279. <Form.Item label='deploy'>
  280. <Checkbox
  281. value={deploy}
  282. checked={deploy}
  283. onChange={e => {
  284. const val = e.target.checked;
  285. setDeploy(val);
  286. }}
  287. />
  288. </Form.Item>
  289. <div className='flex justify-end'>
  290. <Button type='primary' htmlType='submit'>
  291. {t('Submit')}
  292. </Button>
  293. </div>
  294. </Form>
  295. </Modal>
  296. </ConstructLayout>
  297. );
  298. }
  299. export default Flow;