| | |
| | | |
| | | import { useState, useEffect } from 'react'; |
| | | import { motion, AnimatePresence } from 'framer-motion'; |
| | | import { useSearchParams } from 'next/navigation'; |
| | | import { useRouter } from 'next/navigation'; |
| | | |
| | | // 导入场景数据 |
| | | const services = [ |
| | | const defaultServices = [ |
| | | { |
| | | title: '库存管理知识库问答', |
| | | description: '库存知识,提供专业的供应链库存知识问答', |
| | | imageUrl: '/images/know.jpg', |
| | | chatbotId: 'pDDfkU9HyBl2gzXx', |
| | | }, |
| | | { |
| | | title: '扫地机器人销售推荐小助手', |
| | | description: '智能化产品推荐提升导购效率', |
| | | imageUrl: '/images/robot.jpg', |
| | | chatbotId: 'sUAviPXvcEIw3oQC', |
| | | }, |
| | | { |
| | | title: '补料', |
| | | description: '智能动态分析产线,工位的缺料情况,降低停线风险', |
| | |
| | | chatbotId: 'JELkWpPLHQfRNhEH', |
| | | }, |
| | | { |
| | | title: '插单', |
| | | title: '插单1.0', |
| | | description: '智能评估需求插单对产能,原材料和交付服务的影响,提升客户满意度', |
| | | imageUrl: '/images/xuqiu.jpg', |
| | | chatbotId: 'DfH4cIzujVGvn5iR', |
| | | background: '在生产计划执行过程中,常常会遇到紧急订单需要插单的情况。AI系统可以快速评估插单对现有生产计划的影响,并提供最优的插单方案。', |
| | | instructions: '请提供需要插单的订单信息,包括产品类型、数量和期望交付时间,AI助手将为您分析可行性并给出具体的插单建议。' |
| | | }, |
| | | { |
| | | title: '科沃斯销售推荐小助手', |
| | | description: '智能化产品推荐提升导购效率', |
| | | imageUrl: '/images/robot.jpg', |
| | | chatbotId: 'sUAviPXvcEIw3oQC', |
| | | }, |
| | | { |
| | | title: '库存管理知识库问答', |
| | | description: '库存知识,提供专业的供应链库存知识问答', |
| | | imageUrl: '/images/know.jpg', |
| | | chatbotId: 'pDDfkU9HyBl2gzXx', |
| | | title: '插单2.0', |
| | | description: '智能评估需求插单对产能,原材料和交付服务的影响,提升客户满意度', |
| | | imageUrl: '/images/xuqiu.jpg', |
| | | chatbotId: 'zO9YQDEHdIApG9zC', |
| | | background: '在生产计划执行过程中,常常会遇到紧急订单需要插单的情况。AI系统可以快速评估插单对现有生产计划的影响,并提供最优的插单方案。', |
| | | instructions: '请提供需要插单的订单信息,包括产品类型、数量和期望交付时间,AI助手将为您分析可行性并给出具体的插单建议。' |
| | | }, |
| | | ]; |
| | | |
| | | export default function AISceneChatPage() { |
| | | const [selectedScene, setSelectedScene] = useState(services[0]); |
| | | const [iframeKey, setIframeKey] = useState(0); // 用于强制刷新iframe |
| | | const searchParams = useSearchParams(); |
| | | const sceneId = searchParams.get('scene'); |
| | | const router = useRouter(); |
| | | |
| | | // 根据URL参数找到对应的场景 |
| | | const initialScene = defaultServices.find(s => s.chatbotId === sceneId) || defaultServices[0]; |
| | | const [services] = useState([initialScene]); |
| | | const [selectedScene, setSelectedScene] = useState(initialScene); |
| | | const [iframeKey, setIframeKey] = useState(0); |
| | | |
| | | // 切换场景时重新加载iframe |
| | | const handleSceneChange = (scene: typeof services[0]) => { |
| | |
| | | }; |
| | | |
| | | return ( |
| | | <div className="h-screen flex bg-[#0A1033]"> |
| | | <div className="h-screen flex bg-white"> |
| | | {/* 左侧场景选项卡 */} |
| | | <div className="w-64 bg-[#131C41] border-r border-[#6ADBFF]/20 flex flex-col"> |
| | | <div className="p-4 flex-1 overflow-y-auto"> |
| | | <h2 className="text-xl font-bold text-white mb-6 flex items-center"> |
| | | <span className="text-transparent bg-clip-text bg-gradient-to-r from-[#6ADBFF] to-[#5E72EB]"> |
| | | AI场景 |
| | | </span> |
| | | <motion.span |
| | | className="ml-2 inline-block w-2 h-2 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 className="space-y-3"> |
| | | {services.map((scene) => ( |
| | | <motion.button |
| | | key={scene.chatbotId} |
| | | onClick={() => handleSceneChange(scene)} |
| | | className={`w-full p-4 rounded-lg text-left transition-all duration-500 relative overflow-hidden |
| | | ${selectedScene.chatbotId === scene.chatbotId |
| | | ? 'text-[#6ADBFF]' |
| | | : 'text-gray-300 hover:text-[#6ADBFF]' |
| | | }`} |
| | | whileHover={{ scale: 1.02 }} |
| | | whileTap={{ scale: 0.98 }} |
| | | <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" |
| | | > |
| | | {selectedScene.chatbotId === scene.chatbotId && ( |
| | | <motion.div |
| | | className="absolute inset-0 bg-gradient-to-r from-[#1A2547] to-[#1E2B63] -z-10" |
| | | layoutId="activeBackground" |
| | | initial={false} |
| | | transition={{ |
| | | type: "spring", |
| | | stiffness: 200, |
| | | damping: 20 |
| | | }} |
| | | <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" |
| | | /> |
| | | )} |
| | | <div className="flex items-center relative z-10"> |
| | | <div className="w-10 h-10 rounded-lg overflow-hidden mr-3 relative"> |
| | | <img |
| | | src={scene.imageUrl} |
| | | alt={scene.title} |
| | | className="w-full h-full object-cover transform transition-transform duration-700 group-hover:scale-110" |
| | | </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 px-4 [&::-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]"> |
| | | <div className="space-y-3 py-4"> |
| | | {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="absolute inset-0 bg-gradient-to-br from-black/20 to-transparent"></div> |
| | | )} |
| | | <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> |
| | | <div> |
| | | <span className="font-medium block">{scene.title}</span> |
| | | <span className="text-xs text-gray-400 line-clamp-1">{scene.description}</span> |
| | | </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> |
| | | {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> |
| | | </div> |
| | | |
| | | {/* 中间聊天区域 */} |
| | | <div className="flex-1 flex flex-col bg-gradient-to-br from-[#0A1033] to-[#131C41]"> |
| | | {/* 顶部标题栏 */} |
| | | <div className="h-16 border-b border-[#6ADBFF]/20 flex items-center px-6 bg-[#131C41]/50 backdrop-blur-sm"> |
| | | <AnimatePresence mode="wait"> |
| | | <motion.div |
| | | key={selectedScene.chatbotId} |
| | | initial={{ opacity: 0, y: -20 }} |
| | | animate={{ opacity: 1, y: 0 }} |
| | | exit={{ opacity: 0, y: 20 }} |
| | | transition={{ duration: 0.3 }} |
| | | className="flex items-center" |
| | | > |
| | | <div className="w-8 h-8 rounded-lg overflow-hidden mr-3 relative"> |
| | | <img |
| | | src={selectedScene.imageUrl} |
| | | alt={selectedScene.title} |
| | | className="w-full h-full object-cover" |
| | | /> |
| | | <div className="absolute inset-0 bg-gradient-to-br from-black/20 to-transparent"></div> |
| | | </div> |
| | | <div> |
| | | <h1 className="text-xl font-bold text-white"> |
| | | {selectedScene.title} |
| | | </h1> |
| | | <p className="text-sm text-gray-400"> |
| | | {selectedScene.description} |
| | | </p> |
| | | </div> |
| | | </motion.div> |
| | | </AnimatePresence> |
| | | <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"> |
| | | <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} |