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