Files
patchy631--ai-engineering-hub/open-agent-builder/components/app/(home)/sections/workflow-builder/PreviewPanel.tsx
T
2026-07-13 12:37:47 +08:00

132 lines
6.4 KiB
TypeScript

"use client";
import { motion, AnimatePresence } from "framer-motion";
import { WorkflowExecution, NodeExecutionResult } from "@/lib/workflow/types";
interface PreviewPanelProps {
execution: WorkflowExecution | null;
nodeResults: Record<string, NodeExecutionResult>;
isRunning: boolean;
onClose: () => void;
}
export default function PreviewPanel({ execution, nodeResults, isRunning, onClose }: PreviewPanelProps) {
return (
<AnimatePresence>
<motion.aside
initial={{ x: 400, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: 400, opacity: 0 }}
transition={{ duration: 0.3 }}
className="fixed right-20 top-80 h-[calc(100vh-100px)] w-480 bg-accent-white border border-border-faint shadow-lg overflow-y-auto z-50 rounded-16"
>
{/* Header */}
<div className="p-20 border-b border-border-faint sticky top-0 bg-accent-white">
<div className="flex items-center justify-between mb-8">
<h2 className="text-title-h3 text-accent-black">Workflow Preview</h2>
<button
onClick={onClose}
className="w-32 h-32 rounded-6 hover:bg-black-alpha-4 transition-colors flex items-center justify-center"
>
<svg className="w-16 h-16 text-black-alpha-48" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* Status Badge */}
{execution && (
<div className={`inline-flex items-center gap-8 px-12 py-6 rounded-8 text-body-small ${
execution.status === 'running' ? 'bg-heat-4 text-heat-100' :
execution.status === 'completed' ? 'bg-heat-4 text-heat-100' :
execution.status === 'failed' ? 'bg-black-alpha-4 text-accent-black' :
'bg-gray-50 text-gray-600'
}`}>
{execution.status === 'running' && (
<svg className="w-12 h-12 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
)}
{execution.status}
</div>
)}
</div>
{/* Content */}
<div className="p-20">
{isRunning && Object.keys(nodeResults).length === 0 ? (
<div className="text-center py-32">
<svg className="w-48 h-48 mx-auto mb-16 text-heat-100 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
<p className="text-body-medium text-black-alpha-48">Starting workflow...</p>
</div>
) : Object.keys(nodeResults).length === 0 ? (
<div className="text-center py-32">
<svg className="w-48 h-48 mx-auto mb-16 text-black-alpha-32" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<p className="text-body-medium text-black-alpha-48">Click Preview to run workflow</p>
</div>
) : (
<div className="space-y-16">
{Object.entries(nodeResults).map(([nodeId, result]) => (
<div key={nodeId} className="bg-background-base rounded-12 p-16 border border-border-faint">
{/* Node Header */}
<div className="flex items-center justify-between mb-12">
<h3 className="text-label-medium text-accent-black font-medium">
{nodeId}
</h3>
<span className={`text-body-small px-8 py-4 rounded-6 ${
result.status === 'running' ? 'bg-heat-4 text-heat-100' :
result.status === 'completed' ? 'bg-heat-4 text-heat-100' :
result.status === 'failed' ? 'bg-black-alpha-4 text-accent-black' :
'bg-gray-50 text-gray-600'
}`}>
{result.status}
</span>
</div>
{/* Node Output */}
{result.output && (
<div className="mt-12">
<p className="text-body-small text-black-alpha-48 mb-8">Output:</p>
<div className="bg-accent-white rounded-8 p-12 border border-border-faint">
<pre className="text-body-small text-accent-black whitespace-pre-wrap overflow-auto max-h-200">
{typeof result.output === 'string'
? result.output
: JSON.stringify(result.output, null, 2)}
</pre>
</div>
</div>
)}
{/* Error */}
{result.error && (
<div className="mt-12">
<p className="text-body-small text-accent-black mb-8">Error:</p>
<div className="bg-black-alpha-4 rounded-8 p-12 border border-border-faint">
<pre className="text-body-small text-accent-black whitespace-pre-wrap">
{result.error}
</pre>
</div>
</div>
)}
{/* Timing */}
{result.startedAt && result.completedAt && (
<p className="text-body-small text-black-alpha-32 mt-8">
Duration: {Math.round((new Date(result.completedAt).getTime() - new Date(result.startedAt).getTime()) / 1000)}s
</p>
)}
</div>
))}
</div>
)}
</div>
</motion.aside>
</AnimatePresence>
);
}