feat: RightSidebar (~420 行) - 模型/工具/统计 Tab 面板(单文件)
This commit is contained in:
498
src/views/AgentChat/components/RightSidebar/index.js
Normal file
498
src/views/AgentChat/components/RightSidebar/index.js
Normal file
@@ -0,0 +1,498 @@
|
||||
// src/views/AgentChat/components/RightSidebar/index.js
|
||||
// 右侧栏组件 - 配置中心
|
||||
|
||||
import React from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Badge,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
Tooltip,
|
||||
IconButton,
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
AccordionButton,
|
||||
AccordionPanel,
|
||||
AccordionIcon,
|
||||
Tabs,
|
||||
TabList,
|
||||
TabPanels,
|
||||
Tab,
|
||||
TabPanel,
|
||||
Card,
|
||||
CardBody,
|
||||
HStack,
|
||||
VStack,
|
||||
Flex,
|
||||
Text,
|
||||
} from '@chakra-ui/react';
|
||||
import {
|
||||
Settings,
|
||||
ChevronRight,
|
||||
Cpu,
|
||||
Code,
|
||||
BarChart3,
|
||||
Check,
|
||||
MessageSquare,
|
||||
Activity,
|
||||
} from 'lucide-react';
|
||||
import { animations } from '../../constants/animations';
|
||||
import { AVAILABLE_MODELS } from '../../constants/models';
|
||||
import { MCP_TOOLS, TOOL_CATEGORIES } from '../../constants/tools';
|
||||
|
||||
/**
|
||||
* RightSidebar - 右侧栏组件(配置中心)
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {boolean} props.isOpen - 侧边栏是否展开
|
||||
* @param {Function} props.onClose - 关闭侧边栏回调
|
||||
* @param {string} props.selectedModel - 当前选中的模型 ID
|
||||
* @param {Function} props.onModelChange - 模型切换回调
|
||||
* @param {Array} props.selectedTools - 已选工具 ID 列表
|
||||
* @param {Function} props.onToolsChange - 工具选择变化回调
|
||||
* @param {number} props.sessionsCount - 会话总数
|
||||
* @param {number} props.messagesCount - 消息总数
|
||||
* @returns {JSX.Element|null}
|
||||
*/
|
||||
const RightSidebar = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
selectedModel,
|
||||
onModelChange,
|
||||
selectedTools,
|
||||
onToolsChange,
|
||||
sessionsCount,
|
||||
messagesCount,
|
||||
}) => {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
style={{ width: '320px', display: 'flex', flexDirection: 'column' }}
|
||||
initial="initial"
|
||||
animate="animate"
|
||||
exit="exit"
|
||||
variants={animations.slideInRight}
|
||||
>
|
||||
<Box
|
||||
w="320px"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
bg="rgba(17, 24, 39, 0.8)"
|
||||
backdropFilter="blur(20px) saturate(180%)"
|
||||
borderLeft="1px solid"
|
||||
borderColor="rgba(255, 255, 255, 0.1)"
|
||||
boxShadow="-4px 0 24px rgba(0, 0, 0, 0.3)"
|
||||
>
|
||||
{/* 标题栏 */}
|
||||
<Box p={4} borderBottom="1px solid" borderColor="rgba(255, 255, 255, 0.1)">
|
||||
<HStack justify="space-between">
|
||||
<HStack spacing={2}>
|
||||
<Settings className="w-5 h-5" color="#C084FC" />
|
||||
<Text
|
||||
fontWeight="semibold"
|
||||
bgGradient="linear(to-r, purple.300, pink.300)"
|
||||
bgClip="text"
|
||||
fontSize="md"
|
||||
>
|
||||
配置中心
|
||||
</Text>
|
||||
</HStack>
|
||||
<Tooltip label="收起侧边栏">
|
||||
<motion.div whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }}>
|
||||
<IconButton
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
icon={<ChevronRight className="w-4 h-4" />}
|
||||
onClick={onClose}
|
||||
bg="rgba(255, 255, 255, 0.05)"
|
||||
color="gray.300"
|
||||
backdropFilter="blur(10px)"
|
||||
border="1px solid"
|
||||
borderColor="rgba(255, 255, 255, 0.1)"
|
||||
_hover={{
|
||||
bg: 'rgba(255, 255, 255, 0.1)',
|
||||
borderColor: 'purple.400',
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
</Box>
|
||||
|
||||
{/* Tab 面板 */}
|
||||
<Box flex={1} overflowY="auto">
|
||||
<Tabs colorScheme="purple" variant="line">
|
||||
<TabList px={4} borderBottom="1px solid" borderColor="rgba(255, 255, 255, 0.1)">
|
||||
<Tab
|
||||
color="gray.400"
|
||||
_selected={{
|
||||
color: 'purple.400',
|
||||
borderColor: 'purple.500',
|
||||
boxShadow: '0 2px 8px rgba(139, 92, 246, 0.3)',
|
||||
}}
|
||||
>
|
||||
<HStack spacing={2}>
|
||||
<Cpu className="w-4 h-4" />
|
||||
<Text>模型</Text>
|
||||
</HStack>
|
||||
</Tab>
|
||||
<Tab
|
||||
color="gray.400"
|
||||
_selected={{
|
||||
color: 'purple.400',
|
||||
borderColor: 'purple.500',
|
||||
boxShadow: '0 2px 8px rgba(139, 92, 246, 0.3)',
|
||||
}}
|
||||
>
|
||||
<HStack spacing={2}>
|
||||
<Code className="w-4 h-4" />
|
||||
<Text>工具</Text>
|
||||
{selectedTools.length > 0 && (
|
||||
<Badge
|
||||
bgGradient="linear(to-r, blue.500, purple.500)"
|
||||
color="white"
|
||||
borderRadius="full"
|
||||
fontSize="xs"
|
||||
px={2}
|
||||
py={0.5}
|
||||
boxShadow="0 2px 8px rgba(139, 92, 246, 0.3)"
|
||||
>
|
||||
{selectedTools.length}
|
||||
</Badge>
|
||||
)}
|
||||
</HStack>
|
||||
</Tab>
|
||||
<Tab
|
||||
color="gray.400"
|
||||
_selected={{
|
||||
color: 'purple.400',
|
||||
borderColor: 'purple.500',
|
||||
boxShadow: '0 2px 8px rgba(139, 92, 246, 0.3)',
|
||||
}}
|
||||
>
|
||||
<HStack spacing={2}>
|
||||
<BarChart3 className="w-4 h-4" />
|
||||
<Text>统计</Text>
|
||||
</HStack>
|
||||
</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
{/* 模型选择 */}
|
||||
<TabPanel p={4}>
|
||||
<VStack spacing={3} align="stretch">
|
||||
{AVAILABLE_MODELS.map((model, idx) => (
|
||||
<motion.div
|
||||
key={model.id}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: idx * 0.1 }}
|
||||
whileHover={{ scale: 1.02, y: -2 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
>
|
||||
<Card
|
||||
cursor="pointer"
|
||||
onClick={() => onModelChange(model.id)}
|
||||
bg={
|
||||
selectedModel === model.id
|
||||
? 'rgba(139, 92, 246, 0.15)'
|
||||
: 'rgba(255, 255, 255, 0.05)'
|
||||
}
|
||||
backdropFilter="blur(12px)"
|
||||
borderWidth={2}
|
||||
borderColor={
|
||||
selectedModel === model.id ? 'purple.400' : 'rgba(255, 255, 255, 0.1)'
|
||||
}
|
||||
_hover={{
|
||||
borderColor:
|
||||
selectedModel === model.id ? 'purple.400' : 'rgba(255, 255, 255, 0.2)',
|
||||
boxShadow:
|
||||
selectedModel === model.id
|
||||
? '0 8px 20px rgba(139, 92, 246, 0.4)'
|
||||
: '0 4px 12px rgba(0, 0, 0, 0.3)',
|
||||
}}
|
||||
transition="all 0.3s"
|
||||
>
|
||||
<CardBody p={3}>
|
||||
<HStack align="start" spacing={3}>
|
||||
<Box
|
||||
p={2}
|
||||
borderRadius="lg"
|
||||
bgGradient={
|
||||
selectedModel === model.id
|
||||
? 'linear(to-br, purple.500, pink.500)'
|
||||
: 'linear(to-br, rgba(139, 92, 246, 0.2), rgba(236, 72, 153, 0.2))'
|
||||
}
|
||||
boxShadow={
|
||||
selectedModel === model.id
|
||||
? '0 4px 12px rgba(139, 92, 246, 0.4)'
|
||||
: 'none'
|
||||
}
|
||||
>
|
||||
{model.icon}
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<Text fontWeight="semibold" fontSize="sm" color="gray.100">
|
||||
{model.name}
|
||||
</Text>
|
||||
<Text fontSize="xs" color="gray.400" mt={1}>
|
||||
{model.description}
|
||||
</Text>
|
||||
</Box>
|
||||
{selectedModel === model.id && (
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ type: 'spring', stiffness: 500, damping: 30 }}
|
||||
>
|
||||
<Check className="w-5 h-5" color="#A78BFA" />
|
||||
</motion.div>
|
||||
)}
|
||||
</HStack>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</VStack>
|
||||
</TabPanel>
|
||||
|
||||
{/* 工具选择 */}
|
||||
<TabPanel p={4}>
|
||||
<Accordion allowMultiple>
|
||||
{Object.entries(TOOL_CATEGORIES).map(([category, tools], catIdx) => (
|
||||
<motion.div
|
||||
key={category}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: catIdx * 0.05 }}
|
||||
>
|
||||
<AccordionItem
|
||||
border="1px solid"
|
||||
borderColor="rgba(255, 255, 255, 0.1)"
|
||||
borderRadius="lg"
|
||||
mb={2}
|
||||
bg="rgba(255, 255, 255, 0.05)"
|
||||
backdropFilter="blur(12px)"
|
||||
_hover={{
|
||||
bg: 'rgba(255, 255, 255, 0.08)',
|
||||
borderColor: 'rgba(255, 255, 255, 0.2)',
|
||||
}}
|
||||
>
|
||||
<AccordionButton>
|
||||
<HStack flex={1} justify="space-between" pr={2}>
|
||||
<Text color="gray.100" fontSize="sm">
|
||||
{category}
|
||||
</Text>
|
||||
<Badge
|
||||
bgGradient="linear(to-r, blue.500, purple.500)"
|
||||
color="white"
|
||||
variant="subtle"
|
||||
boxShadow="0 2px 8px rgba(139, 92, 246, 0.3)"
|
||||
>
|
||||
{tools.filter((t) => selectedTools.includes(t.id)).length}/{tools.length}
|
||||
</Badge>
|
||||
</HStack>
|
||||
<AccordionIcon color="gray.400" />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4}>
|
||||
<CheckboxGroup value={selectedTools} onChange={onToolsChange}>
|
||||
<VStack align="stretch" spacing={2}>
|
||||
{tools.map((tool) => (
|
||||
<motion.div
|
||||
key={tool.id}
|
||||
whileHover={{ x: 4 }}
|
||||
transition={{ type: 'spring', stiffness: 300 }}
|
||||
>
|
||||
<Checkbox
|
||||
value={tool.id}
|
||||
colorScheme="purple"
|
||||
p={2}
|
||||
borderRadius="lg"
|
||||
bg="rgba(255, 255, 255, 0.02)"
|
||||
_hover={{ bg: 'rgba(255, 255, 255, 0.05)' }}
|
||||
transition="background 0.2s"
|
||||
>
|
||||
<HStack spacing={2} align="start">
|
||||
<Box color="purple.400" mt={0.5}>
|
||||
{tool.icon}
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fontSize="sm" color="gray.200">
|
||||
{tool.name}
|
||||
</Text>
|
||||
<Text fontSize="xs" color="gray.500">
|
||||
{tool.description}
|
||||
</Text>
|
||||
</Box>
|
||||
</HStack>
|
||||
</Checkbox>
|
||||
</motion.div>
|
||||
))}
|
||||
</VStack>
|
||||
</CheckboxGroup>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</motion.div>
|
||||
))}
|
||||
</Accordion>
|
||||
|
||||
<HStack mt={4} spacing={2}>
|
||||
<motion.div flex={1} whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
||||
<Button
|
||||
size="sm"
|
||||
w="full"
|
||||
variant="ghost"
|
||||
onClick={() => onToolsChange(MCP_TOOLS.map((t) => t.id))}
|
||||
bgGradient="linear(to-r, blue.500, purple.500)"
|
||||
color="white"
|
||||
_hover={{
|
||||
bgGradient: 'linear(to-r, blue.600, purple.600)',
|
||||
boxShadow: '0 4px 12px rgba(139, 92, 246, 0.4)',
|
||||
}}
|
||||
>
|
||||
全选
|
||||
</Button>
|
||||
</motion.div>
|
||||
<motion.div flex={1} whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
||||
<Button
|
||||
size="sm"
|
||||
w="full"
|
||||
variant="ghost"
|
||||
onClick={() => onToolsChange([])}
|
||||
bg="rgba(255, 255, 255, 0.05)"
|
||||
color="gray.300"
|
||||
border="1px solid"
|
||||
borderColor="rgba(255, 255, 255, 0.1)"
|
||||
_hover={{
|
||||
bg: 'rgba(255, 255, 255, 0.1)',
|
||||
borderColor: 'rgba(255, 255, 255, 0.2)',
|
||||
}}
|
||||
>
|
||||
清空
|
||||
</Button>
|
||||
</motion.div>
|
||||
</HStack>
|
||||
</TabPanel>
|
||||
|
||||
{/* 统计信息 */}
|
||||
<TabPanel p={4}>
|
||||
<VStack spacing={4} align="stretch">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0 }}
|
||||
>
|
||||
<Card
|
||||
bg="rgba(255, 255, 255, 0.05)"
|
||||
backdropFilter="blur(12px)"
|
||||
border="1px solid"
|
||||
borderColor="rgba(255, 255, 255, 0.1)"
|
||||
boxShadow="0 8px 32px 0 rgba(31, 38, 135, 0.37)"
|
||||
>
|
||||
<CardBody p={4}>
|
||||
<Flex align="center" justify="space-between">
|
||||
<Box>
|
||||
<Text fontSize="xs" color="gray.400">
|
||||
对话数
|
||||
</Text>
|
||||
<Text
|
||||
fontSize="2xl"
|
||||
fontWeight="bold"
|
||||
bgGradient="linear(to-r, blue.400, purple.400)"
|
||||
bgClip="text"
|
||||
>
|
||||
{sessionsCount}
|
||||
</Text>
|
||||
</Box>
|
||||
<MessageSquare
|
||||
className="w-8 h-8"
|
||||
color="#60A5FA"
|
||||
style={{ opacity: 0.5 }}
|
||||
/>
|
||||
</Flex>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
>
|
||||
<Card
|
||||
bg="rgba(255, 255, 255, 0.05)"
|
||||
backdropFilter="blur(12px)"
|
||||
border="1px solid"
|
||||
borderColor="rgba(255, 255, 255, 0.1)"
|
||||
boxShadow="0 8px 32px 0 rgba(31, 38, 135, 0.37)"
|
||||
>
|
||||
<CardBody p={4}>
|
||||
<Flex align="center" justify="space-between">
|
||||
<Box>
|
||||
<Text fontSize="xs" color="gray.400">
|
||||
消息数
|
||||
</Text>
|
||||
<Text
|
||||
fontSize="2xl"
|
||||
fontWeight="bold"
|
||||
bgGradient="linear(to-r, purple.400, pink.400)"
|
||||
bgClip="text"
|
||||
>
|
||||
{messagesCount}
|
||||
</Text>
|
||||
</Box>
|
||||
<Activity className="w-8 h-8" color="#C084FC" style={{ opacity: 0.5 }} />
|
||||
</Flex>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
>
|
||||
<Card
|
||||
bg="rgba(255, 255, 255, 0.05)"
|
||||
backdropFilter="blur(12px)"
|
||||
border="1px solid"
|
||||
borderColor="rgba(255, 255, 255, 0.1)"
|
||||
boxShadow="0 8px 32px 0 rgba(31, 38, 135, 0.37)"
|
||||
>
|
||||
<CardBody p={4}>
|
||||
<Flex align="center" justify="space-between">
|
||||
<Box>
|
||||
<Text fontSize="xs" color="gray.400">
|
||||
已选工具
|
||||
</Text>
|
||||
<Text
|
||||
fontSize="2xl"
|
||||
fontWeight="bold"
|
||||
bgGradient="linear(to-r, green.400, teal.400)"
|
||||
bgClip="text"
|
||||
>
|
||||
{selectedTools.length}
|
||||
</Text>
|
||||
</Box>
|
||||
<Code className="w-8 h-8" color="#34D399" style={{ opacity: 0.5 }} />
|
||||
</Flex>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</VStack>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</Box>
|
||||
</Box>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
};
|
||||
|
||||
export default RightSidebar;
|
||||
Reference in New Issue
Block a user