feat: 添加i18n,美化界面

This commit is contained in:
2025-03-20 02:29:01 +08:00
parent 27d9bdf54e
commit c206add7eb
56 changed files with 2743 additions and 928 deletions

View File

@@ -3,7 +3,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Apps</title>
<link rel="stylesheet" href="./src/assets/index.css">
<link rel="stylesheet" href="./src/global.css">
<style>
html,
body {

View File

@@ -1,67 +0,0 @@
@import 'tailwindcss';
@import '@kevisual/center-components/theme/wind-theme.css';
@layer components {
.test-loading {
@apply w-20 h-20 bg-gray-300 rounded-full animate-spin;
}
}
:root {
--scrollbar-color: #ffbf00;
--primary-color: #ffc107;
--secondary-color: #ffa000;
}
#root {
width: 100%;
height: 100%;
}
#ai-bot-root {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: -100px;
z-index: 9999;
pointer-events: none;
}
.scrollbar {
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-color) #fff;
}
.scrollbar::-webkit-scrollbar {
height: 4px;
width: 4px;
}
.scrollbar::-webkit-scrollbar-thumb {
background-color: var(--scrollbar-color);
border-radius: 10px;
}
.scrollbar::-webkit-scrollbar-track {
background: #fff;
}
.ant-select-outlined.ant-select-multiple .ant-select-selection-item {
background: var(--secondary-color);
color: white;
svg {
color: white;
}
}
.ant-select-selection-item {
color: var(--primary-color);
}
.ant-select {
.ant-select-arrow {
display: none !important;
}
}
.ant-picker-input {
.ant-picker-suffix,
.ant-picker-clear {
color: var(--primary-color);
}
}

View File

@@ -0,0 +1,4 @@
@import 'tailwindcss';
@import '@kevisual/center-components/theme/wind-theme.css';
@import './style.css';

View File

@@ -1,6 +1,5 @@
import { useEffect, useMemo } from 'react';
import { theme } from '@kevisual/center-components/theme/index.tsx';
import { ThemeProvider } from '@mui/material/styles';
import { CustomThemeProvider } from '@kevisual/center-components/theme/index.tsx';
import { Left } from './layout/Left';
import { Main } from './main/index';
import { ToastContainer } from 'react-toastify';
@@ -120,11 +119,11 @@ export const App = ({ providers, noProvider, isSingleApp = true }: AppProps) =>
return <>{children}</>;
}
return (
<ThemeProvider theme={theme}>
<CustomThemeProvider>
<AntdConfigProvider>
{children}
<ToastContainer />
</AntdConfigProvider>
</ThemeProvider>
</CustomThemeProvider>
);
};

View File

