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,312 @@
|
||||
/**
|
||||
* DevUI App - Minimal orchestrator for agent/workflow interactions
|
||||
* Features: Entity selection, layout management, debug coordination
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { AppHeader } from "@/components/shared/app-header";
|
||||
import { DebugPanel } from "@/components/shared/debug-panel";
|
||||
import { AboutModal } from "@/components/shared/about-modal";
|
||||
import { AgentView } from "@/components/agent/agent-view";
|
||||
import { WorkflowView } from "@/components/workflow/workflow-view";
|
||||
import { LoadingState } from "@/components/ui/loading-state";
|
||||
import { apiClient } from "@/services/api";
|
||||
import { ChevronLeft } from "lucide-react";
|
||||
import type {
|
||||
AgentInfo,
|
||||
WorkflowInfo,
|
||||
AppState,
|
||||
ExtendedResponseStreamEvent,
|
||||
} from "@/types";
|
||||
|
||||
export default function App() {
|
||||
const [appState, setAppState] = useState<AppState>({
|
||||
agents: [],
|
||||
workflows: [],
|
||||
isLoading: true,
|
||||
});
|
||||
|
||||
const [debugEvents, setDebugEvents] = useState<ExtendedResponseStreamEvent[]>(
|
||||
[]
|
||||
);
|
||||
const [debugPanelOpen, setDebugPanelOpen] = useState(true);
|
||||
const [debugPanelWidth, setDebugPanelWidth] = useState(() => {
|
||||
// Initialize from localStorage or default to 320
|
||||
const savedWidth = localStorage.getItem("debugPanelWidth");
|
||||
return savedWidth ? parseInt(savedWidth, 10) : 320;
|
||||
});
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
const [showAboutModal, setShowAboutModal] = useState(false);
|
||||
|
||||
// Initialize app - load agents and workflows
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
try {
|
||||
// Load agents and workflows in parallel
|
||||
const [agents, workflows] = await Promise.all([
|
||||
apiClient.getAgents(),
|
||||
apiClient.getWorkflows(),
|
||||
]);
|
||||
|
||||
setAppState((prev) => ({
|
||||
...prev,
|
||||
agents,
|
||||
workflows,
|
||||
selectedAgent:
|
||||
agents.length > 0
|
||||
? agents[0]
|
||||
: workflows.length > 0
|
||||
? workflows[0]
|
||||
: undefined,
|
||||
isLoading: false,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("Failed to load agents/workflows:", error);
|
||||
setAppState((prev) => ({
|
||||
...prev,
|
||||
error: error instanceof Error ? error.message : "Failed to load data",
|
||||
isLoading: false,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
// Save debug panel width to localStorage
|
||||
useEffect(() => {
|
||||
localStorage.setItem("debugPanelWidth", debugPanelWidth.toString());
|
||||
}, [debugPanelWidth]);
|
||||
|
||||
// Handle resize drag
|
||||
const handleMouseDown = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
setIsResizing(true);
|
||||
|
||||
const startX = e.clientX;
|
||||
const startWidth = debugPanelWidth;
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
const deltaX = startX - e.clientX; // Subtract because we're dragging from right
|
||||
const newWidth = Math.max(
|
||||
200,
|
||||
Math.min(window.innerWidth * 0.5, startWidth + deltaX)
|
||||
);
|
||||
setDebugPanelWidth(newWidth);
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
setIsResizing(false);
|
||||
document.removeEventListener("mousemove", handleMouseMove);
|
||||
document.removeEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
},
|
||||
[debugPanelWidth]
|
||||
);
|
||||
|
||||
// Handle double-click to collapse
|
||||
const handleDoubleClick = useCallback(() => {
|
||||
setDebugPanelOpen(false);
|
||||
}, []);
|
||||
|
||||
// Handle entity selection
|
||||
const handleEntitySelect = useCallback((item: AgentInfo | WorkflowInfo) => {
|
||||
setAppState((prev) => ({
|
||||
...prev,
|
||||
selectedAgent: item,
|
||||
currentThread: undefined,
|
||||
}));
|
||||
|
||||
// Clear debug events when switching entities
|
||||
setDebugEvents([]);
|
||||
}, []);
|
||||
|
||||
// Handle debug events from active view
|
||||
const handleDebugEvent = useCallback((event: ExtendedResponseStreamEvent | 'clear') => {
|
||||
if (event === 'clear') {
|
||||
setDebugEvents([]);
|
||||
} else {
|
||||
setDebugEvents((prev) => [...prev, event]);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Show loading state while initializing
|
||||
if (appState.isLoading) {
|
||||
return (
|
||||
<div className="h-screen flex flex-col bg-background">
|
||||
{/* Top Bar - Skeleton */}
|
||||
<header className="flex h-14 items-center gap-4 border-b px-4">
|
||||
<div className="w-64 h-9 bg-muted animate-pulse rounded-md" />
|
||||
<div className="flex items-center gap-2 ml-auto">
|
||||
<div className="w-8 h-8 bg-muted animate-pulse rounded-md" />
|
||||
<div className="w-8 h-8 bg-muted animate-pulse rounded-md" />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Loading Content */}
|
||||
<LoadingState
|
||||
message="Initializing DevUI..."
|
||||
description="Loading agents and workflows from your configuration"
|
||||
fullPage={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show error state if loading failed
|
||||
if (appState.error) {
|
||||
return (
|
||||
<div className="h-screen flex flex-col bg-background">
|
||||
<AppHeader
|
||||
agents={[]}
|
||||
workflows={[]}
|
||||
selectedItem={undefined}
|
||||
onSelect={() => {}}
|
||||
isLoading={false}
|
||||
/>
|
||||
|
||||
{/* Error Content */}
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="text-center space-y-4 max-w-md">
|
||||
<div className="text-destructive text-lg font-medium">
|
||||
Failed to load entities
|
||||
</div>
|
||||
<p className="text-muted-foreground text-sm">{appState.error}</p>
|
||||
<Button onClick={() => window.location.reload()} variant="outline">
|
||||
Retry
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show empty state if no agents or workflows are available
|
||||
if (
|
||||
!appState.isLoading &&
|
||||
appState.agents.length === 0 &&
|
||||
appState.workflows.length === 0
|
||||
) {
|
||||
return (
|
||||
<div className="h-screen flex flex-col bg-background">
|
||||
<AppHeader
|
||||
agents={[]}
|
||||
workflows={[]}
|
||||
selectedItem={undefined}
|
||||
onSelect={() => {}}
|
||||
isLoading={false}
|
||||
/>
|
||||
|
||||
{/* Empty State Content */}
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="text-center space-y-4 max-w-md">
|
||||
<div className="text-lg font-medium">No entities configured</div>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
No agents or workflows were found in your configuration. Please
|
||||
check your setup and ensure entities are properly configured.
|
||||
</p>
|
||||
<Button onClick={() => window.location.reload()} variant="outline">
|
||||
Retry
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen flex flex-col bg-background max-h-screen">
|
||||
<AppHeader
|
||||
agents={appState.agents}
|
||||
workflows={appState.workflows}
|
||||
selectedItem={appState.selectedAgent}
|
||||
onSelect={handleEntitySelect}
|
||||
isLoading={appState.isLoading}
|
||||
onSettingsClick={() => setShowAboutModal(true)}
|
||||
/>
|
||||
|
||||
{/* Main Content - Split Panel */}
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* Left Panel - Main View */}
|
||||
<div className="flex-1 min-w-0">
|
||||
{appState.selectedAgent ? (
|
||||
appState.selectedAgent.type === "agent" ? (
|
||||
<AgentView
|
||||
selectedAgent={appState.selectedAgent as AgentInfo}
|
||||
onDebugEvent={handleDebugEvent}
|
||||
/>
|
||||
) : (
|
||||
<WorkflowView
|
||||
selectedWorkflow={appState.selectedAgent as WorkflowInfo}
|
||||
onDebugEvent={handleDebugEvent}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<div className="flex-1 flex items-center justify-center text-muted-foreground">
|
||||
Select an agent or workflow to get started.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Resize Handle */}
|
||||
{debugPanelOpen && (
|
||||
<div
|
||||
className={`w-1 cursor-col-resize flex-shrink-0 relative group transition-colors duration-200 ease-in-out ${
|
||||
isResizing ? "bg-primary/40" : "bg-border hover:bg-primary/20"
|
||||
}`}
|
||||
onMouseDown={handleMouseDown}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
>
|
||||
<div className="absolute inset-y-0 -left-2 -right-2 flex items-center justify-center">
|
||||
<div
|
||||
className={`h-12 w-1 rounded-full transition-all duration-200 ease-in-out ${
|
||||
isResizing
|
||||
? "bg-primary shadow-lg shadow-primary/25"
|
||||
: "bg-primary/30 group-hover:bg-primary group-hover:shadow-md group-hover:shadow-primary/20"
|
||||
}`}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Button to reopen when closed */}
|
||||
{!debugPanelOpen && (
|
||||
<div className="flex-shrink-0">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setDebugPanelOpen(true)}
|
||||
className="h-full w-8 rounded-none border-l"
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Right Panel - Debug */}
|
||||
{debugPanelOpen && (
|
||||
<div
|
||||
className="flex-shrink-0"
|
||||
style={{ width: `${debugPanelWidth}px` }}
|
||||
>
|
||||
<DebugPanel
|
||||
events={debugEvents}
|
||||
isStreaming={false} // Each view manages its own streaming state
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* About Modal */}
|
||||
<AboutModal
|
||||
open={showAboutModal}
|
||||
onOpenChange={setShowAboutModal}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user