hongjli
2025-04-10 c1c456128bec733c46ad03a09a5bae4c79e02367
新建聊天页面
已添加2个文件
已修改3个文件
231 ■■■■ 文件已修改
src/app/ai-scene/chat/layout.tsx 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/app/ai-scene/chat/page.tsx 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/app/ai-scene/page.tsx 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/SceneIntroDialog.tsx 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/layout/ClientLayoutContent.tsx 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/app/ai-scene/chat/layout.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
import type { Metadata } from 'next';
export const metadata: Metadata = {
  title: 'AI场景模拟聊天',
  description: '智能化供应链管理,AI模拟APS场景的领航者',
};
export default function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  return children;
}
src/app/ai-scene/chat/page.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,176 @@
"use client";
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
// å¯¼å…¥åœºæ™¯æ•°æ®
const services = [
  {
    title: '补料',
    description: '智能动态分析产线,工位的缺料情况,降低停线风险',
    imageUrl: '/images/kanban.jpg',
    chatbotId: 'JELkWpPLHQfRNhEH',
  },
  {
    title: '插单',
    description: '智能评估需求插单对产能,原材料和交付服务的影响,提升客户满意度',
    imageUrl: '/images/xuqiu.jpg',
    chatbotId: 'DfH4cIzujVGvn5iR',
  },
  {
    title: '科沃斯销售推荐小助手',
    description: '智能化产品推荐提升导购效率',
    imageUrl: '/images/robot.jpg',
    chatbotId: 'sUAviPXvcEIw3oQC',
  },
  {
    title: '库存管理知识库问答',
    description: '库存知识,提供专业的供应链库存知识问答',
    imageUrl: '/images/know.jpg',
    chatbotId: 'pDDfkU9HyBl2gzXx',
  },
];
export default function AISceneChatPage() {
  const [selectedScene, setSelectedScene] = useState(services[0]);
  const [iframeKey, setIframeKey] = useState(0); // ç”¨äºŽå¼ºåˆ¶åˆ·æ–°iframe
  // åˆ‡æ¢åœºæ™¯æ—¶é‡æ–°åŠ è½½iframe
  const handleSceneChange = (scene: typeof services[0]) => {
    setSelectedScene(scene);
    setIframeKey(prev => prev + 1);
  };
  return (
    <div className="h-screen flex bg-[#0A1033]">
      {/* å·¦ä¾§åœºæ™¯é€‰é¡¹å¡ */}
      <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 }}
              >
                {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
                    }}
                  />
                )}
                <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"
                    />
                    <div className="absolute inset-0 bg-gradient-to-br from-black/20 to-transparent"></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>
            ))}
          </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>
        {/* èŠå¤©çª—口 */}
        <div className="flex-1 relative">
          <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>
  );
}
src/app/ai-scene/page.tsx
@@ -2,7 +2,6 @@
import { useState } from 'react';
import Card from '@/components/Card';
import ChatDialog from '@/components/ChatDialog';
import SceneIntroDialog from '@/components/SceneIntroDialog';
import { motion } from 'framer-motion';
@@ -83,20 +82,12 @@
];
export default function AIScenePage() {
  const [isChatOpen, setIsChatOpen] = useState(false);
  const [isIntroOpen, setIsIntroOpen] = useState(false);
  const [currentChatbot, setCurrentChatbot] = useState('');
  const [selectedScene, setSelectedScene] = useState(services[0]);
  const handleCardClick = (service: typeof services[0]) => {
    setSelectedScene(service);
    setIsIntroOpen(true);
  };
  const handleStartChat = () => {
    setIsIntroOpen(false);
    setCurrentChatbot(selectedScene.chatbotId);
    setIsChatOpen(true);
  };
  return (
@@ -150,14 +141,7 @@
        <SceneIntroDialog
          isOpen={isIntroOpen}
          onClose={() => setIsIntroOpen(false)}
          onStartChat={handleStartChat}
          scene={selectedScene}
        />
        <ChatDialog
          isOpen={isChatOpen}
          onClose={() => setIsChatOpen(false)}
          chatbotId={currentChatbot}
        />
      </div>
    </>
src/components/SceneIntroDialog.tsx
@@ -4,11 +4,11 @@
import { Fragment, useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import DataPreviewDialog from './DataPreviewDialog';
import { useRouter } from 'next/navigation';
interface SceneIntroDialogProps {
  isOpen: boolean;
  onClose: () => void;
  onStartChat: () => void;
  scene: {
    title: string;
    description: string;
@@ -17,16 +17,17 @@
    instructions?: string;
    dataDescription?: string;
    exampleData?: string;
    chatbotId: string;
  };
}
export default function SceneIntroDialog({
  isOpen,
  onClose,
  onStartChat,
  scene
}: SceneIntroDialogProps) {
  const [showDataPreview, setShowDataPreview] = useState(false);
  const router = useRouter();
  // ç¡®ä¿åŠ¨ç”»çŠ¶æ€åœ¨æ¯æ¬¡æ‰“å¼€æ—¶éƒ½è¢«é‡ç½®
  useEffect(() => {
@@ -42,6 +43,11 @@
      });
    }
  }, [isOpen]);
  const handleStartChat = () => {
    onClose();
    router.push('/ai-scene/chat');
  };
  return (
    <AnimatePresence>
@@ -143,7 +149,7 @@
                            transition={{ duration: 0.5, delay: 0.3 }}
                          >
                            <button
                              onClick={onStartChat}
                              onClick={handleStartChat}
                              className="group relative w-64 cursor-pointer"
                            >
                              <div className="absolute -inset-0.5 rounded-lg bg-gradient-to-r from-[#6ADBFF] to-[#5E72EB] opacity-60 blur group-hover:opacity-100 transition-all duration-300"></div>
src/components/layout/ClientLayoutContent.tsx
@@ -12,14 +12,17 @@
  const [isClient, setIsClient] = useState(false);
  const [hasError, setHasError] = useState(false);
  const pathname = usePathname();
  const isLoginPage = pathname === '/login';
  const isRegisterPage = pathname === '/register';
  const isHomePage = pathname === '/';
  const isAIScenePage = pathname.startsWith('/ai-scene');
  useEffect(() => {
    setIsClient(true);
  }, []);
  // åˆ¤æ–­å½“前页面
  const isLoginPage = pathname === '/login';
  const isRegisterPage = pathname === '/register';
  const isHomePage = pathname === '/';
  const isAIScenePage = pathname === '/ai-scene';
  const isAISceneChatPage = pathname.startsWith('/ai-scene/chat');
  // æ·»åŠ é”™è¯¯æ•èŽ·
  useEffect(() => {
@@ -65,7 +68,7 @@
            <main className={`flex-1 ${isHomePage || isAIScenePage ? '' : 'bg-gradient-to-b from-[var(--ai-surface)] to-white'} pt-0 mt-0`}>
              {children}
            </main>
            {!isLoginPage && !isRegisterPage && (
            {!isLoginPage && !isRegisterPage && !isAISceneChatPage && (
              <footer className="relative z-20 bg-gradient-to-br from-[#0A1033] via-[#1E2B63] to-[#131C41] text-white py-10 overflow-hidden">
                {/* ç§‘技感动态背景元素 */}
                <div className="absolute inset-0 overflow-hidden pointer-events-none">