refactor(terminal-dashboard): move agent list to execution monitor panel

- Remove AgentList component from left sidebar
- Integrate orchestration plans display into ExecutionMonitorPanel
- Execution Monitor now shows both workflow executions and orchestration plans
- Cleaner sidebar with only session tree
This commit is contained in:
catlog22
2026-02-20 22:06:21 +08:00
parent 7e5d47fe8d
commit b2c1d32c86
2 changed files with 243 additions and 144 deletions

View File

@@ -1,9 +1,10 @@
// ======================================== // ========================================
// Execution Monitor Panel // Execution Monitor Panel
// ======================================== // ========================================
// Panel for monitoring workflow executions in Terminal Dashboard. // Panel for monitoring workflow executions and orchestration plans in Terminal Dashboard.
// Displays execution progress, step list, and control buttons. // Displays execution progress, step list, control buttons, and active orchestration plans.
import { useMemo } from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { import {
Play, Play,
@@ -15,6 +16,7 @@ import {
Loader2, Loader2,
Clock, Clock,
Terminal, Terminal,
Bot,
} from 'lucide-react'; } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
@@ -25,7 +27,10 @@ import {
selectCurrentExecution, selectCurrentExecution,
selectActiveExecutions, selectActiveExecutions,
} from '@/stores/executionMonitorStore'; } from '@/stores/executionMonitorStore';
import { useOrchestratorStore, selectActivePlans } from '@/stores';
import type { ExecutionStatus, StepInfo } from '@/stores/executionMonitorStore'; import type { ExecutionStatus, StepInfo } from '@/stores/executionMonitorStore';
import type { OrchestrationRunState } from '@/stores/orchestratorStore';
import type { OrchestrationStatus } from '@/types/orchestrator';
// ========== Status Config ========== // ========== Status Config ==========
@@ -38,6 +43,18 @@ const statusConfig: Record<ExecutionStatus, { label: string; color: string; bgCo
cancelled: { label: 'Cancelled', color: 'text-muted-foreground', bgColor: 'bg-muted' }, cancelled: { label: 'Cancelled', color: 'text-muted-foreground', bgColor: 'bg-muted' },
}; };
const ORCHESTRATION_STATUS_CONFIG: Record<
OrchestrationStatus,
{ variant: 'default' | 'info' | 'success' | 'destructive' | 'secondary' | 'warning'; messageId: string }
> = {
running: { variant: 'info', messageId: 'terminalDashboard.agentList.statusRunning' },
completed: { variant: 'success', messageId: 'terminalDashboard.agentList.statusCompleted' },
failed: { variant: 'destructive', messageId: 'terminalDashboard.agentList.statusFailed' },
paused: { variant: 'warning', messageId: 'terminalDashboard.agentList.statusPaused' },
pending: { variant: 'secondary', messageId: 'terminalDashboard.agentList.statusPending' },
cancelled: { variant: 'secondary', messageId: 'terminalDashboard.agentList.statusPending' },
};
// ========== Step Status Icon ========== // ========== Step Status Icon ==========
function StepStatusIcon({ status }: { status: ExecutionStatus }) { function StepStatusIcon({ status }: { status: ExecutionStatus }) {
@@ -97,6 +114,60 @@ function StepListItem({ step, isCurrent }: StepListItemProps) {
); );
} }
// ========== Orchestration Plan Item ==========
function OrchestrationPlanItem({
runState,
}: {
runState: OrchestrationRunState;
}) {
const { formatMessage } = useIntl();
const { plan, status, stepStatuses } = runState;
const totalSteps = plan.steps.length;
const completedSteps = useMemo(
() =>
Object.values(stepStatuses).filter(
(s) => s.status === 'completed' || s.status === 'skipped'
).length,
[stepStatuses]
);
const config = ORCHESTRATION_STATUS_CONFIG[status] ?? ORCHESTRATION_STATUS_CONFIG.pending;
const isRunning = status === 'running';
return (
<div
className={cn(
'flex items-center gap-2 px-3 py-2 rounded-md',
'hover:bg-muted/30 transition-colors'
)}
>
<div className="shrink-0">
{isRunning ? (
<Loader2 className="w-4 h-4 text-primary animate-spin" />
) : (
<Bot className="w-4 h-4 text-muted-foreground" />
)}
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium truncate">{plan.name}</p>
<p className="text-xs text-muted-foreground">
{formatMessage(
{ id: 'terminalDashboard.agentList.stepLabel' },
{ current: completedSteps, total: totalSteps }
)}
</p>
</div>
<Badge variant={config.variant} className="text-xs px-2 py-0 shrink-0">
{formatMessage({ id: config.messageId })}
</Badge>
</div>
);
}
// ========== Main Component ========== // ========== Main Component ==========
export function ExecutionMonitorPanel() { export function ExecutionMonitorPanel() {
@@ -110,10 +181,17 @@ export function ExecutionMonitorPanel() {
const stopExecution = useExecutionMonitorStore((s) => s.stopExecution); const stopExecution = useExecutionMonitorStore((s) => s.stopExecution);
const clearExecution = useExecutionMonitorStore((s) => s.clearExecution); const clearExecution = useExecutionMonitorStore((s) => s.clearExecution);
const executions = Object.values(activeExecutions); // Orchestration plans
const hasExecutions = executions.length > 0; const activePlans = useOrchestratorStore(selectActivePlans);
const planEntries = useMemo(
() => Object.entries(activePlans),
[activePlans]
);
if (!hasExecutions) { const executions = Object.values(activeExecutions);
const hasContent = executions.length > 0 || planEntries.length > 0;
if (!hasContent) {
return ( return (
<div className="flex flex-col items-center justify-center h-full text-muted-foreground p-8"> <div className="flex flex-col items-center justify-center h-full text-muted-foreground p-8">
<Terminal className="w-10 h-10 mb-3 opacity-30" /> <Terminal className="w-10 h-10 mb-3 opacity-30" />
@@ -129,6 +207,29 @@ export function ExecutionMonitorPanel() {
return ( return (
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
{/* Orchestration Plans Section */}
{planEntries.length > 0 && (
<div className="border-b border-border shrink-0">
<div className="flex items-center gap-2 px-4 py-2 bg-muted/20">
<Bot className="w-4 h-4 text-muted-foreground" />
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
{formatMessage({ id: 'terminalDashboard.agentList.title' })}
</h3>
<Badge variant="secondary" className="text-[10px] px-1.5 py-0 ml-auto">
{planEntries.length}
</Badge>
</div>
<div className="divide-y divide-border/50">
{planEntries.map(([planId, runState]) => (
<OrchestrationPlanItem key={planId} runState={runState} />
))}
</div>
</div>
)}
{/* Workflow Executions Section */}
{executions.length > 0 && (
<>
{/* Execution selector (if multiple) */} {/* Execution selector (if multiple) */}
{executions.length > 1 && ( {executions.length > 1 && (
<div className="border-b border-border p-2 shrink-0"> <div className="border-b border-border p-2 shrink-0">
@@ -277,6 +378,8 @@ export function ExecutionMonitorPanel() {
</div> </div>
</> </>
)} )}
</>
)}
</div> </div>
); );
} }

View File

@@ -2,11 +2,11 @@
// Terminal Dashboard Page (V2) // Terminal Dashboard Page (V2)
// ======================================== // ========================================
// Terminal-first layout with fixed session sidebar + floating panels + right file sidebar. // Terminal-first layout with fixed session sidebar + floating panels + right file sidebar.
// Left sidebar: SessionGroupTree + AgentList (always visible) // Left sidebar: SessionGroupTree (always visible)
// Main area: TerminalGrid (tmux-style split panes) // Main area: TerminalGrid (tmux-style split panes)
// Right sidebar: FileSidebarPanel (file tree, resizable) // Right sidebar: FileSidebarPanel (file tree, resizable)
// Top: DashboardToolbar with panel toggles and layout presets // Top: DashboardToolbar with panel toggles and layout presets
// Floating panels: Issues, Queue, Inspector (overlay, mutually exclusive) // Floating panels: Issues, Queue, Inspector, Execution Monitor (overlay, mutually exclusive)
// Fullscreen mode: Uses global isImmersiveMode to hide app chrome (Header + Sidebar) // Fullscreen mode: Uses global isImmersiveMode to hide app chrome (Header + Sidebar)
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
@@ -18,7 +18,6 @@ import { DashboardToolbar, type PanelId } from '@/components/terminal-dashboard/
import { TerminalGrid } from '@/components/terminal-dashboard/TerminalGrid'; import { TerminalGrid } from '@/components/terminal-dashboard/TerminalGrid';
import { FloatingPanel } from '@/components/terminal-dashboard/FloatingPanel'; import { FloatingPanel } from '@/components/terminal-dashboard/FloatingPanel';
import { SessionGroupTree } from '@/components/terminal-dashboard/SessionGroupTree'; import { SessionGroupTree } from '@/components/terminal-dashboard/SessionGroupTree';
import { AgentList } from '@/components/terminal-dashboard/AgentList';
import { IssuePanel } from '@/components/terminal-dashboard/IssuePanel'; import { IssuePanel } from '@/components/terminal-dashboard/IssuePanel';
import { QueuePanel } from '@/components/terminal-dashboard/QueuePanel'; import { QueuePanel } from '@/components/terminal-dashboard/QueuePanel';
import { InspectorContent } from '@/components/terminal-dashboard/BottomInspector'; import { InspectorContent } from '@/components/terminal-dashboard/BottomInspector';
@@ -74,9 +73,6 @@ export function TerminalDashboardPage() {
<div className="flex-1 min-h-0 overflow-y-auto"> <div className="flex-1 min-h-0 overflow-y-auto">
<SessionGroupTree /> <SessionGroupTree />
</div> </div>
<div className="shrink-0">
<AgentList />
</div>
</div> </div>
</Allotment.Pane> </Allotment.Pane>
)} )}