Merge branch 'develop' into feature/ui_upgrade_zy

This commit is contained in:
zhaoying
2026-03-20 11:49:00 +08:00
286 changed files with 23406 additions and 5328 deletions

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 15:17:48
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-06 14:49:17
* @Last Modified time: 2026-03-20 11:26:43
*/
import { useRef, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
@@ -12,9 +12,11 @@ import { Graph, Node, MiniMap, Snapline, Clipboard, Keyboard, type Edge } from '
import { register } from '@antv/x6-react-shape';
import type { PortMetadata } from '@antv/x6/lib/model/port';
import { nodeRegisterLibrary, graphNodeLibrary, nodeLibrary, portMarkup, portAttrs, edgeAttrs, edge_color, edge_selected_color, portTextAttrs, defaultAbsolutePortGroups, nodeWidth, unknownNode, defaultPortItems, portItemArgsY, edge_width, conditionNodePortItemArgsY, conditionNodeItemHeight, conditionNodeHeight } from '../constant';
import { nodeRegisterLibrary, graphNodeLibrary, nodeLibrary, portMarkup, portAttrs, edgeAttrs, edge_color, edge_selected_color, portTextAttrs, defaultAbsolutePortGroups, nodeWidth, unknownNode, defaultPortItems, portItemArgsY, edge_width, conditionNodePortItemArgsY, conditionNodeItemHeight, conditionNodeHeight, notesConfig } from '../constant';
import type { WorkflowConfig, NodeProperties, ChatVariable } from '../types';
import { getWorkflowConfig, saveWorkflowConfig } from '@/api/application'
import { useUser } from '@/store/user';
import type { FeaturesConfigForm } from '@/views/ApplicationConfig/types'
/**
* Props for useWorkflowGraph hook
@@ -24,6 +26,8 @@ export interface UseWorkflowGraphProps {
containerRef: React.RefObject<HTMLDivElement>;
/** Reference to the minimap container element */
miniMapRef: React.RefObject<HTMLDivElement>;
/** Callback when features config is loaded */
onFeaturesLoad?: (features: FeaturesConfigForm | undefined) => void;
}
/**
@@ -64,6 +68,9 @@ export interface UseWorkflowGraphReturn {
chatVariables: ChatVariable[];
/** Function to update chat variables */
setChatVariables: React.Dispatch<React.SetStateAction<ChatVariable[]>>;
handleAddNotes: () => void;
handleSaveFeaturesConfig: (value: FeaturesConfigForm) => void;
}
/**
@@ -75,11 +82,13 @@ export interface UseWorkflowGraphReturn {
export const useWorkflowGraph = ({
containerRef,
miniMapRef,
onFeaturesLoad,
}: UseWorkflowGraphProps): UseWorkflowGraphReturn => {
// Hooks
const { id } = useParams();
const { message } = App.useApp();
const { t } = useTranslation()
const { user } = useUser();
// Refs
const graphRef = useRef<Graph>();
@@ -111,6 +120,7 @@ export const useWorkflowGraph = ({
})
setChatVariables(initChatVariables)
setConfig({ ...rest, variables: initChatVariables })
onFeaturesLoad?.(rest.features)
})
}
@@ -128,7 +138,7 @@ export const useWorkflowGraph = ({
if (nodes.length) {
const nodeList = nodes.map(node => {
const { id, type, name, position, config = {} } = node
let nodeLibraryConfig = [...nodeLibrary, { nodes: [unknownNode] }]
let nodeLibraryConfig: NodeProperties | undefined = [...nodeLibrary, { nodes: [unknownNode, notesConfig] }]
.flatMap(category => category.nodes)
.find(n => n.type === type) as NodeProperties
nodeLibraryConfig = JSON.parse(JSON.stringify({ ...nodeLibraryConfig, config: nodeLibraryConfig.config || {} }))
@@ -197,6 +207,13 @@ export const useWorkflowGraph = ({
data: { ...node, ...nodeLibraryConfig},
...position,
}
if (type === 'notes') {
const w = config.width;
const h = config.height;
if (w) nodeConfig.width = w as number;
if (h) nodeConfig.height = h as number;
}
// Generate ports dynamically for if-else node based on cases
if (type === 'if-else' && config.cases && Array.isArray(config.cases)) {
@@ -471,11 +488,12 @@ export const useWorkflowGraph = ({
*/
const nodeClick = ({ node }: { node: Node }) => {
// Ignore add-node type node clicks
if (node.getData()?.type === 'add-node' || node.getData().type === 'break' || node.getData().type === 'cycle-start') {
const nodeData = node.getData()
if (nodeData?.type === 'add-node' || nodeData.type === 'break' || nodeData.type === 'cycle-start') {
setSelectedNode(null)
return;
}
const nodes = graphRef.current?.getNodes();
nodes?.forEach(vo => {
@@ -488,11 +506,13 @@ export const useWorkflowGraph = ({
}
});
node.setData({
...node.getData(),
...nodeData,
isSelected: true,
});
setSelectedNode(node);
clearEdgeSelect()
if (nodeData.type !== 'notes') {
setSelectedNode(node);
}
};
/**
* Handle edge click event
@@ -601,7 +621,14 @@ export const useWorkflowGraph = ({
*/
const parseEvent = () => {
if (!graphRef.current?.isClipboardEmpty()) {
graphRef.current?.paste({ offset: 32 });
const pastedNodes = graphRef.current?.paste({ offset: 32 }) ?? [];
pastedNodes.forEach(cell => {
if (cell.isNode()) {
const data = cell.getData();
const newId = `${(data.type as string).replace(/-/g, '_')}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
cell.setData({ ...data, id: newId });
}
});
blankClick();
}
return false;
@@ -749,6 +776,8 @@ export const useWorkflowGraph = ({
panning: isHandMode,
mousewheel: {
enabled: true,
factor: 0.1,
modifiers: null,
},
connecting: {
connector: {
@@ -772,8 +801,23 @@ export const useWorkflowGraph = ({
createEdge() {
return graphRef.current?.createEdge(edgeAttrs);
},
validateConnection({ sourceCell, targetCell, targetMagnet }) {
validateConnection({ sourceCell, targetCell, sourceMagnet, targetMagnet }) {
if (!targetMagnet) return false;
// Only allow right port → left port connections
const getPortGroup = (magnet: Element) => {
let el: Element | null = magnet;
while (el) {
const group = el.getAttribute('port-group');
if (group) return group;
el = el.parentElement;
}
return null;
};
const sourceGroup = sourceMagnet ? getPortGroup(sourceMagnet) : null;
const targetGroup = targetMagnet ? getPortGroup(targetMagnet) : null;
if (sourceGroup === 'left' || targetGroup === 'right') return false;
// Node cannot connect to itself
if (sourceCell?.id === targetCell?.id) return false;
@@ -872,8 +916,31 @@ export const useWorkflowGraph = ({
init();
window.addEventListener('resize', handleResize);
const handleNoteKeydown = (e: KeyboardEvent) => {
if (!graphRef.current) return;
const selectedNote = graphRef.current.getNodes().find(n => n.getData()?.isSelected && n.getData()?.type === 'notes');
if (!selectedNote) return;
const isMeta = e.ctrlKey || e.metaKey;
if (e.key === 'Delete' || e.key === 'Backspace') {
// Only delete node when editor is not focused on text
const active = document.activeElement;
if (active && (active as HTMLElement).isContentEditable) return;
deleteEvent();
} else if (isMeta && e.key === 'c') {
copyEvent();
} else if (isMeta && e.key === 'v') {
parseEvent();
} else if (isMeta && e.key === 'd') {
e.preventDefault();
deleteEvent();
}
};
window.addEventListener('keydown', handleNoteKeydown);
return () => {
window.removeEventListener('resize', handleResize);
window.removeEventListener('keydown', handleNoteKeydown);
graphRef.current?.dispose();
};
}, []);
@@ -897,7 +964,7 @@ export const useWorkflowGraph = ({
.flatMap(category => category.nodes)
.find(n => n.type === dragData.type);
nodeLibraryConfig = JSON.parse(JSON.stringify({ config: {}, ...nodeLibraryConfig })) as NodeProperties
// Create clean node data, only keep necessary fields
const cleanNodeData = {
id: `${dragData.type.replace(/-/g, '_')}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
@@ -948,6 +1015,9 @@ export const useWorkflowGraph = ({
}) || [];
const edges = graphRef.current?.getEdges() || []
console.log('config', config)
const params = {
...config,
variables: chatVariables.map(v => {
@@ -1116,6 +1186,35 @@ export const useWorkflowGraph = ({
})
}
const handleAddNotes = () => {
if (!graphRef.current) return;
const nodeConfig: NodeProperties = JSON.parse(JSON.stringify(notesConfig));
nodeConfig.config = {
...nodeConfig.config,
author: { type: 'define', defaultValue: user?.username || '' },
};
const cleanNodeData = {
id: `notes_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
name: t('workflow.notes'),
...nodeConfig,
};
const container = graphRef.current.container;
const nodeW = graphNodeLibrary.notes?.width || nodeWidth;
const nodeH = graphNodeLibrary.notes?.height || 100;
const rect = container.getBoundingClientRect();
const center = graphRef.current.clientToLocal(rect.left + rect.width / 2, rect.top + rect.height / 2);
graphRef.current.addNode({
...(graphNodeLibrary.notes || graphNodeLibrary.default),
x: center.x - nodeW / 2,
y: center.y - nodeH / 2,
id: cleanNodeData.id,
data: { ...cleanNodeData },
});
}
const handleSaveFeaturesConfig = (value?: FeaturesConfigForm) => {
setConfig(prev => prev ? { ...prev, features: value } as WorkflowConfig : prev)
}
return {
config,
setConfig,
@@ -1133,6 +1232,8 @@ export const useWorkflowGraph = ({
parseEvent,
handleSave,
chatVariables,
setChatVariables
setChatVariables,
handleAddNotes,
handleSaveFeaturesConfig
};
};