fix: 修复TypeScript配置错误并更新项目文档

详细说明:
- 修复了@n8n/config包的TypeScript配置错误
- 移除了不存在的jest-expect-message类型引用
- 清理了所有TypeScript构建缓存
- 更新了可行性分析文档,添加了技术实施方案
- 更新了Agent prompt文档
- 添加了会展策划工作流文档
- 包含了n8n-chinese-translation子项目
- 添加了exhibition-demo展示系统框架
This commit is contained in:
Yep_Q
2025-09-08 10:49:45 +08:00
parent 8cf9d36d81
commit 3db7af209c
426 changed files with 71699 additions and 4401 deletions

View File

@@ -0,0 +1,72 @@
import React, { useState } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import LandingPage from './pages/LandingPage';
import WorkflowPage from './pages/WorkflowPage';
import ResultPage from './pages/ResultPage';
import { useDemoStore } from './store/demoStore';
type PageType = 'landing' | 'workflow' | 'result';
function App() {
const [currentPage, setCurrentPage] = useState<PageType>('landing');
const { status } = useDemoStore();
React.useEffect(() => {
if (status === 'completed') {
setCurrentPage('result');
}
}, [status]);
const handleStartDemo = () => {
setCurrentPage('workflow');
};
const handleRestart = () => {
useDemoStore.getState().reset();
setCurrentPage('landing');
};
return (
<div className="min-h-screen bg-gradient-to-br from-neutral-50 to-neutral-100 dark:from-neutral-950 dark:to-neutral-900">
<AnimatePresence mode="wait">
{currentPage === 'landing' && (
<motion.div
key="landing"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
>
<LandingPage onStart={handleStartDemo} />
</motion.div>
)}
{currentPage === 'workflow' && (
<motion.div
key="workflow"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
>
<WorkflowPage />
</motion.div>
)}
{currentPage === 'result' && (
<motion.div
key="result"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
>
<ResultPage onRestart={handleRestart} />
</motion.div>
)}
</AnimatePresence>
</div>
);
}
export default App;

View File

@@ -0,0 +1,156 @@
import React from 'react';
import { motion } from 'framer-motion';
import { Agent } from '../store/demoStore';
import { Loader2, Check, Brain } from 'lucide-react';
interface AgentCardProps {
agent: Agent;
isActive: boolean;
compact?: boolean;
}
const AgentCard: React.FC<AgentCardProps> = ({ agent, isActive, compact = false }) => {
const getStatusIcon = () => {
switch (agent.status) {
case 'thinking':
return <Brain className="w-4 h-4 animate-pulse" />;
case 'generating':
return <Loader2 className="w-4 h-4 animate-spin" />;
case 'done':
return <Check className="w-4 h-4 text-green-500" />;
default:
return null;
}
};
const getStatusColor = () => {
switch (agent.status) {
case 'thinking':
return 'bg-blue-500';
case 'generating':
return 'bg-purple-500';
case 'done':
return 'bg-green-500';
default:
return 'bg-neutral-400';
}
};
if (compact) {
return (
<motion.div
className={`
relative p-4 rounded-xl transition-all duration-300
${isActive ? 'agent-card active' : 'agent-card'}
${isActive ? 'scale-105' : 'hover:scale-102'}
`}
whileHover={{ y: -2 }}
>
{/* Status Indicator */}
<div className={`absolute top-2 right-2 w-2 h-2 rounded-full ${getStatusColor()}`} />
<div className="flex items-center gap-3">
<span className="text-2xl">{agent.icon}</span>
<div className="flex-1 min-w-0">
<h3 className="font-semibold text-sm truncate">{agent.name}</h3>
<p className="text-xs text-neutral-500 dark:text-neutral-400 truncate">
{agent.model}
</p>
</div>
{getStatusIcon()}
</div>
</motion.div>
);
}
return (
<motion.div
className={`
relative p-6 rounded-2xl transition-all duration-300
${isActive ? 'agent-card active' : 'agent-card'}
`}
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
>
{/* Header */}
<div className="flex items-start justify-between mb-4">
<div className="flex items-center gap-4">
<div className="text-4xl">{agent.icon}</div>
<div>
<h3 className="text-xl font-semibold">{agent.name}</h3>
<p className="text-sm text-neutral-500 dark:text-neutral-400">
{agent.model}
</p>
</div>
</div>
<div className="flex items-center gap-2">
{getStatusIcon()}
<div className={`w-3 h-3 rounded-full ${getStatusColor()} animate-pulse`} />
</div>
</div>
{/* Role Description */}
<p className="text-sm text-neutral-600 dark:text-neutral-300 mb-4">
{agent.role}
</p>
{/* Prompt Display (if active) */}
{isActive && agent.prompt && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
className="mt-4 p-4 bg-neutral-100 dark:bg-neutral-800 rounded-lg"
>
<h4 className="text-sm font-semibold mb-2 text-neutral-700 dark:text-neutral-300">
Prompt:
</h4>
<TypewriterText text={agent.prompt} speed={40} />
</motion.div>
)}
{/* Output Display (if available) */}
{agent.output && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5 }}
className="mt-4 p-4 bg-green-50 dark:bg-green-900/20 rounded-lg border border-green-200 dark:border-green-800"
>
<h4 className="text-sm font-semibold mb-2 text-green-700 dark:text-green-300">
:
</h4>
<p className="text-sm text-neutral-700 dark:text-neutral-300">
{agent.output}
</p>
</motion.div>
)}
</motion.div>
);
};
// Typewriter Component
const TypewriterText: React.FC<{ text: string; speed: number }> = ({ text, speed }) => {
const [displayedText, setDisplayedText] = React.useState('');
const [currentIndex, setCurrentIndex] = React.useState(0);
React.useEffect(() => {
if (currentIndex < text.length) {
const timeout = setTimeout(() => {
setDisplayedText(prev => prev + text[currentIndex]);
setCurrentIndex(prev => prev + 1);
}, speed);
return () => clearTimeout(timeout);
}
}, [currentIndex, text, speed]);
return (
<p className="text-sm font-mono text-neutral-600 dark:text-neutral-400">
{displayedText}
{currentIndex < text.length && (
<span className="inline-block w-2 h-4 bg-neutral-400 animate-pulse ml-1" />
)}
</p>
);
};
export default AgentCard;

