mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Add DevUI to AgentFramework (#781)
* add initial backend service code for devui * add tests * add frontendcode * ui updates * update readme * ui updates and tweaks * update ui bundle * improve ui, add react flow base * add react flow ui, fix background * update ui, fix introspection bug * update readme * update ui build * add support for multimodal input - both backend and frontend * update ui build * refactor as main framework package * backend and tests refactor * ui build update * ui build update and refactor * update pyproject.toml, update uv.lock * update ui build * ui update to fit oai responses types * add backend updat and readme update * mypy and other fixes * add intial dev guide * update ui and fix workflow bug * update ui build, add thread support * type fixes * update workflow view * update uv.lock * fix workflow iport errors * lint and other fixes * mypy fixes * minor update * update ui build * refactor to use oai dependencies directly, update examples to samples, improve typing * readme update * update ui and ui build * fix workflow pyright error * update ui, fix issues with run workflow placement, miniamp menu, etc * make samples integrate serve --------- Co-authored-by: Chris <66376200+crickman@users.noreply.github.com> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
adb6dcd2af
commit
1ef24d3e91
@@ -0,0 +1,139 @@
|
||||
import type { Node, Edge } from "@xyflow/react";
|
||||
import type { ExecutorNodeData } from "@/components/workflow/executor-node";
|
||||
|
||||
/**
|
||||
* Lightweight auto-layout algorithm to replace dagre
|
||||
* Handles fan-out nodes properly by spacing siblings
|
||||
*/
|
||||
export function applySimpleLayout(
|
||||
nodes: Node<ExecutorNodeData>[],
|
||||
edges: Edge[],
|
||||
direction: "TB" | "LR" = "LR"
|
||||
): Node<ExecutorNodeData>[] {
|
||||
if (nodes.length === 0) return nodes;
|
||||
if (nodes.length === 1) {
|
||||
return nodes.map((node) => ({
|
||||
...node,
|
||||
position: { x: 0, y: 0 },
|
||||
}));
|
||||
}
|
||||
|
||||
// Create adjacency maps
|
||||
const outgoingEdges = new Map<string, string[]>();
|
||||
const incomingEdges = new Map<string, string[]>();
|
||||
|
||||
nodes.forEach((node) => {
|
||||
outgoingEdges.set(node.id, []);
|
||||
incomingEdges.set(node.id, []);
|
||||
});
|
||||
|
||||
edges.forEach((edge) => {
|
||||
outgoingEdges.get(edge.source)?.push(edge.target);
|
||||
incomingEdges.get(edge.target)?.push(edge.source);
|
||||
});
|
||||
|
||||
// Find root nodes (nodes with no incoming edges)
|
||||
const rootNodes = nodes.filter(
|
||||
(node) => (incomingEdges.get(node.id) || []).length === 0
|
||||
);
|
||||
|
||||
if (rootNodes.length === 0) {
|
||||
// Fallback: use first node as root if no clear root
|
||||
rootNodes.push(nodes[0]);
|
||||
}
|
||||
|
||||
// Constants for spacing
|
||||
const NODE_WIDTH = 220;
|
||||
const NODE_HEIGHT = 120;
|
||||
const HORIZONTAL_SPACING = direction === "LR" ? 350 : 200;
|
||||
const VERTICAL_SPACING = direction === "TB" ? 250 : 180;
|
||||
|
||||
// Track positioned nodes and level information
|
||||
const positioned = new Map<string, { x: number; y: number; level: number }>();
|
||||
const levelGroups = new Map<number, string[]>();
|
||||
|
||||
// Build level groups using BFS
|
||||
const queue: Array<{ nodeId: string; level: number }> = [];
|
||||
const visited = new Set<string>();
|
||||
|
||||
// Start with root nodes at level 0
|
||||
rootNodes.forEach((node) => {
|
||||
queue.push({ nodeId: node.id, level: 0 });
|
||||
});
|
||||
|
||||
// BFS to assign levels
|
||||
while (queue.length > 0) {
|
||||
const { nodeId, level } = queue.shift()!;
|
||||
|
||||
if (visited.has(nodeId)) continue;
|
||||
visited.add(nodeId);
|
||||
|
||||
// Add to level group
|
||||
if (!levelGroups.has(level)) {
|
||||
levelGroups.set(level, []);
|
||||
}
|
||||
levelGroups.get(level)!.push(nodeId);
|
||||
|
||||
// Add children to next level
|
||||
const children = outgoingEdges.get(nodeId) || [];
|
||||
children.forEach((childId) => {
|
||||
if (!visited.has(childId)) {
|
||||
queue.push({ nodeId: childId, level: level + 1 });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle orphaned nodes (not connected to root)
|
||||
nodes.forEach((node) => {
|
||||
if (!visited.has(node.id)) {
|
||||
const maxLevel = Math.max(...Array.from(levelGroups.keys()), -1);
|
||||
const orphanLevel = maxLevel + 1;
|
||||
|
||||
if (!levelGroups.has(orphanLevel)) {
|
||||
levelGroups.set(orphanLevel, []);
|
||||
}
|
||||
levelGroups.get(orphanLevel)!.push(node.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Position nodes level by level
|
||||
levelGroups.forEach((nodeIds, level) => {
|
||||
const nodeCount = nodeIds.length;
|
||||
|
||||
nodeIds.forEach((nodeId, index) => {
|
||||
let x: number, y: number;
|
||||
|
||||
if (direction === "LR") {
|
||||
// Horizontal layout: X increases with level, Y centers siblings
|
||||
x = level * HORIZONTAL_SPACING;
|
||||
|
||||
// Center siblings vertically
|
||||
const totalHeight = (nodeCount - 1) * VERTICAL_SPACING;
|
||||
const startY = -totalHeight / 2;
|
||||
y = startY + index * VERTICAL_SPACING;
|
||||
} else {
|
||||
// Vertical layout: Y increases with level, X centers siblings
|
||||
y = level * VERTICAL_SPACING;
|
||||
|
||||
// Center siblings horizontally
|
||||
const totalWidth = (nodeCount - 1) * HORIZONTAL_SPACING;
|
||||
const startX = -totalWidth / 2;
|
||||
x = startX + index * HORIZONTAL_SPACING;
|
||||
}
|
||||
|
||||
positioned.set(nodeId, { x, y, level });
|
||||
});
|
||||
});
|
||||
|
||||
// Apply positions to nodes (centering them on their calculated positions)
|
||||
return nodes.map((node) => {
|
||||
const pos = positioned.get(node.id) || { x: 0, y: 0 };
|
||||
return {
|
||||
...node,
|
||||
position: {
|
||||
x: pos.x - NODE_WIDTH / 2, // Center the node
|
||||
y: pos.y - NODE_HEIGHT / 2,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user