mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-07 16:41:06 +08:00
feat(orchestrator): redesign orchestrator page as template editor with terminal execution
Phase 1: Orchestrator Simplification - Remove ExecutionMonitor from OrchestratorPage - Replace "Run Workflow" button with "Send to Terminal" button - Update i18n texts for template editor context Phase 2: Session Lock Mechanism - Add 'locked' status to TerminalStatus type - Extend TerminalMeta with isLocked, lockReason, lockedByExecutionId, lockedAt - Implement lockSession/unlockSession in sessionManagerStore - Create SessionLockConfirmDialog component for input interception Phase 3: Execution Monitor Panel - Create executionMonitorStore for execution state management - Create ExecutionMonitorPanel component with step progress display - Add execution panel to DashboardToolbar and TerminalDashboardPage - Support WebSocket message handling for execution updates Phase 4: Execution Bridge - Add POST /api/orchestrator/flows/:id/execute-in-session endpoint - Create useExecuteFlowInSession hook for frontend API calls - Broadcast EXECUTION_STARTED and CLI_SESSION_LOCKED WebSocket messages - Lock session when execution starts, unlock on completion
This commit is contained in:
@@ -22,6 +22,7 @@ import { AgentList } from '@/components/terminal-dashboard/AgentList';
|
||||
import { IssuePanel } from '@/components/terminal-dashboard/IssuePanel';
|
||||
import { QueuePanel } from '@/components/terminal-dashboard/QueuePanel';
|
||||
import { InspectorContent } from '@/components/terminal-dashboard/BottomInspector';
|
||||
import { ExecutionMonitorPanel } from '@/components/terminal-dashboard/ExecutionMonitorPanel';
|
||||
import { FileSidebarPanel } from '@/components/terminal-dashboard/FileSidebarPanel';
|
||||
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
|
||||
import { useAppStore, selectIsImmersiveMode } from '@/stores/appStore';
|
||||
@@ -128,6 +129,16 @@ export function TerminalDashboardPage() {
|
||||
>
|
||||
<InspectorContent />
|
||||
</FloatingPanel>
|
||||
|
||||
<FloatingPanel
|
||||
isOpen={activePanel === 'execution'}
|
||||
onClose={closePanel}
|
||||
title={formatMessage({ id: 'terminalDashboard.toolbar.executionMonitor', defaultMessage: 'Execution Monitor' })}
|
||||
side="right"
|
||||
width={380}
|
||||
>
|
||||
<ExecutionMonitorPanel />
|
||||
</FloatingPanel>
|
||||
</AssociationHighlightProvider>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// ========================================
|
||||
// Flow Toolbar Component
|
||||
// ========================================
|
||||
// Toolbar for flow operations: Save, Load, Import Template, Export, Run, Monitor
|
||||
// Toolbar for flow operations: Save, Load, Import Template, Export, Send to Terminal
|
||||
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Save,
|
||||
FolderOpen,
|
||||
@@ -15,8 +16,7 @@ import {
|
||||
Loader2,
|
||||
ChevronDown,
|
||||
Library,
|
||||
Play,
|
||||
Activity,
|
||||
Terminal,
|
||||
Maximize2,
|
||||
Minimize2,
|
||||
} from 'lucide-react';
|
||||
@@ -24,8 +24,6 @@ import { cn } from '@/lib/utils';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { useFlowStore, toast } from '@/stores';
|
||||
import { useExecutionStore } from '@/stores/executionStore';
|
||||
import { useExecuteFlow } from '@/hooks/useFlows';
|
||||
import { useAppStore, selectIsImmersiveMode } from '@/stores/appStore';
|
||||
import type { Flow } from '@/types/flow';
|
||||
|
||||
@@ -36,6 +34,7 @@ interface FlowToolbarProps {
|
||||
|
||||
export function FlowToolbar({ className, onOpenTemplateLibrary }: FlowToolbarProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
const navigate = useNavigate();
|
||||
const [isFlowListOpen, setIsFlowListOpen] = useState(false);
|
||||
const [flowName, setFlowName] = useState('');
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
@@ -55,18 +54,6 @@ export function FlowToolbar({ className, onOpenTemplateLibrary }: FlowToolbarPro
|
||||
const duplicateFlow = useFlowStore((state) => state.duplicateFlow);
|
||||
const fetchFlows = useFlowStore((state) => state.fetchFlows);
|
||||
|
||||
// Execution store
|
||||
const currentExecution = useExecutionStore((state) => state.currentExecution);
|
||||
const isMonitorPanelOpen = useExecutionStore((state) => state.isMonitorPanelOpen);
|
||||
const setMonitorPanelOpen = useExecutionStore((state) => state.setMonitorPanelOpen);
|
||||
const startExecution = useExecutionStore((state) => state.startExecution);
|
||||
|
||||
// Mutations
|
||||
const executeFlow = useExecuteFlow();
|
||||
|
||||
const isExecuting = currentExecution?.status === 'running';
|
||||
const isPaused = currentExecution?.status === 'paused';
|
||||
|
||||
// Load flows on mount
|
||||
useEffect(() => {
|
||||
fetchFlows();
|
||||
@@ -194,24 +181,26 @@ export function FlowToolbar({ className, onOpenTemplateLibrary }: FlowToolbarPro
|
||||
toast.success(formatMessage({ id: 'orchestrator.notifications.flowExported' }), formatMessage({ id: 'orchestrator.notifications.flowExported' }));
|
||||
}, [currentFlow]);
|
||||
|
||||
// Handle run workflow
|
||||
const handleRun = useCallback(async () => {
|
||||
if (!currentFlow) return;
|
||||
try {
|
||||
// Open monitor panel automatically
|
||||
setMonitorPanelOpen(true);
|
||||
const result = await executeFlow.mutateAsync(currentFlow.id);
|
||||
startExecution(result.execId, currentFlow.id);
|
||||
} catch (error) {
|
||||
console.error('Failed to execute flow:', error);
|
||||
toast.error(formatMessage({ id: 'orchestrator.notifications.executionFailed' }), formatMessage({ id: 'orchestrator.notifications.couldNotExecute' }));
|
||||
// Handle send to terminal execution
|
||||
const handleSendToTerminal = useCallback(async () => {
|
||||
if (!currentFlow) {
|
||||
toast.error(formatMessage({ id: 'orchestrator.notifications.noFlow' }), formatMessage({ id: 'orchestrator.notifications.saveBeforeExecute' }));
|
||||
return;
|
||||
}
|
||||
}, [currentFlow, executeFlow, startExecution, setMonitorPanelOpen]);
|
||||
|
||||
// Handle monitor toggle
|
||||
const handleToggleMonitor = useCallback(() => {
|
||||
setMonitorPanelOpen(!isMonitorPanelOpen);
|
||||
}, [isMonitorPanelOpen, setMonitorPanelOpen]);
|
||||
// Save flow first if modified
|
||||
if (isModified) {
|
||||
const saved = await saveFlow();
|
||||
if (!saved) {
|
||||
toast.error(formatMessage({ id: 'orchestrator.notifications.saveFailed' }), formatMessage({ id: 'orchestrator.notifications.couldNotSave' }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Navigate to terminal dashboard with flow execution request
|
||||
navigate(`/terminal?executeFlow=${currentFlow.id}`);
|
||||
toast.success(formatMessage({ id: 'orchestrator.notifications.flowSent' }), formatMessage({ id: 'orchestrator.notifications.sentToTerminal' }, { name: currentFlow.name }));
|
||||
}, [currentFlow, isModified, saveFlow, navigate, formatMessage]);
|
||||
|
||||
return (
|
||||
<div className={cn('flex items-center gap-3 p-3 bg-card border-b border-border', className)}>
|
||||
@@ -346,29 +335,15 @@ export function FlowToolbar({ className, onOpenTemplateLibrary }: FlowToolbarPro
|
||||
|
||||
<div className="w-px h-6 bg-border" />
|
||||
|
||||
{/* Run & Monitor Group */}
|
||||
<Button
|
||||
variant={isMonitorPanelOpen ? 'secondary' : 'outline'}
|
||||
size="sm"
|
||||
onClick={handleToggleMonitor}
|
||||
title={formatMessage({ id: 'orchestrator.monitor.toggleMonitor' })}
|
||||
>
|
||||
<Activity className={cn('w-4 h-4 mr-1', (isExecuting || isPaused) && 'text-primary animate-pulse')} />
|
||||
{formatMessage({ id: 'orchestrator.toolbar.monitor' })}
|
||||
</Button>
|
||||
|
||||
{/* Execute in Terminal */}
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
onClick={handleRun}
|
||||
disabled={!currentFlow || isExecuting || isPaused || executeFlow.isPending}
|
||||
onClick={handleSendToTerminal}
|
||||
disabled={!currentFlow}
|
||||
>
|
||||
{executeFlow.isPending ? (
|
||||
<Loader2 className="w-4 h-4 mr-1 animate-spin" />
|
||||
) : (
|
||||
<Play className="w-4 h-4 mr-1" />
|
||||
)}
|
||||
{formatMessage({ id: 'orchestrator.toolbar.runWorkflow' })}
|
||||
<Terminal className="w-4 h-4 mr-1" />
|
||||
{formatMessage({ id: 'orchestrator.toolbar.sendToTerminal' })}
|
||||
</Button>
|
||||
|
||||
<div className="w-px h-6 bg-border" />
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
// ========================================
|
||||
// Orchestrator Page
|
||||
// ========================================
|
||||
// Visual workflow editor with React Flow, drag-drop node palette, and property panel
|
||||
// Visual workflow template editor with React Flow, drag-drop node palette, and property panel
|
||||
// Execution functionality moved to Terminal Dashboard
|
||||
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { useFlowStore } from '@/stores';
|
||||
import { useExecutionStore } from '@/stores/executionStore';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { FlowCanvas } from './FlowCanvas';
|
||||
import { LeftSidebar } from './LeftSidebar';
|
||||
import { PropertyPanel } from './PropertyPanel';
|
||||
import { FlowToolbar } from './FlowToolbar';
|
||||
import { TemplateLibrary } from './TemplateLibrary';
|
||||
import { ExecutionMonitor } from './ExecutionMonitor';
|
||||
|
||||
export function OrchestratorPage() {
|
||||
const fetchFlows = useFlowStore((state) => state.fetchFlows);
|
||||
const isPaletteOpen = useFlowStore((state) => state.isPaletteOpen);
|
||||
const setIsPaletteOpen = useFlowStore((state) => state.setIsPaletteOpen);
|
||||
const isPropertyPanelOpen = useFlowStore((state) => state.isPropertyPanelOpen);
|
||||
const isMonitorPanelOpen = useExecutionStore((state) => state.isMonitorPanelOpen);
|
||||
const [isTemplateLibraryOpen, setIsTemplateLibraryOpen] = useState(false);
|
||||
|
||||
// Load flows on mount
|
||||
@@ -60,15 +58,12 @@ export function OrchestratorPage() {
|
||||
<FlowCanvas className="absolute inset-0" />
|
||||
|
||||
{/* Property Panel as overlay - only shown when a node is selected */}
|
||||
{!isMonitorPanelOpen && isPropertyPanelOpen && (
|
||||
{isPropertyPanelOpen && (
|
||||
<div className="absolute top-2 right-2 bottom-2 z-10">
|
||||
<PropertyPanel className="h-full" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Execution Monitor Panel (Right) */}
|
||||
<ExecutionMonitor />
|
||||
</div>
|
||||
|
||||
{/* Template Library Dialog */}
|
||||
|
||||
Reference in New Issue
Block a user