View File

@@ -0,0 +1,104 @@
import React, { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import { FileText, Image, Loader2 } from 'lucide-react';
interface ContentGeneratorProps {
content?: string;
images?: string[];
speed?: number;
}
const ContentGenerator: React.FC<ContentGeneratorProps> = ({
content = "正在生成会展策划方案内容...",
images = [],
speed = 35
}) => {
const [displayedContent, setDisplayedContent] = useState('');
const [currentIndex, setCurrentIndex] = useState(0);
const [loadingImages, setLoadingImages] = useState<Set<number>>(new Set());
useEffect(() => {
if (currentIndex < content.length) {
const timeout = setTimeout(() => {
setDisplayedContent(prev => prev + content[currentIndex]);
setCurrentIndex(prev => prev + 1);
}, speed);
return () => clearTimeout(timeout);
}
}, [currentIndex, content, speed]);
const handleImageLoad = (index: number) => {
setLoadingImages(prev => {
const newSet = new Set(prev);
newSet.delete(index);
return newSet;
});
};
return (
<div className="space-y-4">
{/* Text Content */}
<div className="p-4 bg-neutral-50 dark:bg-neutral-900 rounded-lg">
<div className="flex items-center gap-2 mb-3">
<FileText className="w-4 h-4 text-blue-500" />
<span className="text-sm font-medium text-neutral-600 dark:text-neutral-400">
...
</span>
</div>
<div className="font-mono text-sm text-neutral-700 dark:text-neutral-300 whitespace-pre-wrap">
{displayedContent}
{currentIndex < content.length && (
<span className="inline-block w-2 h-4 bg-blue-500 animate-pulse ml-1" />
)}
</div>
</div>
{/* Images */}
{images.length > 0 && (
<div className="space-y-3">
<div className="flex items-center gap-2">
<Image className="w-4 h-4 text-purple-500" />
<span className="text-sm font-medium text-neutral-600 dark:text-neutral-400">
</span>
</div>
<div className="grid grid-cols-3 gap-3">
{images.map((src, index) => (
<motion.div
key={index}
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: index * 0.2 }}
className="relative aspect-video rounded-lg overflow-hidden bg-neutral-100 dark:bg-neutral-800"
>
{loadingImages.has(index) && (
<div className="absolute inset-0 flex items-center justify-center">
<Loader2 className="w-6 h-6 animate-spin text-neutral-400" />
</div>
)}
<img
src={src}
alt={`Generated content ${index + 1}`}
className="w-full h-full object-cover"
onLoad={() => handleImageLoad(index)}
onLoadStart={() => setLoadingImages(prev => new Set(prev).add(index))}
/>
</motion.div>
))}
</div>
</div>
)}
{/* Stats */}
<div className="flex items-center gap-6 text-xs text-neutral-500 dark:text-neutral-400">
<span>: {displayedContent.length}</span>
<span>: {speed}/</span>
<span>: {Math.round((currentIndex / content.length) * 100)}%</span>
</div>
</div>
);
};
export default ContentGenerator;

