fix(frontend): Fix builder UI glitch (#10139)

There are a few UI bugs on the builder that this PR addresses.

<img width="554" alt="image"
src="https://github.com/user-attachments/assets/1be70197-de7e-40fe-ab11-405c145e763d"
/>

### Changes 🏗️

Fix these UI issues:
* (screenshot attached above) Key-value input width was unintentionally
maxed out due to a stale CSS rule.
* When multiple executions within the same node are running, we pick the
latest status, making one running and one completed execution displayed
as completed.
* No balance errors were executed, only displayed while at least one
node execution was triggered, while this can be done directly when the
execution request is triggered.
* Run & Stop button glitch: it's still showing as stopped when the graph
is still running, this is due to way the UI code tracks execution in the
node-level, instead of graph level.

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  <!-- Put your test plan here: -->
  - [x] Manual tests on the described behaviours.
This commit is contained in:
Zamil Majdy
2025-06-10 12:46:18 +07:00
committed by GitHub
parent 014b276552
commit 7165958feb
6 changed files with 71 additions and 60 deletions

View File

@@ -575,6 +575,13 @@ async def execute_graph(
graph_version: Optional[int] = None,
preset_id: Optional[str] = None,
) -> ExecuteGraphResponse:
current_balance = await _user_credit_model.get_credits(user_id)
if current_balance <= 0:
raise HTTPException(
status_code=402,
detail="Insufficient balance to execute the agent. Please top up your account.",
)
graph_exec = await execution_utils.add_graph_execution_async(
graph_id=graph_id,
user_id=user_id,

View File

@@ -82,6 +82,7 @@ export type CustomNodeData = {
executionResults?: {
execId: string;
data: NodeExecutionResult["output_data"];
status: NodeExecutionResult["status"];
}[];
block_id: string;
backend_id?: string;

View File

@@ -9,19 +9,6 @@
margin-bottom: 1rem;
}
.custom-node input:not([type="checkbox"]):not([type="file"]),
.custom-node textarea,
.custom-node select,
.custom-node [data-id^="date-picker"],
.custom-node [data-list-container],
.custom-node [data-add-item],
.custom-node [data-content-settings] .array-item-container {
display: flex;
align-items: center;
min-width: calc(100% - 2.5rem);
max-width: 100%;
}
.custom-node .custom-switch {
padding: 0.5rem 1.25rem;
display: flex;

View File

@@ -229,7 +229,6 @@ const NodeFileInput: FC<{
const handleFileChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
console.log(">>> file", file);
if (!file) return;
const reader = new FileReader();

View File

@@ -354,6 +354,55 @@ export default function useAgentGraph(
[getFrontendId, nodes],
);
const addExecutionDataToNode = useCallback(
(node: CustomNode, executionData: NodeExecutionResult) => {
if (!executionData.output_data) {
console.warn(
`Execution data for node ${executionData.node_id} is empty, skipping update`,
);
return node;
}
const executionResults = [
// Execution updates are not cumulative, so we need to filter out the old ones.
...(node.data.executionResults?.filter(
(result) => result.execId !== executionData.node_exec_id,
) || []),
{
execId: executionData.node_exec_id,
data: {
"[Input]": [executionData.input_data],
...executionData.output_data,
},
status: executionData.status,
},
];
const statusRank = {
RUNNING: 0,
QUEUED: 1,
INCOMPLETE: 2,
TERMINATED: 3,
COMPLETED: 4,
FAILED: 5,
};
const status = executionResults
.map((v) => v.status)
.reduce((a, b) => (statusRank[a] < statusRank[b] ? a : b));
return {
...node,
data: {
...node.data,
status,
executionResults,
isOutputOpen: true,
},
};
},
[],
);
const updateNodesWithExecutionData = useCallback(
(executionData: NodeExecutionResult) => {
if (!executionData.node_id) return;
@@ -374,31 +423,7 @@ export default function useAgentGraph(
}
return nodes.map((node) =>
node.id === nodeId
? {
...node,
data: {
...node.data,
status: executionData.status,
executionResults:
Object.keys(executionData.output_data).length > 0
? [
// Execution updates are not cumulative, so we need to filter out the old ones.
...(node.data.executionResults?.filter(
(result) =>
result.execId !== executionData.node_exec_id,
) || []),
{
execId: executionData.node_exec_id,
data: {
"[Input]": [executionData.input_data],
...executionData.output_data,
},
},
]
: node.data.executionResults,
isOutputOpen: true,
},
}
? addExecutionDataToNode(node, executionData)
: node,
);
});
@@ -694,20 +719,17 @@ export default function useAgentGraph(
return [...prev, ...execution.node_executions];
});
// Track execution until completed
const pendingNodeExecutions: Set<string> = new Set();
const cancelExecListener = api.onWebSocketMessage(
"node_execution_event",
(nodeResult) => {
// We are racing the server here, since we need the ID to filter events
if (nodeResult.graph_exec_id != flowExecutionID) {
const cancelGraphExecListener = api.onWebSocketMessage(
"graph_execution_event",
(graphExec) => {
if (graphExec.id != flowExecutionID) {
return;
}
if (
nodeResult.status === "FAILED" &&
nodeResult.output_data?.error?.[0]
.toLowerCase()
.includes("insufficient balance")
graphExec.status === "FAILED" &&
graphExec?.stats?.error
?.toLowerCase()
?.includes("insufficient balance")
) {
// Show no credits toast if user has low credits
toast({
@@ -731,17 +753,11 @@ export default function useAgentGraph(
});
}
if (
!["COMPLETED", "TERMINATED", "FAILED"].includes(nodeResult.status)
graphExec.status === "COMPLETED" ||
graphExec.status === "TERMINATED" ||
graphExec.status === "FAILED"
) {
pendingNodeExecutions.add(nodeResult.node_exec_id);
} else {
pendingNodeExecutions.delete(nodeResult.node_exec_id);
}
if (pendingNodeExecutions.size == 0) {
// Assuming the first event is always a QUEUED node, and
// following nodes are QUEUED before all preceding nodes are COMPLETED,
// an empty set means the graph has finished running.
cancelExecListener();
cancelGraphExecListener();
setSaveRunRequest({ request: "none", state: "none" });
incrementRuns();
}

View File

@@ -281,6 +281,7 @@ export type GraphExecutionMeta = {
started_at: Date;
ended_at: Date;
stats?: {
error?: string;
cost: number;
duration: number;
duration_cpu_only: number;