Files
zclaw_openfang/desktop/src/components/pipeline/PipelineResultPreview.tsx
iven 894c0d7b15
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
feat(desktop): pipeline result preview + industry templates + onboarding auto-trigger
Sprint 2: 产品体验打磨 + 行业模板

- Create PipelineResultPreview component with tab-based output switching
- Connect workflow/hand messages to PresentationContainer in ChatArea
- Add auto-trigger first Hand after onboarding (industry-specific queries)
- Seed 3 industry agent templates (education, healthcare, design-shantou)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 10:48:47 +08:00

86 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* PipelineResultPreview — Pipeline 输出结果预览
*
* 将 Pipeline outputs 的多个顶层键以 tab 切换展示,
* 每个 tab 内嵌 PresentationContainer 做智能渲染。
*/
import { useState, useMemo } from 'react';
import { PresentationContainer } from '../presentation/PresentationContainer';
import { FileText, BarChart3, HelpCircle, Presentation, PenTool } from 'lucide-react';
interface PipelineResultPreviewProps {
/** Pipeline outputs 对象(多键 JSON */
outputs: Record<string, unknown>;
/** Pipeline ID */
pipelineId?: string;
className?: string;
}
const KEY_ICONS: Record<string, React.ReactNode> = {
summary: <FileText className="w-3.5 h-3.5" />,
report: <FileText className="w-3.5 h-3.5" />,
analysis: <BarChart3 className="w-3.5 h-3.5" />,
quiz: <HelpCircle className="w-3.5 h-3.5" />,
scenes: <Presentation className="w-3.5 h-3.5" />,
slides: <Presentation className="w-3.5 h-3.5" />,
plan: <PenTool className="w-3.5 h-3.5" />,
};
function getKeyIcon(key: string): React.ReactNode {
for (const [pattern, icon] of Object.entries(KEY_ICONS)) {
if (key.toLowerCase().includes(pattern)) return icon;
}
return <FileText className="w-3.5 h-3.5" />;
}
export function PipelineResultPreview({ outputs, pipelineId, className = '' }: PipelineResultPreviewProps) {
const keys = useMemo(() => Object.keys(outputs), [outputs]);
const [activeKey, setActiveKey] = useState(keys[0]);
if (keys.length === 0) {
return (
<div className="text-sm text-gray-400 italic p-4">
Pipeline
</div>
);
}
return (
<div className={`rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden ${className}`}>
{/* Tab bar */}
{keys.length > 1 && (
<div className="flex border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 overflow-x-auto">
{keys.map((key) => (
<button
key={key}
onClick={() => setActiveKey(key)}
className={`flex items-center gap-1.5 px-4 py-2.5 text-xs font-medium whitespace-nowrap transition-colors border-b-2 ${
activeKey === key
? 'border-emerald-500 text-emerald-700 dark:text-emerald-400 bg-white dark:bg-gray-800'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'
}`}
>
{getKeyIcon(key)}
<span>{key}</span>
</button>
))}
</div>
)}
{/* Content */}
<div className="p-4 bg-white dark:bg-gray-900">
{outputs[activeKey] !== undefined && outputs[activeKey] !== null ? (
<PresentationContainer
data={outputs[activeKey]}
pipelineId={pipelineId}
allowSwitch={true}
/>
) : (
<p className="text-sm text-gray-400"></p>
)}
</div>
</div>
);
}