View File

@@ -0,0 +1,29 @@
import React from 'react';
import { motion } from 'framer-motion';
interface ProgressBarProps {
progress: number;
}
const ProgressBar: React.FC<ProgressBarProps> = ({ progress }) => {
return (
<div className="relative w-full h-2 bg-neutral-200 dark:bg-neutral-800 rounded-full overflow-hidden">
<motion.div
className="absolute left-0 top-0 h-full bg-gradient-to-r from-blue-500 to-purple-500 rounded-full"
initial={{ width: '0%' }}
animate={{ width: `${progress}%` }}
transition={{ duration: 0.5, ease: 'easeInOut' }}
>
{/* Shimmer effect */}
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent animate-shimmer" />
</motion.div>
{/* Progress text */}
<div className="absolute -top-8 left-0 text-sm text-neutral-600 dark:text-neutral-400">
: {Math.round(progress)}%
</div>
</div>
);
};
export default ProgressBar;

View File

@@ -0,0 +1,178 @@
import React from 'react';
import { motion } from 'framer-motion';
import { Agent } from '../store/demoStore';
interface WorkflowVisualizationProps {
agents: Agent[];
currentAgent: string | null;
}
const WorkflowVisualization: React.FC<WorkflowVisualizationProps> = ({
agents,
currentAgent
}) => {
// 定义工作流布局
const layout = {
phase1: ['retrieval', 'design', 'budget'],
coordinator: ['coordinator'],
phase2: ['format', 'execution', 'marketing'],
};
const getNodePosition = (agentId: string) => {
if (layout.phase1.includes(agentId)) {
const index = layout.phase1.indexOf(agentId);
return { x: 100 + index * 150, y: 50 };
}
if (agentId === 'coordinator') {
return { x: 250, y: 200 };
}
if (layout.phase2.includes(agentId)) {
const index = layout.phase2.indexOf(agentId);
return { x: 100 + index * 150, y: 350 };
}
return { x: 0, y: 0 };
};
return (
<div className="relative w-full h-[450px]">
<svg className="absolute inset-0 w-full h-full">
{/* Draw connections */}
{/* Phase 1 to Coordinator */}
{layout.phase1.map((agentId) => {
const start = getNodePosition(agentId);
const end = getNodePosition('coordinator');
return (
<motion.line
key={`${agentId}-coordinator`}
x1={start.x}
y1={start.y + 30}
x2={end.x}
y2={end.y - 30}
stroke="url(#gradient)"
strokeWidth="2"
strokeDasharray="5,5"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 2, delay: 0.5 }}
className={currentAgent === agentId ? 'animate-pulse' : ''}
/>
);
})}
{/* Coordinator to Phase 2 */}
{layout.phase2.map((agentId) => {
const start = getNodePosition('coordinator');
const end = getNodePosition(agentId);
return (
<motion.line
key={`coordinator-${agentId}`}
x1={start.x}
y1={start.y + 30}
x2={end.x}
y2={end.y - 30}
stroke="url(#gradient)"
strokeWidth="2"
strokeDasharray="5,5"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 2, delay: 1 }}
className={currentAgent === agentId ? 'animate-pulse' : ''}
/>
);
})}
{/* Phase 2 back to Coordinator */}
{layout.phase2.map((agentId) => {
const start = getNodePosition(agentId);
const end = getNodePosition('coordinator');
return (
<motion.line
key={`${agentId}-coordinator-back`}
x1={start.x}
y1={start.y - 30}
x2={end.x}
y2={end.y + 30}
stroke="url(#gradient)"
strokeWidth="2"
strokeDasharray="5,5"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 2, delay: 1.5 }}
className={currentAgent === agentId ? 'animate-pulse' : ''}
opacity={0.5}
/>
);
})}
{/* Gradient definition */}
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#3b82f6" />
<stop offset="100%" stopColor="#8b5cf6" />
</linearGradient>
</defs>
</svg>
{/* Agent Nodes */}
{agents.map((agent) => {
const position = getNodePosition(agent.id);
const isActive = currentAgent === agent.id;
return (
<motion.div
key={agent.id}
className={`
absolute transform -translate-x-1/2 -translate-y-1/2
w-20 h-20 rounded-2xl flex flex-col items-center justify-center
${isActive ? 'bg-gradient-to-br from-blue-500 to-purple-500 scale-110' : 'bg-white dark:bg-neutral-800'}
${isActive ? 'shadow-2xl' : 'shadow-lg'}
transition-all duration-300
${!isActive && 'hover:scale-105'}
border-2 ${isActive ? 'border-transparent' : 'border-neutral-200 dark:border-neutral-700'}
`}
style={{ left: position.x, top: position.y }}
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{
delay: agents.indexOf(agent) * 0.1,
type: 'spring',
stiffness: 200
}}
>
<span className={`text-2xl ${isActive ? 'scale-125' : ''}`}>
{agent.icon}
</span>
<span className={`
text-xs mt-1 font-medium text-center px-1
${isActive ? 'text-white' : 'text-neutral-700 dark:text-neutral-300'}
`}>
{agent.name.split('专家')[0]}
</span>
{/* Status dot */}
<div className={`
absolute -top-1 -right-1 w-3 h-3 rounded-full
${agent.status === 'done' ? 'bg-green-500' : ''}
${agent.status === 'thinking' ? 'bg-blue-500 animate-pulse' : ''}
${agent.status === 'generating' ? 'bg-purple-500 animate-pulse' : ''}
${agent.status === 'waiting' ? 'bg-neutral-400' : ''}
`} />
</motion.div>
);
})}
{/* Labels */}
<div className="absolute top-0 left-0 text-sm text-neutral-500 dark:text-neutral-400">
</div>
<div className="absolute top-[180px] left-[180px] text-sm text-neutral-500 dark:text-neutral-400">
</div>
<div className="absolute bottom-20 left-0 text-sm text-neutral-500 dark:text-neutral-400">
</div>
</div>
);
};
export default WorkflowVisualization;

