-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Closed
Labels
waiting for maintainerTriage or intervention needed from a maintainerTriage or intervention needed from a maintainer
Description
问题描述
Ports connection to the target node is always in accurate, but moving it or manually adding edge never causes this issue. Watch video please.
预期行为
The issue did not happen in the older version of x6 (which was a very old version), i updated the library to v3 and started having this issue since.
屏幕截图或视频(可选但最好有)
https://drive.google.com/file/d/1o-dYLJToroal07BJcVQUkSKl8JoxGW3m/view?usp=sharing
x6.bug.mp4
平台
- 操作系统: Windows
- 网页浏览器: Google Chrome, FIrefox
- X6 版本: 3~3.1.3
重现步骤
Code:
function registerScriptNodes() {
// Register Start Node
X6.Shape.HTML.register({
shape: SCRIPT_NODE_TYPES.START,
width: 250, // Smaller width for pill shape
height: 70, // Fixed height for pill shape
effect: [],
ports: {
groups: {
output: {
position: "bottom",
attrs: {
circle: {
r: 8,
magnet: true,
stroke: "#198754",
strokeWidth: 2,
fill: "#fff",
},
},
},
},
},
html(cell) {
const div = document.createElement("div");
div.className = "script-node script-start-node";
div.innerHTML = `
<div class="script-node-content">
<div class="btn-ic-span-align">
<i class="fa-regular fa-flag"></i>
<span>Start</span>
</div>
</div>
`;
return div;
},
});
// User Query/Message Node
X6.Shape.HTML.register({
shape: SCRIPT_NODE_TYPES.USER_QUERY,
width: SCRIPT_NODE_WIDTH,
height: SCRIPT_NODE_MIN_HEIGHT,
effect: [],
ports: {
groups: {
input: {
position: "top",
attrs: {
circle: {
r: 10,
magnet: true,
stroke: "#8f8f8f",
strokeWidth: 2,
fill: "#fff",
},
},
},
output: {
position: "bottom",
attrs: {
circle: {
r: 8,
magnet: true,
stroke: "#8f8f8f",
strokeWidth: 2,
fill: "#fff",
},
},
},
},
},
html(cell) {
const div = document.createElement("div");
div.className = "script-node script-user-query-node";
const data = cell.getData() || {};
const currentLanguage = scriptsManagerLanguageDropdown.getSelectedLanguage().id;
div.innerHTML = `
<div class="script-node-header">
<div>
<div class="d-flex align-items-center btn-ic-span-align node-title">
<i class="fa-regular fa-message me-2"></i>
<span>User Query</span>
</div>
<span class="node-id">${cell.id}</span>
</div>
<div class="node-actions html-shape-immovable">
<button class="btn btn-light btn-sm me-2" data-action="configure-user-query">
<i class="fa-regular fa-gear"></i>
</button>
<button class="btn btn-danger btn-sm" data-action="delete-node">
<i class="fa-regular fa-trash"></i>
</button>
</div>
</div>
<div class="script-node-content">
<div class="script-node-input-group html-shape-immovable">
<textarea
class="form-control"
placeholder="Type the user query..."
data-input="user-query"
rows="3"
>${data.query?.[currentLanguage] || ""}</textarea>
</div>
</div>
`;
return div;
},
});
// AI Response Node
X6.Shape.HTML.register({
shape: SCRIPT_NODE_TYPES.AI_RESPONSE,
width: SCRIPT_NODE_WIDTH,
height: SCRIPT_NODE_MIN_HEIGHT,
effect: [],
ports: {
groups: {
input: {
position: "top",
attrs: {
circle: {
r: 10,
magnet: true,
stroke: "#8f8f8f",
strokeWidth: 2,
fill: "#fff",
},
},
},
output: {
position: "bottom",
attrs: {
circle: {
r: 8,
magnet: true,
stroke: "#8f8f8f",
strokeWidth: 2,
fill: "#fff",
},
},
},
},
},
html(cell) {
const div = document.createElement("div");
div.className = "script-node script-ai-response-node";
const data = cell.getData() || {};
const currentLanguage = scriptsManagerLanguageDropdown.getSelectedLanguage().id;
div.innerHTML = `
<div class="script-node-header">
<div>
<div class="d-flex align-items-center btn-ic-span-align node-title">
<i class="fa-regular fa-robot me-2"></i>
<span>AI Response</span>
</div>
<span class="node-id">${cell.id}</span>
</div>
<div class="node-actions html-shape-immovable">
<button class="btn btn-light btn-sm me-2" data-action="configure-ai-response">
<i class="fa-regular fa-gear"></i>
</button>
<button class="btn btn-danger btn-sm" data-action="delete-node">
<i class="fa-regular fa-trash"></i>
</button>
</div>
</div>
<div class="script-node-content">
<div class="script-node-input-group html-shape-immovable">
<textarea
class="form-control"
placeholder="Type the AI response..."
data-input="ai-response"
rows="3"
>${data.response?.[currentLanguage] || ""}</textarea>
</div>
</div>
`;
return div;
},
});
}
function initializeScriptGraph(isNew = true) {
const container = $("#script-graph")[0];
return resizeScriptGraphCSS((graphSize) => {
// Set Default Shape Attributes
X6.Shape.Edge.defaults.attrs.line.stroke = "#fff";
X6.Shape.Edge.defaults.attrs.line.sourceMarker = "classic";
X6.Shape.Edge.defaults.attrs.line.targetMarker = "classic";
// Create the graph instance
const graph = new X6.Graph({
container: container,
width: graphSize.width,
height: graphSize.height,
// Grid settings
grid: {
visible: true,
type: "fixedDot",
size: 30,
args: {
color: "#2a2a2a",
thickness: 3,
},
},
// Background settings
background: {
color: "#0f0f0f",
},
// Interaction settings
mousewheel: {
enabled: true,
modifiers: [],
factor: 1.1,
maxScale: 16,
minScale: 0.01,
},
scaling: {
min: 0.01,
max: 16
},
panning: {
enabled: true,
modifiers: [],
},
connecting: {
connector: {
name: "rounded",
args: {
radius: 20,
},
},
allowBlank: false,
allowLoop: false,
allowNode: false,
allowEdge: false,
allowMulti: false,
highlight: true,
router: {
name: "orth",
args: {
padding: 4,
},
},
validateConnection({ sourceView, targetView, sourceMagnet, targetMagnet }) {
if (!sourceView || !targetView) {
return false;
}
if (!sourceMagnet || !targetMagnet) {
return false;
}
const sourcePort = sourceMagnet.attributes["port-group"].value;
const targetPort = targetMagnet.attributes["port-group"].value;
if ((sourcePort === "output" && targetPort === "output") || (sourcePort === "input" && targetPort === "input")) {
return false;
}
let inputCell;
let outputCell;
let inputPort;
let outputPort;
if (sourcePort === "input") {
inputCell = sourceView.cell;
outputCell = targetView.cell;
inputPort = sourceMagnet.attributes.port.value;
outputPort = targetMagnet.attributes.port.value;
} else {
inputCell = targetView.cell;
outputCell = sourceView.cell;
inputPort = targetMagnet.attributes.port.value;
outputPort = sourceMagnet.attributes.port.value;
}
// start node can not connect to ai response node
if (outputCell.shape === SCRIPT_NODE_TYPES.START && inputCell.shape === SCRIPT_NODE_TYPES.AI_RESPONSE) {
return false;
}
// ai response node can only connect to user query node
if (outputCell.shape === SCRIPT_NODE_TYPES.AI_RESPONSE && inputCell.shape !== SCRIPT_NODE_TYPES.USER_QUERY) {
return false;
}
// validate if source already has connected nodes
let validateNoDiffOuputTypes = false;
CurrentScriptGraph.getEdges().forEach((edge) => {
const letEdgeSource = edge.getSource();
if (letEdgeSource.cell === outputCell.id && letEdgeSource.port === outputPort) {
const letEdgeTarget = edge.getTarget();
if (letEdgeTarget.cell) {
letEdgeTargetCell = CurrentScriptGraph.getCellById(letEdgeTarget.cell);
// if atleast one user query is connected, then no other node type can be connected
if (letEdgeTargetCell.shape === SCRIPT_NODE_TYPES.USER_QUERY && inputCell.shape !== SCRIPT_NODE_TYPES.USER_QUERY) {
validateNoDiffOuputTypes = true;
}
// if one custom tool/system tool/ai response is connected, then no other type and no more nodes can be connected
if (
(letEdgeTargetCell.shape === SCRIPT_NODE_TYPES.CUSTOM_TOOL ||
letEdgeTargetCell.shape === SCRIPT_NODE_TYPES.SYSTEM_TOOL ||
letEdgeTargetCell.shape === SCRIPT_NODE_TYPES.AI_RESPONSE) &&
letEdgeTargetCell.shape !== inputCell.shape
) {
validateNoDiffOuputTypes = true;
}
}
}
});
if (validateNoDiffOuputTypes) return false;
return true;
},
},
// Prevent node text selection
preventDefaultContextMenu: ({ view, event }) => {
if (!view || view == null) {
return true;
}
// if event.target is textarea, then reutrn flase
if ($(event.target).is("textarea") || $(event.target).is("input")) {
return false;
}
return true;
}
});
// Add minimap plugin
const minimapContainer = document.getElementById("script-graph-minimap");
const enableMinimap = true;
if (minimapContainer && enableMinimap) {
setTimeout(() => {
graph.use(
new SCRIPT_GRAPH_PLUGINS.Minimap({
container: minimapContainer,
width: 180,
height: 150,
scalable: false,
padding: 0,
graphOptions: {
width: 180,
height: 150,
autoResize: true,
grid: {
visible: true,
type: "fixedDot",
size: 30,
args: {
color: "#2a2a2a",
thickness: 3,
},
},
// Background settings
background: {
color: "#0f0f0f",
}
}
}),
);
}, 2000);
}
// Add keyboard shortcuts plugin
if (SCRIPT_GRAPH_PLUGINS.Keyboard) {
graph.use(
new SCRIPT_GRAPH_PLUGINS.Keyboard({
enabled: true,
global: true,
}),
);
}
// Add clipboard plugin
if (SCRIPT_GRAPH_PLUGINS.Clipboard) {
graph.use(
new SCRIPT_GRAPH_PLUGINS.Clipboard({
enabled: true,
}),
);
}
// Add history plugin (undo/redo)
if (SCRIPT_GRAPH_PLUGINS.History) {
CurrentScriptGraphHistory = new SCRIPT_GRAPH_PLUGINS.History({
enabled: true,
beforeAddCommand: (event, args) => {
// Validate before adding to history
return true;
},
});
graph.use(CurrentScriptGraphHistory);
}
// Add selection plugin
if (SCRIPT_GRAPH_PLUGINS.Selection) {
CurrentScriptGraphSelection = new SCRIPT_GRAPH_PLUGINS.Selection({
enabled: true,
modifiers: ["ctrl"],
rubberband: true,
multiple: true,
movable: true,
showNodeSelectionBox: true,
eventTypes: ["leftMouseDown"],
});
graph.use(CurrentScriptGraphSelection);
}
// Add start node if new graph
if (isNew) {
const CurrentScriptGraphStartNode = graph.addNode({
id: "start_node",
shape: SCRIPT_NODE_TYPES.START,
data: { type: SCRIPT_NODE_TYPES.START },
x: graphSize.width / 2,
y: graphSize.height / 5,
ports: {
items: [{ group: "output" }],
},
});
ManageCurrentScriptData.nodes.push({
id: CurrentScriptGraphStartNode.id,
type: SCRIPT_NODE_TYPES.START,
position: CurrentScriptGraphStartNode.getPosition(),
});
}
// Event Listeners
graph.on("scale", ({ sx, sy }) => {
const scale = sx;
$("#script-graph-zoom-in").prop("disabled", scale >= 16);
$("#script-graph-zoom-out").prop("disabled", scale <= 0.01);
});
graph.on("history:change", (event) => {
$("#script-graph-undo").prop("disabled", !CurrentScriptGraphHistory.canUndo());
$("#script-graph-redo").prop("disabled", !CurrentScriptGraphHistory.canRedo());
});
graph.on("cell:click", ({ cell, e }) => {
const target = e.target;
if (target.closest('[data-action="delete-node"]')) {
const nodeType = cell.getData()?.type;
if (nodeType === SCRIPT_NODE_TYPES.START) {
return;
}
if (CurrentCanvasConfigCell !== null && cell.id === CurrentCanvasConfigCell.id) {
nodeConfigOffcanvas.hide();
}
cell.remove();
}
});
graph.on("edge:mouseenter", (event) => {
event.edge.setAttrs({ line: { strokeDasharray: 5, style: "animation: ant-line 30s infinite linear" } });
});
graph.on("edge:mouseleave", (event) => {
event.edge.setAttrs({ line: { strokeDasharray: 0, style: {} } });
});
graph.on("cell:click", ({ cell, e }) => {
if ($(e.target).is("textarea") || $(e.target).is("input") || $(e.target).is("select") || $(e.target).is("button")) {
CurrentScriptGraphSelection.clean();
return;
}
if (CurrentScriptGraphSelection.getSelectedCellCount() === 1) {
if (CurrentScriptGraphSelection.getSelectedCells()[0].id === cell.id) {
return;
}
}
CurrentScriptGraphSelection.clean();
console.log(cell);
});
graph.on("edge:added", (event) => {
console.log("edge:added", event);
// Add Remove Button Tool
event.edge.addTools({
name: "button-remove",
args: { distance: "50%" },
});
});
graph.on("edge:connected", (event) => {
console.log("edge:connected", event);
});
graph.on("blank:click", () => {
nodeConfigOffcanvas.hide();
});
CurrentScriptGraph = graph;
});
}
CSS:
/* Node */
/* Common Node Styles */
.x6-widget-minimap .script-node,
.x6-widget-minimap .script-node-header
{
background: var(--tcolor-darker-green) !important;
}
.script-node
{
background: #1a1a1a;
border: 1px solid var(--tcolor-darker-green) !important;
border-radius: 8px;
box-shadow: -2px 3px 2px 0px rgb(64 93 55 / 20%);
position: relative;
transition: all 0.2s ease;
width: 100%;
height: auto;
color: #fff;
cursor: pointer;
}
.node-moving .script-node {
cursor: move;
}
.x6-widget-selection-inner
{
border: 1px solid var(--tcolor-lighter-green) !important;
box-shadow: none !important;
border-radius: 4px;
padding: 30px !important;
margin-left: -27px !important;
margin-top: -27px !important;
}
.x6-node-selected .script-node, .x6-node-selected .script-node:hover
{
border: 1px solid var(--tcolor-lighter-green) !important;
box-shadow: -2px 3px 2px 0px rgb(236 254 143 / 80%);
}
.script-node:hover
{
box-shadow: -1px 1px 2px 0px rgb(236 254 143 / 70%);
}
.script-node.invalid-multilang
{
border: 1px solid var(--bs-danger) !important;
box-shadow: -1px 1px 2px 0px var(--bs-danger) !important;
}
.script-node-header
{
background: #1a1a1a;
padding: 8px;
border-bottom: 1px solid var(--tcolor-darker-green) !important;
display: flex;
justify-content: flex-end;
}
.script-node-header .node-title {
margin-bottom: 6px;
}
.script-node-header .node-id {
opacity: 0.5;
font-size: 0.8rem;
}
.script-node-delete-btn {
background: none;
border: none;
color: #dc3545;
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.2s ease;
}
.script-node-delete-btn:hover {
background: #fee2e2;
}
.script-node-content {
padding: 12px;
}
.script-node-input-group textarea {
resize: none; /** keep for now, requires complex js for updating port position */
min-height: 60px;
}
/* User Message Node Specific */
.script-user-query-node .script-node-header {
padding: 8px 12px;
border-radius: 6px 6px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.script-user-query-node .script-node-content {
padding: 12px;
}
.script-user-query-node .node-actions {
display: flex;
gap: 5px;
}
.script-user-query-node.invalid-multilang [data-input="user-query"]
{
border: 1px solid var(--bs-danger) !important;
box-shadow: -1px 1px 2px 0px var(--bs-danger) !important;
}
/* AI Response Node Specific */
.script-ai-response-node {
border: 1px solid #e9ecef;
border-radius: 6px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}
.script-ai-response-node .script-node-header {
padding: 8px 12px;
border-radius: 6px 6px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.script-ai-response-node .script-node-content {
padding: 12px;
}
.script-ai-response-node .node-actions {
display: flex;
gap: 5px;
}
.script-ai-response-node.invalid-multilang [data-input="ai-response"]
{
border: 1px solid var(--bs-danger) !important;
box-shadow: -1px 1px 2px 0px var(--bs-danger) !important;
}
Metadata
Metadata
Assignees
Labels
waiting for maintainerTriage or intervention needed from a maintainerTriage or intervention needed from a maintainer