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:
Victor Dibia
2025-09-22 16:30:08 -07:00
committed by GitHub
Unverified
parent adb6dcd2af
commit 1ef24d3e91
98 changed files with 18045 additions and 4 deletions
@@ -0,0 +1,126 @@
import { useMemo, useState, useCallback } from "react";
import type { ExtendedResponseStreamEvent } from "@/types";
// import type { ExecutorNodeData } from "@/components/workflow/executor-node";
// Type for executor input/output data - can be various types based on workflow events
export type ExecutorData =
| string
| number
| boolean
| Record<string, unknown>
| null;
// State tracking for a specific executor
interface ExecutorState {
executorId: string;
state: "pending" | "running" | "completed" | "failed" | "cancelled";
inputData?: ExecutorData;
outputData?: ExecutorData;
error?: string;
timestamp: string;
}
interface WorkflowEventCorrelationResult {
// State access
isWorkflowRunning: boolean;
selectedExecutorId: string | null;
recentlyActive: string[];
// Actions
selectExecutor: (executorId: string) => void;
getExecutorData: (executorId: string) => ExecutorState | null;
getExecutorEvents: (executorId: string) => ExtendedResponseStreamEvent[];
}
// Hook for correlating workflow events with executor states
export function useWorkflowEventCorrelation(
events: ExtendedResponseStreamEvent[],
isStreaming: boolean
): WorkflowEventCorrelationResult {
const [selectedExecutorId, setSelectedExecutorId] = useState<string | null>(null);
// Process events into executor states
const { executors, recentlyActive, isWorkflowRunning } = useMemo(() => {
const executorMap: Record<string, ExecutorState> = {};
const activeExecutors: string[] = [];
let workflowActive = isStreaming;
// Process workflow events
events.forEach((event) => {
if (event.type === "response.workflow_event.complete" && "data" in event && event.data) {
const data = event.data as any;
const executorId = data.executor_id;
if (!executorId) return;
// Initialize executor if not exists
if (!executorMap[executorId]) {
executorMap[executorId] = {
executorId,
state: "pending",
timestamp: new Date().toISOString(),
};
}
const executor = executorMap[executorId];
const eventType = data.event_type;
// Update state based on event type
if (eventType === "ExecutorInvokedEvent") {
executor.state = "running";
executor.inputData = data.data;
if (!activeExecutors.includes(executorId)) {
activeExecutors.push(executorId);
}
} else if (eventType === "ExecutorCompletedEvent") {
executor.state = "completed";
executor.outputData = data.data;
} else if (eventType?.includes("Error") || eventType?.includes("Failed")) {
executor.state = "failed";
executor.error = typeof data.data === "string" ? data.data : "Execution failed";
} else if (eventType?.includes("Cancel")) {
executor.state = "cancelled";
}
executor.timestamp = new Date().toISOString();
}
});
return {
executors: executorMap,
recentlyActive: activeExecutors.slice(-3), // Keep last 3 active executors
isWorkflowRunning: workflowActive,
};
}, [events, isStreaming]);
const selectExecutor = useCallback((executorId: string) => {
setSelectedExecutorId(executorId);
}, []);
const getExecutorData = useCallback((executorId: string): ExecutorState | null => {
return executors[executorId] || null;
}, [executors]);
const getExecutorEvents = useCallback(
(executorId: string): ExtendedResponseStreamEvent[] => {
return events.filter((event) => {
if (event.type === "response.workflow_event.complete" && "data" in event && event.data) {
const data = event.data as any;
return data.executor_id === executorId;
}
return false;
});
},
[events]
);
return {
isWorkflowRunning,
selectedExecutorId,
recentlyActive,
selectExecutor,
getExecutorData,
getExecutorEvents,
};
}