View File

@@ -0,0 +1,140 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 47.4% 11.2%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 47.4% 11.2%;
}
* {
@apply border-neutral-200 dark:border-neutral-800;
}
body {
@apply bg-white dark:bg-neutral-950 text-neutral-900 dark:text-neutral-50;
font-feature-settings: "rlig" 1, "calt" 1;
}
}
@layer components {
.glass-morphism {
@apply backdrop-blur-xl bg-white/80 dark:bg-neutral-900/80 border border-neutral-200/50 dark:border-neutral-800/50;
}
.gradient-border {
background: linear-gradient(white, white) padding-box,
linear-gradient(to right, #3b82f6, #8b5cf6) border-box;
border: 2px solid transparent;
}
.text-gradient {
@apply bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent;
}
.animate-typewriter {
overflow: hidden;
white-space: nowrap;
border-right: 2px solid;
animation: typing 3.5s steps(40, end), blink-caret 0.75s step-end infinite;
}
@keyframes typing {
from { width: 0 }
to { width: 100% }
}
@keyframes blink-caret {
from, to { border-color: transparent }
50% { border-color: currentColor }
}
.loading-spinner {
@apply inline-block w-8 h-8 border-4 border-neutral-200 dark:border-neutral-800 rounded-full;
border-top-color: #3b82f6;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg) }
}
.agent-card {
@apply relative overflow-hidden rounded-2xl p-6 transition-all duration-300;
@apply hover:scale-105 hover:shadow-2xl;
@apply bg-gradient-to-br from-neutral-50 to-neutral-100;
@apply dark:from-neutral-900 dark:to-neutral-800;
}
.agent-card.active {
@apply ring-2 ring-blue-500 ring-offset-2 dark:ring-offset-neutral-950;
animation: pulse-glow 2s infinite;
}
@keyframes pulse-glow {
0%, 100% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4);
}
50% {
box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
}
}
.workflow-line {
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
animation: draw 2s ease-in-out forwards;
}
@keyframes draw {
to {
stroke-dashoffset: 0;
}
}
.content-section {
@apply opacity-0 transform translate-y-4;
animation: fadeInUp 0.6s ease-out forwards;
}
@keyframes fadeInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
}
@layer utilities {
.scrollbar-thin {
scrollbar-width: thin;
scrollbar-color: theme('colors.neutral.400') transparent;
}
.scrollbar-thin::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.scrollbar-thin::-webkit-scrollbar-track {
background: transparent;
}
.scrollbar-thin::-webkit-scrollbar-thumb {
@apply bg-neutral-400 dark:bg-neutral-600 rounded-full;
}
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
@apply bg-neutral-500 dark:bg-neutral-500;
}
}

