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,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;