mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
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:
@@ -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,
|
||||
|
||||
@@ -82,6 +82,7 @@ export type CustomNodeData = {
|
||||
executionResults?: {
|
||||
execId: string;
|
||||
data: NodeExecutionResult["output_data"];
|
||||
status: NodeExecutionResult["status"];
|
||||
}[];
|
||||
block_id: string;
|
||||
backend_id?: string;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -281,6 +281,7 @@ export type GraphExecutionMeta = {
|
||||
started_at: Date;
|
||||
ended_at: Date;
|
||||
stats?: {
|
||||
error?: string;
|
||||
cost: number;
|
||||
duration: number;
|
||||
duration_cpu_only: number;
|
||||
|
||||
Reference in New Issue
Block a user