View File

@@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@@ -0,0 +1,131 @@
import React from 'react';
import { motion } from 'framer-motion';
import { ArrowRight, Sparkles, Layers, Zap } from 'lucide-react';
interface LandingPageProps {
onStart: () => void;
}
const LandingPage: React.FC<LandingPageProps> = ({ onStart }) => {
return (
<div className="min-h-screen flex items-center justify-center p-8">
<div className="max-w-4xl w-full">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
className="text-center space-y-8"
>
{/* Logo/Icon */}
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ delay: 0.2, type: "spring", stiffness: 200 }}
className="inline-flex items-center justify-center w-24 h-24 rounded-3xl bg-gradient-to-br from-blue-500 to-purple-500 shadow-2xl"
>
<Sparkles className="w-12 h-12 text-white" />
</motion.div>
{/* Title */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4 }}
>
<h1 className="text-6xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
AI
</h1>
<p className="mt-4 text-xl text-neutral-600 dark:text-neutral-400">
Agent协同 · ·
</p>
</motion.div>
{/* Features */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.6 }}
className="grid grid-cols-3 gap-6 mt-12"
>
<div className="glass-morphism rounded-2xl p-6 hover:scale-105 transition-transform">
<Layers className="w-8 h-8 text-blue-500 mb-4" />
<h3 className="font-semibold text-lg mb-2">7Agent</h3>
<p className="text-sm text-neutral-600 dark:text-neutral-400">
</p>
</div>
<div className="glass-morphism rounded-2xl p-6 hover:scale-105 transition-transform">
<Zap className="w-8 h-8 text-purple-500 mb-4" />
<h3 className="font-semibold text-lg mb-2"></h3>
<p className="text-sm text-neutral-600 dark:text-neutral-400">
</p>
</div>
<div className="glass-morphism rounded-2xl p-6 hover:scale-105 transition-transform">
<Sparkles className="w-8 h-8 text-green-500 mb-4" />
<h3 className="font-semibold text-lg mb-2"></h3>
<p className="text-sm text-neutral-600 dark:text-neutral-400">
</p>
</div>
</motion.div>
{/* Demo Info */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.8 }}
className="glass-morphism rounded-2xl p-8 text-left"
>
<h2 className="text-2xl font-semibold mb-4"></h2>
<div className="space-y-2 text-neutral-600 dark:text-neutral-400">
<p>
<span className="font-medium text-neutral-900 dark:text-neutral-100"></span>
2024
</p>
<p>
<span className="font-medium text-neutral-900 dark:text-neutral-100"></span>
50,00035050,000
</p>
<p>
<span className="font-medium text-neutral-900 dark:text-neutral-100"></span>
3
</p>
</div>
</motion.div>
{/* Start Button */}
<motion.button
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 1 }}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={onStart}
className="group relative inline-flex items-center gap-3 px-8 py-4 bg-gradient-to-r from-blue-500 to-purple-500 text-white font-semibold text-lg rounded-2xl shadow-xl hover:shadow-2xl transition-all"
>
<span></span>
<ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
{/* Glow effect */}
<div className="absolute inset-0 rounded-2xl bg-gradient-to-r from-blue-500 to-purple-500 blur-xl opacity-50 group-hover:opacity-70 transition-opacity" />
</motion.button>
{/* Footer */}
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1.2 }}
className="text-sm text-neutral-500 dark:text-neutral-500 mt-8"
>
Powered by n8n Workflow · DeepSeek · Google Gemini
</motion.p>
</motion.div>
</div>
</div>
);
};
export default LandingPage;

View File

