mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python:DevUI Fixes (#1035)
* fix event reset on thread change, enable multiline input, enable pasting of files and screenshots * UI updates and improved remove discovery * ui and other fixes --------- Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
This commit is contained in:
co-authored by
Evan Mattson
parent
042099009f
commit
fa9f5c1aed
@@ -7,12 +7,14 @@ 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 { SettingsModal } from "@/components/shared/settings-modal";
|
||||
import { GalleryView } from "@/components/gallery";
|
||||
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 { ChevronLeft, ChevronDown, ServerOff } from "lucide-react";
|
||||
import type { SampleEntity } from "@/data/gallery";
|
||||
import type {
|
||||
AgentInfo,
|
||||
WorkflowInfo,
|
||||
@@ -27,23 +29,23 @@ export default function App() {
|
||||
isLoading: true,
|
||||
});
|
||||
|
||||
const [debugEvents, setDebugEvents] = useState<ExtendedResponseStreamEvent[]>(
|
||||
[]
|
||||
);
|
||||
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);
|
||||
const [showGallery, setShowGallery] = useState(false);
|
||||
const [addingEntityId, setAddingEntityId] = useState<string | null>(null);
|
||||
const [errorEntityId, setErrorEntityId] = useState<string | null>(null);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
|
||||
// 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(),
|
||||
@@ -135,6 +137,109 @@ export default function App() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Handle adding sample entity
|
||||
const handleAddSample = useCallback(async (sample: SampleEntity) => {
|
||||
setAddingEntityId(sample.id);
|
||||
setErrorEntityId(null);
|
||||
setErrorMessage(null);
|
||||
|
||||
try {
|
||||
// Call backend to fetch and add entity
|
||||
const newEntity = await apiClient.addEntity(sample.url, {
|
||||
source: 'remote_gallery',
|
||||
originalUrl: sample.url,
|
||||
sampleId: sample.id
|
||||
});
|
||||
|
||||
// Convert backend entity to frontend format
|
||||
const convertedEntity = {
|
||||
id: newEntity.id,
|
||||
name: newEntity.name,
|
||||
description: newEntity.description,
|
||||
type: newEntity.type,
|
||||
source: (newEntity.source as "directory" | "in_memory" | "remote_gallery") || 'remote_gallery',
|
||||
has_env: false,
|
||||
module_path: undefined
|
||||
};
|
||||
|
||||
// Update app state
|
||||
if (newEntity.type === 'agent') {
|
||||
const agentEntity = {
|
||||
...convertedEntity,
|
||||
tools: (newEntity.tools || []).map(tool =>
|
||||
typeof tool === 'string' ? tool : JSON.stringify(tool)
|
||||
)
|
||||
} as AgentInfo;
|
||||
|
||||
setAppState(prev => ({
|
||||
...prev,
|
||||
agents: [...prev.agents, agentEntity],
|
||||
selectedAgent: agentEntity
|
||||
}));
|
||||
} else {
|
||||
const workflowEntity = {
|
||||
...convertedEntity,
|
||||
executors: (newEntity.tools || []).map(tool =>
|
||||
typeof tool === 'string' ? tool : JSON.stringify(tool)
|
||||
),
|
||||
input_schema: { type: "string" },
|
||||
input_type_name: "Input",
|
||||
start_executor_id: (newEntity.tools && newEntity.tools.length > 0)
|
||||
? (typeof newEntity.tools[0] === 'string' ? newEntity.tools[0] : JSON.stringify(newEntity.tools[0]))
|
||||
: "unknown"
|
||||
} as WorkflowInfo;
|
||||
|
||||
setAppState(prev => ({
|
||||
...prev,
|
||||
workflows: [...prev.workflows, workflowEntity],
|
||||
selectedAgent: workflowEntity
|
||||
}));
|
||||
}
|
||||
|
||||
// Close gallery and clear debug events
|
||||
setShowGallery(false);
|
||||
setDebugEvents([]);
|
||||
|
||||
} catch (error) {
|
||||
const errMsg = error instanceof Error ? error.message : 'Failed to add sample entity';
|
||||
console.error('Failed to add sample entity:', errMsg);
|
||||
setErrorEntityId(sample.id);
|
||||
setErrorMessage(errMsg);
|
||||
} finally {
|
||||
setAddingEntityId(null);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleClearError = useCallback(() => {
|
||||
setErrorEntityId(null);
|
||||
setErrorMessage(null);
|
||||
}, []);
|
||||
|
||||
// Handle removing entity
|
||||
const handleRemoveEntity = useCallback(async (entityId: string) => {
|
||||
try {
|
||||
await apiClient.removeEntity(entityId);
|
||||
|
||||
// Update app state
|
||||
setAppState(prev => ({
|
||||
...prev,
|
||||
agents: prev.agents.filter(a => a.id !== entityId),
|
||||
workflows: prev.workflows.filter(w => w.id !== entityId),
|
||||
selectedAgent: prev.selectedAgent?.id === entityId
|
||||
? undefined
|
||||
: prev.selectedAgent
|
||||
}));
|
||||
|
||||
// Clear debug events if we removed the selected entity
|
||||
if (appState.selectedAgent?.id === entityId) {
|
||||
setDebugEvents([]);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to remove entity:', error);
|
||||
}
|
||||
}, [appState.selectedAgent?.id]);
|
||||
|
||||
// Show loading state while initializing
|
||||
if (appState.isLoading) {
|
||||
return (
|
||||
@@ -167,54 +272,77 @@ export default function App() {
|
||||
workflows={[]}
|
||||
selectedItem={undefined}
|
||||
onSelect={() => {}}
|
||||
onRemove={handleRemoveEntity}
|
||||
isLoading={false}
|
||||
onSettingsClick={() => setShowAboutModal(true)}
|
||||
/>
|
||||
|
||||
{/* 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 className="flex-1 flex items-center justify-center p-8">
|
||||
<div className="text-center space-y-6 max-w-2xl">
|
||||
{/* Icon */}
|
||||
<div className="flex justify-center">
|
||||
<div className="rounded-full bg-muted p-4 animate-pulse">
|
||||
<ServerOff className="h-12 w-12 text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-muted-foreground text-sm">{appState.error}</p>
|
||||
<Button onClick={() => window.location.reload()} variant="outline">
|
||||
Retry
|
||||
|
||||
{/* Heading */}
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-2xl font-semibold text-foreground">
|
||||
Can't Connect to Backend
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-base">
|
||||
No worries! Just start the DevUI backend server and you'll be good to go.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Command Instructions */}
|
||||
<div className="space-y-3">
|
||||
<div className="text-left bg-muted/50 rounded-lg p-4 space-y-3">
|
||||
<p className="text-sm font-medium text-foreground">Start the backend:</p>
|
||||
<code className="block bg-background px-3 py-2 rounded border text-sm font-mono text-foreground">
|
||||
devui ./agents --port 8080
|
||||
</code>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Or launch programmatically with <code className="text-xs">serve(entities=[agent])</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Default: <span className="font-mono">http://localhost:8080</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Error Details (Collapsible) */}
|
||||
{appState.error && (
|
||||
<details className="text-left group">
|
||||
<summary className="text-sm text-muted-foreground cursor-pointer hover:text-foreground flex items-center gap-2">
|
||||
<ChevronDown className="h-4 w-4 transition-transform group-open:rotate-180" />
|
||||
Error details
|
||||
</summary>
|
||||
<p className="mt-2 text-xs text-muted-foreground font-mono bg-muted/30 p-3 rounded border">
|
||||
{appState.error}
|
||||
</p>
|
||||
</details>
|
||||
)}
|
||||
|
||||
{/* Retry Button */}
|
||||
<Button
|
||||
onClick={() => window.location.reload()}
|
||||
variant="default"
|
||||
className="mt-2"
|
||||
>
|
||||
Retry Connection
|
||||
</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}
|
||||
{/* Settings Modal */}
|
||||
<SettingsModal
|
||||
open={showAboutModal}
|
||||
onOpenChange={setShowAboutModal}
|
||||
/>
|
||||
|
||||
{/* 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>
|
||||
);
|
||||
}
|
||||
@@ -226,35 +354,63 @@ export default function App() {
|
||||
workflows={appState.workflows}
|
||||
selectedItem={appState.selectedAgent}
|
||||
onSelect={handleEntitySelect}
|
||||
onRemove={handleRemoveEntity}
|
||||
onBrowseGallery={() => setShowGallery(true)}
|
||||
isLoading={appState.isLoading}
|
||||
onSettingsClick={() => setShowAboutModal(true)}
|
||||
/>
|
||||
|
||||
{/* Main Content - Split Panel */}
|
||||
{/* Main Content - Split Panel or Gallery */}
|
||||
<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.
|
||||
{showGallery ? (
|
||||
// Show gallery full screen (w-full ensures it takes entire width)
|
||||
<div className="flex-1 w-full">
|
||||
<GalleryView
|
||||
variant="route"
|
||||
onAdd={handleAddSample}
|
||||
addingEntityId={addingEntityId}
|
||||
errorEntityId={errorEntityId}
|
||||
errorMessage={errorMessage}
|
||||
onClearError={handleClearError}
|
||||
onClose={() => setShowGallery(false)}
|
||||
hasExistingEntities={appState.agents.length > 0 || appState.workflows.length > 0}
|
||||
/>
|
||||
</div>
|
||||
) : appState.agents.length === 0 && appState.workflows.length === 0 ? (
|
||||
// Empty state - show gallery inline (full width, no debug panel)
|
||||
<GalleryView
|
||||
variant="inline"
|
||||
onAdd={handleAddSample}
|
||||
addingEntityId={addingEntityId}
|
||||
errorEntityId={errorEntityId}
|
||||
errorMessage={errorMessage}
|
||||
onClearError={handleClearError}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{/* 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>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Resize Handle */}
|
||||
{debugPanelOpen && (
|
||||
{/* 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"
|
||||
@@ -288,22 +444,24 @@ export default function App() {
|
||||
</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>
|
||||
{/* 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
|
||||
{/* Settings Modal */}
|
||||
<SettingsModal
|
||||
open={showAboutModal}
|
||||
onOpenChange={setShowAboutModal}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user