balibabu
commited on
Commit
·
bdb8bf3
1
Parent(s):
9741edc
feat: Automatically save agent page data #3301 (#3302)
Browse files### What problem does this PR solve?
feat: Automatically save agent page data #3301
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
- web/src/locales/en.ts +1 -0
- web/src/locales/zh-traditional.ts +1 -0
- web/src/locales/zh.ts +1 -0
- web/src/pages/flow/canvas/index.tsx +3 -12
- web/src/pages/flow/canvas/node/relevant-node.tsx +5 -2
- web/src/pages/flow/header/index.less +1 -1
- web/src/pages/flow/header/index.tsx +11 -3
- web/src/pages/flow/hooks.ts +49 -4
- web/src/pages/flow/store.ts +5 -1
- web/src/pages/flow/utils.ts +28 -0
web/src/locales/en.ts
CHANGED
|
@@ -1036,6 +1036,7 @@ The above is the content you need to summarize.`,
|
|
| 1036 |
howUseId: 'How to use agent ID?',
|
| 1037 |
content: 'Content',
|
| 1038 |
operationResults: 'Operation Results',
|
|
|
|
| 1039 |
},
|
| 1040 |
footer: {
|
| 1041 |
profile: 'All rights reserved @ React',
|
|
|
|
| 1036 |
howUseId: 'How to use agent ID?',
|
| 1037 |
content: 'Content',
|
| 1038 |
operationResults: 'Operation Results',
|
| 1039 |
+
autosave: 'Automatically saved',
|
| 1040 |
},
|
| 1041 |
footer: {
|
| 1042 |
profile: 'All rights reserved @ React',
|
web/src/locales/zh-traditional.ts
CHANGED
|
@@ -984,6 +984,7 @@ export default {
|
|
| 984 |
howUseId: '如何使用Agent ID?',
|
| 985 |
content: '內容',
|
| 986 |
operationResults: '運行結果',
|
|
|
|
| 987 |
},
|
| 988 |
footer: {
|
| 989 |
profile: '“保留所有權利 @ react”',
|
|
|
|
| 984 |
howUseId: '如何使用Agent ID?',
|
| 985 |
content: '內容',
|
| 986 |
operationResults: '運行結果',
|
| 987 |
+
autosave: '已自動儲存',
|
| 988 |
},
|
| 989 |
footer: {
|
| 990 |
profile: '“保留所有權利 @ react”',
|
web/src/locales/zh.ts
CHANGED
|
@@ -1004,6 +1004,7 @@ export default {
|
|
| 1004 |
howUseId: '如何使用Agent ID?',
|
| 1005 |
content: '内容',
|
| 1006 |
operationResults: '运行结果',
|
|
|
|
| 1007 |
},
|
| 1008 |
footer: {
|
| 1009 |
profile: 'All rights reserved @ React',
|
|
|
|
| 1004 |
howUseId: '如何使用Agent ID?',
|
| 1005 |
content: '内容',
|
| 1006 |
operationResults: '运行结果',
|
| 1007 |
+
autosave: '已自动保存',
|
| 1008 |
},
|
| 1009 |
footer: {
|
| 1010 |
profile: 'All rights reserved @ React',
|
web/src/pages/flow/canvas/index.tsx
CHANGED
|
@@ -128,6 +128,9 @@ function FlowCanvas({ chatDrawerVisible, hideChatDrawer }: IProps) {
|
|
| 128 |
onSelectionChange={onSelectionChange}
|
| 129 |
nodeOrigin={[0.5, 0]}
|
| 130 |
isValidConnection={isValidConnection}
|
|
|
|
|
|
|
|
|
|
| 131 |
onChange={(...params) => {
|
| 132 |
console.info('params:', ...params);
|
| 133 |
}}
|
|
@@ -140,18 +143,6 @@ function FlowCanvas({ chatDrawerVisible, hideChatDrawer }: IProps) {
|
|
| 140 |
},
|
| 141 |
}}
|
| 142 |
deleteKeyCode={['Delete', 'Backspace']}
|
| 143 |
-
onPaste={(...params) => {
|
| 144 |
-
console.info('onPaste:', ...params);
|
| 145 |
-
}}
|
| 146 |
-
onPasteCapture={(...params) => {
|
| 147 |
-
console.info('onPasteCapture:', ...params);
|
| 148 |
-
}}
|
| 149 |
-
onCopy={(...params) => {
|
| 150 |
-
console.info('onCopy:', ...params);
|
| 151 |
-
}}
|
| 152 |
-
onCopyCapture={(...params) => {
|
| 153 |
-
console.info('onCopyCapture:', ...params);
|
| 154 |
-
}}
|
| 155 |
>
|
| 156 |
<Background />
|
| 157 |
<Controls />
|
|
|
|
| 128 |
onSelectionChange={onSelectionChange}
|
| 129 |
nodeOrigin={[0.5, 0]}
|
| 130 |
isValidConnection={isValidConnection}
|
| 131 |
+
onChangeCapture={(...params) => {
|
| 132 |
+
console.info('onChangeCapture:', ...params);
|
| 133 |
+
}}
|
| 134 |
onChange={(...params) => {
|
| 135 |
console.info('params:', ...params);
|
| 136 |
}}
|
|
|
|
| 143 |
},
|
| 144 |
}}
|
| 145 |
deleteKeyCode={['Delete', 'Backspace']}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
>
|
| 147 |
<Background />
|
| 148 |
<Controls />
|
web/src/pages/flow/canvas/node/relevant-node.tsx
CHANGED
|
@@ -5,12 +5,15 @@ import { NodeData } from '../../interface';
|
|
| 5 |
import { RightHandleStyle } from './handle-icon';
|
| 6 |
|
| 7 |
import { get } from 'lodash';
|
|
|
|
| 8 |
import styles from './index.less';
|
| 9 |
import NodeHeader from './node-header';
|
| 10 |
|
| 11 |
export function RelevantNode({ id, data, selected }: NodeProps<NodeData>) {
|
| 12 |
const yes = get(data, 'form.yes');
|
| 13 |
const no = get(data, 'form.no');
|
|
|
|
|
|
|
| 14 |
return (
|
| 15 |
<section
|
| 16 |
className={classNames(styles.logicNode, {
|
|
@@ -50,11 +53,11 @@ export function RelevantNode({ id, data, selected }: NodeProps<NodeData>) {
|
|
| 50 |
<Flex vertical gap={10}>
|
| 51 |
<Flex vertical>
|
| 52 |
<div className={styles.relevantLabel}>Yes</div>
|
| 53 |
-
<div className={styles.nodeText}>{yes}</div>
|
| 54 |
</Flex>
|
| 55 |
<Flex vertical>
|
| 56 |
<div className={styles.relevantLabel}>No</div>
|
| 57 |
-
<div className={styles.nodeText}>{no}</div>
|
| 58 |
</Flex>
|
| 59 |
</Flex>
|
| 60 |
</section>
|
|
|
|
| 5 |
import { RightHandleStyle } from './handle-icon';
|
| 6 |
|
| 7 |
import { get } from 'lodash';
|
| 8 |
+
import { useReplaceIdWithName } from '../../hooks';
|
| 9 |
import styles from './index.less';
|
| 10 |
import NodeHeader from './node-header';
|
| 11 |
|
| 12 |
export function RelevantNode({ id, data, selected }: NodeProps<NodeData>) {
|
| 13 |
const yes = get(data, 'form.yes');
|
| 14 |
const no = get(data, 'form.no');
|
| 15 |
+
const replaceIdWithName = useReplaceIdWithName();
|
| 16 |
+
|
| 17 |
return (
|
| 18 |
<section
|
| 19 |
className={classNames(styles.logicNode, {
|
|
|
|
| 53 |
<Flex vertical gap={10}>
|
| 54 |
<Flex vertical>
|
| 55 |
<div className={styles.relevantLabel}>Yes</div>
|
| 56 |
+
<div className={styles.nodeText}>{replaceIdWithName(yes)}</div>
|
| 57 |
</Flex>
|
| 58 |
<Flex vertical>
|
| 59 |
<div className={styles.relevantLabel}>No</div>
|
| 60 |
+
<div className={styles.nodeText}>{replaceIdWithName(no)}</div>
|
| 61 |
</Flex>
|
| 62 |
</Flex>
|
| 63 |
</section>
|
web/src/pages/flow/header/index.less
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
.flowHeader {
|
| 2 |
-
padding:
|
| 3 |
}
|
|
|
|
| 1 |
.flowHeader {
|
| 2 |
+
padding: 10px 20px;
|
| 3 |
}
|
web/src/pages/flow/header/index.tsx
CHANGED
|
@@ -5,7 +5,11 @@ import { ArrowLeftOutlined } from '@ant-design/icons';
|
|
| 5 |
import { Button, Flex, Space } from 'antd';
|
| 6 |
import { Link, useParams } from 'umi';
|
| 7 |
import FlowIdModal from '../flow-id-modal';
|
| 8 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
import styles from './index.less';
|
| 10 |
|
| 11 |
interface IProps {
|
|
@@ -20,10 +24,11 @@ const FlowHeader = ({ showChatDrawer }: IProps) => {
|
|
| 20 |
const {
|
| 21 |
visible: overviewVisible,
|
| 22 |
hideModal: hideOverviewModal,
|
| 23 |
-
showModal: showOverviewModal,
|
| 24 |
} = useSetModalState();
|
| 25 |
const { visible, hideModal, showModal } = useSetModalState();
|
| 26 |
const { id } = useParams();
|
|
|
|
| 27 |
|
| 28 |
return (
|
| 29 |
<>
|
|
@@ -37,7 +42,10 @@ const FlowHeader = ({ showChatDrawer }: IProps) => {
|
|
| 37 |
<Link to={`/flow`}>
|
| 38 |
<ArrowLeftOutlined />
|
| 39 |
</Link>
|
| 40 |
-
<
|
|
|
|
|
|
|
|
|
|
| 41 |
</Space>
|
| 42 |
<Space size={'large'}>
|
| 43 |
<Button onClick={handleRun}>
|
|
|
|
| 5 |
import { Button, Flex, Space } from 'antd';
|
| 6 |
import { Link, useParams } from 'umi';
|
| 7 |
import FlowIdModal from '../flow-id-modal';
|
| 8 |
+
import {
|
| 9 |
+
useSaveGraph,
|
| 10 |
+
useSaveGraphBeforeOpeningDebugDrawer,
|
| 11 |
+
useWatchAgentChange,
|
| 12 |
+
} from '../hooks';
|
| 13 |
import styles from './index.less';
|
| 14 |
|
| 15 |
interface IProps {
|
|
|
|
| 24 |
const {
|
| 25 |
visible: overviewVisible,
|
| 26 |
hideModal: hideOverviewModal,
|
| 27 |
+
// showModal: showOverviewModal,
|
| 28 |
} = useSetModalState();
|
| 29 |
const { visible, hideModal, showModal } = useSetModalState();
|
| 30 |
const { id } = useParams();
|
| 31 |
+
const time = useWatchAgentChange();
|
| 32 |
|
| 33 |
return (
|
| 34 |
<>
|
|
|
|
| 42 |
<Link to={`/flow`}>
|
| 43 |
<ArrowLeftOutlined />
|
| 44 |
</Link>
|
| 45 |
+
<div className="flex flex-col">
|
| 46 |
+
<span className="font-semibold text-[18px]">{data.title}</span>
|
| 47 |
+
<span className="font-normal text-sm">已自动保存 {time}</span>
|
| 48 |
+
</div>
|
| 49 |
</Space>
|
| 50 |
<Space size={'large'}>
|
| 51 |
<Button onClick={handleRun}>
|
web/src/pages/flow/hooks.ts
CHANGED
|
@@ -19,7 +19,9 @@ import {
|
|
| 19 |
import { useFetchModelId, useSendMessageWithSse } from '@/hooks/logic-hooks';
|
| 20 |
import { Variable } from '@/interfaces/database/chat';
|
| 21 |
import api from '@/utils/api';
|
|
|
|
| 22 |
import { FormInstance, message } from 'antd';
|
|
|
|
| 23 |
import { humanId } from 'human-id';
|
| 24 |
import { lowerFirst } from 'lodash';
|
| 25 |
import trim from 'lodash/trim';
|
|
@@ -446,12 +448,21 @@ export const useSaveGraphBeforeOpeningDebugDrawer = (show: () => void) => {
|
|
| 446 |
return handleRun;
|
| 447 |
};
|
| 448 |
|
| 449 |
-
export const
|
| 450 |
const getNode = useGraphStore((state) => state.getNode);
|
| 451 |
|
| 452 |
-
const
|
| 453 |
-
|
| 454 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 455 |
|
| 456 |
return {
|
| 457 |
replacedOutput: replaceIdWithText(output, getNameById),
|
|
@@ -547,6 +558,7 @@ export const useWatchNodeFormDataChange = () => {
|
|
| 547 |
);
|
| 548 |
|
| 549 |
useEffect(() => {
|
|
|
|
| 550 |
nodes.forEach((node) => {
|
| 551 |
const currentNode = getNode(node.id);
|
| 552 |
const form = currentNode?.data.form ?? {};
|
|
@@ -668,3 +680,36 @@ export const useCopyPaste = () => {
|
|
| 668 |
};
|
| 669 |
}, [onPasteCapture]);
|
| 670 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
import { useFetchModelId, useSendMessageWithSse } from '@/hooks/logic-hooks';
|
| 20 |
import { Variable } from '@/interfaces/database/chat';
|
| 21 |
import api from '@/utils/api';
|
| 22 |
+
import { useDebounceEffect } from 'ahooks';
|
| 23 |
import { FormInstance, message } from 'antd';
|
| 24 |
+
import dayjs from 'dayjs';
|
| 25 |
import { humanId } from 'human-id';
|
| 26 |
import { lowerFirst } from 'lodash';
|
| 27 |
import trim from 'lodash/trim';
|
|
|
|
| 448 |
return handleRun;
|
| 449 |
};
|
| 450 |
|
| 451 |
+
export const useReplaceIdWithName = () => {
|
| 452 |
const getNode = useGraphStore((state) => state.getNode);
|
| 453 |
|
| 454 |
+
const replaceIdWithName = useCallback(
|
| 455 |
+
(id?: string) => {
|
| 456 |
+
return getNode(id)?.data.name;
|
| 457 |
+
},
|
| 458 |
+
[getNode],
|
| 459 |
+
);
|
| 460 |
+
|
| 461 |
+
return replaceIdWithName;
|
| 462 |
+
};
|
| 463 |
+
|
| 464 |
+
export const useReplaceIdWithText = (output: unknown) => {
|
| 465 |
+
const getNameById = useReplaceIdWithName();
|
| 466 |
|
| 467 |
return {
|
| 468 |
replacedOutput: replaceIdWithText(output, getNameById),
|
|
|
|
| 558 |
);
|
| 559 |
|
| 560 |
useEffect(() => {
|
| 561 |
+
console.info('xxx');
|
| 562 |
nodes.forEach((node) => {
|
| 563 |
const currentNode = getNode(node.id);
|
| 564 |
const form = currentNode?.data.form ?? {};
|
|
|
|
| 680 |
};
|
| 681 |
}, [onPasteCapture]);
|
| 682 |
};
|
| 683 |
+
|
| 684 |
+
export const useWatchAgentChange = () => {
|
| 685 |
+
const [time, setTime] = useState<string>();
|
| 686 |
+
const nodes = useGraphStore((state) => state.nodes);
|
| 687 |
+
const edges = useGraphStore((state) => state.edges);
|
| 688 |
+
const { saveGraph } = useSaveGraph();
|
| 689 |
+
const { data: flowDetail } = useFetchFlow();
|
| 690 |
+
|
| 691 |
+
const setSaveTime = useCallback((updateTime: number) => {
|
| 692 |
+
setTime(dayjs(updateTime).format('YYYY-MM-DD HH:mm:ss'));
|
| 693 |
+
}, []);
|
| 694 |
+
|
| 695 |
+
useEffect(() => {
|
| 696 |
+
setSaveTime(flowDetail?.update_time);
|
| 697 |
+
}, [flowDetail, setSaveTime]);
|
| 698 |
+
|
| 699 |
+
const saveAgent = useCallback(async () => {
|
| 700 |
+
const ret = await saveGraph();
|
| 701 |
+
setSaveTime(ret.data.update_time);
|
| 702 |
+
}, [saveGraph, setSaveTime]);
|
| 703 |
+
|
| 704 |
+
useDebounceEffect(
|
| 705 |
+
() => {
|
| 706 |
+
saveAgent();
|
| 707 |
+
},
|
| 708 |
+
[nodes, edges],
|
| 709 |
+
{
|
| 710 |
+
wait: 1000 * 20,
|
| 711 |
+
},
|
| 712 |
+
);
|
| 713 |
+
|
| 714 |
+
return time;
|
| 715 |
+
};
|
web/src/pages/flow/store.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { immer } from 'zustand/middleware/immer';
|
|
| 24 |
import { Operator, SwitchElseTo } from './constant';
|
| 25 |
import { NodeData } from './interface';
|
| 26 |
import {
|
|
|
|
| 27 |
generateNodeNamesWithIncreasingIndex,
|
| 28 |
getNodeDragHandle,
|
| 29 |
getOperatorIndex,
|
|
@@ -242,7 +243,10 @@ const useGraphStore = create<RFState>()(
|
|
| 242 |
|
| 243 |
addNode({
|
| 244 |
...(node || {}),
|
| 245 |
-
data: {
|
|
|
|
|
|
|
|
|
|
| 246 |
selected: false,
|
| 247 |
dragging: false,
|
| 248 |
id: `${node?.data?.label}:${humanId()}`,
|
|
|
|
| 24 |
import { Operator, SwitchElseTo } from './constant';
|
| 25 |
import { NodeData } from './interface';
|
| 26 |
import {
|
| 27 |
+
duplicateNodeForm,
|
| 28 |
generateNodeNamesWithIncreasingIndex,
|
| 29 |
getNodeDragHandle,
|
| 30 |
getOperatorIndex,
|
|
|
|
| 243 |
|
| 244 |
addNode({
|
| 245 |
...(node || {}),
|
| 246 |
+
data: {
|
| 247 |
+
...duplicateNodeForm(node?.data),
|
| 248 |
+
name: generateNodeName(name),
|
| 249 |
+
},
|
| 250 |
selected: false,
|
| 251 |
dragging: false,
|
| 252 |
id: `${node?.data?.label}:${humanId()}`,
|
web/src/pages/flow/utils.ts
CHANGED
|
@@ -289,3 +289,31 @@ export const generateNodeNamesWithIncreasingIndex = (
|
|
| 289 |
|
| 290 |
return `${name}_${index}`;
|
| 291 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
|
| 290 |
return `${name}_${index}`;
|
| 291 |
};
|
| 292 |
+
|
| 293 |
+
export const duplicateNodeForm = (nodeData?: NodeData) => {
|
| 294 |
+
const form: Record<string, any> = { ...(nodeData?.form ?? {}) };
|
| 295 |
+
|
| 296 |
+
// Delete the downstream node corresponding to the to field of the Categorize operator
|
| 297 |
+
if (nodeData?.label === Operator.Categorize) {
|
| 298 |
+
form.category_description = Object.keys(form.category_description).reduce<
|
| 299 |
+
Record<string, Record<string, any>>
|
| 300 |
+
>((pre, cur) => {
|
| 301 |
+
pre[cur] = {
|
| 302 |
+
...form.category_description[cur],
|
| 303 |
+
to: undefined,
|
| 304 |
+
};
|
| 305 |
+
return pre;
|
| 306 |
+
}, {});
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
// Delete the downstream nodes corresponding to the yes and no fields of the Relevant operator
|
| 310 |
+
if (nodeData?.label === Operator.Relevant) {
|
| 311 |
+
form.yes = undefined;
|
| 312 |
+
form.no = undefined;
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
return {
|
| 316 |
+
...(nodeData ?? {}),
|
| 317 |
+
form,
|
| 318 |
+
};
|
| 319 |
+
};
|