@@ -0,0 +1,261 @@
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
FileText,
Target,
TrendingUp,
Calendar,
DollarSign,
AlertTriangle,
ChevronLeft,
ChevronRight,
RotateCcw,
Download
} from 'lucide-react';
import { useDemoStore } from '../store/demoStore';
interface ResultPageProps {
onRestart: () => void;
}
const sections = [
{ id: 'overview', title: '策划案概述', icon: FileText },
{ id: 'introduction', title: '展会介绍与预期效果', icon: Target },
{ id: 'marketing', title: '营销方案', icon: TrendingUp },
{ id: 'operation', title: '现场运营方案', icon: Calendar },
{ id: 'budget', title: '预算与收益分析', icon: DollarSign },
{ id: 'risk', title: '风险评估与应急预案', icon: AlertTriangle },
];
const ResultPage: React.FC<ResultPageProps> = ({ onRestart }) => {
const [currentSection, setCurrentSection] = useState(0);
const { generatedContent } = useDemoStore();
const handlePrevious = () => {
if (currentSection > 0) {
setCurrentSection(currentSection - 1);
}
};
const handleNext = () => {
if (currentSection < sections.length - 1) {
setCurrentSection(currentSection + 1);
}
};
// Mock content for demonstration
const getSectionContent = (sectionId: string) => {
const mockContent = {
overview: {
title: '2024长三角国际新能源汽车与智能交通产业博览会',
content: `
## 策划背景
在全球碳中和目标推动下,新能源汽车产业迎来爆发式增长。长三角地区作为中国汽车产业核心集群,聚集了特斯拉、上汽、蔚来等龙头企业。
## 策划目的
- 打造长三角地区新能源汽车与智能交通领域第一展会品牌
- 吸引300家优质展商实现现场意向交易额超8亿元人民币
- 促进产业链上下游合作,推动技术创新和产业升级
- 推广绿色出行理念,助力碳中和目标实现
`,
images: ['/api/placeholder/600/400', '/api/placeholder/600/400']
},
introduction: {
title: '展会规模与预期',
content: `
## 展会主题
「智行未来,绿动长三角」
## 预计规模
- 展览面积50,000平方米
- 标准展位1,200个9平米/个)
- 特装展位20,000平方米
- 参展商家数350家
- 参观人次50,000人次
## 预期效果
- 现场成交额预计超10亿元
- 带动相关产业收入约30亿元
- 媒体曝光量超1亿次
`,
images: []
},
// Add more sections...
};
return mockContent[sectionId] || generatedContent[sectionId] || mockContent.overview;
};
const currentSectionData = getSectionContent(sections[currentSection].id);
return (
<div className="min-h-screen p-6">
<div className="max-w-7xl mx-auto">
{/* Header */}
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
className="mb-8"
>
<div className="flex items-center justify-between">
<h1 className="text-4xl font-bold text-gradient">
</h1>
<div className="flex items-center gap-4">
<button
onClick={() => {}}
className="px-4 py-2 glass-morphism rounded-xl hover:scale-105 transition-transform flex items-center gap-2"
>
<Download className="w-4 h-4" />
</button>
<button
onClick={onRestart}
className="px-4 py-2 glass-morphism rounded-xl hover:scale-105 transition-transform flex items-center gap-2"
>
<RotateCcw className="w-4 h-4" />
</button>
</div>
</div>
</motion.div>
{/* Section Tabs */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2 }}
className="mb-8"
>
<div className="flex items-center gap-2 overflow-x-auto scrollbar-thin pb-2">
{sections.map((section, index) => {
const Icon = section.icon;
return (
<button
key={section.id}
onClick={() => setCurrentSection(index)}
className={`
flex items-center gap-2 px-4 py-2 rounded-xl transition-all whitespace-nowrap
${currentSection === index
? 'bg-gradient-to-r from-blue-500 to-purple-500 text-white shadow-lg'
: 'glass-morphism hover:scale-105'
}
`}
>
<Icon className="w-4 h-4" />
<span className="font-medium">{section.title}</span>
</button>
);
})}
</div>
</motion.div>
{/* Content Area */}
<AnimatePresence mode="wait">
<motion.div
key={currentSection}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3 }}
className="glass-morphism rounded-2xl p-8"
>
{/* Section Header */}
<div className="mb-6">
<h2 className="text-3xl font-bold mb-2">
{currentSectionData.title}
</h2>
<div className="h-1 w-20 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full" />
</div>
{/* Section Content */}
<div className="prose prose-neutral dark:prose-invert max-w-none">
<div className="whitespace-pre-wrap text-neutral-700 dark:text-neutral-300">
{currentSectionData.content}
</div>
</div>
{/* Images */}
{currentSectionData.images && currentSectionData.images.length > 0 && (
<div className="mt-8 grid grid-cols-2 gap-4">
{currentSectionData.images.map((src, index) => (
<motion.div
key={index}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: index * 0.1 }}
className="rounded-xl overflow-hidden shadow-lg"
>
<img
src={src}
alt={`Section image ${index + 1}`}
className="w-full h-full object-cover"
/>
</motion.div>
))}
</div>
)}
</motion.div>
</AnimatePresence>
{/* Navigation */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4 }}
className="mt-8 flex items-center justify-between"
>
<button
onClick={handlePrevious}
disabled={currentSection === 0}
className={`
px-4 py-2 rounded-xl flex items-center gap-2 transition-all
${currentSection === 0
? 'opacity-50 cursor-not-allowed glass-morphism'
: 'glass-morphism hover:scale-105'
}
`}
>
<ChevronLeft className="w-4 h-4" />
</button>
{/* Page Indicators */}
<div className="flex items-center gap-2">
{sections.map((_, index) => (
<div
key={index}
className={`
w-2 h-2 rounded-full transition-all
${currentSection === index
? 'w-8 bg-gradient-to-r from-blue-500 to-purple-500'
: 'bg-neutral-400'
}
`}
/>
))}
</div>
<button
onClick={handleNext}
disabled={currentSection === sections.length - 1}
className={`
px-4 py-2 rounded-xl flex items-center gap-2 transition-all
${currentSection === sections.length - 1
? 'opacity-50 cursor-not-allowed glass-morphism'
: 'glass-morphism hover:scale-105'
}
`}
>
<ChevronRight className="w-4 h-4" />
</button>
</motion.div>
</div>
</div>
);
};
export default ResultPage;

