"use client";
|
|
import { useState, useEffect } from 'react';
|
import { motion, AnimatePresence } from 'framer-motion';
|
import { useSearchParams } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
import DataPreviewDialog from '@/components/DataPreviewDialog';
|
|
// 导入场景数据
|
const defaultServices = [
|
{
|
title: '库存管理知识库问答',
|
description: '库存知识,提供专业的供应链库存知识问答',
|
imageUrl: '/images/know.jpg',
|
chatbotId: 'pDDfkU9HyBl2gzXx',
|
background: '高效的库存管理需要丰富的专业知识和经验。AI知识库整合了大量供应链和库存管理的专业知识,可以快速解答各类库存管理问题。',
|
instructions: '您可以询问任何与库存管理相关的问题,包括库存策略、成本控制、周转率优化等,AI助手将为您提供专业的解答和建议。'
|
},
|
{
|
title: '扫地机器人销售推荐小助手',
|
description: '智能化产品推荐提升导购效率',
|
imageUrl: '/images/robot.jpg',
|
chatbotId: 'sUAviPXvcEIw3oQC',
|
background: '面对不同客户的个性化需求,如何准确推荐合适的产品是提升销售效率的关键。AI助手基于深度学习算法,能够精准分析客户需求,提供个性化的产品推荐。',
|
instructions: '您可以描述客户的具体需求和偏好,AI助手将根据这些信息,为您推荐最适合的产品,并提供专业的产品介绍和销售建议。'
|
},
|
{
|
title: '补料',
|
description: '智能动态分析产线,工位的缺料情况,降低停线风险',
|
imageUrl: '/images/kanban.jpg',
|
chatbotId: 'JELkWpPLHQfRNhEH',
|
background: '在生产线运行过程中,及时发现并解决缺料问题是保证生产效率的关键。AI系统通过实时监控和分析,帮助您提前预警可能的缺料风险,优化补料计划。',
|
instructions: '您可以描述具体的生产线情况和缺料问题,AI助手将基于历史数据和实时状态,为您提供最优的补料方案和建议。',
|
dataDescription: '本场景通过整合MES、SCADA、WMS等系统的实时数据,结合AI算法进行智能分析和预警。以下为演示数据,实际使用时将自动对接相关业务系统。',
|
exampleData: `
|
### 生产产品P时,详细工位的物料需求信息
|
|
| 工位 | 原材料需求 | 原材料消耗速率 | 生产的产品 | 产线名 |
|
| :--: | :--: | :--: | :--: | :--: |
|
| 1 | A1 | 2/小时 | P | 产线1 |
|
| 2 | A2 | 1/小时 | P | 产线1 |
|
| 3 | A3 | 2/小时 | P | 产线1 |
|
| 4 | A4 | 4/小时 | P | 产线1 |
|
| 5 | A5 | 5/小时 | P | 产线1 |
|
| 6 | - | - | P | 产线1 |
|
| 7 | A7 | 3/小时 | P | 产线1 |
|
| 8 | - | - | P | 产线1 |
|
| 9 | A9 | 3/小时 | P | 产线1 |
|
|
### 可以通过集成线边库存获取(IOT,传感器,ERP系统等),人工目视发现缺料或者物料较少时,人为触发(飞书,微信等IM工具上输入/语音,XX工位,XX物料缺料)
|
### 当前产线现场库存量
|
|
| 原材料 | 数量 | 产线名 |
|
| :--: | :--: | :--: |
|
| A1 | 10 | 产线1 |
|
| A2 | 10 | 产线1 |
|
| A3 | 10 | 产线1 |
|
| A4 | 10 | 产线1 |
|
| A5 | 10 | 产线1 |
|
| A6 | 10 | 产线1 |
|
| A7 | 10 | 产线1 |
|
| A8 | 10 | 产线1 |
|
| A9 | 10 | 产线1 |
|
|
### 需要通过ERP/WMS获取数据,若无法获取ERP/WMS系统数据,可直接由人工呼叫具体补货数量,生成仓库补货看板
|
### 当前原材料库中,存放库位和库存量
|
|
| 原材料 | 库位 | 数量 |
|
| :--: | :--: | :--: |
|
| A1 | 3-2 | 100 |
|
| A2 | 3-1 | 100 |
|
| A3 | 1-1 | 100 |
|
| A4 | 1-2 | 100 |
|
| A5 | 2-1 | 100 |
|
| A7 | 2-2 | 100 |
|
| A9 | 4-1 | 100 |
|
`
|
},
|
{
|
title: '插单1.0',
|
description: '智能评估需求插单对产能,原材料和交付服务的影响,提升客户满意度',
|
imageUrl: '/images/xuqiu.jpg',
|
chatbotId: 'RhMYLHI1SZNiX4kl',
|
background: '在生产计划执行过程中,常常会遇到紧急订单需要插单的情况。AI系统可以快速评估插单对现有生产计划的影响,并提供最优的插单方案。',
|
instructions: '请提供需要插单的订单信息,包括产品类型、数量和期望交付时间,AI助手将为您分析可行性并给出具体的插单建议。',
|
dataDescription: '本场景通过整合ERP、MES等系统的实时数据,结合AI算法进行智能分析和预警。以下为演示数据,实际使用时将自动对接相关业务系统。',
|
exampleData: `
|
### 基础数据:产品:A,B,C
|
### A, B, C的BOM
|
|
| 产品 | 物料 | 物料单位用量 |
|
| :--: | :--: | :--: |
|
| A | 1001 | 1 |
|
| A | 1002 | 3 |
|
| A | 1003 | 3 |
|
| B | 1001 | 1 |
|
| B | 1002 | 2 |
|
| B | 1004 | 3 |
|
| C | 1005 | 1 |
|
| C | 1002 | 3 |
|
| C | 1003 | 2 |
|
|
### 需求数据
|
|
| 产品 | 需求数量 | 需求日期 | 需求单号 | 客户 | 单价 |
|
| :--: | :--: | :--: | :--: | :--: | :--: |
|
| A | 1000 | 2/2/2025 | A1001 | 中石油 | 100.0 |
|
| A | 200 | 3/2/2025 | A1002 | 中建二局 | 95.0 |
|
| A | 500 | 5/2/2025 | A1003 | 宝武集团 | 90.0 |
|
| B | 2000 | 3/2/2025 | B1001 | 中石油 | 95.0 |
|
| B | 300 | 4/2/2025 | B1002 | 中建二局 | 90.0 |
|
| B | 100 | 7/2/2025 | B1003 | 宝武集团 | 100.0 |
|
| C | 800 | 4/2/2025 | C1001 | 中石油 | 85.0 |
|
| C | 600 | 7/2/2025 | C1002 | 中建二局 | 90.0 |
|
| C | 400 | 8/2/2025 | C1003 | 宝武集团 | 90.0 |
|
|
### 假设当前日期是2月1日,计划开始日期是2月2日,当前有一个插单需求:
|
|
| 产品 | 需求数量 | 需求日期 | 需求单号 | 客户 | 单价 |
|
| :--: | :--: | :--: | :--: | :--: | :--: |
|
| A | 200 | 5/2/2025 | D1001 | 中石油 | 100.0 |
|
|
### 已有采购订单
|
|
| 供应商名称 | 物料 | 采购订单号 | 预计到货日期 | 采购数量 |
|
| :--: | :--: | :--: | :--: | :--: |
|
| S1 | 1001 | PO1 | 2/2/2025 | 2000 |
|
| S2 | 1002 | PO2 | 2/2/2025 | 1000 |
|
| S3 | 1003 | PO3 | 2/2/2025 | 1000 |
|
| S4 | 1004 | PO4 | 2/2/2025 | 800 |
|
| S5 | 1005 | PO5 | 2/2/2025 | 800 |
|
|
### 供应商列表
|
|
| 供应商名称 | 物料 | 联系方式 |
|
| :--: | :--: | :--: |
|
| S1 | 1001 | chenqinghonghs@163.com |
|
| S2 | 1002 | chenqinghonghs@163.com |
|
| S3 | 1003 | chenqinghonghs@163.com |
|
| S4 | 1004 | chenqinghonghs@163.com |
|
| S5 | 1005 | 16673976114@163.com |
|
`
|
},
|
{
|
title: '插单2.0',
|
description: '智能评估需求插单对产能,原材料和交付服务的影响,提升客户满意度',
|
imageUrl: '/images/chadan2.jpg',
|
chatbotId: 'zO9YQDEHdIApG9zC',
|
background: '在生产计划执行过程中,常常会遇到紧急订单需要插单的情况。AI系统可以快速评估插单对现有生产计划的影响,并提供最优的插单方案。',
|
instructions: '请提供需要插单的订单信息,包括产品类型、数量和期望交付时间,AI助手将为您分析可行性并给出具体的插单建议。',
|
dataDescription: '本场景通过整合ERP、MES等系统的实时数据,结合AI算法进行智能分析和预警。以下为演示数据,实际使用时将自动对接相关业务系统。',
|
exampleData: `
|
### 基础数据:产品:A,B,C
|
### A, B, C的BOM
|
|
| 产品 | 物料 | 物料单位用量 |
|
| :--: | :--: | :--: |
|
| A | 1001 | 1 |
|
| A | 1002 | 3 |
|
| A | 1003 | 3 |
|
| B | 1001 | 1 |
|
| B | 1002 | 2 |
|
| B | 1004 | 3 |
|
| C | 1005 | 1 |
|
| C | 1002 | 3 |
|
| C | 1003 | 2 |
|
|
### 需求数据
|
|
| 产品 | 需求数量 | 需求日期 | 需求单号 | 客户 | 单价 |
|
| :--: | :--: | :--: | :--: | :--: | :--: |
|
| A | 1000 | 2/2/2025 | A1001 | 中石油 | 100.0 |
|
| A | 200 | 3/2/2025 | A1002 | 中建二局 | 95.0 |
|
| A | 500 | 5/2/2025 | A1003 | 宝武集团 | 90.0 |
|
| B | 2000 | 3/2/2025 | B1001 | 中石油 | 95.0 |
|
| B | 300 | 4/2/2025 | B1002 | 中建二局 | 90.0 |
|
| B | 100 | 7/2/2025 | B1003 | 宝武集团 | 100.0 |
|
| C | 800 | 4/2/2025 | C1001 | 中石油 | 85.0 |
|
| C | 600 | 7/2/2025 | C1002 | 中建二局 | 90.0 |
|
| C | 400 | 8/2/2025 | C1003 | 宝武集团 | 90.0 |
|
|
### 假设当前日期是2月1日,计划开始日期是2月2日,当前有一个插单需求:
|
|
| 产品 | 需求数量 | 需求日期 | 需求单号 | 客户 | 单价 |
|
| :--: | :--: | :--: | :--: | :--: | :--: |
|
| A | 200 | 5/2/2025 | D1001 | 中石油 | 100.0 |
|
|
### 已有采购订单
|
|
| 供应商名称 | 物料 | 采购订单号 | 预计到货日期 | 采购数量 |
|
| :--: | :--: | :--: | :--: | :--: |
|
| S1 | 1001 | PO1 | 2/2/2025 | 2000 |
|
| S2 | 1002 | PO2 | 2/2/2025 | 1000 |
|
| S3 | 1003 | PO3 | 2/2/2025 | 1000 |
|
| S4 | 1004 | PO4 | 2/2/2025 | 800 |
|
| S5 | 1005 | PO5 | 2/2/2025 | 800 |
|
|
### 供应商列表
|
|
| 供应商名称 | 物料 | 联系方式 |
|
| :--: | :--: | :--: |
|
| S1 | 1001 | chenqinghonghs@163.com |
|
| S2 | 1002 | chenqinghonghs@163.com |
|
| S3 | 1003 | chenqinghonghs@163.com |
|
| S4 | 1004 | chenqinghonghs@163.com |
|
| S5 | 1005 | 16673976114@163.com |
|
`
|
},
|
];
|
|
export default function AISceneChatPage() {
|
const searchParams = useSearchParams();
|
const sceneId = searchParams.get('scene');
|
const router = useRouter();
|
const [showDataPreview, setShowDataPreview] = useState(false);
|
|
// 根据URL参数找到对应的场景
|
const initialScene = defaultServices.find(s => s.chatbotId === sceneId) || defaultServices[0];
|
// 修改为使用完整的场景数据
|
const [services] = useState([defaultServices.find(s => s.chatbotId === sceneId) || defaultServices[0]]);
|
const [selectedScene, setSelectedScene] = useState(initialScene);
|
const [iframeKey, setIframeKey] = useState(0);
|
|
// 切换场景时重新加载iframe
|
const handleSceneChange = (scene: typeof services[0]) => {
|
setSelectedScene(scene);
|
setIframeKey(prev => prev + 1);
|
};
|
|
return (
|
<div className="h-screen flex bg-white">
|
{/* 左侧场景选项卡 */}
|
<div className="w-64 bg-white border-r flex flex-col">
|
{/* 固定头部 */}
|
<div className="pt-[65px] bg-white">
|
<div className="h-12 px-6 flex items-center border-b">
|
<div className="flex items-center">
|
<button
|
onClick={() => router.push('/ai-scene')}
|
className="group inline-flex items-center text-sm text-gray-500 hover:text-red-500 transition-all duration-300 cursor-pointer"
|
>
|
<svg
|
xmlns="http://www.w3.org/2000/svg"
|
className="h-4 w-4 transition-transform duration-300 group-hover:-translate-x-0.5"
|
fill="none"
|
viewBox="0 0 24 24"
|
stroke="currentColor"
|
>
|
<path
|
strokeLinecap="round"
|
strokeLinejoin="round"
|
strokeWidth={2}
|
d="M10 19l-7-7m0 0l7-7m-7 7h18"
|
/>
|
</svg>
|
<span className="ml-1.5">返回</span>
|
</button>
|
<div className="mx-3 h-4 w-px bg-gray-200"></div>
|
<h2 className="text-base font-medium text-gray-900 flex items-center">
|
AI场景
|
<motion.span
|
className="ml-2 inline-block w-1.5 h-1.5 rounded-full bg-[#6ADBFF]"
|
animate={{
|
scale: [1, 1.5, 1],
|
opacity: [0.7, 1, 0.7]
|
}}
|
transition={{
|
duration: 2,
|
repeat: Infinity,
|
ease: "easeInOut"
|
}}
|
/>
|
</h2>
|
</div>
|
</div>
|
</div>
|
|
{/* 滚动内容区 */}
|
<div className="flex-1 overflow-y-auto">
|
{/* 场景列表 */}
|
<div className="px-4 py-2 space-y-3">
|
{services.map((scene) => (
|
<motion.div
|
key={scene.chatbotId}
|
className="relative group"
|
>
|
<motion.button
|
onClick={() => handleSceneChange(scene)}
|
className={`w-full p-4 rounded-lg text-left transition-all duration-500 relative cursor-pointer
|
${selectedScene.chatbotId === scene.chatbotId
|
? 'text-[#6ADBFF] bg-gray-100 shadow-sm'
|
: 'text-gray-600 hover:text-[#6ADBFF] hover:bg-gray-50'
|
}`}
|
whileHover={{ scale: 1.02 }}
|
whileTap={{ scale: 0.98 }}
|
>
|
{selectedScene.chatbotId === scene.chatbotId && (
|
<motion.div
|
className="absolute inset-0 bg-gradient-to-r from-gray-100 to-gray-50 -z-10"
|
layoutId="activeBackground"
|
initial={false}
|
transition={{
|
type: "spring",
|
stiffness: 200,
|
damping: 20
|
}}
|
/>
|
)}
|
<div className="flex items-center relative z-10">
|
<div className="w-8 h-8 rounded-lg overflow-hidden mr-3 relative flex-shrink-0">
|
<img
|
src={scene.imageUrl}
|
alt={scene.title}
|
className="w-full h-full object-cover transform transition-transform duration-700 group-hover:scale-110"
|
/>
|
<div className="absolute inset-0 bg-gradient-to-br from-black/20 to-transparent"></div>
|
</div>
|
<div className="relative max-w-[120px] group/text">
|
<span className="font-medium truncate block w-full" ref={(el) => {
|
if (el) {
|
el.dataset.truncated = (el.scrollWidth > el.clientWidth).toString();
|
}
|
}}>{scene.title}</span>
|
{/* Tooltip - 只在文本被截断时显示 */}
|
<div
|
className="absolute left-1/2 -translate-x-1/2 -top-2 -translate-y-full bg-gray-800/95 text-white text-sm px-3 py-2 rounded-lg opacity-0 invisible data-[show=true]:group-hover/text:opacity-100 data-[show=true]:group-hover/text:visible transition-all duration-200 whitespace-nowrap shadow-lg z-[1000]"
|
data-show={scene.title.length > 8}
|
>
|
<div className="absolute left-1/2 -translate-x-1/2 top-full border-[6px] border-transparent border-t-gray-800/95"></div>
|
{scene.title}
|
</div>
|
</div>
|
</div>
|
{selectedScene.chatbotId === scene.chatbotId && (
|
<motion.div
|
className="absolute right-3 top-1/2 -translate-y-1/2 w-1.5 h-6 bg-[#6ADBFF] rounded-full"
|
layoutId="activeIndicator"
|
initial={{ opacity: 0 }}
|
animate={{ opacity: 1 }}
|
exit={{ opacity: 0 }}
|
/>
|
)}
|
</motion.button>
|
</motion.div>
|
))}
|
</div>
|
|
{/* 场景信息展示区域 */}
|
<div className="p-4 space-y-4">
|
{/* 场景背景 */}
|
<div className="bg-gray-50 rounded-lg p-4 border border-gray-100 hover:border-gray-200 transition-colors duration-300 shadow-sm">
|
<div className="flex items-center gap-2 mb-2">
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
</svg>
|
<h3 className="text-sm font-medium text-gray-700">场景背景</h3>
|
</div>
|
<p className="text-gray-600 text-sm leading-relaxed">
|
{selectedScene.background}
|
</p>
|
</div>
|
|
{/* 使用说明 */}
|
<div className="bg-gray-50 rounded-lg p-4 border border-gray-100 hover:border-gray-200 transition-colors duration-300 shadow-sm">
|
<div className="flex items-center gap-2 mb-2">
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
</svg>
|
<h3 className="text-sm font-medium text-gray-700">使用说明</h3>
|
</div>
|
<p className="text-gray-600 text-sm leading-relaxed">
|
{selectedScene.instructions}
|
</p>
|
</div>
|
|
{/* 数据说明 - 只在补料和插单场景显示 */}
|
{(selectedScene.chatbotId === 'JELkWpPLHQfRNhEH' ||
|
selectedScene.chatbotId === 'RhMYLHI1SZNiX4kl' ||
|
selectedScene.chatbotId === 'zO9YQDEHdIApG9zC') && (
|
<>
|
<div className="bg-gray-50 rounded-lg p-4 border border-gray-100 hover:border-gray-200 transition-colors duration-300 shadow-sm">
|
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center gap-2">
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 7v10c0 2 1 3 3 3h10c2 0 3-1 3-3V7c0-2-1-3-3-3H7C5 4 4 5 4 7z M9 11h6 M12 8v6" />
|
</svg>
|
<h3 className="text-sm font-medium text-gray-700">数据说明</h3>
|
</div>
|
<button
|
onClick={() => setShowDataPreview(true)}
|
className="p-1.5 rounded-md text-gray-400 hover:text-gray-600 hover:bg-gray-100 transition-all duration-300"
|
title="查看演示数据"
|
>
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
</svg>
|
</button>
|
</div>
|
<p className="text-gray-600 text-sm leading-relaxed">
|
{selectedScene.dataDescription || '本场景所使用的数据均为模拟数据,仅用于演示目的。在实际应用中,将根据您的具体需求使用真实数据进行分析和处理。'}
|
</p>
|
</div>
|
|
<DataPreviewDialog
|
isOpen={showDataPreview}
|
onClose={() => setShowDataPreview(false)}
|
markdownContent={selectedScene.exampleData || ''}
|
sceneType={selectedScene.chatbotId === 'JELkWpPLHQfRNhEH' ? 'buliao' : 'chadan'}
|
/>
|
</>
|
)}
|
</div>
|
</div>
|
</div>
|
|
{/* 中间聊天区域 */}
|
<div className="flex-1 flex flex-col bg-white pt-[65px] overflow-hidden">
|
{/* 场景标题 */}
|
<div className="h-16 bg-white flex items-center justify-center px-6 border-b">
|
<h1 className="text-lg font-medium text-gray-900">
|
{selectedScene.title}
|
</h1>
|
</div>
|
|
{/* 聊天窗口 */}
|
<div className="flex-1 relative [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-[#E5E6EB] [&::-webkit-scrollbar-thumb]:rounded-full hover:[&::-webkit-scrollbar-thumb]:bg-[#C9CDD4]">
|
<AnimatePresence mode="wait">
|
<motion.div
|
key={iframeKey}
|
className="absolute inset-0"
|
initial={{ opacity: 0, scale: 0.98 }}
|
animate={{ opacity: 1, scale: 1 }}
|
exit={{ opacity: 0, scale: 1.02 }}
|
transition={{ duration: 0.3 }}
|
>
|
<iframe
|
src={`http://121.43.139.99/chatbot/${selectedScene.chatbotId}`}
|
className="w-full h-full"
|
style={{ border: 'none' }}
|
allow="microphone"
|
/>
|
</motion.div>
|
</AnimatePresence>
|
</div>
|
</div>
|
</div>
|
);
|
}
|