/** * PipelinesPanel - Pipeline Discovery and Execution UI * * Displays available Pipelines (DSL-based workflows) with * category filtering, search, and execution capabilities. * * Pipelines orchestrate Skills and Hands to accomplish complex tasks. */ import { useState, useEffect } from 'react'; import { Play, RefreshCw, Search, Loader2, XCircle, Package, Filter, X, } from 'lucide-react'; import { PipelineClient, PipelineInfo, PipelineRunResponse, usePipelines, validateInputs, getDefaultForType, formatInputType, } from '../lib/pipeline-client'; import { useToast } from './ui/Toast'; import { PresentationContainer } from './presentation'; // === Category Badge Component === const CATEGORY_CONFIG: Record = { education: { label: '教育', className: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400' }, marketing: { label: '营销', className: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400' }, legal: { label: '法律', className: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400' }, productivity: { label: '生产力', className: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400' }, research: { label: '研究', className: 'bg-cyan-100 text-cyan-700 dark:bg-cyan-900/30 dark:text-cyan-400' }, sales: { label: '销售', className: 'bg-rose-100 text-rose-700 dark:bg-rose-900/30 dark:text-rose-400' }, hr: { label: '人力', className: 'bg-teal-100 text-teal-700 dark:bg-teal-900/30 dark:text-teal-400' }, finance: { label: '财务', className: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400' }, default: { label: '其他', className: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-400' }, }; function CategoryBadge({ category }: { category: string }) { const config = CATEGORY_CONFIG[category] || CATEGORY_CONFIG.default; return ( {config.label} ); } // === Pipeline Card Component === interface PipelineCardProps { pipeline: PipelineInfo; onRun: (pipeline: PipelineInfo) => void; } function PipelineCard({ pipeline, onRun }: PipelineCardProps) { return (
{pipeline.icon}

{pipeline.displayName}

{pipeline.id} · v{pipeline.version}

{pipeline.description}

{pipeline.tags.length > 0 && (
{pipeline.tags.slice(0, 3).map((tag) => ( {tag} ))} {pipeline.tags.length > 3 && ( +{pipeline.tags.length - 3} )}
)}
{pipeline.inputs.length} 个输入参数
); } // === Pipeline Result Modal === interface ResultModalProps { result: PipelineRunResponse; pipeline: PipelineInfo; onClose: () => void; } function ResultModal({ result, pipeline, onClose }: ResultModalProps) { return (
{/* Header */}
{pipeline.icon}

{pipeline.displayName} - 执行结果

状态: {result.status === 'completed' ? '已完成' : '失败'}

{/* Content */}
{result.outputs ? ( ) : result.error ? (

{result.error}

) : (

无输出结果

)}
); } // === Pipeline Run Modal === interface RunModalProps { pipeline: PipelineInfo; onClose: () => void; onComplete: (result: PipelineRunResponse) => void; } function RunModal({ pipeline, onClose, onComplete }: RunModalProps) { const [values, setValues] = useState>(() => { const defaults: Record = {}; pipeline.inputs.forEach((input) => { defaults[input.name] = input.default ?? getDefaultForType(input.inputType); }); return defaults; }); const [errors, setErrors] = useState([]); const [running, setRunning] = useState(false); const [progress, setProgress] = useState(null); const handleInputChange = (name: string, value: unknown) => { setValues((prev) => ({ ...prev, [name]: value })); setErrors([]); }; const handleRun = async () => { // Validate inputs const validation = validateInputs(pipeline.inputs, values); if (!validation.valid) { setErrors(validation.errors); return; } setRunning(true); setProgress(null); try { const result = await PipelineClient.runAndWait( { pipelineId: pipeline.id, inputs: values }, (p) => setProgress(p) ); if (result.status === 'completed') { onComplete(result); } else if (result.error) { setErrors([result.error]); } } catch (err) { setErrors([err instanceof Error ? err.message : String(err)]); } finally { setRunning(false); } }; const renderInput = (input: typeof pipeline.inputs[0]) => { const value = values[input.name]; switch (input.inputType) { case 'string': case 'text': return input.inputType === 'text' ? (