feat: 完善AI会展策划多Agent协同演示系统

功能实现:
-  添加需求输入弹窗,支持新能源汽车展模板快速填充
-  实现7个AI Agent完整执行序列,包含详细的终端输出
-  添加图片生成过程展示,支持实际图片预览
-  实现结果查看弹窗,展示生成统计和内容章节
-  添加图片骨架屏loading动画,优化加载体验

技术优化:
- 🎨 实现真实的进度条卡顿效果,模拟实际加载过程
- 🎨 优化终端滚动和内容显示,支持多种输出类型
- 🎨 添加Agent头像显示和执行状态指示
- 🎨 实现图片延迟加载,确保执行流程连贯
- 🎨 简化骨架屏动画,提升真实感

文件修改:
- web_frontend/exhibition-demo/src/pages/WorkflowPageV4.tsx
- web_frontend/exhibition-demo/src/components/RequirementModal.tsx
- web_frontend/exhibition-demo/src/components/ResultModal.tsx
- web_frontend/exhibition-demo/public/data/ (添加展会图片资源)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Yep_Q
2025-09-08 12:23:45 +08:00
parent d6f48d6d14
commit c0644d4bea
43 changed files with 2366 additions and 27 deletions

View File

@@ -45,6 +45,7 @@ const WorkflowPageV4 = () => {
const [showRequirementModal, setShowRequirementModal] = useState(false);
const [showResultModal, setShowResultModal] = useState(false);
const [userRequirement, setUserRequirement] = useState('');
const [imageLoadingStates, setImageLoadingStates] = useState<{ [key: string]: boolean }>({});
const terminalRef = useRef<HTMLDivElement>(null);
const intervalRef = useRef<number | null>(null);
const progressLineIdRef = useRef<string | null>(null);
@@ -170,25 +171,27 @@ const WorkflowPageV4 = () => {
{ type: 'info', content: '🎨 Generating test drive area visualization...' },
{ type: 'output', content: 'Prompt: "EV test drive track, outdoor exhibition area"' },
{ type: 'progress', content: 'Generating: 试驾小景.jpg', target: 100, stutters: [45, 78] },
{ type: 'output', content: '' },
{ type: 'output', content: '╔═══════════════════════════════════════════╗' },
{ type: 'output', content: '║ IMAGE PREVIEW: 试驾体验区 ║' },
{ type: 'output', content: '╠═══════════════════════════════════════════╣' },
{ type: 'output', content: '║ ╭──────────────────────────╮ ║' },
{ type: 'output', content: '║ │ ═══════════════════════ │ ║' },
{ type: 'output', content: '║ │ ║ TEST DRIVE TRACK ║ │ ║' },
{ type: 'output', content: '║ │ ║ ╱╲ 🚗 ➜➜➜ ║ │ ║' },
{ type: 'output', content: '║ │ ║ ╲ ╱──────╲ ║ │ ║' },
{ type: 'output', content: '║ │ ║ ╲ ║ │ ║' },
{ type: 'output', content: '║ │ ═══════════════════════ │ ║' },
{ type: 'output', content: '║ ╰──────────────────────────╯ ║' },
{ type: 'output', content: '╚═══════════════════════════════════════════╝' },
{ type: 'image',
content: '📷 IMAGE PREVIEW: 试驾体验区',
imageSrc: '/data/会展策划/image/2.试驾小景.jpg',
imageAlt: '试驾体验区实景'
},
{ type: 'file', content: '✓ Generated: 试驾小景.jpg (2.8MB)' },
{ type: 'info', content: '' },
{ type: 'info', content: '🎨 Generating brand showcase images...' },
{ type: 'progress', content: 'Generating: 小米汽车.jpg', target: 100, stutters: [34, 67] },
{ type: 'image',
content: '📷 IMAGE PREVIEW: 小米汽车展台',
imageSrc: '/data/会展策划/image/3.小米汽车.jpg',
imageAlt: '小米汽车展示'
},
{ type: 'file', content: '✓ Generated: 小米汽车.jpg (1.9MB)' },
{ type: 'progress', content: 'Generating: 博览会.jpg', target: 100, stutters: [56, 89] },
{ type: 'image',
content: '📷 IMAGE PREVIEW: 博览会全景',
imageSrc: '/data/会展策划/image/博览会.jpg',
imageAlt: '博览会全景图'
},
{ type: 'file', content: '✓ Generated: 博览会.jpg (3.5MB)' },
{ type: 'info', content: '' },
{ type: 'info', content: 'Creating exhibition hall 3D layout...' },
@@ -492,14 +495,24 @@ const WorkflowPageV4 = () => {
const now = new Date();
const timestamp = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}.${now.getMilliseconds().toString().padStart(3, '0')}`;
const lineId = Math.random().toString(36).substr(2, 9);
const newLine = {
...line,
id: Math.random().toString(36).substr(2, 9),
id: lineId,
timestamp
};
setTerminalLines(prev => [...prev, newLine]);
// 如果是图片类型,设置加载状态
if (line.type === 'image' && line.imageSrc) {
setImageLoadingStates(prev => ({ ...prev, [lineId]: true }));
// 确保loading动画显示足够时间
setTimeout(() => {
setImageLoadingStates(prev => ({ ...prev, [lineId]: false }));
}, getRandomDelay(2000, 3500));
}
// 自动滚动到底部
setTimeout(() => {
if (terminalRef.current) {
@@ -617,10 +630,16 @@ const WorkflowPageV4 = () => {
content: '=' .repeat(60)
});
// 更新store状态为完成
const store = useDemoStore.getState();
store.setProgress(100);
// 显示结果弹窗
console.log('All agents completed, showing result modal...');
setTimeout(() => {
console.log('Setting showResultModal to true');
setShowResultModal(true);
}, 1000);
}, 2000);
return;
}
@@ -641,13 +660,18 @@ const WorkflowPageV4 = () => {
});
// 根据类型设置延迟
const delay =
let delay =
output.type === 'system' ? getRandomDelay(100, 300) :
output.type === 'install' ? getRandomDelay(200, 400) :
output.type === 'file' ? getRandomDelay(100, 200) :
output.type === 'info' && output.content === '' ? 50 :
getRandomDelay(30, 100);
// 如果是图片类型,等待图片加载完成
if (output.type === 'image') {
delay = getRandomDelay(2500, 3500); // 等待图片加载动画完成
}
await new Promise(resolve => setTimeout(resolve, delay));
}
}
@@ -667,7 +691,7 @@ const WorkflowPageV4 = () => {
// 监听Agent变化
useEffect(() => {
if (status === 'running' && currentAgentIndex >= 0 && currentAgentIndex < agentSequence.length) {
if (status === 'running' && currentAgentIndex >= 0) {
executeAgent(currentAgentIndex);
}
}, [currentAgentIndex]);
@@ -842,6 +866,12 @@ const WorkflowPageV4 = () => {
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #555;
}
.animation-delay-200 {
animation-delay: 200ms;
}
.animation-delay-400 {
animation-delay: 400ms;
}
`}</style>
<AnimatePresence>
@@ -863,16 +893,39 @@ const WorkflowPageV4 = () => {
{line.content}
</span>
{line.imageSrc && (
<div className="mt-2 mb-2 inline-block">
<img
src={line.imageSrc}
alt={line.imageAlt || 'Generated image'}
className="max-w-md rounded-lg border-2 border-gray-700 shadow-xl"
style={{ maxHeight: '300px' }}
onError={(e) => {
e.currentTarget.style.display = 'none';
}}
/>
<div className="mt-3 mb-3">
{imageLoadingStates[line.id] ? (
// 简单的骨架屏Loading效果
<div className="relative w-96 h-64 bg-gray-800 rounded-lg border border-gray-700 overflow-hidden">
{/* 骨架屏脉冲动画 */}
<div className="absolute inset-0">
<div className="h-full w-full bg-gradient-to-r from-gray-800 via-gray-700 to-gray-800 animate-pulse" />
</div>
{/* 简单的loading指示器 */}
<div className="absolute inset-0 flex items-center justify-center">
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-gray-500 rounded-full animate-pulse" />
<div className="w-2 h-2 bg-gray-500 rounded-full animate-pulse animation-delay-200" />
<div className="w-2 h-2 bg-gray-500 rounded-full animate-pulse animation-delay-400" />
</div>
</div>
</div>
) : (
// 实际图片(淡入效果)
<motion.img
src={line.imageSrc}
alt={line.imageAlt || 'Generated image'}
className="max-w-md rounded-lg border-2 border-gray-700 shadow-xl"
style={{ maxHeight: '300px' }}
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5 }}
onError={(e) => {
e.currentTarget.style.display = 'none';
}}
/>
)}
</div>
)}
</div>