View File

@@ -0,0 +1,169 @@
import React, { useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useDemoStore } from '../store/demoStore';
import AgentCard from '../components/AgentCard';
import WorkflowVisualization from '../components/WorkflowVisualization';
import ContentGenerator from '../components/ContentGenerator';
import ProgressBar from '../components/ProgressBar';
import { Play, Pause, RotateCcw } from 'lucide-react';
const WorkflowPage: React.FC = () => {
const {
status,
agents,
currentAgent,
progress,
startDemo,
pauseDemo,
resumeDemo,
reset,
} = useDemoStore();
const [showContent, setShowContent] = useState(false);
useEffect(() => {
// 自动开始演示
if (status === 'idle') {
setTimeout(() => {
startDemo();
}, 500);
}
}, [status, startDemo]);
const handleTogglePlay = () => {
if (status === 'running') {
pauseDemo();
} else if (status === 'paused') {
resumeDemo();
}
};
return (
<div className="min-h-screen p-6">
<div className="max-w-7xl mx-auto">
{/* Header */}
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
className="mb-8"
>
<div className="flex items-center justify-between mb-4">
<h1 className="text-3xl font-bold text-gradient">
AI Agent
</h1>
{/* Control Buttons */}
<div className="flex items-center gap-4">
<button
onClick={handleTogglePlay}
className="p-3 rounded-xl glass-morphism hover:scale-105 transition-transform"
>
{status === 'running' ? (
<Pause className="w-5 h-5" />
) : (
<Play className="w-5 h-5" />
)}
</button>
<button
onClick={reset}
className="p-3 rounded-xl glass-morphism hover:scale-105 transition-transform"
>
<RotateCcw className="w-5 h-5" />
</button>
</div>
</div>
{/* Progress Bar */}
<ProgressBar progress={progress} />
</motion.div>
{/* Main Content Area */}
<div className="grid grid-cols-12 gap-6">
{/* Left: Workflow Visualization */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.2 }}
className="col-span-5"
>
<div className="glass-morphism rounded-2xl p-6 h-full">
<h2 className="text-xl font-semibold mb-4"></h2>
<WorkflowVisualization agents={agents} currentAgent={currentAgent} />
</div>
</motion.div>
{/* Right: Content Area */}
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.3 }}
className="col-span-7 space-y-6"
>
{/* Active Agent Display */}
<div className="glass-morphism rounded-2xl p-6">
<h2 className="text-xl font-semibold mb-4"></h2>
<AnimatePresence mode="wait">
{currentAgent && (
<motion.div
key={currentAgent}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
>
<AgentCard
agent={agents.find(a => a.id === currentAgent)!}
isActive={true}
/>
</motion.div>
)}
</AnimatePresence>
</div>
{/* Content Generation Display */}
{showContent && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="glass-morphism rounded-2xl p-6"
>
<h2 className="text-xl font-semibold mb-4"></h2>
<ContentGenerator />
</motion.div>
)}
</motion.div>
</div>
{/* Bottom: Agent List */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4 }}
className="mt-8"
>
<div className="glass-morphism rounded-2xl p-6">
<h2 className="text-xl font-semibold mb-4">Agent </h2>
<div className="grid grid-cols-4 gap-4">
{agents.map((agent) => (
<motion.div
key={agent.id}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: agents.indexOf(agent) * 0.1 }}
>
<AgentCard
agent={agent}
isActive={currentAgent === agent.id}
compact={true}
/>
</motion.div>
))}
</div>
</div>
</motion.div>
</div>
</div>
);
};
export default WorkflowPage;

