feat: 组组件拆分
This commit is contained in:
118
src/views/AgentChat/components/RightSidebar/ModelSelector.tsx
Normal file
118
src/views/AgentChat/components/RightSidebar/ModelSelector.tsx
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
// src/views/AgentChat/components/RightSidebar/ModelSelector.tsx
|
||||||
|
// 模型选择组件
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardBody,
|
||||||
|
HStack,
|
||||||
|
VStack,
|
||||||
|
Box,
|
||||||
|
Text,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { Check } from 'lucide-react';
|
||||||
|
import { AVAILABLE_MODELS } from '../../constants/models';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ModelSelector 组件的 Props 类型
|
||||||
|
*/
|
||||||
|
interface ModelSelectorProps {
|
||||||
|
/** 当前选中的模型 ID */
|
||||||
|
selectedModel: string;
|
||||||
|
/** 模型切换回调 */
|
||||||
|
onModelChange: (modelId: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ModelSelector - 模型选择组件
|
||||||
|
*
|
||||||
|
* 职责:
|
||||||
|
* 1. 渲染模型选择卡片列表
|
||||||
|
* 2. 显示模型图标、名称、描述
|
||||||
|
* 3. 高亮当前选中模型(紫色边框 + 发光效果)
|
||||||
|
* 4. 显示选中标记(Check 图标 + 弹簧动画)
|
||||||
|
*
|
||||||
|
* 设计特性:
|
||||||
|
* - 卡片渐进入场动画(延迟 `idx * 0.1`)
|
||||||
|
* - 悬停缩放 1.02x + 上移 2px
|
||||||
|
* - 选中态:紫色边框 + 发光效果
|
||||||
|
* - 模型图标渐变色背景
|
||||||
|
*/
|
||||||
|
const ModelSelector: React.FC<ModelSelectorProps> = ({ selectedModel, onModelChange }) => {
|
||||||
|
return (
|
||||||
|
<VStack spacing={3} align="stretch">
|
||||||
|
{AVAILABLE_MODELS.map((model, idx) => {
|
||||||
|
const isSelected = selectedModel === model.id;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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={isSelected ? 'rgba(139, 92, 246, 0.15)' : 'rgba(255, 255, 255, 0.05)'}
|
||||||
|
backdropFilter="blur(12px)"
|
||||||
|
borderWidth={2}
|
||||||
|
borderColor={isSelected ? 'purple.400' : 'rgba(255, 255, 255, 0.1)'}
|
||||||
|
_hover={{
|
||||||
|
borderColor: isSelected ? 'purple.400' : 'rgba(255, 255, 255, 0.2)',
|
||||||
|
boxShadow: isSelected
|
||||||
|
? '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={
|
||||||
|
isSelected
|
||||||
|
? 'linear(to-br, purple.500, pink.500)'
|
||||||
|
: 'linear(to-br, rgba(139, 92, 246, 0.2), rgba(236, 72, 153, 0.2))'
|
||||||
|
}
|
||||||
|
boxShadow={isSelected ? '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>
|
||||||
|
|
||||||
|
{/* 选中标记(Check 图标) */}
|
||||||
|
{isSelected && (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModelSelector;
|
||||||
119
src/views/AgentChat/components/RightSidebar/Statistics.tsx
Normal file
119
src/views/AgentChat/components/RightSidebar/Statistics.tsx
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
// src/views/AgentChat/components/RightSidebar/Statistics.tsx
|
||||||
|
// 统计信息组件
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardBody,
|
||||||
|
VStack,
|
||||||
|
Flex,
|
||||||
|
Box,
|
||||||
|
Text,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { MessageSquare, Activity, Code } from 'lucide-react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Statistics 组件的 Props 类型
|
||||||
|
*/
|
||||||
|
interface StatisticsProps {
|
||||||
|
/** 对话总数 */
|
||||||
|
sessionsCount: number;
|
||||||
|
/** 消息总数 */
|
||||||
|
messagesCount: number;
|
||||||
|
/** 已选工具数 */
|
||||||
|
selectedToolsCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Statistics - 统计信息组件
|
||||||
|
*
|
||||||
|
* 职责:
|
||||||
|
* 1. 显示统计卡片(对话数、消息数、已选工具数)
|
||||||
|
* 2. 渐变色大数字展示
|
||||||
|
* 3. 图标装饰
|
||||||
|
*
|
||||||
|
* 设计特性:
|
||||||
|
* - 卡片渐进入场动画(延迟 `idx * 0.1`)
|
||||||
|
* - 毛玻璃效果卡片
|
||||||
|
* - 渐变色数字(蓝紫/紫粉/绿青)
|
||||||
|
* - 半透明图标装饰
|
||||||
|
*/
|
||||||
|
const Statistics: React.FC<StatisticsProps> = ({
|
||||||
|
sessionsCount,
|
||||||
|
messagesCount,
|
||||||
|
selectedToolsCount,
|
||||||
|
}) => {
|
||||||
|
const stats = [
|
||||||
|
{
|
||||||
|
label: '对话数',
|
||||||
|
value: sessionsCount,
|
||||||
|
gradient: 'linear(to-r, blue.400, purple.400)',
|
||||||
|
icon: MessageSquare,
|
||||||
|
iconColor: '#60A5FA',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '消息数',
|
||||||
|
value: messagesCount,
|
||||||
|
gradient: 'linear(to-r, purple.400, pink.400)',
|
||||||
|
icon: Activity,
|
||||||
|
iconColor: '#C084FC',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '已选工具',
|
||||||
|
value: selectedToolsCount,
|
||||||
|
gradient: 'linear(to-r, green.400, teal.400)',
|
||||||
|
icon: Code,
|
||||||
|
iconColor: '#34D399',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VStack spacing={4} align="stretch">
|
||||||
|
{stats.map((stat, idx) => {
|
||||||
|
const Icon = stat.icon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
key={stat.label}
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: idx * 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">
|
||||||
|
{stat.label}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
fontSize="2xl"
|
||||||
|
fontWeight="bold"
|
||||||
|
bgGradient={stat.gradient}
|
||||||
|
bgClip="text"
|
||||||
|
>
|
||||||
|
{stat.value}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* 右侧:图标装饰 */}
|
||||||
|
<Icon className="w-8 h-8" color={stat.iconColor} style={{ opacity: 0.5 }} />
|
||||||
|
</Flex>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</VStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Statistics;
|
||||||
@@ -155,46 +155,42 @@ const ToolSelector: React.FC<ToolSelectorProps> = ({ selectedTools, onToolsChang
|
|||||||
{/* 全选/清空按钮 */}
|
{/* 全选/清空按钮 */}
|
||||||
<HStack mt={4} spacing={2}>
|
<HStack mt={4} spacing={2}>
|
||||||
{/* 全选按钮 */}
|
{/* 全选按钮 */}
|
||||||
<Box flex={1}>
|
<motion.div flex={1} whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
||||||
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
<Button
|
||||||
<Button
|
size="sm"
|
||||||
size="sm"
|
w="full"
|
||||||
w="full"
|
variant="ghost"
|
||||||
variant="ghost"
|
onClick={handleSelectAll}
|
||||||
onClick={handleSelectAll}
|
bgGradient="linear(to-r, blue.500, purple.500)"
|
||||||
bgGradient="linear(to-r, blue.500, purple.500)"
|
color="white"
|
||||||
color="white"
|
_hover={{
|
||||||
_hover={{
|
bgGradient: 'linear(to-r, blue.600, purple.600)',
|
||||||
bgGradient: 'linear(to-r, blue.600, purple.600)',
|
boxShadow: '0 4px 12px rgba(139, 92, 246, 0.4)',
|
||||||
boxShadow: '0 4px 12px rgba(139, 92, 246, 0.4)',
|
}}
|
||||||
}}
|
>
|
||||||
>
|
全选
|
||||||
全选
|
</Button>
|
||||||
</Button>
|
</motion.div>
|
||||||
</motion.div>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* 清空按钮 */}
|
{/* 清空按钮 */}
|
||||||
<Box flex={1}>
|
<motion.div flex={1} whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
||||||
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
<Button
|
||||||
<Button
|
size="sm"
|
||||||
size="sm"
|
w="full"
|
||||||
w="full"
|
variant="ghost"
|
||||||
variant="ghost"
|
onClick={handleClearAll}
|
||||||
onClick={handleClearAll}
|
bg="rgba(255, 255, 255, 0.05)"
|
||||||
bg="rgba(255, 255, 255, 0.05)"
|
color="gray.300"
|
||||||
color="gray.300"
|
border="1px solid"
|
||||||
border="1px solid"
|
borderColor="rgba(255, 255, 255, 0.1)"
|
||||||
borderColor="rgba(255, 255, 255, 0.1)"
|
_hover={{
|
||||||
_hover={{
|
bg: 'rgba(255, 255, 255, 0.1)',
|
||||||
bg: 'rgba(255, 255, 255, 0.1)',
|
borderColor: 'rgba(255, 255, 255, 0.2)',
|
||||||
borderColor: 'rgba(255, 255, 255, 0.2)',
|
}}
|
||||||
}}
|
>
|
||||||
>
|
清空
|
||||||
清空
|
</Button>
|
||||||
</Button>
|
</motion.div>
|
||||||
</motion.div>
|
|
||||||
</Box>
|
|
||||||
</HStack>
|
</HStack>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,499 +0,0 @@
|
|||||||
// 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"
|
|
||||||
h="100%"
|
|
||||||
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;
|
|
||||||
240
src/views/AgentChat/components/RightSidebar/index.tsx
Normal file
240
src/views/AgentChat/components/RightSidebar/index.tsx
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
// src/views/AgentChat/components/RightSidebar/index.tsx
|
||||||
|
// 右侧栏组件 - 配置中心(重构版本)
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Tooltip,
|
||||||
|
IconButton,
|
||||||
|
Tabs,
|
||||||
|
TabList,
|
||||||
|
TabPanels,
|
||||||
|
Tab,
|
||||||
|
TabPanel,
|
||||||
|
Badge,
|
||||||
|
HStack,
|
||||||
|
Text,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
Settings,
|
||||||
|
ChevronRight,
|
||||||
|
Cpu,
|
||||||
|
Code,
|
||||||
|
BarChart3,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { animations } from '../../constants/animations';
|
||||||
|
import ModelSelector from './ModelSelector';
|
||||||
|
import ToolSelector from './ToolSelector';
|
||||||
|
import Statistics from './Statistics';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RightSidebar 组件的 Props 类型
|
||||||
|
*/
|
||||||
|
interface RightSidebarProps {
|
||||||
|
/** 侧边栏是否展开 */
|
||||||
|
isOpen: boolean;
|
||||||
|
/** 关闭侧边栏回调 */
|
||||||
|
onClose: () => void;
|
||||||
|
/** 当前选中的模型 ID */
|
||||||
|
selectedModel: string;
|
||||||
|
/** 模型切换回调 */
|
||||||
|
onModelChange: (modelId: string) => void;
|
||||||
|
/** 已选工具 ID 列表 */
|
||||||
|
selectedTools: string[];
|
||||||
|
/** 工具选择变化回调 */
|
||||||
|
onToolsChange: (tools: string[]) => void;
|
||||||
|
/** 会话总数 */
|
||||||
|
sessionsCount: number;
|
||||||
|
/** 消息总数 */
|
||||||
|
messagesCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RightSidebar - 右侧栏组件(配置中心)(重构版本)
|
||||||
|
*
|
||||||
|
* 架构改进:
|
||||||
|
* - 模型选择提取到 ModelSelector 组件(120 行)
|
||||||
|
* - 工具选择提取到 ToolSelector 组件(200 行)
|
||||||
|
* - 统计信息提取到 Statistics 组件(100 行)
|
||||||
|
* - 主组件只负责 Tabs 管理和布局组合(150 行)
|
||||||
|
*
|
||||||
|
* 主组件职责:
|
||||||
|
* 1. 管理 Tabs 切换(模型/工具/统计)
|
||||||
|
* 2. 渲染标题栏(配置中心 + 收起按钮)
|
||||||
|
* 3. 组合三个子组件(TabPanels)
|
||||||
|
* 4. 处理侧边栏动画(滑入/滑出)
|
||||||
|
*/
|
||||||
|
const RightSidebar: React.FC<RightSidebarProps> = ({
|
||||||
|
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"
|
||||||
|
h="100%"
|
||||||
|
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}
|
||||||
|
aria-label="收起侧边栏"
|
||||||
|
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">
|
||||||
|
{/* Tab 标签栏 */}
|
||||||
|
<TabList px={4} borderBottom="1px solid" borderColor="rgba(255, 255, 255, 0.1)">
|
||||||
|
{/* 模型 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}>
|
||||||
|
<Cpu className="w-4 h-4" />
|
||||||
|
<Text>模型</Text>
|
||||||
|
</HStack>
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
{/* 工具 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 */}
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{/* Tab 内容面板 */}
|
||||||
|
<TabPanels>
|
||||||
|
{/* 模型选择面板 */}
|
||||||
|
<TabPanel p={4}>
|
||||||
|
<ModelSelector
|
||||||
|
selectedModel={selectedModel}
|
||||||
|
onModelChange={onModelChange}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
|
{/* 工具选择面板 */}
|
||||||
|
<TabPanel p={4}>
|
||||||
|
<ToolSelector
|
||||||
|
selectedTools={selectedTools}
|
||||||
|
onToolsChange={onToolsChange}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
|
{/* 统计信息面板 */}
|
||||||
|
<TabPanel p={4}>
|
||||||
|
<Statistics
|
||||||
|
sessionsCount={sessionsCount}
|
||||||
|
messagesCount={messagesCount}
|
||||||
|
selectedToolsCount={selectedTools.length}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
</TabPanels>
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RightSidebar;
|
||||||
27
src/views/AgentChat/components/RightSidebar/types.ts
Normal file
27
src/views/AgentChat/components/RightSidebar/types.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// src/views/AgentChat/components/RightSidebar/types.ts
|
||||||
|
// RightSidebar 组件的 TypeScript 类型定义
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型数据结构(来自 constants/models)
|
||||||
|
*/
|
||||||
|
export interface Model {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
icon: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具数据结构(来自 constants/tools)
|
||||||
|
*/
|
||||||
|
export interface Tool {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
icon: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具分类(Map<分类名, 工具列表>)
|
||||||
|
*/
|
||||||
|
export type ToolCategories = Record<string, Tool[]>;
|
||||||
Reference in New Issue
Block a user