Files
Agent-n8n/web_frontend/food-order-demo/src/pages/WorkflowPageV2.tsx
Yep_Q c3eb7125cc feat: 创建食品订单班演示系统基础框架
详细说明:
- 基于文旅订单班框架复制创建food-order-demo项目
- 修改端口配置为4174避免冲突
- 更新LandingPage为青莳轻食主题(绿色健康风格)
- 重新定义7个食品行业专业Agent:
  * 市场研究专家:轻食市场分析、客群画像
  * 营养配方师:营养成分配比、低卡高蛋白设计
  * 供应链管理专家:有机食材供应、溯源体系
  * 品牌策划师:品牌定位、店铺空间布局
  * 财务分析师:投资预算、ROI分析
  * 运营管理专家:运营流程、品控标准
  * 食品创业导师:中央协调、方案整合
- 创建专用启动脚本start.sh
- 验证系统可正常运行在端口4174
- 实现代码复用率90%,符合预期目标

影响文件: web_frontend/food-order-demo/
技术栈: React 18 + TypeScript + Tailwind CSS + Zustand
2025-09-28 10:32:44 +08:00

546 lines
19 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.

import { useState, useEffect, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useDemoStore } from '@/store/demoStore';
import { Play, Pause, RotateCcw, Maximize2, Terminal } from 'lucide-react';
// Terminal line type
interface TerminalLine {
id: string;
timestamp: string;
type: 'info' | 'success' | 'warning' | 'error' | 'system' | 'output';
agent?: string;
content: string;
typing?: boolean;
}
const WorkflowPageV2 = () => {
const { agents, startDemo, pauseDemo, reset, status } = useDemoStore();
const [terminalLines, setTerminalLines] = useState<TerminalLine[]>([]);
const [currentAgentIndex, setCurrentAgentIndex] = useState(0);
const [elapsedTime, setElapsedTime] = useState(0);
const terminalRef = useRef<HTMLDivElement>(null);
const intervalRef = useRef<number | null>(null);
// Agent执行序列 - 更丰富的输出
const agentSequence = [
{
agent: agents[0], // 信息检索
duration: 12000,
output: [
'>>> 初始化信息检索系统...',
'[INFO] 连接数据库: mongodb://data-server:27017',
'[INFO] 加载索引: automotive_industry_2024.idx',
'正在执行查询...',
'SELECT * FROM exhibitions WHERE region="长三角" AND industry="新能源汽车"',
'> 查询耗时: 342ms',
'> 返回记录: 2,847 条',
'',
'=== 数据分析开始 ===',
'[1/5] 产业规模分析...',
' └─ 2023年产值: 3.2万亿元 (↑32% YoY)',
' └─ 企业数量: 5,832家',
' └─ 从业人员: 186万人',
'[2/5] 主要企业扫描...',
' └─ 特斯拉(上海): 年产能75万辆',
' └─ 蔚来汽车: 累计交付38万辆',
' └─ 理想汽车: 月销量突破5万',
' └─ 小鹏汽车: 智能化领先',
'[3/5] 展会历史数据...',
' └─ 2023年展会: 126场',
' └─ 平均规模: 3.2万平方米',
' └─ 平均观众: 4.5万人次',
'[4/5] 竞品分析...',
' └─ 北京车展: 影响力指数92',
' └─ 广州车展: 影响力指数88',
' └─ 成都车展: 影响力指数76',
'[5/5] 趋势预测...',
' └─ 2024年增长预期: 28%',
' └─ 热点技术: 固态电池、自动驾驶L4',
'',
'✓ 市场调研报告已生成 (15.3MB)',
'✓ 数据已同步至共享空间'
]
},
{
agent: agents[1], // 设计专家
duration: 12000,
output: [
'>>> 启动设计引擎 v3.2.1...',
'[LOAD] 加载品牌识别系统...',
'[LOAD] 加载空间规划算法...',
'',
'=== 视觉识别设计 ===',
'Analyzing brand requirements...',
'> 主题关键词: [科技, 创新, 绿色, 智能]',
'> 色彩心理学分析中...',
' Primary: #0EA5E9 (Trust & Technology)',
' Secondary: #10B981 (Growth & Sustainability)',
' Accent: #F59E0B (Energy & Innovation)',
'',
'=== 空间布局优化 ===',
'Running layout optimization algorithm...',
'Iteration 1/100: Score=72.3',
'Iteration 50/100: Score=86.7',
'Iteration 100/100: Score=94.2',
'',
'最优布局方案:',
'┌─────────────────────────┐',
'│ A区: 整车展示 (15,000㎡) │',
'│ ├─ 豪华品牌: 5,000㎡ │',
'│ ├─ 新势力: 6,000㎡ │',
'│ └─ 传统转型: 4,000㎡ │',
'├─────────────────────────┤',
'│ B区: 核心零部件 (10,000㎡)│',
'│ ├─ 动力电池: 4,000㎡ │',
'│ ├─ 电机电控: 3,000㎡ │',
'│ └─ 智能座舱: 3,000㎡ │',
'└─────────────────────────┘',
'',
'Generating 3D preview...',
'> Rendering: ████████████ 100%',
'',
'✓ 设计方案已完成',
'✓ 导出文件: design_plan_v1.pdf (48.2MB)'
]
},
{
agent: agents[2], // 财务预算
duration: 10000,
output: [
'>>> 财务分析系统 v2.0',
'Loading financial models...',
'',
'=== 成本核算 ===',
'Calculating venue costs...',
'> 场地租赁: ¥3,000,000',
' └─ 展馆: ¥2,500,000',
' └─ 会议室: ¥500,000',
'Calculating construction costs...',
'> 展台搭建: ¥4,500,000',
' └─ 特装展位: ¥3,000,000',
' └─ 标准展位: ¥1,500,000',
'Calculating operational costs...',
'> 运营费用: ¥2,000,000',
' └─ 人员: ¥800,000',
' └─ 营销: ¥1,200,000',
'',
'=== 收入预测 ===',
'Revenue projection model running...',
'> 展位销售: ¥8,500,000',
' └─ 特装: 100个×¥100,000',
' └─ 标准: 500个×¥15,000',
'> 赞助收入: ¥3,000,000',
'> 门票收入: ¥1,500,000',
'',
'Financial metrics:',
'> Total Cost: ¥10,000,000',
'> Total Revenue: ¥13,000,000',
'> Net Profit: ¥3,000,000',
'> ROI: 30%',
'> Break-even: Day 2',
'',
'✓ 财务模型构建完成',
'✓ 风险评估: 低风险'
]
},
{
agent: agents[3], // 格式编辑
duration: 8000,
output: [
'>>> 文档处理引擎启动...',
'[FORMAT] 检查文档结构...',
'> 章节数量: 6',
'> 总字数: 12,847',
'> 图表数量: 24',
'',
'=== 格式优化 ===',
'Applying style guide...',
'> 标题层级: H1-H4',
'> 字体规范: 思源黑体',
'> 行间距: 1.5倍',
'',
'Checking consistency...',
'> 术语统一: 187处修正',
'> 数字格式: 92处标准化',
'> 标点符号: 45处优化',
'',
'Generating TOC...',
'> 一级标题: 6个',
'> 二级标题: 18个',
'> 三级标题: 42个',
'',
'✓ 文档格式优化完成'
]
},
{
agent: agents[4], // 活动执行
duration: 10000,
output: [
'>>> 执行计划生成器 v1.5',
'',
'=== 时间轴规划 ===',
'D-180: 项目启动',
'D-150: 招展启动',
'D-120: 媒体发布',
'D-90: 展位确认',
'D-60: 搭建方案确定',
'D-30: 现场勘察',
'D-7: 搭建开始',
'D-1: 最终检查',
'D+0: 展会开幕',
'',
'=== 人员部署 ===',
'Total staff: 126人',
'> 管理团队: 6人',
'> 展务团队: 30人',
'> 接待团队: 40人',
'> 技术团队: 20人',
'> 安保团队: 30人',
'',
'=== 物料清单 ===',
'Generating BOM...',
'> 展台物料: 2,847项',
'> 宣传物料: 523项',
'> 办公用品: 189项',
'',
'✓ 执行方案已生成'
]
},
{
agent: agents[5], // 营销宣传
duration: 10000,
output: [
'>>> 营销策略引擎 v4.0',
'',
'=== 渠道分析 ===',
'Analyzing marketing channels...',
'> 社交媒体覆盖: 500万+',
'> 行业媒体: 126家',
'> KOL资源: 89位',
'',
'=== 推广计划 ===',
'Phase 1: 预热期 (D-90 to D-60)',
'> 软文发布: 30篇',
'> 视频内容: 15条',
'> 直播预告: 5场',
'',
'Phase 2: 高峰期 (D-60 to D-30)',
'> 广告投放: ¥800,000',
'> 媒体专访: 10次',
'> 路演活动: 8场',
'',
'Phase 3: 冲刺期 (D-30 to D-0)',
'> 倒计时海报: 每日更新',
'> 展商专访: 20家',
'> 观众互动: 10个活动',
'',
'Expected reach:',
'> 曝光量: 2000万+',
'> 互动量: 50万+',
'> 转化率: 2.5%',
'',
'✓ 营销方案制定完成'
]
},
{
agent: agents[6], // 中央协调
duration: 8000,
output: [
'>>> 协调控制中心 v3.0',
'',
'=== 数据汇总 ===',
'Collecting agent outputs...',
'> 市场分析: ✓ Complete',
'> 设计方案: ✓ Complete',
'> 财务预算: ✓ Complete',
'> 文档格式: ✓ Complete',
'> 执行计划: ✓ Complete',
'> 营销策略: ✓ Complete',
'',
'=== 协同优化 ===',
'Cross-validating data...',
'> 预算-设计匹配度: 96%',
'> 营销-市场匹配度: 92%',
'> 执行可行性评分: 94%',
'',
'Resolving conflicts...',
'> 时间冲突: 0',
'> 资源冲突: 0',
'> 逻辑冲突: 0',
'',
'=== 最终输出 ===',
'Generating final document...',
'> 合并章节: 6个',
'> 总页数: 68页',
'> 附件数: 12个',
'',
'✓ 策划方案生成完成',
'✓ 所有系统已同步'
]
}
];
// 添加终端行
const addTerminalLine = (line: Omit<TerminalLine, 'id' | 'timestamp'>) => {
const now = new Date();
const timestamp = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
setTerminalLines(prev => [...prev, {
...line,
id: Math.random().toString(36).substr(2, 9),
timestamp
}]);
// 自动滚动到底部
setTimeout(() => {
if (terminalRef.current) {
terminalRef.current.scrollTop = terminalRef.current.scrollHeight;
}
}, 50);
};
// 模拟Agent执行
const simulateAgentExecution = async () => {
if (currentAgentIndex >= agentSequence.length) {
addTerminalLine({
type: 'system',
content: '========== 所有Agent执行完成 =========='
});
return;
}
const current = agentSequence[currentAgentIndex];
const agent = current.agent;
// 开始执行
addTerminalLine({
type: 'system',
content: `[Agent启动] ${agent.name} (${agent.model})`
});
// 逐行输出
for (let i = 0; i < current.output.length; i++) {
await new Promise(resolve => setTimeout(resolve, current.duration / current.output.length));
if (status !== 'running') return;
addTerminalLine({
type: current.output[i].startsWith('✓') ? 'success' :
current.output[i].startsWith('>') ? 'output' : 'info',
agent: agent.name,
content: current.output[i]
});
}
// 执行下一个Agent
setCurrentAgentIndex(prev => prev + 1);
};
// 开始演示
useEffect(() => {
if (status === 'running' && currentAgentIndex < agentSequence.length) {
simulateAgentExecution();
}
}, [status, currentAgentIndex]);
// 计时器
useEffect(() => {
if (status === 'running') {
intervalRef.current = setInterval(() => {
setElapsedTime(prev => prev + 100);
}, 100);
} else {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
}
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, [status]);
// 重置
const handleReset = () => {
reset();
setTerminalLines([]);
setCurrentAgentIndex(0);
setElapsedTime(0);
};
// 格式化时间
const formatTime = (ms: number) => {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
};
return (
<div className="min-h-screen bg-gray-50 flex flex-col">
{/* 顶部控制栏 */}
<div className="bg-white border-b border-gray-200 px-6 py-3 flex items-center justify-between">
<div className="flex items-center gap-4">
<h1 className="text-lg font-semibold text-gray-900">AI会展策划系统 - Agent协同演示</h1>
<div className="flex items-center gap-2">
<button
onClick={status === 'idle' ? startDemo : pauseDemo}
className="px-3 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors flex items-center gap-1.5"
>
{status === 'idle' || status === 'paused' ? (
<>
<Play className="w-4 h-4" />
<span></span>
</>
) : (
<>
<Pause className="w-4 h-4" />
<span></span>
</>
)}
</button>
<button
onClick={handleReset}
className="px-3 py-1.5 bg-gray-600 text-white rounded-md hover:bg-gray-700 transition-colors flex items-center gap-1.5"
>
<RotateCcw className="w-4 h-4" />
<span></span>
</button>
</div>
</div>
<div className="text-sm text-gray-600">
: {formatTime(elapsedTime)} / 03:00
</div>
</div>
{/* 主内容区 */}
<div className="flex-1 flex">
{/* 左侧n8n工作流 */}
<div className="w-1/2 border-r border-gray-200 bg-white">
<div className="h-full flex flex-col">
<div className="px-4 py-2 border-b border-gray-200 flex items-center justify-between bg-gray-50">
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-green-500"></div>
<span className="text-sm font-medium text-gray-700"></span>
</div>
<button className="p-1 hover:bg-gray-200 rounded transition-colors">
<Maximize2 className="w-4 h-4 text-gray-600" />
</button>
</div>
<div className="flex-1 relative">
<iframe
src="http://localhost:5678/workflow/XbfF8iRI4a69hmYS"
className="w-full h-full border-0"
title="n8n Workflow"
/>
</div>
</div>
</div>
{/* 右侧:终端执行区 */}
<div className="w-1/2 bg-gray-900">
<div className="h-full flex flex-col">
<div className="px-4 py-2 bg-gray-800 flex items-center justify-between">
<div className="flex items-center gap-2">
<Terminal className="w-4 h-4 text-green-400" />
<span className="text-sm font-mono text-green-400">Agent Execution Terminal</span>
</div>
<div className="flex gap-1">
<div className="w-3 h-3 rounded-full bg-red-500"></div>
<div className="w-3 h-3 rounded-full bg-yellow-500"></div>
<div className="w-3 h-3 rounded-full bg-green-500"></div>
</div>
</div>
<div
ref={terminalRef}
className="flex-1 overflow-y-auto p-4 font-mono text-sm"
style={{ backgroundColor: '#0a0a0a' }}
>
<AnimatePresence>
{terminalLines.map((line) => (
<motion.div
key={line.id}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.2 }}
className="mb-1"
>
<span className="text-gray-500">[{line.timestamp}]</span>
{line.agent && (
<span className="text-cyan-400 ml-2">{line.agent}:</span>
)}
<span className={`ml-2 ${
line.type === 'success' ? 'text-green-400' :
line.type === 'error' ? 'text-red-400' :
line.type === 'warning' ? 'text-yellow-400' :
line.type === 'system' ? 'text-purple-400' :
line.type === 'output' ? 'text-blue-400' :
'text-gray-300'
}`}>
{line.content}
</span>
</motion.div>
))}
</AnimatePresence>
{/* 光标 */}
{status === 'running' && (
<motion.span
animate={{ opacity: [1, 0] }}
transition={{ duration: 0.5, repeat: Infinity }}
className="inline-block w-2 h-4 bg-green-400"
/>
)}
</div>
{/* Agent状态栏 */}
<div className="px-4 py-3 bg-gray-800 border-t border-gray-700">
<div className="grid grid-cols-7 gap-2">
{agents.map((agent, index) => (
<div
key={agent.id}
className={`flex flex-col items-center gap-1 px-2 py-2 rounded-lg transition-all ${
index < currentAgentIndex ? 'bg-green-900/50 border border-green-700' :
index === currentAgentIndex ? 'bg-blue-900 border border-blue-500 animate-pulse' :
'bg-gray-800 border border-gray-700'
}`}
>
<span className={`text-2xl ${
index < currentAgentIndex ? 'animate-none' :
index === currentAgentIndex ? 'animate-bounce' :
''
}`}>{agent.icon}</span>
<span className={`text-xs text-center ${
index < currentAgentIndex ? 'text-green-400' :
index === currentAgentIndex ? 'text-blue-400' :
'text-gray-500'
}`}>{agent.name}</span>
<div className={`w-full h-1 rounded-full mt-1 ${
index < currentAgentIndex ? 'bg-green-500' :
index === currentAgentIndex ? 'bg-blue-500' :
'bg-gray-700'
}`}>
{index === currentAgentIndex && (
<div className="h-full bg-blue-400 rounded-full animate-pulse"
style={{width: '50%'}}></div>
)}
</div>
</div>
))}
</div>
<div className="mt-2 text-center text-xs text-gray-400">
: {Math.round((currentAgentIndex / agentSequence.length) * 100)}% |
: {currentAgentIndex < agentSequence.length ? agentSequence[currentAgentIndex]?.agent.name : '已完成'}
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default WorkflowPageV2;