View File

@@ -0,0 +1,165 @@
import { create } from 'zustand';
export interface Agent {
id: string;
name: string;
icon: string;
model: string;
role: string;
status: 'waiting' | 'thinking' | 'generating' | 'done';
input?: string;
output?: string;
prompt?: string;
}
export interface DemoState {
// 演示状态
status: 'idle' | 'running' | 'paused' | 'completed';
currentPhase: number;
currentAgent: string | null;
progress: number;
// Agent配置
agents: Agent[];
// 生成的内容
generatedContent: {
[key: string]: {
title: string;
content: string;
images: string[];
isComplete: boolean;
};
};
// 控制选项
controls: {
speed: number; // 1-5倍速
autoAdvance: boolean;
showDetails: boolean;
};
// Actions
startDemo: () => void;
pauseDemo: () => void;
resumeDemo: () => void;
setCurrentAgent: (agentId: string) => void;
updateAgentStatus: (agentId: string, status: Agent['status']) => void;
updateAgentOutput: (agentId: string, output: string) => void;
addGeneratedContent: (section: string, content: any) => void;
setProgress: (progress: number) => void;
reset: () => void;
}
const initialAgents: Agent[] = [
{
id: 'retrieval',
name: '信息检索专家',
icon: '🔍',
model: 'DeepSeek Chat Model5',
role: '市场调研、数据收集、竞品分析',
status: 'waiting',
},
{
id: 'design',
name: '设计专家',
icon: '🎨',
model: 'Google Gemini Chat Model2',
role: '视觉设计、空间布局、品牌形象',
status: 'waiting',
},
{
id: 'budget',
name: '财务预算专家',
icon: '💰',
model: 'DeepSeek Chat Model2',
role: '成本核算、预算规划、ROI分析',
status: 'waiting',
},
{
id: 'format',
name: '格式编辑专家',
icon: '📝',
model: 'DeepSeek Chat Model4',
role: '文档格式化、内容结构优化',
status: 'waiting',
},
{
id: 'execution',
name: '活动执行专家',
icon: '⚡',
model: 'DeepSeek Chat Model1',
role: '执行计划、时间线管理、任务分配',
status: 'waiting',
},
{
id: 'marketing',
name: '营销宣传专家',
icon: '📢',
model: 'DeepSeek Chat Model3',
role: '推广策略、媒体规划、品牌传播',
status: 'waiting',
},
{
id: 'coordinator',
name: '会展策划专家',
icon: '🎯',
model: 'Chat Models + Memories',
role: '中央协调、方案整合、决策支持',
status: 'waiting',
},
];
export const useDemoStore = create<DemoState>((set) => ({
status: 'idle',
currentPhase: 0,
currentAgent: null,
progress: 0,
agents: initialAgents,
generatedContent: {},
controls: {
speed: 1,
autoAdvance: true,
showDetails: true,
},
startDemo: () => set({ status: 'running', progress: 0 }),
pauseDemo: () => set({ status: 'paused' }),
resumeDemo: () => set({ status: 'running' }),
setCurrentAgent: (agentId) => set({ currentAgent: agentId }),
updateAgentStatus: (agentId, status) =>
set((state) => ({
agents: state.agents.map((agent) =>
agent.id === agentId ? { ...agent, status } : agent
),
})),
updateAgentOutput: (agentId, output) =>
set((state) => ({
agents: state.agents.map((agent) =>
agent.id === agentId ? { ...agent, output } : agent
),
})),
addGeneratedContent: (section, content) =>
set((state) => ({
generatedContent: {
...state.generatedContent,
[section]: content,
},
})),
setProgress: (progress) => set({ progress }),
reset: () =>
set({
status: 'idle',
currentPhase: 0,
currentAgent: null,
progress: 0,
agents: initialAgents,
generatedContent: {},
}),
}));