first-update
This commit is contained in:
112
easy-dataset-main/components/Navbar/ActionButtons.js
Normal file
112
easy-dataset-main/components/Navbar/ActionButtons.js
Normal file
@@ -0,0 +1,112 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Box, IconButton, Tooltip } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import LightModeOutlinedIcon from '@mui/icons-material/LightModeOutlined';
|
||||
import DarkModeOutlinedIcon from '@mui/icons-material/DarkModeOutlined';
|
||||
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
|
||||
import GitHubIcon from '@mui/icons-material/GitHub';
|
||||
import BarChartIcon from '@mui/icons-material/BarChart';
|
||||
import LanguageSwitcher from '../LanguageSwitcher';
|
||||
import UpdateChecker from '../UpdateChecker';
|
||||
import TaskIcon from '../TaskIcon';
|
||||
import ModelSelect from '../ModelSelect';
|
||||
import * as styles from './styles';
|
||||
|
||||
/**
|
||||
* ActionButtons 组件
|
||||
* 右侧操作区按钮:语言切换、主题切换、文档、GitHub、更新检查
|
||||
*/
|
||||
export default function ActionButtons({
|
||||
theme,
|
||||
resolvedTheme,
|
||||
toggleTheme,
|
||||
isProjectDetail,
|
||||
currentProject,
|
||||
onActionAreaEnter
|
||||
}) {
|
||||
const { t, i18n } = useTranslation();
|
||||
const isZhLanguage = String(i18n.language || '')
|
||||
.toLowerCase()
|
||||
.startsWith('zh');
|
||||
|
||||
return (
|
||||
<Box sx={styles.actionAreaStyles} onMouseEnter={onActionAreaEnter}>
|
||||
{isProjectDetail && <ModelSelect projectId={currentProject} />}
|
||||
{isProjectDetail && <TaskIcon theme={theme} projectId={currentProject} />}
|
||||
|
||||
{/* Monitoring Dashboard - Only visible on Home page */}
|
||||
{!isProjectDetail && (
|
||||
<Tooltip title={t('monitoring.title', 'Resource Monitoring')}>
|
||||
<IconButton component="a" href="/monitoring" size="medium" sx={styles.getIconButtonStyles(theme)}>
|
||||
<BarChartIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* Language Switcher - Always visible */}
|
||||
<LanguageSwitcher />
|
||||
|
||||
{/* Theme Toggle - Always visible */}
|
||||
<Tooltip
|
||||
title={
|
||||
resolvedTheme === 'dark'
|
||||
? t('theme.switchToLight', 'Switch to light mode')
|
||||
: t('theme.switchToDark', 'Switch to dark mode')
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
onClick={toggleTheme}
|
||||
size="medium"
|
||||
aria-label={
|
||||
resolvedTheme === 'dark'
|
||||
? t('theme.switchToLight', 'Switch to light mode')
|
||||
: t('theme.switchToDark', 'Switch to dark mode')
|
||||
}
|
||||
sx={styles.getIconButtonStyles(theme)}
|
||||
>
|
||||
{resolvedTheme === 'dark' ? (
|
||||
<LightModeOutlinedIcon fontSize="small" />
|
||||
) : (
|
||||
<DarkModeOutlinedIcon fontSize="small" />
|
||||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
{/* Documentation - Hide below xl */}
|
||||
<Tooltip title={t('documentation')}>
|
||||
<IconButton
|
||||
component="a"
|
||||
href={isZhLanguage ? 'https://docs.easy-dataset.com/' : 'https://docs.easy-dataset.com/ed/en'}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
size="medium"
|
||||
sx={styles.getIconButtonStyles(theme)}
|
||||
>
|
||||
<HelpOutlineIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
{/* GitHub - Hide at larger tablet screens and below */}
|
||||
<Tooltip title={t('common.visitGitHub', 'View on GitHub')}>
|
||||
<IconButton
|
||||
component="a"
|
||||
href="https://github.com/ConardLi/easy-dataset"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
size="medium"
|
||||
aria-label={t('common.visitGitHub', 'Open GitHub repository')}
|
||||
sx={styles.getIconButtonStyles(theme)}
|
||||
>
|
||||
<GitHubIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
{/* Update Checker - Hide below xl */}
|
||||
<Box sx={{ display: { xs: 'none', xl: 'flex' } }}>
|
||||
<UpdateChecker />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
175
easy-dataset-main/components/Navbar/ContextBar.js
Normal file
175
easy-dataset-main/components/Navbar/ContextBar.js
Normal file
@@ -0,0 +1,175 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Chip,
|
||||
Typography,
|
||||
useTheme,
|
||||
Menu,
|
||||
MenuItem,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Paper,
|
||||
Tooltip
|
||||
} from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { modelConfigListAtom, selectedModelInfoAtom } from '@/lib/store';
|
||||
import { toast } from 'sonner';
|
||||
import axios from 'axios';
|
||||
|
||||
// Icons
|
||||
import FolderIcon from '@mui/icons-material/Folder';
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||
|
||||
// 样式
|
||||
import * as styles from './contextBarStyles';
|
||||
|
||||
export default function ContextBar({ projects = [], currentProjectId, onMouseLeave }) {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const router = useRouter();
|
||||
|
||||
// State
|
||||
const [projectMenuAnchor, setProjectMenuAnchor] = useState(null);
|
||||
|
||||
// Jotai atoms
|
||||
const setConfigList = useSetAtom(modelConfigListAtom);
|
||||
const setSelectedModelInfo = useSetAtom(selectedModelInfoAtom);
|
||||
|
||||
// Get current project
|
||||
const currentProject = projects.find(p => p.id === currentProjectId);
|
||||
|
||||
// Handlers
|
||||
const handleProjectMenuOpen = event => {
|
||||
event.preventDefault();
|
||||
setProjectMenuAnchor(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleProjectMenuClose = () => {
|
||||
setProjectMenuAnchor(null);
|
||||
// 菜单关闭时,如果提供了 onMouseLeave 回调,则调用它
|
||||
if (onMouseLeave) {
|
||||
onMouseLeave();
|
||||
}
|
||||
};
|
||||
|
||||
const handleProjectChange = async newProjectId => {
|
||||
handleProjectMenuClose();
|
||||
|
||||
try {
|
||||
// Fetch model config for new project
|
||||
const response = await axios.get(`/api/projects/${newProjectId}/model-config`);
|
||||
setConfigList(response.data.data);
|
||||
|
||||
if (response.data.defaultModelConfigId) {
|
||||
const defaultModel = response.data.data.find(item => item.id === response.data.defaultModelConfigId);
|
||||
setSelectedModelInfo(defaultModel || null);
|
||||
} else {
|
||||
setSelectedModelInfo(null);
|
||||
}
|
||||
|
||||
// Navigate to the new project's text-split page
|
||||
router.push(`/projects/${newProjectId}/text-split`);
|
||||
} catch (error) {
|
||||
console.error('Error switching project:', error);
|
||||
toast.error(t('common.error', 'Error switching project'));
|
||||
}
|
||||
};
|
||||
|
||||
if (!currentProjectId || !currentProject) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper
|
||||
elevation={0}
|
||||
component="nav"
|
||||
aria-label={t('common.contextNavigation', 'Context navigation')}
|
||||
sx={styles.getContextBarPaperStyles(theme)}
|
||||
>
|
||||
<Box sx={styles.contextBarContainerStyles}>
|
||||
{/* Project Selector */}
|
||||
<Box sx={styles.selectorContainerStyles}>
|
||||
<Typography variant="caption" sx={styles.labelTypographyStyles}>
|
||||
{t('common.project', 'Project')}:
|
||||
</Typography>
|
||||
<Tooltip
|
||||
title={currentProject?.name || t('projects.selectProject', 'Select Project')}
|
||||
placement="bottom-start"
|
||||
arrow
|
||||
>
|
||||
<Chip
|
||||
icon={<FolderIcon fontSize="small" />}
|
||||
label={
|
||||
<Box sx={styles.chipLabelBoxStyles}>
|
||||
<Typography variant="body2" noWrap sx={styles.chipTextStyles}>
|
||||
{currentProject?.name || t('projects.selectProject', 'Select Project')}
|
||||
</Typography>
|
||||
<ArrowDropDownIcon fontSize="small" sx={styles.chipArrowStyles} />
|
||||
</Box>
|
||||
}
|
||||
onClick={handleProjectMenuOpen}
|
||||
clickable
|
||||
variant="outlined"
|
||||
size="medium"
|
||||
sx={styles.getProjectChipStyles(theme)}
|
||||
aria-label={t('projects.selectProject', 'Select project')}
|
||||
aria-controls={projectMenuAnchor ? 'project-menu' : undefined}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={Boolean(projectMenuAnchor)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Project Menu */}
|
||||
<Menu
|
||||
id="project-menu"
|
||||
anchorEl={projectMenuAnchor}
|
||||
open={Boolean(projectMenuAnchor)}
|
||||
onClose={handleProjectMenuClose}
|
||||
role="menu"
|
||||
aria-label={t('projects.projectMenu', 'Project menu')}
|
||||
PaperProps={{
|
||||
elevation: 8,
|
||||
sx: styles.getMenuPaperStyles(theme)
|
||||
}}
|
||||
transformOrigin={{ horizontal: 'left', vertical: 'top' }}
|
||||
anchorOrigin={{ horizontal: 'left', vertical: 'bottom' }}
|
||||
MenuListProps={{
|
||||
'aria-labelledby': 'project-selector',
|
||||
...styles.menuListPropsStyles
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption" sx={styles.menuHeaderTypographyStyles}>
|
||||
{t('projects.allProjects', 'All Projects')}
|
||||
</Typography>
|
||||
{projects.map((project, index) => (
|
||||
<MenuItem
|
||||
key={project.id}
|
||||
onClick={() => handleProjectChange(project.id)}
|
||||
selected={project.id === currentProjectId}
|
||||
role="menuitem"
|
||||
sx={styles.getMenuItemStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.menuItemIconStyles}>
|
||||
{project.id === currentProjectId ? (
|
||||
<CheckIcon fontSize="small" color="primary" />
|
||||
) : (
|
||||
<FolderIcon fontSize="small" />
|
||||
)}
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={project.name}
|
||||
primaryTypographyProps={styles.getMenuItemTextPrimaryProps(project.id === currentProjectId)}
|
||||
/>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
315
easy-dataset-main/components/Navbar/DesktopMenus.js
Normal file
315
easy-dataset-main/components/Navbar/DesktopMenus.js
Normal file
@@ -0,0 +1,315 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Menu, MenuItem, ListItemIcon, ListItemText, Divider } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Link from 'next/link';
|
||||
import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined';
|
||||
import ImageIcon from '@mui/icons-material/Image';
|
||||
import DatasetOutlinedIcon from '@mui/icons-material/DatasetOutlined';
|
||||
import ChatIcon from '@mui/icons-material/Chat';
|
||||
import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined';
|
||||
import ScienceOutlinedIcon from '@mui/icons-material/ScienceOutlined';
|
||||
import StorageIcon from '@mui/icons-material/Storage';
|
||||
import AssessmentOutlinedIcon from '@mui/icons-material/AssessmentOutlined';
|
||||
import PlaylistPlayIcon from '@mui/icons-material/PlaylistPlay';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import * as styles from './styles';
|
||||
|
||||
/**
|
||||
* DesktopMenus 缂備礁瀚▎?
|
||||
* 婵℃鐭傚鎵博椤栨稑浜鹃柛瀣矎瑜板秹宕¢弴顏嗙闁告牕鎳庨幆鍫ュ极閻楀牆绁︽繝褎鍔戦埀顑跨劍閺嗙喖骞戦鈧▔锔剧不閿涘嫭鍊為柕鍡曠劍濞叉寧寰勫顐ょ憦濞戞搩浜hぐ宥夊础?
|
||||
*/
|
||||
export default function DesktopMenus({
|
||||
theme,
|
||||
menuState,
|
||||
isMenuOpen,
|
||||
handleMenuClose,
|
||||
currentProject,
|
||||
onNavigateStart
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 闁轰胶澧楀畵浣糕攦閹邦垰缍呴柛?*/}
|
||||
<Menu
|
||||
anchorEl={menuState.anchorEl}
|
||||
open={isMenuOpen('source')}
|
||||
onClose={handleMenuClose}
|
||||
hideBackdrop
|
||||
disableScrollLock
|
||||
sx={{ pointerEvents: 'none' }}
|
||||
aria-label={t('common.dataSource', 'Data source menu')}
|
||||
PaperProps={{
|
||||
elevation: 8,
|
||||
sx: {
|
||||
...styles.getMenuPaperStyles(theme),
|
||||
pointerEvents: 'auto'
|
||||
},
|
||||
onMouseLeave: handleMenuClose
|
||||
}}
|
||||
transformOrigin={{ horizontal: 'center', vertical: 'top' }}
|
||||
anchorOrigin={{ horizontal: 'center', vertical: 'bottom' }}
|
||||
MenuListProps={{
|
||||
dense: false,
|
||||
onMouseLeave: handleMenuClose,
|
||||
sx: styles.menuListStyles,
|
||||
role: 'menu'
|
||||
}}
|
||||
transitionDuration={200}
|
||||
>
|
||||
<MenuItem
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/text-split`}
|
||||
onClick={() => {
|
||||
onNavigateStart?.();
|
||||
handleMenuClose();
|
||||
}}
|
||||
role="menuitem"
|
||||
sx={styles.getMenuItemStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.listItemIconStyles}>
|
||||
<DescriptionOutlinedIcon fontSize="small" sx={styles.getPrimaryIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('textSplit.title')} primaryTypographyProps={styles.smallListItemTextStyles} />
|
||||
</MenuItem>
|
||||
<Divider sx={{ my: 0.75, mx: 1.5 }} />
|
||||
<MenuItem
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/images`}
|
||||
onClick={() => {
|
||||
onNavigateStart?.();
|
||||
handleMenuClose();
|
||||
}}
|
||||
role="menuitem"
|
||||
sx={styles.getMenuItemStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.listItemIconStyles}>
|
||||
<ImageIcon fontSize="small" sx={styles.getPrimaryIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('images.title')} primaryTypographyProps={styles.smallListItemTextStyles} />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
{/* 闁轰胶澧楀畵渚€姊块崱娆樺悁闁荤偛妫滆ぐ宥夊础?*/}
|
||||
<Menu
|
||||
anchorEl={menuState.anchorEl}
|
||||
open={isMenuOpen('dataset')}
|
||||
onClose={handleMenuClose}
|
||||
hideBackdrop
|
||||
disableScrollLock
|
||||
sx={{ pointerEvents: 'none' }}
|
||||
PaperProps={{
|
||||
elevation: 8,
|
||||
sx: {
|
||||
...styles.getSimpleMenuPaperStyles(theme),
|
||||
pointerEvents: 'auto'
|
||||
},
|
||||
onMouseLeave: handleMenuClose
|
||||
}}
|
||||
transformOrigin={{ horizontal: 'center', vertical: 'top' }}
|
||||
anchorOrigin={{ horizontal: 'center', vertical: 'bottom' }}
|
||||
MenuListProps={{
|
||||
dense: true,
|
||||
onMouseLeave: handleMenuClose,
|
||||
sx: styles.simpleMenuListStyles
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/datasets`}
|
||||
onClick={() => {
|
||||
onNavigateStart?.();
|
||||
handleMenuClose();
|
||||
}}
|
||||
sx={styles.getSimpleMenuItemStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<DatasetOutlinedIcon fontSize="small" sx={styles.getPrimaryIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t('datasets.singleTurn', '单轮问答数据集')}
|
||||
primaryTypographyProps={styles.smallListItemTextStyles}
|
||||
/>
|
||||
</MenuItem>
|
||||
<Divider sx={{ my: 0.5, mx: 1 }} />
|
||||
<MenuItem
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/multi-turn`}
|
||||
onClick={() => {
|
||||
onNavigateStart?.();
|
||||
handleMenuClose();
|
||||
}}
|
||||
sx={styles.getSimpleMenuItemStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<ChatIcon fontSize="small" sx={styles.getPrimaryIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t('datasets.multiTurn', '多轮对话数据集')}
|
||||
primaryTypographyProps={styles.smallListItemTextStyles}
|
||||
/>
|
||||
</MenuItem>
|
||||
<Divider sx={{ my: 0.5, mx: 1 }} />
|
||||
<MenuItem
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/image-datasets`}
|
||||
onClick={() => {
|
||||
onNavigateStart?.();
|
||||
handleMenuClose();
|
||||
}}
|
||||
sx={styles.getSimpleMenuItemStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<ImageIcon fontSize="small" sx={styles.getPrimaryIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t('datasets.imageQA', '图片问答数据集')}
|
||||
primaryTypographyProps={styles.smallListItemTextStyles}
|
||||
/>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
{/* 閻犲洤瀚崣濠囨嚕濠婂啫绀?*/}
|
||||
<Menu
|
||||
anchorEl={menuState.anchorEl}
|
||||
open={isMenuOpen('eval')}
|
||||
onClose={handleMenuClose}
|
||||
hideBackdrop
|
||||
disableScrollLock
|
||||
sx={{ pointerEvents: 'none' }}
|
||||
PaperProps={{
|
||||
elevation: 8,
|
||||
sx: {
|
||||
...styles.getSimpleMenuPaperStyles(theme),
|
||||
pointerEvents: 'auto'
|
||||
},
|
||||
onMouseLeave: handleMenuClose
|
||||
}}
|
||||
transformOrigin={{ horizontal: 'center', vertical: 'top' }}
|
||||
anchorOrigin={{ horizontal: 'center', vertical: 'bottom' }}
|
||||
MenuListProps={{
|
||||
dense: true,
|
||||
onMouseLeave: handleMenuClose,
|
||||
sx: styles.simpleMenuListStyles
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/eval-datasets`}
|
||||
onClick={() => {
|
||||
onNavigateStart?.();
|
||||
handleMenuClose();
|
||||
}}
|
||||
sx={styles.getSimpleMenuItemStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<AssessmentOutlinedIcon fontSize="small" sx={styles.getPrimaryIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('eval.datasets')} primaryTypographyProps={styles.smallListItemTextStyles} />
|
||||
</MenuItem>
|
||||
<Divider sx={{ my: 0.5, mx: 1 }} />
|
||||
<MenuItem
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/eval-tasks`}
|
||||
onClick={() => {
|
||||
onNavigateStart?.();
|
||||
handleMenuClose();
|
||||
}}
|
||||
sx={styles.getSimpleMenuItemStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<PlaylistPlayIcon fontSize="small" sx={styles.getPrimaryIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('eval.tasks')} primaryTypographyProps={styles.smallListItemTextStyles} />
|
||||
</MenuItem>
|
||||
<Divider sx={{ my: 0.5, mx: 1 }} />
|
||||
<MenuItem
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/blind-test-tasks`}
|
||||
onClick={() => {
|
||||
onNavigateStart?.();
|
||||
handleMenuClose();
|
||||
}}
|
||||
sx={styles.getSimpleMenuItemStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<VisibilityIcon fontSize="small" sx={styles.getPrimaryIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('blindTest.title')} primaryTypographyProps={styles.smallListItemTextStyles} />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
{/* 闁哄洦娼欓ˇ鍧楁嚕濠婂啫绀?*/}
|
||||
<Menu
|
||||
anchorEl={menuState.anchorEl}
|
||||
open={isMenuOpen('more')}
|
||||
onClose={handleMenuClose}
|
||||
hideBackdrop
|
||||
disableScrollLock
|
||||
sx={{ pointerEvents: 'none' }}
|
||||
PaperProps={{
|
||||
elevation: 8,
|
||||
sx: {
|
||||
...styles.getSimpleMenuPaperStyles(theme),
|
||||
pointerEvents: 'auto'
|
||||
},
|
||||
onMouseLeave: handleMenuClose
|
||||
}}
|
||||
transformOrigin={{ horizontal: 'center', vertical: 'top' }}
|
||||
anchorOrigin={{ horizontal: 'center', vertical: 'bottom' }}
|
||||
MenuListProps={{
|
||||
dense: true,
|
||||
onMouseLeave: handleMenuClose,
|
||||
sx: styles.simpleMenuListStyles
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/settings`}
|
||||
onClick={() => {
|
||||
onNavigateStart?.();
|
||||
handleMenuClose();
|
||||
}}
|
||||
sx={styles.getSimpleMenuItemStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<SettingsOutlinedIcon fontSize="small" sx={styles.getPrimaryIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('settings.title')} primaryTypographyProps={styles.smallListItemTextStyles} />
|
||||
</MenuItem>
|
||||
<Divider sx={{ my: 0.5, mx: 1 }} />
|
||||
<MenuItem
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/playground`}
|
||||
onClick={() => {
|
||||
onNavigateStart?.();
|
||||
handleMenuClose();
|
||||
}}
|
||||
sx={styles.getSimpleMenuItemStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<ScienceOutlinedIcon fontSize="small" sx={styles.getPrimaryIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('playground.title')} primaryTypographyProps={styles.smallListItemTextStyles} />
|
||||
</MenuItem>
|
||||
<Divider sx={{ my: 0.5, mx: 1 }} />
|
||||
<MenuItem
|
||||
component={Link}
|
||||
href="/dataset-square"
|
||||
onClick={() => {
|
||||
onNavigateStart?.();
|
||||
handleMenuClose();
|
||||
}}
|
||||
sx={styles.getSimpleMenuItemStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<StorageIcon fontSize="small" sx={styles.getPrimaryIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('datasetSquare.title')} primaryTypographyProps={styles.smallListItemTextStyles} />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
}
|
||||
42
easy-dataset-main/components/Navbar/Logo.js
Normal file
42
easy-dataset-main/components/Navbar/Logo.js
Normal file
@@ -0,0 +1,42 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Box, Typography, Tooltip } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import * as styles from './styles';
|
||||
|
||||
/**
|
||||
* Logo 组件
|
||||
* 显示应用 Logo 和标题,支持点击跳转到首页
|
||||
*/
|
||||
export default function Logo({ theme }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Tooltip title={t('common.goHome', 'Go to Home')} placement="bottom">
|
||||
<Box
|
||||
component="a"
|
||||
href="/"
|
||||
role="link"
|
||||
aria-label={t('common.goToHomePage', 'Go to home page')}
|
||||
tabIndex={0}
|
||||
sx={styles.getLogoLinkStyles(theme)}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
window.location.href = '/';
|
||||
}}
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
window.location.href = '/';
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box component="img" src="/imgs/logo.svg" alt="Easy Dataset Logo" sx={styles.logoImageStyles} />
|
||||
<Typography variant="h6" sx={styles.getLogoTextStyles(theme)}>
|
||||
Easy DataSet
|
||||
</Typography>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
405
easy-dataset-main/components/Navbar/MobileDrawer.js
Normal file
405
easy-dataset-main/components/Navbar/MobileDrawer.js
Normal file
@@ -0,0 +1,405 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Drawer,
|
||||
Box,
|
||||
Typography,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Collapse
|
||||
} from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Link from 'next/link';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined';
|
||||
import TokenOutlinedIcon from '@mui/icons-material/TokenOutlined';
|
||||
import QuestionAnswerOutlinedIcon from '@mui/icons-material/QuestionAnswerOutlined';
|
||||
import DatasetOutlinedIcon from '@mui/icons-material/DatasetOutlined';
|
||||
import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined';
|
||||
import ScienceOutlinedIcon from '@mui/icons-material/ScienceOutlined';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import ChatIcon from '@mui/icons-material/Chat';
|
||||
import ImageIcon from '@mui/icons-material/Image';
|
||||
import StorageIcon from '@mui/icons-material/Storage';
|
||||
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
|
||||
import GitHubIcon from '@mui/icons-material/GitHub';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||
import AssessmentOutlinedIcon from '@mui/icons-material/AssessmentOutlined';
|
||||
import PlaylistPlayIcon from '@mui/icons-material/PlaylistPlay';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import UpdateChecker from '../UpdateChecker';
|
||||
import * as styles from './styles';
|
||||
|
||||
/**
|
||||
* MobileDrawer 组件
|
||||
* 移动端抽屉菜单,包含所有导航项
|
||||
*/
|
||||
export default function MobileDrawer({
|
||||
theme,
|
||||
drawerOpen,
|
||||
toggleDrawer,
|
||||
expandedMenu,
|
||||
toggleMobileSubmenu,
|
||||
currentProject,
|
||||
onNavigateStart
|
||||
}) {
|
||||
const { t, i18n } = useTranslation();
|
||||
const handleNavigateStart = () => {
|
||||
onNavigateStart?.();
|
||||
toggleDrawer();
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
id="mobile-navigation-drawer"
|
||||
anchor="left"
|
||||
open={drawerOpen}
|
||||
onClose={toggleDrawer}
|
||||
PaperProps={{
|
||||
role: 'navigation',
|
||||
'aria-label': t('common.mobileNavigation', 'Mobile navigation menu'),
|
||||
sx: styles.getDrawerPaperStyles(theme)
|
||||
}}
|
||||
ModalProps={{
|
||||
keepMounted: true // Better mobile performance
|
||||
}}
|
||||
transitionDuration={300}
|
||||
SlideProps={{
|
||||
easing: 'cubic-bezier(0.4, 0, 0.2, 1)'
|
||||
}}
|
||||
>
|
||||
{/* Drawer Header */}
|
||||
<Box sx={styles.getDrawerHeaderStyles(theme)}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
||||
<Box component="img" src="/imgs/logo.svg" alt="Easy Dataset Logo" sx={{ width: 32, height: 32 }} />
|
||||
<Typography variant="h6" component="h2" sx={{ fontWeight: 700, fontSize: '1.15rem' }}>
|
||||
{t('common.navigation', 'Navigation')}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Tooltip title={t('common.closeMenu', 'Close menu')}>
|
||||
<IconButton
|
||||
onClick={toggleDrawer}
|
||||
aria-label={t('common.closeMenu', 'Close menu')}
|
||||
size="medium"
|
||||
sx={styles.getDrawerCloseButtonStyles(theme)}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
{/* Drawer Menu List */}
|
||||
<List sx={styles.drawerListStyles} role="menu">
|
||||
{/* 数据源菜单 */}
|
||||
<ListItem disablePadding sx={{ mb: 0.5 }}>
|
||||
<ListItemButton
|
||||
onClick={() => toggleMobileSubmenu('source')}
|
||||
aria-expanded={expandedMenu === 'source'}
|
||||
aria-controls="source-submenu"
|
||||
role="menuitem"
|
||||
sx={styles.getDrawerListItemButtonStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.listItemIconStyles}>
|
||||
<DescriptionOutlinedIcon sx={styles.getIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('common.dataSource')} primaryTypographyProps={styles.listItemTextStyles} />
|
||||
{expandedMenu === 'source' ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<Collapse id="source-submenu" in={expandedMenu === 'source'} timeout="auto" unmountOnExit>
|
||||
<List component="div" disablePadding role="menu" sx={styles.getDrawerSubmenuContainerStyles(theme)}>
|
||||
<ListItemButton
|
||||
role="menuitem"
|
||||
sx={styles.getDrawerSubmenuItemStyles(theme)}
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/text-split`}
|
||||
onClick={toggleDrawer}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<DescriptionOutlinedIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('textSplit.title')} primaryTypographyProps={styles.smallListItemTextStyles} />
|
||||
</ListItemButton>
|
||||
<ListItemButton
|
||||
role="menuitem"
|
||||
sx={styles.getDrawerSubmenuItemStyles(theme)}
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/images`}
|
||||
onClick={toggleDrawer}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<ImageIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('images.title')} primaryTypographyProps={styles.smallListItemTextStyles} />
|
||||
</ListItemButton>
|
||||
</List>
|
||||
</Collapse>
|
||||
|
||||
{/* 数据蒸馏 */}
|
||||
<ListItem disablePadding sx={{ mb: 0.5 }}>
|
||||
<ListItemButton
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/distill`}
|
||||
onClick={toggleDrawer}
|
||||
role="menuitem"
|
||||
sx={styles.getDrawerListItemButtonStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.listItemIconStyles}>
|
||||
<TokenOutlinedIcon sx={styles.getIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('distill.title')} primaryTypographyProps={styles.listItemTextStyles} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
|
||||
{/* 问题管理 */}
|
||||
<ListItem disablePadding sx={{ mb: 0.5 }}>
|
||||
<ListItemButton
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/questions`}
|
||||
onClick={toggleDrawer}
|
||||
role="menuitem"
|
||||
sx={styles.getDrawerListItemButtonStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.listItemIconStyles}>
|
||||
<QuestionAnswerOutlinedIcon sx={styles.getIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('questions.title')} primaryTypographyProps={styles.listItemTextStyles} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
|
||||
{/* 数据集管理 */}
|
||||
<ListItem disablePadding sx={{ mb: 0.5 }}>
|
||||
<ListItemButton
|
||||
onClick={() => toggleMobileSubmenu('dataset')}
|
||||
role="menuitem"
|
||||
aria-expanded={expandedMenu === 'dataset'}
|
||||
aria-controls="dataset-submenu"
|
||||
sx={styles.getDrawerListItemButtonStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.listItemIconStyles}>
|
||||
<DatasetOutlinedIcon sx={styles.getIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('datasets.management')} primaryTypographyProps={styles.listItemTextStyles} />
|
||||
{expandedMenu === 'dataset' ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<Collapse in={expandedMenu === 'dataset'} timeout="auto" unmountOnExit id="dataset-submenu">
|
||||
<List component="div" disablePadding sx={styles.getDrawerSubmenuContainerStyles(theme)}>
|
||||
<ListItemButton
|
||||
role="menuitem"
|
||||
sx={styles.getDrawerSubmenuItemStyles(theme)}
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/datasets`}
|
||||
onClick={toggleDrawer}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<DatasetOutlinedIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t('datasets.singleTurn', '单轮问答数据集')}
|
||||
primaryTypographyProps={styles.smallListItemTextStyles}
|
||||
/>
|
||||
</ListItemButton>
|
||||
<ListItemButton
|
||||
sx={styles.getDrawerSubmenuItemStyles(theme)}
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/multi-turn`}
|
||||
onClick={handleNavigateStart}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<ChatIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t('datasets.multiTurn', '多轮对话数据集')}
|
||||
primaryTypographyProps={styles.smallListItemTextStyles}
|
||||
/>
|
||||
</ListItemButton>
|
||||
<ListItemButton
|
||||
sx={styles.getDrawerSubmenuItemStyles(theme)}
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/image-datasets`}
|
||||
onClick={toggleDrawer}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<ImageIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t('datasets.imageQA', '图片问答数据集')}
|
||||
primaryTypographyProps={styles.smallListItemTextStyles}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</List>
|
||||
</Collapse>
|
||||
|
||||
{/* 评估菜单 */}
|
||||
<ListItem disablePadding sx={{ mb: 0.5 }}>
|
||||
<ListItemButton
|
||||
onClick={() => toggleMobileSubmenu('eval')}
|
||||
role="menuitem"
|
||||
aria-expanded={expandedMenu === 'eval'}
|
||||
aria-controls="eval-submenu"
|
||||
sx={styles.getDrawerListItemButtonStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.listItemIconStyles}>
|
||||
<AssessmentOutlinedIcon sx={styles.getIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('eval.title')} primaryTypographyProps={styles.listItemTextStyles} />
|
||||
{expandedMenu === 'eval' ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<Collapse in={expandedMenu === 'eval'} timeout="auto" unmountOnExit id="eval-submenu">
|
||||
<List component="div" disablePadding sx={styles.getDrawerSubmenuContainerStyles(theme)}>
|
||||
<ListItemButton
|
||||
role="menuitem"
|
||||
sx={styles.getDrawerSubmenuItemStyles(theme)}
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/eval-datasets`}
|
||||
onClick={handleNavigateStart}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<AssessmentOutlinedIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('eval.datasets')} primaryTypographyProps={styles.smallListItemTextStyles} />
|
||||
</ListItemButton>
|
||||
<ListItemButton
|
||||
role="menuitem"
|
||||
sx={styles.getDrawerSubmenuItemStyles(theme)}
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/eval-tasks`}
|
||||
onClick={handleNavigateStart}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<PlaylistPlayIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('eval.tasks')} primaryTypographyProps={styles.smallListItemTextStyles} />
|
||||
</ListItemButton>
|
||||
<ListItemButton
|
||||
role="menuitem"
|
||||
sx={styles.getDrawerSubmenuItemStyles(theme)}
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/blind-test-tasks`}
|
||||
onClick={toggleDrawer}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<VisibilityIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('blindTest.title')} primaryTypographyProps={styles.smallListItemTextStyles} />
|
||||
</ListItemButton>
|
||||
</List>
|
||||
</Collapse>
|
||||
|
||||
{/* 更多菜单 */}
|
||||
<ListItem disablePadding sx={{ mb: 0.5 }}>
|
||||
<ListItemButton
|
||||
onClick={() => toggleMobileSubmenu('more')}
|
||||
role="menuitem"
|
||||
aria-expanded={expandedMenu === 'more'}
|
||||
aria-controls="more-submenu"
|
||||
sx={styles.getDrawerListItemButtonStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.listItemIconStyles}>
|
||||
<MoreVertIcon sx={styles.getIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('common.more')} primaryTypographyProps={styles.listItemTextStyles} />
|
||||
{expandedMenu === 'more' ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<Collapse in={expandedMenu === 'more'} timeout="auto" unmountOnExit id="more-submenu">
|
||||
<List component="div" disablePadding sx={styles.getDrawerSubmenuContainerStyles(theme)}>
|
||||
<ListItemButton
|
||||
sx={styles.getDrawerSubmenuItemStyles(theme)}
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/settings`}
|
||||
onClick={toggleDrawer}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<SettingsOutlinedIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('settings.title')} primaryTypographyProps={styles.smallListItemTextStyles} />
|
||||
</ListItemButton>
|
||||
<ListItemButton
|
||||
sx={styles.getDrawerSubmenuItemStyles(theme)}
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/playground`}
|
||||
onClick={toggleDrawer}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<ScienceOutlinedIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('playground.title')} primaryTypographyProps={styles.smallListItemTextStyles} />
|
||||
</ListItemButton>
|
||||
<ListItemButton
|
||||
sx={styles.getDrawerSubmenuItemStyles(theme)}
|
||||
component={Link}
|
||||
href="/dataset-square"
|
||||
onClick={toggleDrawer}
|
||||
>
|
||||
<ListItemIcon sx={styles.smallListItemIconStyles}>
|
||||
<StorageIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t('datasetSquare.title')}
|
||||
primaryTypographyProps={styles.smallListItemTextStyles}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</List>
|
||||
</Collapse>
|
||||
|
||||
{/* Utilities Section */}
|
||||
<Box sx={styles.getDrawerUtilitiesStyles(theme)}>
|
||||
<ListItem disablePadding sx={{ mb: 0.5 }}>
|
||||
<ListItemButton
|
||||
component="a"
|
||||
href={
|
||||
i18n.language === 'zh-CN' ? 'https://docs.easy-dataset.com/' : 'https://docs.easy-dataset.com/ed/en'
|
||||
}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={toggleDrawer}
|
||||
sx={styles.getDrawerListItemButtonStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.listItemIconStyles}>
|
||||
<HelpOutlineIcon sx={styles.getIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t('common.documentation', 'Documentation')}
|
||||
primaryTypographyProps={styles.listItemTextStyles}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
|
||||
<ListItem disablePadding sx={{ mb: 0.5 }}>
|
||||
<ListItemButton
|
||||
onClick={() => {
|
||||
window.open('https://github.com/ConardLi/easy-dataset', '_blank');
|
||||
toggleDrawer();
|
||||
}}
|
||||
sx={styles.getDrawerListItemButtonStyles(theme)}
|
||||
>
|
||||
<ListItemIcon sx={styles.listItemIconStyles}>
|
||||
<GitHubIcon sx={styles.getIconColorStyles(theme)} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t('common.viewOnGitHub', 'View on GitHub')}
|
||||
primaryTypographyProps={styles.listItemTextStyles}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
|
||||
<ListItem disablePadding sx={{ mb: 1 }}>
|
||||
<Box sx={{ px: 1, width: '100%' }}>
|
||||
<UpdateChecker />
|
||||
</Box>
|
||||
</ListItem>
|
||||
</Box>
|
||||
</List>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
139
easy-dataset-main/components/Navbar/NavigationTabs.js
Normal file
139
easy-dataset-main/components/Navbar/NavigationTabs.js
Normal file
@@ -0,0 +1,139 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Box, Tabs, Tab } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Link from 'next/link';
|
||||
import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined';
|
||||
import TokenOutlinedIcon from '@mui/icons-material/TokenOutlined';
|
||||
import QuestionAnswerOutlinedIcon from '@mui/icons-material/QuestionAnswerOutlined';
|
||||
import DatasetOutlinedIcon from '@mui/icons-material/DatasetOutlined';
|
||||
import AssessmentOutlinedIcon from '@mui/icons-material/AssessmentOutlined';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||
import * as styles from './styles';
|
||||
|
||||
/**
|
||||
* NavigationTabs 组件
|
||||
* 桌面端导航 Tabs,包含数据源、数据蒸馏、问题管理、数据集管理、更多等 Tab
|
||||
*/
|
||||
export default function NavigationTabs({
|
||||
theme,
|
||||
pathname,
|
||||
currentProject,
|
||||
handleMenuOpen,
|
||||
handleMenuClose,
|
||||
onNavigateStart
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// 计算当前 Tab 值
|
||||
const getCurrentTabValue = () => {
|
||||
if (pathname.includes('/settings') || pathname.includes('/playground') || pathname.includes('/datasets-sq')) {
|
||||
return 'more';
|
||||
}
|
||||
if (pathname.includes('/eval-datasets') || pathname.includes('/eval-tasks')) {
|
||||
return 'eval';
|
||||
}
|
||||
if (pathname.includes('/datasets') || pathname.includes('/multi-turn') || pathname.includes('/image-datasets')) {
|
||||
return 'datasets';
|
||||
}
|
||||
if (pathname.includes('/text-split') || pathname.includes('/images')) {
|
||||
return 'source';
|
||||
}
|
||||
return pathname;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={styles.navContainerStyles}>
|
||||
<Tabs
|
||||
value={getCurrentTabValue()}
|
||||
textColor="inherit"
|
||||
indicatorColor="secondary"
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
allowScrollButtonsMobile
|
||||
sx={styles.getTabsStyles(theme)}
|
||||
>
|
||||
<Tab
|
||||
icon={<DescriptionOutlinedIcon fontSize="small" />}
|
||||
iconPosition="start"
|
||||
label={
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.25 }}>
|
||||
{t('common.dataSource')}
|
||||
<ArrowDropDownIcon fontSize="small" sx={{ ml: 0.25 }} />
|
||||
</Box>
|
||||
}
|
||||
value="source"
|
||||
onMouseEnter={e => handleMenuOpen(e, 'source')}
|
||||
sx={styles.tabIconWrapperStyles}
|
||||
/>
|
||||
<Tab
|
||||
icon={<TokenOutlinedIcon fontSize="small" />}
|
||||
iconPosition="start"
|
||||
label={t('distill.title')}
|
||||
value={`/projects/${currentProject}/distill`}
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/distill`}
|
||||
onClick={() => {
|
||||
onNavigateStart?.();
|
||||
handleMenuClose();
|
||||
}}
|
||||
sx={styles.tabIconWrapperStyles}
|
||||
/>
|
||||
<Tab
|
||||
icon={<QuestionAnswerOutlinedIcon fontSize="small" />}
|
||||
iconPosition="start"
|
||||
label={t('questions.title')}
|
||||
value={`/projects/${currentProject}/questions`}
|
||||
component={Link}
|
||||
href={`/projects/${currentProject}/questions`}
|
||||
onClick={() => {
|
||||
onNavigateStart?.();
|
||||
handleMenuClose();
|
||||
}}
|
||||
sx={styles.tabIconWrapperStyles}
|
||||
/>
|
||||
<Tab
|
||||
icon={<DatasetOutlinedIcon fontSize="small" />}
|
||||
iconPosition="start"
|
||||
label={
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.25 }}>
|
||||
{t('datasets.management')}
|
||||
<ArrowDropDownIcon fontSize="small" sx={{ ml: 0.25 }} />
|
||||
</Box>
|
||||
}
|
||||
value="datasets"
|
||||
onMouseEnter={e => handleMenuOpen(e, 'dataset')}
|
||||
sx={styles.tabIconWrapperStyles}
|
||||
/>
|
||||
<Tab
|
||||
icon={<AssessmentOutlinedIcon fontSize="small" />}
|
||||
iconPosition="start"
|
||||
label={
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.25 }}>
|
||||
{t('eval.title')}
|
||||
<ArrowDropDownIcon fontSize="small" sx={{ ml: 0.25 }} />
|
||||
</Box>
|
||||
}
|
||||
value="eval"
|
||||
onMouseEnter={e => handleMenuOpen(e, 'eval')}
|
||||
sx={styles.tabIconWrapperStyles}
|
||||
/>
|
||||
<Tab
|
||||
icon={<MoreVertIcon fontSize="small" />}
|
||||
iconPosition="start"
|
||||
label={
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.25 }}>
|
||||
{t('common.more')}
|
||||
<ArrowDropDownIcon fontSize="small" sx={{ ml: 0.25 }} />
|
||||
</Box>
|
||||
}
|
||||
onMouseEnter={e => handleMenuOpen(e, 'more')}
|
||||
value="more"
|
||||
sx={styles.tabIconWrapperStyles}
|
||||
/>
|
||||
</Tabs>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
247
easy-dataset-main/components/Navbar/contextBarStyles.js
Normal file
247
easy-dataset-main/components/Navbar/contextBarStyles.js
Normal file
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* ContextBar 组件样式
|
||||
* 包含项目选择器和模型选择器的所有样式
|
||||
*/
|
||||
|
||||
import { alpha } from '@mui/material';
|
||||
|
||||
// ===== 主容器样式 =====
|
||||
export const getContextBarPaperStyles = theme => ({
|
||||
position: 'absolute',
|
||||
top: 64, // Below navbar
|
||||
left: 0,
|
||||
zIndex: 1100,
|
||||
borderBottom: 1,
|
||||
borderColor: 'divider',
|
||||
bgcolor:
|
||||
theme.palette.mode === 'dark'
|
||||
? alpha(theme.palette.background.paper, 0.9)
|
||||
: alpha(theme.palette.background.paper, 0.95),
|
||||
backdropFilter: 'blur(16px)',
|
||||
WebkitBackdropFilter: 'blur(16px)',
|
||||
px: { xs: 2, sm: 3, md: 4 },
|
||||
py: { xs: 1.25, sm: 1.5 },
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
boxShadow: theme.palette.mode === 'dark' ? '0 1px 3px rgba(0, 0, 0, 0.2)' : '0 1px 3px rgba(0, 0, 0, 0.08)',
|
||||
width: 'auto'
|
||||
});
|
||||
|
||||
export const contextBarContainerStyles = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: { xs: 1, sm: 1.5, md: 2 },
|
||||
flexWrap: 'nowrap',
|
||||
width: 'auto'
|
||||
};
|
||||
|
||||
// ===== 选择器容器样式 =====
|
||||
export const selectorContainerStyles = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1
|
||||
};
|
||||
|
||||
// ===== 标签样式 =====
|
||||
export const labelTypographyStyles = {
|
||||
color: 'text.secondary',
|
||||
fontWeight: 600,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.5px',
|
||||
fontSize: '0.7rem',
|
||||
display: { xs: 'none', sm: 'block' }
|
||||
};
|
||||
|
||||
// ===== Chip 内部文本样式 =====
|
||||
export const chipLabelBoxStyles = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 0.5
|
||||
};
|
||||
|
||||
export const chipTextStyles = {
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '0.8rem', sm: '0.875rem' },
|
||||
maxWidth: { xs: '80px', sm: '120px', md: '150px' },
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
};
|
||||
|
||||
export const chipArrowStyles = {
|
||||
ml: -0.25,
|
||||
flexShrink: 0
|
||||
};
|
||||
|
||||
// ===== 项目选择器 Chip 样式 =====
|
||||
export const getProjectChipStyles = theme => ({
|
||||
minWidth: 'auto',
|
||||
maxWidth: { xs: '120px', sm: '150px', md: '180px' },
|
||||
height: { xs: 32, sm: 36 },
|
||||
minWidth: { xs: 120, sm: 150, md: 180 },
|
||||
maxWidth: { xs: '120px', sm: '150px', md: '180px' },
|
||||
borderRadius: 1.5,
|
||||
borderColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.23)' : 'rgba(0, 0, 0, 0.23)',
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.02)',
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
'&:hover': {
|
||||
borderColor: 'primary.main',
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.08)' : 'rgba(25, 118, 210, 0.04)',
|
||||
transform: 'translateY(-1px)',
|
||||
boxShadow:
|
||||
theme.palette.mode === 'dark' ? '0 4px 12px rgba(144, 202, 249, 0.15)' : '0 4px 12px rgba(25, 118, 210, 0.15)'
|
||||
},
|
||||
'&:active': {
|
||||
transform: 'translateY(0)'
|
||||
},
|
||||
'&:focus-visible': {
|
||||
outline: `2px solid ${theme.palette.primary.main}`,
|
||||
outlineOffset: 2
|
||||
},
|
||||
'& .MuiChip-icon': {
|
||||
color: 'text.primary',
|
||||
fontSize: '1.1rem',
|
||||
ml: 0.5,
|
||||
flexShrink: 0
|
||||
},
|
||||
'& .MuiChip-label': {
|
||||
px: 1,
|
||||
overflow: 'hidden'
|
||||
}
|
||||
});
|
||||
|
||||
// ===== 模型选择器 Chip 样式 =====
|
||||
export const getModelChipStyles = theme => ({
|
||||
minWidth: { xs: 140, sm: 160, md: 180 },
|
||||
maxWidth: { xs: 200, sm: 280, md: 360 },
|
||||
height: { xs: 36, sm: 40 },
|
||||
borderRadius: 2,
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.08)' : 'rgba(25, 118, 210, 0.04)',
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
'&:hover': {
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.15)' : 'rgba(25, 118, 210, 0.08)',
|
||||
transform: 'translateY(-1px)',
|
||||
boxShadow:
|
||||
theme.palette.mode === 'dark' ? '0 4px 12px rgba(144, 202, 249, 0.25)' : '0 4px 12px rgba(25, 118, 210, 0.25)'
|
||||
},
|
||||
'&:active': {
|
||||
transform: 'translateY(0)'
|
||||
},
|
||||
'&:focus-visible': {
|
||||
outline: `2px solid ${theme.palette.primary.main}`,
|
||||
outlineOffset: 2
|
||||
},
|
||||
'& .MuiChip-icon': {
|
||||
color: 'primary.main',
|
||||
fontSize: '1.1rem',
|
||||
ml: 0.5,
|
||||
flexShrink: 0
|
||||
},
|
||||
'& .MuiChip-label': {
|
||||
px: 1,
|
||||
overflow: 'hidden'
|
||||
}
|
||||
});
|
||||
|
||||
// ===== 菜单样式 =====
|
||||
export const getMenuPaperStyles = theme => ({
|
||||
mt: 1,
|
||||
minWidth: 240,
|
||||
maxWidth: 400,
|
||||
maxHeight: 400,
|
||||
borderRadius: 2,
|
||||
overflow: 'visible',
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'background.paper' : 'background.paper',
|
||||
backdropFilter: 'blur(20px)',
|
||||
WebkitBackdropFilter: 'blur(20px)',
|
||||
boxShadow:
|
||||
theme.palette.mode === 'dark'
|
||||
? '0 12px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.1)'
|
||||
: '0 12px 40px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05)',
|
||||
'&::before': {
|
||||
content: '""',
|
||||
display: 'block',
|
||||
position: 'absolute',
|
||||
top: -6,
|
||||
left: 24,
|
||||
width: 12,
|
||||
height: 12,
|
||||
bgcolor: 'background.paper',
|
||||
transform: 'translateY(-50%) rotate(45deg)',
|
||||
zIndex: 0,
|
||||
borderLeft: `1px solid ${theme.palette.divider}`,
|
||||
borderTop: `1px solid ${theme.palette.divider}`
|
||||
}
|
||||
});
|
||||
|
||||
export const menuListPropsStyles = {
|
||||
dense: false,
|
||||
sx: { py: 1 }
|
||||
};
|
||||
|
||||
// ===== 菜单标题样式 =====
|
||||
export const menuHeaderTypographyStyles = {
|
||||
px: 2,
|
||||
py: 1,
|
||||
display: 'block',
|
||||
color: 'text.secondary',
|
||||
fontWeight: 600,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.5px',
|
||||
fontSize: '0.7rem'
|
||||
};
|
||||
|
||||
// ===== 菜单项样式 =====
|
||||
export const getMenuItemStyles = theme => ({
|
||||
mx: 1,
|
||||
borderRadius: 1.5,
|
||||
minHeight: 44,
|
||||
py: 1.25,
|
||||
px: 1.5,
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
'&:hover': {
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.08)' : 'rgba(25, 118, 210, 0.04)',
|
||||
transform: 'translateX(4px)'
|
||||
},
|
||||
'&.Mui-selected': {
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.16)' : 'rgba(25, 118, 210, 0.08)',
|
||||
'&:hover': {
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.24)' : 'rgba(25, 118, 210, 0.12)'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const menuItemIconStyles = {
|
||||
minWidth: 36
|
||||
};
|
||||
|
||||
export const getMenuItemTextPrimaryProps = isSelected => ({
|
||||
variant: 'body2',
|
||||
fontWeight: isSelected ? 600 : 400
|
||||
});
|
||||
|
||||
export const menuItemTextSecondaryProps = {
|
||||
variant: 'caption',
|
||||
sx: { fontSize: '0.7rem' }
|
||||
};
|
||||
|
||||
// ===== 模型图标样式 =====
|
||||
export const modelIconStyles = {
|
||||
width: 20,
|
||||
height: 20,
|
||||
objectFit: 'contain',
|
||||
flexShrink: 0,
|
||||
borderRadius: '50%',
|
||||
mr: 1
|
||||
};
|
||||
|
||||
// ===== 分组标题样式 =====
|
||||
export const getProviderHeaderStyles = theme => ({
|
||||
pl: 2,
|
||||
color: theme.palette.text.secondary,
|
||||
fontWeight: 600,
|
||||
fontSize: '0.75rem',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.5px',
|
||||
mt: 1,
|
||||
mb: 0.5
|
||||
});
|
||||
257
easy-dataset-main/components/Navbar/index.js
Normal file
257
easy-dataset-main/components/Navbar/index.js
Normal file
@@ -0,0 +1,257 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import {
|
||||
AppBar,
|
||||
Toolbar,
|
||||
Box,
|
||||
IconButton,
|
||||
useTheme as useMuiTheme,
|
||||
Tooltip,
|
||||
useMediaQuery,
|
||||
LinearProgress
|
||||
} from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useTheme } from 'next-themes';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
|
||||
// 样式
|
||||
import * as styles from './styles';
|
||||
|
||||
// 子组件
|
||||
import Logo from './Logo';
|
||||
import ActionButtons from './ActionButtons';
|
||||
import NavigationTabs from './NavigationTabs';
|
||||
import MobileDrawer from './MobileDrawer';
|
||||
import DesktopMenus from './DesktopMenus';
|
||||
import ContextBar from './ContextBar';
|
||||
|
||||
export default function Navbar({ projects = [], currentProject }) {
|
||||
const { t } = useTranslation();
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const theme = useMuiTheme();
|
||||
const { resolvedTheme, setTheme } = useTheme();
|
||||
const isProjectDetail = pathname.includes('/projects/') && pathname.split('/').length > 3;
|
||||
|
||||
// 检测移动设备
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('lg'));
|
||||
|
||||
// 移动端抽屉状态
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const [expandedMenu, setExpandedMenu] = useState(null);
|
||||
|
||||
// 桌面端菜单状态
|
||||
const [menuState, setMenuState] = useState({ anchorEl: null, menuType: null });
|
||||
const [navLoading, setNavLoading] = useState(false);
|
||||
const navLoadingTimeoutRef = useRef(null);
|
||||
|
||||
// ContextBar 悬浮状态
|
||||
const [contextBarHovered, setContextBarHovered] = useState(false);
|
||||
const contextTriggerRef = useRef(null);
|
||||
const contextBarRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!contextBarHovered) return;
|
||||
|
||||
const handleOutsideClick = event => {
|
||||
if (contextBarRef.current?.contains(event.target)) return;
|
||||
if (contextTriggerRef.current?.contains(event.target)) return;
|
||||
const projectMenuEl = document.getElementById('project-menu');
|
||||
if (projectMenuEl?.contains(event.target)) return;
|
||||
setContextBarHovered(false);
|
||||
};
|
||||
|
||||
document.addEventListener('pointerdown', handleOutsideClick, true);
|
||||
return () => {
|
||||
document.removeEventListener('pointerdown', handleOutsideClick, true);
|
||||
};
|
||||
}, [contextBarHovered]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!menuState.menuType) return;
|
||||
|
||||
const handleOutsideMenuClick = event => {
|
||||
if (menuState.anchorEl?.contains(event.target)) return;
|
||||
if (event.target?.closest?.('.MuiMenu-root')) return;
|
||||
setMenuState({ anchorEl: null, menuType: null });
|
||||
};
|
||||
|
||||
document.addEventListener('pointerdown', handleOutsideMenuClick, true);
|
||||
return () => {
|
||||
document.removeEventListener('pointerdown', handleOutsideMenuClick, true);
|
||||
};
|
||||
}, [menuState.anchorEl, menuState.menuType]);
|
||||
|
||||
useEffect(() => {
|
||||
setNavLoading(false);
|
||||
if (navLoadingTimeoutRef.current) {
|
||||
clearTimeout(navLoadingTimeoutRef.current);
|
||||
navLoadingTimeoutRef.current = null;
|
||||
}
|
||||
}, [pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isProjectDetail || !currentProject) return;
|
||||
const prefetchRoutes = [
|
||||
`/projects/${currentProject}/multi-turn`,
|
||||
`/projects/${currentProject}/eval-datasets`,
|
||||
`/projects/${currentProject}/eval-tasks`
|
||||
];
|
||||
prefetchRoutes.forEach(route => router.prefetch(route));
|
||||
}, [router, currentProject, isProjectDetail]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (navLoadingTimeoutRef.current) {
|
||||
clearTimeout(navLoadingTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleNavigateStart = () => {
|
||||
setNavLoading(true);
|
||||
if (navLoadingTimeoutRef.current) {
|
||||
clearTimeout(navLoadingTimeoutRef.current);
|
||||
}
|
||||
navLoadingTimeoutRef.current = setTimeout(() => {
|
||||
setNavLoading(false);
|
||||
navLoadingTimeoutRef.current = null;
|
||||
}, 12000);
|
||||
};
|
||||
|
||||
const handleMenuOpen = (event, menuType) => {
|
||||
setMenuState({ anchorEl: event.currentTarget, menuType });
|
||||
};
|
||||
|
||||
const handleMenuClose = () => {
|
||||
setMenuState({ anchorEl: null, menuType: null });
|
||||
};
|
||||
|
||||
const isMenuOpen = menuType => menuState.menuType === menuType;
|
||||
|
||||
const toggleDrawer = () => {
|
||||
setDrawerOpen(!drawerOpen);
|
||||
setExpandedMenu(null);
|
||||
};
|
||||
|
||||
const toggleMobileSubmenu = menuType => {
|
||||
setExpandedMenu(expandedMenu === menuType ? null : menuType);
|
||||
};
|
||||
|
||||
const toggleTheme = () => {
|
||||
setTheme(resolvedTheme === 'dark' ? 'light' : 'dark');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppBar
|
||||
component="nav"
|
||||
position="sticky"
|
||||
elevation={0}
|
||||
color={theme.palette.mode === 'dark' ? 'transparent' : 'primary'}
|
||||
sx={styles.getAppBarStyles(theme)}
|
||||
style={{ borderRadius: 0, zIndex: 1200 }}
|
||||
role="navigation"
|
||||
aria-label={t('common.mainNavigation', 'Main navigation')}
|
||||
>
|
||||
<Toolbar sx={styles.toolbarStyles}>
|
||||
{/* 左侧: 汉堡菜单(移动端) + Logo */}
|
||||
<Box
|
||||
ref={contextTriggerRef}
|
||||
sx={styles.logoContainerStyles}
|
||||
onMouseEnter={() => isProjectDetail && setContextBarHovered(true)}
|
||||
>
|
||||
{/* 汉堡菜单按钮 */}
|
||||
{isProjectDetail && isMobile && (
|
||||
<Tooltip title={t('common.menu', 'Menu')} placement="bottom">
|
||||
<IconButton
|
||||
onClick={toggleDrawer}
|
||||
size="medium"
|
||||
aria-label={t('common.openMenu', 'Open navigation menu')}
|
||||
aria-expanded={drawerOpen}
|
||||
aria-controls="mobile-navigation-drawer"
|
||||
sx={styles.getHamburgerButtonStyles(theme)}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* Logo 组件 */}
|
||||
<Logo theme={theme} />
|
||||
</Box>
|
||||
|
||||
{/* 中间导航 - 仅桌面端 */}
|
||||
{isProjectDetail && !isMobile && (
|
||||
<NavigationTabs
|
||||
theme={theme}
|
||||
pathname={pathname}
|
||||
currentProject={currentProject}
|
||||
handleMenuOpen={handleMenuOpen}
|
||||
handleMenuClose={handleMenuClose}
|
||||
onNavigateStart={handleNavigateStart}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 右侧操作区 */}
|
||||
<ActionButtons
|
||||
theme={theme}
|
||||
resolvedTheme={resolvedTheme}
|
||||
toggleTheme={toggleTheme}
|
||||
isProjectDetail={isProjectDetail}
|
||||
currentProject={currentProject}
|
||||
onActionAreaEnter={!isMobile ? handleMenuClose : undefined}
|
||||
/>
|
||||
</Toolbar>
|
||||
{isProjectDetail && (
|
||||
<LinearProgress
|
||||
color="secondary"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
height: 2,
|
||||
opacity: navLoading ? 1 : 0,
|
||||
transition: 'opacity 180ms ease'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AppBar>
|
||||
|
||||
{/* ContextBar - 在 Logo 或 ContextBar 悬浮时展示 */}
|
||||
{isProjectDetail && contextBarHovered && (
|
||||
<Box ref={contextBarRef} onMouseLeave={() => setContextBarHovered(false)}>
|
||||
<ContextBar
|
||||
projects={projects}
|
||||
currentProjectId={currentProject}
|
||||
onMouseLeave={() => setContextBarHovered(false)}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* 移动端抽屉组件 */}
|
||||
<MobileDrawer
|
||||
theme={theme}
|
||||
drawerOpen={drawerOpen}
|
||||
toggleDrawer={toggleDrawer}
|
||||
expandedMenu={expandedMenu}
|
||||
toggleMobileSubmenu={toggleMobileSubmenu}
|
||||
currentProject={currentProject}
|
||||
onNavigateStart={handleNavigateStart}
|
||||
/>
|
||||
|
||||
{/* 桌面端菜单组件 */}
|
||||
<DesktopMenus
|
||||
theme={theme}
|
||||
menuState={menuState}
|
||||
isMenuOpen={isMenuOpen}
|
||||
handleMenuClose={handleMenuClose}
|
||||
currentProject={currentProject}
|
||||
onNavigateStart={handleNavigateStart}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
374
easy-dataset-main/components/Navbar/styles.js
Normal file
374
easy-dataset-main/components/Navbar/styles.js
Normal file
@@ -0,0 +1,374 @@
|
||||
/**
|
||||
* Navbar 组件样式配置
|
||||
*/
|
||||
|
||||
// AppBar 样式
|
||||
export const getAppBarStyles = theme => ({
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'background.paper' : 'primary.main',
|
||||
backdropFilter: 'blur(20px)',
|
||||
WebkitBackdropFilter: 'blur(20px)',
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
boxShadow: theme.palette.mode === 'dark' ? '0 1px 3px rgba(0, 0, 0, 0.3)' : '0 1px 3px rgba(0, 0, 0, 0.1)'
|
||||
});
|
||||
|
||||
// Toolbar 样式
|
||||
export const toolbarStyles = {
|
||||
height: '64px',
|
||||
minHeight: '64px !important',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
px: { xs: 2, sm: 2, md: 3 },
|
||||
gap: 2
|
||||
};
|
||||
|
||||
// Logo 容器样式
|
||||
export const logoContainerStyles = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1.5,
|
||||
flexShrink: 0
|
||||
};
|
||||
|
||||
// 汉堡菜单按钮样式
|
||||
export const getHamburgerButtonStyles = theme => ({
|
||||
color: theme.palette.mode === 'dark' ? 'inherit' : 'white',
|
||||
minWidth: 44,
|
||||
minHeight: 44,
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
'&:hover': {
|
||||
transform: 'scale(1.1)',
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.08)' : 'rgba(255, 255, 255, 0.15)'
|
||||
},
|
||||
'&:active': {
|
||||
transform: 'scale(0.95)'
|
||||
},
|
||||
'&:focus-visible': {
|
||||
outline: `2px solid ${theme.palette.mode === 'dark' ? theme.palette.secondary.main : 'white'}`,
|
||||
outlineOffset: 2
|
||||
}
|
||||
});
|
||||
|
||||
// Logo 链接样式
|
||||
export const getLogoLinkStyles = theme => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
textDecoration: 'none',
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
borderRadius: 1.5,
|
||||
px: 0.5,
|
||||
'&:hover': {
|
||||
opacity: 0.85,
|
||||
transform: 'translateY(-1px)'
|
||||
},
|
||||
'&:active': {
|
||||
transform: 'translateY(0)'
|
||||
},
|
||||
'&:focus-visible': {
|
||||
outline: `2px solid ${theme.palette.mode === 'dark' ? theme.palette.secondary.main : 'white'}`,
|
||||
outlineOffset: 2
|
||||
}
|
||||
});
|
||||
|
||||
// Logo 图片样式
|
||||
export const logoImageStyles = {
|
||||
width: 32,
|
||||
height: 32,
|
||||
mr: 1.5,
|
||||
transition: 'transform 0.2s ease'
|
||||
};
|
||||
|
||||
// Logo 文字样式
|
||||
export const getLogoTextStyles = theme => ({
|
||||
fontWeight: 700,
|
||||
letterSpacing: '-0.5px',
|
||||
fontSize: '1.125rem',
|
||||
display: { xs: 'none', md: 'block' },
|
||||
color: 'white',
|
||||
...(theme.palette.mode === 'dark' && {
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
backgroundClip: 'text'
|
||||
})
|
||||
});
|
||||
|
||||
// 中间导航容器样式
|
||||
export const navContainerStyles = {
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
mx: { lg: 1, xl: 3 },
|
||||
overflow: 'hidden'
|
||||
};
|
||||
|
||||
// Tabs 样式
|
||||
export const getTabsStyles = theme => ({
|
||||
minHeight: '64px',
|
||||
'& .MuiTab-root': {
|
||||
minWidth: 100,
|
||||
maxWidth: 180,
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 500,
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
color: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.7)' : 'rgba(255, 255, 255, 1)',
|
||||
px: 2,
|
||||
minHeight: '64px',
|
||||
textTransform: 'none',
|
||||
letterSpacing: '0.3px',
|
||||
'&:hover': {
|
||||
color: 'white',
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.08)' : 'rgba(255, 255, 255, 0.15)'
|
||||
}
|
||||
},
|
||||
'& .Mui-selected': {
|
||||
color: 'white !important',
|
||||
fontWeight: 600,
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.12)' : 'rgba(255, 255, 255, 0.2)'
|
||||
},
|
||||
'& .MuiTabs-indicator': {
|
||||
height: 3,
|
||||
borderRadius: '3px 3px 0 0',
|
||||
backgroundColor: theme.palette.mode === 'dark' ? theme.palette.secondary.main : 'white',
|
||||
boxShadow: theme.palette.mode === 'dark' ? '0 0 8px rgba(103, 126, 234, 0.5)' : '0 0 8px rgba(255, 255, 255, 0.5)'
|
||||
}
|
||||
});
|
||||
|
||||
// Tab 图标包装器样式
|
||||
export const tabIconWrapperStyles = {
|
||||
'& .MuiTab-iconWrapper': { mr: 1 }
|
||||
};
|
||||
|
||||
// 右侧操作区容器样式
|
||||
export const actionAreaStyles = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
flexShrink: 0
|
||||
};
|
||||
|
||||
// 文档/GitHub 按钮样式
|
||||
export const getIconButtonStyles = theme => ({
|
||||
display: { xs: 'none', xl: 'flex' },
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.08)' : 'rgba(255, 255, 255, 0.2)',
|
||||
color: theme.palette.mode === 'dark' ? 'inherit' : 'white',
|
||||
borderRadius: 1.5,
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
'&:hover': {
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(255, 255, 255, 0.35)'
|
||||
},
|
||||
'&:focus-visible': {
|
||||
outline: `2px solid ${theme.palette.mode === 'dark' ? theme.palette.secondary.main : 'white'}`,
|
||||
outlineOffset: 2
|
||||
}
|
||||
});
|
||||
|
||||
// Drawer Paper 样式
|
||||
export const getDrawerPaperStyles = theme => ({
|
||||
width: { xs: '85vw', sm: 320 },
|
||||
maxWidth: 380,
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'background.paper' : 'background.default',
|
||||
backgroundImage:
|
||||
theme.palette.mode === 'dark' ? 'linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05))' : 'none',
|
||||
boxShadow: theme.palette.mode === 'dark' ? '0 8px 32px rgba(0, 0, 0, 0.6)' : '0 8px 32px rgba(0, 0, 0, 0.15)'
|
||||
});
|
||||
|
||||
// Drawer 头部样式
|
||||
export const getDrawerHeaderStyles = theme => ({
|
||||
p: 2.5,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
minHeight: 64
|
||||
});
|
||||
|
||||
// Drawer 关闭按钮样式
|
||||
export const getDrawerCloseButtonStyles = theme => ({
|
||||
minWidth: 44,
|
||||
minHeight: 44,
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
'&:hover': {
|
||||
transform: 'rotate(90deg)',
|
||||
bgcolor: 'action.hover'
|
||||
},
|
||||
'&:focus-visible': {
|
||||
outline: `2px solid ${theme.palette.primary.main}`,
|
||||
outlineOffset: 2
|
||||
}
|
||||
});
|
||||
|
||||
// Drawer 列表样式
|
||||
export const drawerListStyles = {
|
||||
pt: 1,
|
||||
px: 1
|
||||
};
|
||||
|
||||
// Drawer 列表项按钮样式
|
||||
export const getDrawerListItemButtonStyles = theme => ({
|
||||
borderRadius: '8px',
|
||||
minHeight: 48,
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
'&:hover': {
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(103, 126, 234, 0.12)' : 'rgba(103, 126, 234, 0.08)'
|
||||
},
|
||||
'&:focus-visible': {
|
||||
outline: `2px solid ${theme.palette.primary.main}`,
|
||||
outlineOffset: -2
|
||||
}
|
||||
});
|
||||
|
||||
// Drawer 子菜单容器样式
|
||||
export const getDrawerSubmenuContainerStyles = theme => ({
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(0, 0, 0, 0.02)',
|
||||
borderRadius: '8px',
|
||||
my: 0.5
|
||||
});
|
||||
|
||||
// Drawer 子菜单项样式
|
||||
export const getDrawerSubmenuItemStyles = theme => ({
|
||||
pl: 4,
|
||||
mx: 1,
|
||||
borderRadius: '8px',
|
||||
minHeight: 44,
|
||||
py: 1.5,
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
'&:hover': {
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(103, 126, 234, 0.08)' : 'rgba(103, 126, 234, 0.05)'
|
||||
},
|
||||
'&:focus-visible': {
|
||||
outline: `2px solid ${theme.palette.primary.main}`,
|
||||
outlineOffset: -2
|
||||
}
|
||||
});
|
||||
|
||||
// Drawer 工具区域样式
|
||||
export const getDrawerUtilitiesStyles = theme => ({
|
||||
mt: 'auto',
|
||||
pt: 2,
|
||||
borderTop: `1px solid ${theme.palette.divider}`
|
||||
});
|
||||
|
||||
// Menu Paper 样式
|
||||
export const getMenuPaperStyles = theme => ({
|
||||
mt: 1.5,
|
||||
borderRadius: '12px',
|
||||
minWidth: 220,
|
||||
overflow: 'visible',
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(30, 30, 30, 0.98)' : 'rgba(255, 255, 255, 0.98)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
WebkitBackdropFilter: 'blur(20px)',
|
||||
boxShadow:
|
||||
theme.palette.mode === 'dark'
|
||||
? '0 12px 40px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.1)'
|
||||
: '0 12px 40px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.05)',
|
||||
'&::before': {
|
||||
content: '""',
|
||||
display: 'block',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: '50%',
|
||||
width: 12,
|
||||
height: 12,
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(30, 30, 30, 0.98)' : 'rgba(255, 255, 255, 0.98)',
|
||||
transform: 'translateY(-50%) translateX(50%) rotate(45deg)',
|
||||
zIndex: 0,
|
||||
boxShadow: theme.palette.mode === 'dark' ? '-2px -2px 4px rgba(0, 0, 0, 0.3)' : '-2px -2px 4px rgba(0, 0, 0, 0.1)'
|
||||
}
|
||||
});
|
||||
|
||||
// Menu 列表样式
|
||||
export const menuListStyles = {
|
||||
py: 1.5
|
||||
};
|
||||
|
||||
// Menu 项样式
|
||||
export const getMenuItemStyles = theme => ({
|
||||
mx: 1,
|
||||
borderRadius: '8px',
|
||||
py: 1.25,
|
||||
minHeight: 44,
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
'&:hover': {
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(103, 126, 234, 0.15)' : 'rgba(103, 126, 234, 0.1)',
|
||||
transform: 'translateX(4px)'
|
||||
},
|
||||
'&:focus-visible': {
|
||||
outline: `2px solid ${theme.palette.primary.main}`,
|
||||
outlineOffset: -2
|
||||
}
|
||||
});
|
||||
|
||||
// Dataset/More Menu Paper 样式(简化版)
|
||||
export const getSimpleMenuPaperStyles = theme => ({
|
||||
mt: 1.5,
|
||||
borderRadius: '12px',
|
||||
minWidth: 220,
|
||||
overflow: 'visible',
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(30, 30, 30, 0.98)' : 'rgba(255, 255, 255, 0.98)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
boxShadow:
|
||||
theme.palette.mode === 'dark'
|
||||
? '0 8px 32px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.1)'
|
||||
: '0 8px 32px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05)',
|
||||
'&::before': {
|
||||
content: '""',
|
||||
display: 'block',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: '50%',
|
||||
width: 12,
|
||||
height: 12,
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(30, 30, 30, 0.98)' : 'rgba(255, 255, 255, 0.98)',
|
||||
transform: 'translateY(-50%) translateX(50%) rotate(45deg)',
|
||||
zIndex: 0
|
||||
}
|
||||
});
|
||||
|
||||
// 简化 Menu 列表样式
|
||||
export const simpleMenuListStyles = {
|
||||
py: 1
|
||||
};
|
||||
|
||||
// 简化 Menu 项样式
|
||||
export const getSimpleMenuItemStyles = theme => ({
|
||||
mx: 0.75,
|
||||
borderRadius: '8px',
|
||||
py: 1,
|
||||
transition: 'all 0.15s ease',
|
||||
'&:hover': {
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'rgba(103, 126, 234, 0.15)' : 'rgba(103, 126, 234, 0.1)',
|
||||
transform: 'translateX(4px)'
|
||||
}
|
||||
});
|
||||
|
||||
// ListItemIcon 样式
|
||||
export const listItemIconStyles = {
|
||||
minWidth: 40
|
||||
};
|
||||
|
||||
export const smallListItemIconStyles = {
|
||||
minWidth: 36
|
||||
};
|
||||
|
||||
// ListItemText 样式
|
||||
export const listItemTextStyles = {
|
||||
fontWeight: 600,
|
||||
fontSize: '0.95rem'
|
||||
};
|
||||
|
||||
export const smallListItemTextStyles = {
|
||||
fontSize: '0.9rem',
|
||||
fontWeight: 500
|
||||
};
|
||||
|
||||
// 图标颜色样式
|
||||
export const getIconColorStyles = theme => ({
|
||||
color: theme.palette.mode === 'dark' ? 'primary.light' : 'primary.main'
|
||||
});
|
||||
|
||||
export const getPrimaryIconColorStyles = theme => ({
|
||||
color: theme.palette.primary.main
|
||||
});
|
||||
Reference in New Issue
Block a user