@@ -6,107 +6,116 @@ import { getIcon } from '../FileIcon';
import { Download, Trash } from 'lucide-react';
import clsx from 'clsx';
import { useResourceFileStore } from '@kevisual/resources/pages/store/resource-file';
import { useConfirm } from '@kevisual/center-components/modal/Confirm.tsx';
export const FileTable = () => {
const { list, prefix, download, onOpenPrefix, deleteFile } = useResourceStore();
const { setOpenDrawer, setPrefix } = useResourceFileStore();
const { confirm, contextHolder } = useConfirm();
return (
<TableContainer
className='scrollbar'
sx={{
'&': {
// scrollbarWidth: 'none',
// scrollbarColor: '#888 #fff',
},
'&::-webkit-scrollbar': {
width: '4px !important',
height: '4px !important',
background: '#fff',
},
'&::-webkit-scrollbar-thumb': {
background: '#888',
borderRadius: '2px',
},
}}
component={Paper}>
<Table
<>
{contextHolder}
<TableContainer
className='scrollbar'
sx={{
minWidth: 650,
'&': {
// scrollbarWidth: 'none',
// scrollbarColor: '#888 #fff',
},
'&::-webkit-scrollbar': {
width: '4px !important',
height: '4px !important',
background: '#fff',
},
'&::-webkit-scrollbar-thumb': {
background: '#888',
borderRadius: '2px',
},
}}
aria-label='simple table'>
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell sx={{ minWidth: 100 }}>Size</TableCell>
<TableCell sx={{ minWidth: 180 }}>Last Modified</TableCell>
<TableCell sx={{ minWidth: 110 }}>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{list.map((row) => {
const isFile = !!row.name;
return (
<TableRow key={row.etag || row.name || row.prefix}>
<TableCell>
<div
className={clsx('flex items-center gap-2 max-w-[300px] line-clamp-2 text-ellipsis', {
'cursor-pointer': true,
})}
onClick={(e) => {
if (!row.name) {
onOpenPrefix(row.prefix || '');
} else {
setPrefix(row.name || '');
setOpenDrawer(true);
}
e.stopPropagation();
}}>
<div className='shrink-0'>{getIcon(row.name)}</div>
{row.name ? row.name.replace(prefix, '') : row.prefix?.replace?.(prefix, '')}
</div>
</TableCell>
<TableCell>{row.size ? prettyBytes(row.size) : ''}</TableCell>
<TableCell>{row.lastModified ? dayjs(row.lastModified).format('YYYY-MM-DD HH:mm:ss') : ''}</TableCell>
<TableCell>
{isFile && (
<Button
variant='contained'
color='primary'
component={Paper}>
<Table
sx={{
minWidth: 650,
}}
aria-label='simple table'>
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell sx={{ minWidth: 100 }}>Size</TableCell>
<TableCell sx={{ minWidth: 180 }}>Last Modified</TableCell>
<TableCell sx={{ minWidth: 110 }}>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{list.map((row) => {
const isFile = !!row.name;
return (
<TableRow key={row.etag || row.name || row.prefix}>
<TableCell>
<div
className={clsx('flex items-center gap-2 max-w-[300px] line-clamp-2 text-ellipsis', {
'cursor-pointer': true,
})}
onClick={(e) => {
if (!row.name) {
onOpenPrefix(row.prefix || '');
} else {
setPrefix(row.name || '');
setOpenDrawer(true);
}
e.stopPropagation();
download(row);
}}
sx={{
color: 'white',
minWidth: '32px',
padding: '4px',
}}>
<Download />
</Button>
)}
{isFile && (
<Button
variant='contained'
color='error'
className='ml-2!'
onClick={(e) => {
e.stopPropagation();
deleteFile(row);
}}
sx={{
color: 'white',
minWidth: '32px',
padding: '4px',
}}>
<Trash />
</Button>
)}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
<div className='shrink-0'>{getIcon(row.name)}</div>
{row.name ? row.name.replace(prefix, '') : row.prefix?.replace?.(prefix, '')}
</div>
</TableCell>
<TableCell>{row.size ? prettyBytes(row.size) : ''}</TableCell>
<TableCell>{row.lastModified ? dayjs(row.lastModified).format('YYYY-MM-DD HH:mm:ss') : ''}</TableCell>
<TableCell>
{isFile && (
<Button
variant='contained'
color='primary'
onClick={(e) => {
e.stopPropagation();
download(row);
}}
sx={{
color: 'white',
minWidth: '32px',
padding: '4px',
}}>
<Download />
</Button>
)}
{isFile && (
<Button
variant='contained'
color='error'
className='ml-2!'
onClick={(e) => {
e.stopPropagation();
confirm('删除文件', '确定删除该文件吗?', {
onConfirm: () => {
deleteFile(row);
},
});
}}
sx={{
color: 'white',
minWidth: '32px',
padding: '4px',
}}>
<Trash />
</Button>
)}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
</>
);
};

View File

@@ -1,37 +1,16 @@
import React, { Fragment, useEffect, useMemo } from 'react';
import {
AppBar,
Box,
CssBaseline,
Divider,
List,
ListItem,
ListItemButton,
ListItemText,
Toolbar,
Typography,
Button,
useTheme,
ListItemIcon,
Tooltip,
Container,
} from '@mui/material';
import { Box, Divider, List, ListItem, ListItemButton, ListItemText, useTheme, ListItemIcon, Tooltip, Container } from '@mui/material';
import { ActiveMenu, useLayoutStore } from '../store/layout';
import { activeMenuList } from '../modules/MenuList';
import { amber } from '@mui/material/colors';
import { lighten, rgbToHex } from '@mui/material/styles';
import { SquareMenu } from 'lucide-react';
const drawerWidth = 240;
export type LeftProps = {
children?: React.ReactNode;
};
export const Left = ({ children }: LeftProps) => {
const { setActiveMenu, init, showLabel, setShowLabel } = useLayoutStore();
const theme = useTheme();
// console.log(theme.palette.primary.main, rgbToHex(lighten(theme.palette.primary.main, 0.4)));
// console.log(theme.palette.divider);
useEffect(() => {
init();
}, []);
@@ -49,23 +28,24 @@ export const Left = ({ children }: LeftProps) => {
}, [activeMenuList]);
return (
<Box sx={{ display: 'flex', backgroundColor: amber[50] }}>
<Box sx={{ height: '100%', display: 'flex', backgroundColor: amber[50] }}>
<Box component='nav' sx={{ flexShrink: { sm: 0 } }}>
<Box
sx={{
flexShrink: 0,
'& .MuiDrawer-paper': { boxSizing: 'border-box' },
height: '100%',
}}>
<Box
sx={{
textAlign: 'center',
height: '100vh',
borderRight: 1,
borderColor: theme.palette.divider,
display: 'flex',
height: '100%',
flexDirection: 'column',
}}>
<List sx={{ flexGrow: 1 }} key={list.length}>
<List sx={{ flexGrow: 1, height: '100%' }} key={list.length}>
{list.map((item) => (
<Fragment key={item.value}>
<ListItem disablePadding>
@@ -106,7 +86,7 @@ export const Left = ({ children }: LeftProps) => {
</Box>
</Box>
</Box>
<Box sx={{ flexGrow: 1, height: '100vh', overflow: 'hidden' }}>
<Box sx={{ flexGrow: 1, height: '100%', overflow: 'hidden' }}>
<Container sx={{ width: '100%', height: '100%' }}>{children}</Container>
</Box>
</Box>

View File

@@ -18,5 +18,5 @@ export const Main = () => {
if (activeMenu === ActiveMenu.Statistic) {
return <Statistic />;
}
return <div className='h-full'>{activeMenu}</div>;
return <div className='h-full overflow-hidden'>{activeMenu}</div>;
};

View File

@@ -9,10 +9,12 @@ type ConvertOpts = {
version?: string;
username?: string;
directory?: string;
isPublic?: boolean;
filename?: string;
};
export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
const { directory, appKey, version, username } = opts;
const { directory, appKey, version, username, isPublic } = opts;
return new Promise(async (resolve, reject) => {
const token = localStorage.getItem('token');
if (!token) {
@@ -21,11 +23,16 @@ export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
}
const taskId = nanoid();
const filename = file.name;
const filename = opts.filename || file.name;
const load = toast.loading(`${filename} 上传中...`);
NProgress.start();
// const eventSource = new EventSource('http://49.232.155.236:11015/api/s1/events?taskId=' + taskId);
const eventSource = new EventSource('/api/s1/events?taskId=' + taskId);
const searchParams = new URLSearchParams();
searchParams.set('taskId', taskId);
if (isPublic) {
searchParams.set('public', 'true');
}
const eventSource = new EventSource('/api/s1/events?' + searchParams.toString());
// 监听服务器推送的进度更新
eventSource.onmessage = function (event) {
console.log('Progress update:', event.data);
@@ -60,9 +67,10 @@ export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('file', chunk, file.name);
formData.append('file', chunk, filename);
formData.append('chunkIndex', currentChunk.toString());
formData.append('totalChunks', totalChunks.toString());
const isLast = currentChunk === totalChunks - 1;
if (directory) {
formData.append('directory', directory);
}
@@ -83,10 +91,12 @@ export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
},
}).then((response) => response.json());
fetch('/api/s1/events/close?taskId=' + taskId);
eventSource.close();
NProgress.done();
toast.dismiss(load);
resolve(res);
if (isLast) {
NProgress.done();
eventSource.close();
toast.dismiss(load);
resolve(res);
}
// console.log(`Chunk ${currentChunk + 1}/${totalChunks} uploaded`, res);
} catch (error) {
console.log('Error uploading chunk', error);

View File

@@ -1,3 +1,22 @@
:root {
--scrollbar-color: #ffbf00;
--primary-color: #ffc107;
--secondary-color: #ffa000;
}
#root {
width: 100%;
height: 100%;
}
#ai-bot-root {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: -100px;
z-index: 9999;
pointer-events: none;
}
.scrollbar {
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-color) #fff;
@@ -15,5 +34,4 @@
.scrollbar::-webkit-scrollbar-track {
background: #fff;
}
}