From c1c456128bec733c46ad03a09a5bae4c79e02367 Mon Sep 17 00:00:00 2001 From: hongjli <3117313295@qq.com> Date: 星期四, 10 四月 2025 10:18:54 +0800 Subject: [PATCH] 新建聊天页面 --- src/components/SceneIntroDialog.tsx | 12 ++ src/app/ai-scene/chat/page.tsx | 176 ++++++++++++++++++++++++++++++++++++++++++++ src/app/ai-scene/chat/layout.tsx | 14 +++ src/app/ai-scene/page.tsx | 16 ---- src/components/layout/ClientLayoutContent.tsx | 13 ++- 5 files changed, 207 insertions(+), 24 deletions(-) diff --git a/src/app/ai-scene/chat/layout.tsx b/src/app/ai-scene/chat/layout.tsx new file mode 100644 index 0000000..b11194e --- /dev/null +++ b/src/app/ai-scene/chat/layout.tsx @@ -0,0 +1,14 @@ +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'AI鍦烘櫙妯℃嫙鑱婂ぉ', + description: '鏅鸿兘鍖栦緵搴旈摼绠$悊锛孉I妯℃嫙APS鍦烘櫙鐨勯鑸��', +}; + +export default function Layout({ + children, +}: { + children: React.ReactNode; +}) { + return children; +} \ No newline at end of file diff --git a/src/app/ai-scene/chat/page.tsx b/src/app/ai-scene/chat/page.tsx new file mode 100644 index 0000000..52f1cb3 --- /dev/null +++ b/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 + + // 鍒囨崲鍦烘櫙鏃堕噸鏂板姞杞絠frame + 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> + ); +} \ No newline at end of file diff --git a/src/app/ai-scene/page.tsx b/src/app/ai-scene/page.tsx index c39a22c..7828665 100644 --- a/src/app/ai-scene/page.tsx +++ b/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> </> diff --git a/src/components/SceneIntroDialog.tsx b/src/components/SceneIntroDialog.tsx index 89a999a..c5d8a36 100644 --- a/src/components/SceneIntroDialog.tsx +++ b/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> diff --git a/src/components/layout/ClientLayoutContent.tsx b/src/components/layout/ClientLayoutContent.tsx index 2022e09..19286e6 100644 --- a/src/components/layout/ClientLayoutContent.tsx +++ b/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"> -- Gitblit v1.9.3