build: output files to dir

main
jialin 2 years ago
parent 2352c316c0
commit 9fa61b9849

@ -1 +1 @@
PORT=9000
PORT=9000

@ -1,13 +1,93 @@
import { defineConfig } from '@umijs/max';
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const DeleteCssPlugin = require('./plugins/delete-css-plugin');
import proxy from './proxy';
import routes from './routes';
const env = process.env.NODE_ENV;
const isProduction = env === 'production';
export default defineConfig({
proxy: {
...proxy()
},
base: process.env.npm_config_base || '/',
...(isProduction
? {
extraBabelPlugins: [
[
'babel-plugin-named-asset-import',
{
loaderMap: {
css: {
loader: 'css-loader',
options: {
modules: {
localIdentName: 'css/[name]__[local]___[hash:base64:5]'
}
}
}
}
}
]
],
cssLoader: {
modules: {
localIdentName: 'css/[name]__[local]___[hash:base64:5]'
}
},
chainWebpack(config: any) {
config.module.rules.delete('image');
config.module.rules.delete('images');
config.plugin('extract-css').use(MiniCssExtractPlugin, [
{
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].chunk.css',
ignoreOrder: true
}
]);
config.plugin('delete-css').use(DeleteCssPlugin, [
{
outputPath: path.resolve(__dirname, '../', 'dist')
}
]);
config.output
.filename('js/[name].[contenthash:8].js')
.chunkFilename('js/[name].[contenthash:8].chunk.js');
config
.plugin('compression-webpack-plugin')
.use(CompressionWebpackPlugin, [
{
filename: '[path][base].gz',
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 10240,
minRatio: 0.8
}
]);
config.module
.rule('images')
.test(/\.(png|jpe?g|gif|svg|ico)(\?.*)?$/)
.use('url-loader')
.loader(require.resolve('url-loader'))
.tap((options: any) => {
console.log('iamges==options========', options);
return {
...options,
limit: 8192, // 小于8KB的图片会被转为base64
fallback: {
loader: require.resolve('file-loader'),
options: {
name: 'static/[name].[hash:8].[ext]' // 将所有图片输出到 static 目录
}
}
};
});
}
}
: {}),
// esbuildMinifyIIFE: true,
jsMinifier: 'terser',
cssMinifier: 'cssnano',

@ -0,0 +1,38 @@
// @ts-nocheck
const fs = require('fs');
const path = require('path');
class DeleteCssPlugin {
constructor(options) {
this.options = options || {};
}
apply(compiler) {
compiler.hooks.done.tap('DeleteCssPlugin', (stats) => {
const outputPath =
this.options.outputPath || path.resolve(__dirname, 'dist');
fs.readdir(outputPath, (err, files) => {
if (err) {
console.error(`Failed to read directory: ${outputPath}`, err);
return;
}
files.forEach((file) => {
if (file.endsWith('.css')) {
const filePath = path.join(outputPath, file);
fs.unlink(filePath, (err) => {
if (err) {
console.error(`Failed to delete file: ${filePath}`, err);
} else {
console.log(`Deleted: ${filePath}`);
}
});
}
});
});
});
}
}
module.exports = DeleteCssPlugin;

@ -32,15 +32,23 @@
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"@umijs/case-sensitive-paths-webpack-plugin": "^1.0.1",
"babel-plugin-named-asset-import": "^0.3.8",
"case-sensitive-paths-webpack-plugin": "^2.4.0",
"compression-webpack-plugin": "^11.1.0",
"css-loader": "^7.1.2",
"eslint": "^8.56.0",
"eslint-plugin-unused-imports": "^3.2.0",
"extract-css-loader": "^0.0.1",
"file-loader": "^6.2.0",
"husky": "^9.0.11",
"less-loader": "^12.2.0",
"lint-staged": "^15.2.2",
"mini-css-extract-plugin": "^2.9.0",
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^3.2.4",
"prettier-plugin-packagejson": "^2.5.0",
"prettier-plugin-two-style-order": "^1.0.1",
"typescript": "^5.4.5"
"typescript": "^5.4.5",
"url-loader": "^4.1.1"
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,61 @@
import { Bar } from '@ant-design/plots';
interface BarChartProps {
data: any[];
xField: string;
yField: string;
title?: string;
height?: number;
}
const BarChart: React.FC<BarChartProps> = (props) => {
const { data, xField, yField, title, height = 300 } = props;
const config = {
data,
xField,
yField,
title,
// colorField: 'name',
direction: 'vertical',
height,
group: true,
legend: {
color: {
position: 'top',
layout: {
justifyContent: 'center'
}
}
},
axis: {
x: {
xAxis: true
}
},
split: {
type: 'line',
line: {
color: 'red',
style: {
color: 'red',
lineDash: [4, 5]
}
}
},
style: {
fill: '#2fbf85',
radiusTopLeft: 8,
radiusTopRight: 8,
height: 30
}
};
return (
<>
<Bar {...config} />
</>
);
};
export default BarChart;

@ -0,0 +1,61 @@
import { Column } from '@ant-design/plots';
interface BarChartProps {
data: any[];
xField: string;
yField: string;
title?: string;
height?: number;
}
const BarChart: React.FC<BarChartProps> = (props) => {
const { data, xField, yField, title, height = 260 } = props;
const config = {
data,
xField,
yField,
title,
// colorField: 'name',
direction: 'vertical',
height,
group: true,
legend: {
color: {
position: 'top',
layout: {
justifyContent: 'center'
}
}
},
axis: {
x: {
xAxis: true
}
},
split: {
type: 'line',
line: {
color: 'red',
style: {
color: 'red',
lineDash: [4, 5]
}
}
},
style: {
fill: '#2fbf85',
radiusTopLeft: 8,
radiusTopRight: 8,
width: 30
}
};
return (
<>
<Column {...config} />
</>
);
};
export default BarChart;

@ -0,0 +1,17 @@
.content-wrapper {
.content {
padding-block-start: 0;
padding-block-end: 32px;
padding-inline: 40px;
}
.title {
font-size: var(--font-size-large);
font-weight: 600;
line-height: 32px;
padding-block-start: 8px;
padding-block-end: 16px;
padding-inline-start: 40px;
padding-inline-end: 40px;
}
}

@ -0,0 +1,24 @@
import React from 'react';
import './index.less';
const ContentWrapper: React.FC<{
children: React.ReactNode;
title: React.ReactNode;
titleStyle?: React.CSSProperties;
contentStyle?: React.CSSProperties;
}> = ({ children, title = false, titleStyle, contentStyle }) => {
return (
<div className="content-wrapper">
{title && (
<div className="title" style={{ ...titleStyle }}>
{title}
</div>
)}
<div className="content" style={{ ...contentStyle }}>
{children}
</div>
</div>
);
};
export default ContentWrapper;

@ -14,6 +14,8 @@ html {
--color-text-1: var(--ant-color-text);
--color-bg-light-1: #f0fff6;
--font-size-base: 12px;
--font-size-large: 16px;
--font-size-middle: 14px;
--ant-color-fill-secondary: rgba(0, 0, 0, 6%);
--table-td-radius: 24px;
--checkbox-border-radius: 4px;

@ -161,7 +161,7 @@ const ModelBills: React.FC = () => {
<PageTools
marginBottom={10}
marginTop={0}
left={<span>Bills by model</span>}
left={false}
right={
<Space>
<Select

@ -1,12 +1,15 @@
import { Card, Col, Row } from 'antd';
import { Card, Col, Row, Space } from 'antd';
import React from 'react';
import { overviewConfigs } from '../config';
import '../styles/index.less';
import styles from './over-view.less';
const CardItem: React.FC<{ label: string; value: number; bgColor: string }> = ({
label,
value,
bgColor
const renderCardItem = (data: {
label: string;
value: React.ReactNode;
bgColor: string;
}) => {
const { label, value, bgColor } = data;
return (
<Card
bordered={false}
@ -21,16 +24,52 @@ const CardItem: React.FC<{ label: string; value: number; bgColor: string }> = ({
);
};
const Overview: React.FC = (props) => {
const { data = {} } = props;
// const { data = {} } = props;
const data = {
workers: 8,
models: {
healthy: 10,
warning: 2,
error: 1
},
gpus: 30,
allocatedGpus: 12,
instances: {
healthy: 32,
warning: 3,
error: 2
}
};
const renderValue = (
value:
| number
| {
healthy: number;
warning: number;
error: number;
}
) => {
if (typeof value === 'number') {
return value;
}
return (
<Space className="value-box">
<span className={'value-healthy'}>{value.healthy}</span>
<span className={'value-warning'}>{value.warning}</span>
<span className={'value-error'}>{value.error}</span>
</Space>
);
};
return (
<Row gutter={[20, 20]} className={styles.row}>
{overviewConfigs.map((config, index) => (
<Col span={5} key={config.key}>
<CardItem
label={config.label}
value={data[config.key] || 0}
bgColor={config.backgroundColor}
/>
{renderCardItem({
label: config.label,
value: renderValue(data[config.key] || 0),
bgColor: config.backgroundColor
})}
</Col>
))}
</Row>

@ -1,15 +1,21 @@
import PageTools from '@/components/page-tools';
import { generateRandomArray } from '@/utils';
import { Line } from '@ant-design/plots';
import { DatePicker } from 'antd';
import _ from 'lodash';
const mockData = {
GPU: generateRandomArray(),
CPU: generateRandomArray(),
Memory: generateRandomArray(),
VRAM: generateRandomArray()
};
const UtilizationOvertime: React.FC = () => {
const timeList = [
'01:00:00',
'02:00:00',
'03:00:00',
'04:00:00',
'05:00:00',
// '01:00:00',
// '02:00:00',
// '03:00:00',
// '04:00:00',
// '05:00:00',
'06:00:00',
'07:00:00',
'08:00:00',
@ -21,7 +27,7 @@ const UtilizationOvertime: React.FC = () => {
'14:00:00',
'15:00:00'
];
const typeList = ['GPU', 'CPU', 'Memory'];
const typeList = ['GPU', 'CPU', 'Memory', 'VRAM'];
const generateData = () => {
const data = [];
for (let i = 0; i < timeList.length; i++) {
@ -29,7 +35,7 @@ const UtilizationOvertime: React.FC = () => {
data.push({
time: timeList[i],
type: typeList[j],
value: _.round(Math.random(), 3)
value: _.get(mockData, typeList[j])[i]
});
}
}
@ -41,10 +47,12 @@ const UtilizationOvertime: React.FC = () => {
console.log('dateString============', date);
};
const config = {
// title: 'Resource Utilization',
xField: 'time',
yField: 'value',
color: ['red', 'blue', 'green'],
colorField: 'type',
autoFit: true,
slider: false,
shapeField: 'smooth',
axis: {
@ -69,21 +77,15 @@ const UtilizationOvertime: React.FC = () => {
tooltip: {
title: 'time',
items: [{ channel: 'y' }]
},
label: {
autoRotate: true
}
// label: {
// autoRotate: true
// }
};
// <DatePicker onChange={handleSelectDate} style={{ width: 300 }} />
return (
<>
<PageTools
marginBottom={10}
marginTop={0}
left={<span>Utilization Over Time</span>}
right={
<DatePicker onChange={handleSelectDate} style={{ width: 300 }} />
}
/>
<PageTools marginBottom={10} marginTop={0} left={false} right={false} />
<Line height={400} data={data} {...config} />
</>
);

@ -1,31 +1,31 @@
export const overviewConfigs = [
{
key: 'models',
label: 'Models',
key: 'workers',
label: 'Workers',
backgroundColor:
'linear-gradient(180deg, rgba(0,188,203,.2) 0%, rgba(40,207,181,.2) 100%)'
},
{
key: 'nodes',
label: 'Nodes',
key: 'gpus',
label: 'Total GPUs',
backgroundColor:
'linear-gradient(180deg, rgba(0,188,203,.2) 0%, rgba(40,207,181,.2) 100%)'
},
{
key: 'gpus',
label: 'Total GPUs',
key: 'allocatedGpus',
label: 'Allocated GPUs',
backgroundColor:
'linear-gradient(180deg, rgba(0,188,203,.2) 0%, rgba(40,207,181,.2) 100%)'
},
{
key: 'idleGpus',
label: 'Idle GPUs',
key: 'models',
label: 'Models',
backgroundColor:
'linear-gradient(180deg, rgba(0,188,203,.2) 0%, rgba(40,207,181,.2) 100%)'
},
{
key: 'allocatedGpus',
label: 'Allocated GPUs',
key: 'instances',
label: 'Instances',
backgroundColor:
'linear-gradient(180deg, rgba(0,188,203,.2) 0%, rgba(40,207,181,.2) 100%)'
}

@ -1,11 +1,94 @@
import BarChart from '@/components/bar-chart';
import HBar from '@/components/bar-chart/h-bar';
import ContentWrapper from '@/components/content-wrapper';
import DividerLine from '@/components/divider-line';
import PageTools from '@/components/page-tools';
import { generateRandomArray } from '@/utils';
import { PageContainer } from '@ant-design/pro-components';
import { Col, Row } from 'antd';
import GPUUtilization from './components/gpu-utilization';
import Overview from './components/over-view';
import UtilizationOvertime from './components/utilization-overtime';
const times = [
'june 1',
'june 2',
'june 3',
'june 4',
'june 5',
'june 6',
'june 7'
];
const users = ['Jim', 'Lucy', 'Lily', 'Tom', 'Jack', 'Rose', 'Jerry'];
const projects = [
'copilot-dev',
'rag-wiki',
'smart-auto-agent',
'office-auto-docs',
'smart-customer-service'
];
const APIRequestData = generateRandomArray({
length: times.length,
max: 100,
min: 0,
offset: 10
});
const TokensData = generateRandomArray({
length: times.length,
max: 2000,
min: 200,
offset: 200
});
const usersData = generateRandomArray({
length: users.length,
max: 100,
min: 0,
offset: 10
});
const projectsData = generateRandomArray({
length: projects.length,
max: 100,
min: 0,
offset: 10
});
const userDataList = usersData
.map((val, i) => {
return {
time: users[i],
value: val
};
})
.sort((a, b) => b.value - a.value);
const projectDataList = projectsData
.map((val, i) => {
return {
time: projects[i],
value: val
};
})
.sort((a, b) => b.value - a.value);
const dataList = APIRequestData.map((val, i) => {
console.log('val', val);
return {
time: times[i],
value: val
};
});
const tokenUsage = TokensData.map((val, i) => {
console.log('val', val);
return {
time: times[i],
value: val
};
});
const Dashboard: React.FC = () => {
return (
<>
@ -13,6 +96,10 @@ const Dashboard: React.FC = () => {
<Overview></Overview>
</PageContainer>
<DividerLine></DividerLine>
<PageContainer ghost title="System Load">
<UtilizationOvertime></UtilizationOvertime>
</PageContainer>
<DividerLine></DividerLine>
<PageContainer ghost title={false}>
<Row gutter={20} style={{ marginTop: '0px' }}>
<Col span={12}>
@ -54,18 +141,53 @@ const Dashboard: React.FC = () => {
</Col>
</Row>
</PageContainer>
{/* <DividerLine></DividerLine>
<PageContainer ghost title={false}>
<div style={{ marginTop: '0px' }}>
<ModelBills></ModelBills>
</div>
</PageContainer> */}
<DividerLine></DividerLine>
<PageContainer ghost title={false}>
<div style={{ marginTop: '0px' }}>
<UtilizationOvertime></UtilizationOvertime>
</div>
</PageContainer>
<Row gutter={10}>
<Col span={12}>
<ContentWrapper
contentStyle={{ paddingRight: 0 }}
title={<span style={{ lineHeight: '48px' }}>API Request</span>}
>
<BarChart data={dataList} xField="time" yField="value"></BarChart>
</ContentWrapper>
</Col>
<Col span={12}>
<ContentWrapper
contentStyle={{ paddingRight: 0 }}
title={<span style={{ lineHeight: '48px' }}>Tokens</span>}
>
<BarChart data={tokenUsage} xField="time" yField="value"></BarChart>
</ContentWrapper>
</Col>
</Row>
<Row gutter={10}>
<Col span={12}>
<ContentWrapper
contentStyle={{ paddingRight: 0 }}
title={<span style={{ lineHeight: '48px' }}>Top Users</span>}
>
<HBar
data={userDataList}
xField="time"
yField="value"
height={360}
></HBar>
</ContentWrapper>
</Col>
<Col span={12}>
<ContentWrapper
contentStyle={{ paddingRight: 0 }}
title={<span style={{ lineHeight: '48px' }}>Top Projects</span>}
>
<HBar
data={projectDataList}
xField="time"
yField="value"
height={300}
></HBar>
</ContentWrapper>
</Col>
</Row>
</>
);
};

@ -0,0 +1,17 @@
.value-box {
display: flex;
justify-content: space-around;
align-items: center;
.value-healthy {
color: var(--ant-green-6);
}
.value-warning {
color: var(--ant-gold-6);
}
.value-danger {
color: var(--ant-red-6);
}
}

@ -129,6 +129,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
onChange={handleInputRepoChange}
onSearch={debounceSearch}
options={repoOptions}
description="Only .gguf format is supported"
></SealAutoComplete>
</Form.Item>
<Form.Item<FormData>
@ -210,11 +211,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
>
<Form name="addModalForm" form={form} onFinish={onOk} preserve={false}>
<Form.Item<FormData> name="name" rules={[{ required: true }]}>
<SealInput.Input
label="Name"
required
description="description info"
></SealInput.Input>
<SealInput.Input label="Name" required></SealInput.Input>
</Form.Item>
<Form.Item<FormData> name="source" rules={[{ required: true }]}>
<SealSelect

@ -16,14 +16,21 @@ import ViewCodeModal from './view-code-modal';
interface MessageProps {
parameters: any;
}
interface MessageItemProps {
role: string;
content: string;
uid: number;
}
const MessageList: React.FC<MessageProps> = (props) => {
const { parameters } = props;
const [messageList, setMessageList] = useState<
{ role: string; content: string }[]
>([
const messageId = useRef<number>(0);
const [messageList, setMessageList] = useState<MessageItemProps[]>([
{
role: 'user',
content: ''
content: '',
uid: messageId.current
}
]);
@ -37,11 +44,17 @@ const MessageList: React.FC<MessageProps> = (props) => {
const handleSystemMessageChange = (e: any) => {
setSystemMessage(e.target.value);
};
const setMessageId = () => {
messageId.current = messageId.current + 1;
};
const handleNewMessage = () => {
messageList.push({
role: 'user',
content: ''
content: '',
uid: messageId.current + 1
});
setMessageId();
setMessageList([...messageList]);
setActiveIndex(messageList.length - 1);
};
@ -71,9 +84,11 @@ const MessageList: React.FC<MessageProps> = (props) => {
...messageList,
{
role: Roles.Assistant,
content: assistant.content
content: assistant.content,
uid: messageId.current + 1
}
]);
setMessageId();
setLoading(false);
} catch (error) {
setLoading(false);
@ -101,10 +116,7 @@ const MessageList: React.FC<MessageProps> = (props) => {
setMessageList([...messageList]);
};
const handleUpdateMessage = (
index: number,
message: { role: string; content: string }
) => {
const handleUpdateMessage = (index: number, message: MessageItemProps) => {
messageList[index] = message;
console.log('updatemessage========', index, message);
setMessageList([...messageList]);
@ -143,12 +155,12 @@ const MessageList: React.FC<MessageProps> = (props) => {
{messageList.map((item, index) => {
return (
<MessageItem
key={index}
key={item.uid}
isFocus={index === activeIndex}
islast={index === messageList.length - 1}
loading={loading}
onDelete={() => handleDelete(index)}
updateMessage={(message: { role: string; content: string }) =>
updateMessage={(message: MessageItemProps) =>
handleUpdateMessage(index, message)
}
message={item}

@ -4,18 +4,20 @@ import _ from 'lodash';
import { memo, useEffect, useRef, useState } from 'react';
import { Roles } from '../config';
import '../style/message-item.less';
interface MessageItemProps {
role: string;
content: string;
uid: number;
}
const MessageItem: React.FC<{
message: {
role: string;
content: string;
};
message: MessageItemProps;
loading?: boolean;
islast?: boolean;
updateMessage: (message: { role: string; content: string }) => void;
updateMessage: (message: MessageItemProps) => void;
isFocus: boolean;
onDelete: () => void;
}> = ({ message, isFocus, onDelete, updateMessage, loading, islast }) => {
}> = ({ message, isFocus, onDelete, updateMessage }) => {
const [roleType, setRoleType] = useState(message.role);
const [isTyping, setIsTyping] = useState(false);
const [messageContent, setMessageContent] = useState(message.content);
@ -51,7 +53,11 @@ const MessageItem: React.FC<{
useEffect(() => {
if (!isAnimating && !isInitialRender.current) {
updateMessage({ role: roleType, content: messageContent });
updateMessage({
role: roleType,
content: messageContent,
uid: message.uid
});
} else {
isInitialRender.current = false;
}

@ -28,3 +28,30 @@ export const convertFileSize = (sizeInBytes: number) => {
return `${(sizeInBytes / (1024 * 1024 * 1024 * 1024)).toFixed(2)} TB`;
}
};
export const generateRandomArray = (config?: {
min: number;
max: number;
length: number;
offset: number;
}) => {
const { min, max, length, offset } = config || {
min: 10,
max: 80,
length: 10,
offset: 10
};
const data = [];
let prevValue = Math.floor(Math.random() * (max - min + 1)) + min;
for (let i = 0; i < length; i++) {
// 确保波动不太大
let newValue = prevValue + Math.floor(Math.random() * 21) - offset; // 波动范围 [-10, 10]
newValue = Math.max(min, Math.min(max, newValue)); // 保证在 [10, 100] 范围内
data.push(newValue);
prevValue = newValue;
}
return data;
};

Loading…
Cancel
Save