diff --git a/config/config.ts b/config/config.ts index 2f794d2e..d6874c03 100644 --- a/config/config.ts +++ b/config/config.ts @@ -65,6 +65,7 @@ export default defineConfig({ } : {}), // esbuildMinifyIIFE: true, + favicons: ['/static/favicon.ico'], jsMinifier: 'terser', cssMinifier: 'cssnano', presets: ['umi-presets-pro'], diff --git a/src/favicon.ico b/public/static/favicon.ico similarity index 100% rename from src/favicon.ico rename to public/static/favicon.ico diff --git a/src/assets/images/bg-5.png b/src/assets/images/bg-5.png new file mode 100644 index 00000000..a494136a Binary files /dev/null and b/src/assets/images/bg-5.png differ diff --git a/src/assets/images/bg-6.png b/src/assets/images/bg-6.png new file mode 100644 index 00000000..b5c82747 Binary files /dev/null and b/src/assets/images/bg-6.png differ diff --git a/src/assets/images/bg-7.png b/src/assets/images/bg-7.png new file mode 100644 index 00000000..03367dac Binary files /dev/null and b/src/assets/images/bg-7.png differ diff --git a/src/assets/images/bg-8.png b/src/assets/images/bg-8.png new file mode 100644 index 00000000..09d62af9 Binary files /dev/null and b/src/assets/images/bg-8.png differ diff --git a/src/assets/images/bg.png b/src/assets/images/bg.png new file mode 100644 index 00000000..de7f0623 Binary files /dev/null and b/src/assets/images/bg.png differ diff --git a/src/assets/images/favicon.ico b/src/assets/images/favicon.ico new file mode 100644 index 00000000..76597bea Binary files /dev/null and b/src/assets/images/favicon.ico differ diff --git a/src/components/card-wrapper/index.less b/src/components/card-wrapper/index.less new file mode 100644 index 00000000..a5e2de39 --- /dev/null +++ b/src/components/card-wrapper/index.less @@ -0,0 +1,6 @@ +.cardWrapper { + border-radius: var(--border-radius-middle); + background-color: var(--color-white-1); + box-shadow: var(--box-shadow-base); + padding: 10px; +} diff --git a/src/components/card-wrapper/index.tsx b/src/components/card-wrapper/index.tsx new file mode 100644 index 00000000..90c076b3 --- /dev/null +++ b/src/components/card-wrapper/index.tsx @@ -0,0 +1,12 @@ +import styles from './index.less'; + +const CardWrapper = (props: any) => { + const { children, style } = props; + return ( +
+ {children} +
+ ); +}; + +export default CardWrapper; diff --git a/src/components/charts/column-bar.tsx b/src/components/charts/column-bar.tsx index cee60d98..d9200054 100644 --- a/src/components/charts/column-bar.tsx +++ b/src/components/charts/column-bar.tsx @@ -6,34 +6,69 @@ interface BarChartProps { yField: string; title?: string; height?: number; + group?: boolean; + colorField?: string; + seriesField?: string; + stack?: boolean; + legend?: any; } const BarChart: React.FC = (props) => { - const { data, xField, yField, title, height = 260 } = props; + const { + data, + xField, + yField, + title, + height = 260, + group, + colorField, + seriesField, + stack, + legend = undefined + } = props; const config = { data, xField, yField, - // colorField: 'name', + colorField: colorField || 'name', direction: 'vertical', + stack, + seriesField, height, - group: true, - legend: { - color: { - position: 'top', - layout: { - justifyContent: 'center' - } + group, + scale: { + x: { + type: 'band', + padding: 0.5 } }, + legend: + legend === 'undefined' + ? { + color: { + position: 'top', + layout: { + justifyContent: 'center' + } + } + } + : legend, axis: { x: { - xAxis: true + xAxis: true, + tick: false + }, + y: { + tick: false, + labelAutoWrap: true } }, title: { title, style: { - align: 'center' + align: 'center', + titleFontSize: 14, + titleFill: 'rgba(0,0,0,0.88)', + titleFontWeight: 500 } }, split: { @@ -48,10 +83,16 @@ const BarChart: React.FC = (props) => { }, style: { - fill: '#54cc98', - radiusTopLeft: 8, - radiusTopRight: 8, - width: 30 + fill: (params: any) => { + return ( + params.color || + 'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgb(0, 168, 143,.7) 100%)' + ); + }, + radiusTopLeft: 12, + radiusTopRight: 12, + align: 'center', + width: 20 } }; diff --git a/src/components/charts/gauge.tsx b/src/components/charts/gauge.tsx index 1b6cca2e..e226e247 100644 --- a/src/components/charts/gauge.tsx +++ b/src/components/charts/gauge.tsx @@ -16,6 +16,19 @@ const GaugeChart: React.FC = (props) => { const config = { width, height, + box: { + padding: [0, 0, 0, 0], + style: { + padding: [0, 0, 0, 0] + } + }, + guide: { + arc: { + style: { + lineWidth: 30 + } + } + }, autoFit: true, data: { target, @@ -34,9 +47,11 @@ const GaugeChart: React.FC = (props) => { title: { title, size: 0, - titleFontSize: 14, style: { - align: 'center' + align: 'center', + titleFontSize: 14, + titleFill: 'rgba(0,0,0,0.88)', + titleFontWeight: 500 } }, style: { diff --git a/src/components/charts/h-bar.tsx b/src/components/charts/h-bar.tsx index 12edd9fc..4d6c9d15 100644 --- a/src/components/charts/h-bar.tsx +++ b/src/components/charts/h-bar.tsx @@ -6,52 +6,89 @@ interface BarChartProps { yField: string; title?: string; height?: number; + group?: boolean; + colorField?: string; + seriesField?: string; + stack?: boolean; + legend?: any; } const BarChart: React.FC = (props) => { - const { data, xField, yField, title, height = 300 } = props; + const { + data, + xField, + yField, + title, + height, + group, + colorField, + seriesField, + stack, + legend = undefined + } = props; const config = { data, xField, yField, - // colorField: 'name', + colorField: colorField || 'name', direction: 'vertical', + seriesField, height, - group: true, - legend: { - color: { - position: 'top', - layout: { - justifyContent: 'center' - } + group, + stack, + legend: + legend === 'undefined' + ? { + color: { + position: 'top', + layout: { + justifyContent: 'center' + } + } + } + : legend, + scale: { + x: { + type: 'band', + padding: 0.5 } }, axis: { x: { - xAxis: true + xAxis: true, + tick: false + }, + y: { + tick: false } }, title: { title, style: { - align: 'center' + align: 'center', + titleFontSize: 14, + titleFill: 'rgba(0,0,0,0.88)', + titleFontWeight: 500 } }, split: { type: 'line', line: { - color: 'red', style: { - color: 'red', lineDash: [4, 5] } } }, - + markBackground: {}, style: { - fill: '#54cc98', - radiusTopLeft: 8, - radiusTopRight: 8, - height: 30 + fill: (params: any) => { + return ( + params.color || + 'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgb(0, 168, 143,.7) 100%)' + ); + }, + radiusTopLeft: 12, + radiusTopRight: 12, + height: 20 } }; diff --git a/src/components/charts/line-chart.tsx b/src/components/charts/line-chart.tsx index aea2ec27..f93ba481 100644 --- a/src/components/charts/line-chart.tsx +++ b/src/components/charts/line-chart.tsx @@ -7,16 +7,18 @@ interface LineChartProps { color?: string[]; xField?: string; yField?: string; - slider?: boolean; + labelFormatter?: (v: any) => string; + slider?: any; } const LineChart: React.FC = (props) => { - const { data, title, color, xField, yField, slider, height } = props; + const { data, title, color, xField, yField, slider, height, labelFormatter } = + props; const config = { title, height, xField: xField || 'time', yField: yField || 'value', - color: color || ['red', 'blue', 'green'], + // color: color || ['red', 'blue', 'green', 'yellow'], colorField: 'type', autoFit: true, slider, @@ -26,21 +28,31 @@ const LineChart: React.FC = (props) => { textStyle: { autoRoate: true } + }, + y: { + tick: false, + // size: 14, + // title: '%', + titlePosition: 'top', + titleFontSize: 12, + labelFormatter } }, - point: { - shapeField: 'circle', - sizeField: 2 - }, + // point: { + // shapeField: 'circle', + // sizeField: 2 + // }, style: { - lineWidth: 1.5 + lineWidth: 1.5, + opacity: 0.8 }, legend: { - itemMarker: { - symbol: 'circle' - }, color: { layout: { justifyContent: 'center' } + }, + size: { + itemLabelFontSize: 14, + itemLabelFontWeight: 500 } }, tooltip: { diff --git a/src/components/charts/liquid.tsx b/src/components/charts/liquid.tsx new file mode 100644 index 00000000..3b7a0513 --- /dev/null +++ b/src/components/charts/liquid.tsx @@ -0,0 +1,79 @@ +import { Liquid } from '@ant-design/plots'; +import { useEffect, useState } from 'react'; + +interface LiquidChartProps { + percent: number; + color?: string; + title?: string; + height?: number; + width?: number; + thresholds?: number[]; + rangColor?: string[]; +} +const LiquidChart: React.FC = (props) => { + const { + percent = 0, + color, + title, + width, + height, + thresholds = [], + rangColor = [] + } = props; + + const primaryColor = 'rgba(84, 204, 152,0.8)'; + const [fillColor, setFillColor] = useState(primaryColor); + + const calcColorByPercent = () => { + if (color) { + return color; + } + if (!rangColor.length) { + return primaryColor; + } + + if (rangColor.length && thresholds.length) { + let index = 0; + for (let i = 0; i < thresholds.length; i++) { + if (percent <= thresholds[i]) { + index = i; + break; + } + } + return rangColor[index]; + } + return primaryColor; + }; + + useEffect(() => { + const fc = calcColorByPercent(); + setFillColor(fc); + }, [percent]); + + const config = { + percent, + textAlign: 'center', + autoFit: true, + title: { + title, + size: 0, + style: { + align: 'center', + titleFontSize: 12, + titleFill: 'rgba(0,0,0,0.88)', + titleFontWeight: 500 + } + }, + + style: { + textAlign: 'center', + outlineBorder: 2, + waveLength: 128, + stroke: fillColor, + fill: fillColor + } + }; + return ; +}; + +export default LiquidChart; diff --git a/src/components/glass-bg/index.less b/src/components/glass-bg/index.less new file mode 100644 index 00000000..5b338e4a --- /dev/null +++ b/src/components/glass-bg/index.less @@ -0,0 +1,39 @@ +.g-polygon { + position: absolute; + opacity: 0.5; +} + +.g-polygon-1 { + // 定位代码,容器高宽随意 + background: #fe5; + clip-path: polygon(0 10%, 30% 0, 100% 40%, 70% 100%, 20% 90%); + width: 50%; + height: 50%; +} + +.g-polygon-2 { + // 定位代码,容器高宽随意 + background: #e950d1; + clip-path: polygon(10% 0, 100% 70%, 100% 100%, 20% 90%); + width: 50%; + height: 50%; +} + +.g-polygon-3 { + // 定位代码,容器高宽随意 + background: rgba(87, 80, 233); + clip-path: polygon(80% 0, 100% 70%, 100% 100%, 20% 90%); + width: 50%; + height: 50%; +} + +.g-bg::before { + content: ''; + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + backdrop-filter: blur(150px); + z-index: 1; +} diff --git a/src/components/glass-bg/index.tsx b/src/components/glass-bg/index.tsx new file mode 100644 index 00000000..0dd4374e --- /dev/null +++ b/src/components/glass-bg/index.tsx @@ -0,0 +1,13 @@ +import './index.less'; + +const GlassBg = () => { + return ( +
+
+
+
+
+ ); +}; + +export default GlassBg; diff --git a/src/components/seal-table/styles/index.less b/src/components/seal-table/styles/index.less index 642814eb..c6c05b5f 100644 --- a/src/components/seal-table/styles/index.less +++ b/src/components/seal-table/styles/index.less @@ -2,57 +2,69 @@ display: flex; flex-direction: column; width: 100%; + .header-row-wrapper { height: 40px; margin-bottom: 10px; display: flex; justify-content: flex-start; align-items: center; + .header-row-prefix-wrapper { padding-left: var(--ant-table-cell-padding-inline); } + .row { flex: 1; } } + .row-box { margin-bottom: 20px; border-radius: var(--ant-table-header-border-radius); overflow: hidden; } + .expanded-row { background-color: var(--color-white-1); - padding: 16px 16px; + padding: 16px; border: 1px solid var(--color-fill-1); border-top: 0; border-radius: 0 0 var(--ant-table-header-border-radius) var(--ant-table-header-border-radius); } + .row-wrapper { display: flex; justify-content: flex-start; align-items: center; - background-color: var(--color-fill-1); + background-color: var(--color-white-1); transition: all 0.2s ease; + &:hover { background-color: var(--ant-table-row-hover-bg); transition: all 0.2s ease; } + &-selected { background-color: var(--ant-table-row-selected-bg); transition: all 0.2s ease; + &:hover { background-color: var(--ant-table-row-selected-hover-bg); transition: all 0.2s ease; } } + .row-prefix-wrapper { padding-left: var(--ant-table-cell-padding-inline); } } + .seal-table-row { flex: 1; } + .spin { text-align: center; display: flex; diff --git a/src/config/breakpoints.ts b/src/config/breakpoints.ts new file mode 100644 index 00000000..38cf41dc --- /dev/null +++ b/src/config/breakpoints.ts @@ -0,0 +1,8 @@ +export default { + xs: 0, + sm: 576, + md: 768, + lg: 992, + xl: 1200, + xxl: 1600 +}; diff --git a/src/global.less b/src/global.less index 872239da..b21d96e4 100644 --- a/src/global.less +++ b/src/global.less @@ -4,13 +4,14 @@ html { --ant-color-fill-tertiary: rgba(0, 0, 0, 4%); --color-fill-1: var(--ant-color-fill-tertiary); - // --color-fill-1: #f3f6fa; + // --color-fill-1: #fff; --color-fill-2: #fff; --color-fill-3: #f3f6fa; - --menu-border-radius-base: 32px; + --menu-border-radius-base: 20px; --border-radius-base: 16px; + --border-radius-middle: 20px; --border-radius-small: 8px; - --color-white-1: #fff; + --color-white-1: rgba(255, 255, 255, 100%); --font-weight-normal: 500; --font-weight-bold: 700; --color-text-1: var(--ant-color-text); @@ -19,7 +20,7 @@ html { --font-size-large: 16px; --font-size-middle: 14px; --ant-color-fill-secondary: rgba(0, 0, 0, 6%); - --table-td-radius: 24px; + --table-td-radius: 20px; --checkbox-border-radius: 4px; --ant-table-cell-padding-inline: 16px; --ant-table-cell-padding-block: 16px; @@ -27,7 +28,7 @@ html { --ant-table-header-split-color: #f0f0f0; --ant-table-row-selected-bg: #f0fff6; --ant-table-row-selected-hover-bg: #d8f2e4; - --ant-table-row-hover-bg: #e6e6e6; + --ant-table-row-hover-bg: rgba(230, 230, 230, 70%); --color-chart-red: #ff7875; --color-chart-green: #54cc98; --color-chart-glod: #ffd666; @@ -36,6 +37,7 @@ html { --ant-input-active-shadow: 0 0 0 2px rgba(5, 255, 105, 6%); --ant-input-active-border-color: #2fbf85; --ant-input-hover-border-color: #54cc98; + --box-shadow-base: 0 4px 12px rgb(227, 232, 240); .css-var-rf { --ant-font-size: var(--font-size-base); @@ -49,7 +51,7 @@ html { &.ant-menu-css-var { --ant-menu-item-height: 46px; --ant-menu-item-selected-bg: var(--color-white-1); - --ant-menu-item-border-radius: 24px; + --ant-menu-item-border-radius: 16px; --ant-menu-item-selected-color: var(--ant-color-primary); --ant-menu-item-hover-bg: var(--color-white-1); --ant-menu-item-color: var(--color-text-1); @@ -67,6 +69,7 @@ html { --ant-border-radius-lg: 16px; --ant-color-error: #ff4d4f; --ant-color-bg-mask: rgba(0, 0, 0, 35%); + --ant-color-border: rgb(217 217 217 / 50%); &.ant-popover { --ant-popover-inner-padding: 26px; @@ -95,7 +98,7 @@ html { .css-var-rh.ant-menu-css-var { --ant-menu-item-height: 46px; --ant-menu-item-selected-bg: var(--color-white-1); - --ant-menu-item-border-radius: 24px; + --ant-menu-item-border-radius: 20px; --ant-menu-item-selected-color: var(--ant-color-primary); --ant-menu-item-hover-bg: var(--color-white-1); --ant-menu-item-color: var(--color-text-1); @@ -161,7 +164,7 @@ body { } tr > td { - background-color: var(--color-fill-1); + // background-color: var(--color-fill-1); border-bottom: none; height: 70px; @@ -222,12 +225,43 @@ body { font-size: 16px; } } + + // ============== new theme style start =============== + .ant-pro-layout { + background-color: var(--color-fill-1); + height: 100%; + + .ant-pro-sider .ant-layout-sider-children { + background-color: var(--color-white-1); + box-shadow: var(--box-shadow-base); + } + } + + .ant-table-wrapper .ant-table { + background-color: unset; + } + + .ant-table-content table { + .ant-table-thead > tr > th { + background-color: unset; + } + + .ant-table-tbody { + .ant-table-row { + border-radius: var(--table-td-radius); + background-color: var(--color-white-1); + box-shadow: var(--box-shadow-base); + + > td { + background-color: unset; + } + } + } + } + + // ============== new theme style end ================= } -// ======== basic layout style start============ - -// ======== basic layout style end ============ - // ======== menu style start ============ .ant-pro-sider-collapsed-button { display: none; @@ -250,11 +284,19 @@ body { } .ant-pro-layout-container { - background-color: var(--color-fill-2); + // background-color: var(--color-fill-2); } .ant-pro-sider { .ant-menu { + .ant-menu-item { + border-radius: 16px; + + &:active { + background-color: var(--ant-menu-item-selected-bg); + } + } + .ant-menu-item:not(.ant-menu-item-selected) { color: var(--ant-color-text); } @@ -266,6 +308,11 @@ body { .ant-menu-item.ant-menu-item-selected:hover { color: var(--ant-color-primary); } + + .ant-menu-item.ant-menu-item-selected { + color: var(--ant-color-primary); + background-color: unset; + } } } } @@ -275,20 +322,35 @@ body { .monaco-editor { border-radius: 16px; } -// .background { -// position: fixed; -// top: 0; -// left: 0; -// bottom: 0; -// right: 0; -// background: radial-gradient( -// circle, -// rgba(47, 191, 133, 0.02), -// rgba(55, 125, 184, 0.2) -// ); -// filter: blur(100px); -// height: 100%; -// } + +.background { + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + // background: radial-gradient( + // circle, + // rgba(47, 191, 133, 0.02), + // rgba(55, 125, 184, 0.2) + // ); + background: url('./assets/images/bg.png') center center no-repeat; + background-size: cover; + + &::after { + content: ''; + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + z-index: 1; + backdrop-filter: blur(100px); + filter: blur(100px); + height: 100%; + } +} + .ant-pro-page-container { background: transparent; } @@ -308,7 +370,7 @@ body { background: linear-gradient( 90deg, rgba(255, 255, 255, 4%) 25%, - rgba(0, 0, 0, 10%) 37%, + rgba(0, 0, 0, 8%) 37%, rgba(255, 255, 255, 4%) 63% ); background-size: 400% 100%; diff --git a/src/hooks/use-window-resize.ts b/src/hooks/use-window-resize.ts new file mode 100644 index 00000000..9bcebb98 --- /dev/null +++ b/src/hooks/use-window-resize.ts @@ -0,0 +1,60 @@ +import breakpoints from '@/config/breakpoints'; +import { useEffect, useState } from 'react'; + +export default function useWindowResize() { + const [size, setSize] = useState<{ width: number; height: number }>({ + width: window.innerWidth, + height: window.innerHeight + }); + const [isMobile, setIsMobile] = useState(false); + const [isTablet, setIsTablet] = useState(false); + const [isDesktop, setIsDesktop] = useState(false); + const [currentPoint, setCurrentPoint] = useState(''); + + const checkBreakpoint = (width: number) => { + if (width < breakpoints.sm) { + setIsMobile(true); + setIsTablet(false); + setIsDesktop(false); + setCurrentPoint('sm'); + return; + } + if (width < breakpoints.md) { + setIsMobile(false); + setIsTablet(true); + setIsDesktop(false); + setCurrentPoint('md'); + return; + } + if (width < breakpoints.lg) { + setIsMobile(false); + setIsTablet(false); + setIsDesktop(true); + setCurrentPoint('lg'); + return; + } + setIsMobile(false); + setIsTablet(false); + setIsDesktop(true); + setCurrentPoint('xl'); + }; + + useEffect(() => { + const handleResize = () => { + setSize({ + width: window.innerWidth, + height: window.innerHeight + }); + }; + window.addEventListener('resize', handleResize); + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); + + useEffect(() => { + checkBreakpoint(size.width); + }, [size.width]); + + return { size, isMobile, isTablet, isDesktop, currentPoint }; +} diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index 3ed73779..edb88887 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -144,7 +144,7 @@ export default (props: any) => { location={location} title={userConfig.title} navTheme="light" - siderWidth={270} + siderWidth={220} onMenuHeaderClick={(e) => { e.stopPropagation(); e.preventDefault(); diff --git a/src/pages/dashboard/apis/index.ts b/src/pages/dashboard/apis/index.ts new file mode 100644 index 00000000..bb2d0661 --- /dev/null +++ b/src/pages/dashboard/apis/index.ts @@ -0,0 +1,7 @@ +import { request } from '@umijs/max'; + +export const DASHBOARD_API = '/dashboard'; + +export async function queryDashboardData() { + return request(DASHBOARD_API); +} diff --git a/src/pages/dashboard/components/active-table.tsx b/src/pages/dashboard/components/active-table.tsx index 6c4b40e6..ea065d84 100644 --- a/src/pages/dashboard/components/active-table.tsx +++ b/src/pages/dashboard/components/active-table.tsx @@ -1,6 +1,9 @@ -import ContentWrapper from '@/components/content-wrapper'; import PageTools from '@/components/page-tools'; +import ProgressBar from '@/components/progress-bar'; import { Col, Row, Table } from 'antd'; +import _ from 'lodash'; +import { useContext } from 'react'; +import { DashboardContext } from '../config/dashboard-context'; const modelColumns = [ { @@ -9,28 +12,30 @@ const modelColumns = [ key: 'name' }, { - title: 'Allocated GPUs', - dataIndex: 'allocated', - key: 'allocated', - render: (text: any, record: any) => {record.gpu.allocated} + title: 'GPU Utilization', + dataIndex: 'gpu_utilization', + key: 'gpu_utilization', + render: (text: any, record: any) => ( + + ) }, { - title: 'GPU Utilization', - dataIndex: 'utilization', - key: 'utilization', - render: (text: any, record: any) => {record.gpu.utilization} + title: 'VRAM Utilization', + dataIndex: 'gpu_memory_utilization', + key: 'gpu_memory_utilization', + render: (text: any, record: any) => ( + + ) }, { title: 'Running Instances', - dataIndex: 'running', - key: 'running', - render: (text: any, record: any) => {record.instances.running} + dataIndex: 'instance_count', + key: 'instance_count' }, { - title: 'Pending Instances', - dataIndex: 'pending', - key: 'pending', - render: (text: any, record: any) => {record.instances.pending} + title: 'Tokens', + dataIndex: 'token_count', + key: 'token_count' } ]; @@ -59,39 +64,6 @@ const projectColumns = [ } ]; -const modelData = [ - { - id: 1, - name: 'qwen2', - gpu: { allocated: 4, utilization: '50%' }, - instances: { running: 1, pending: 0 } - }, - { - id: 2, - name: 'llama3:70b', - gpu: { allocated: 3, utilization: '70%' }, - instances: { running: 1, pending: 0 } - }, - { - id: 3, - name: 'llama3', - gpu: { allocated: 5, utilization: '20%' }, - instances: { running: 1, pending: 0 } - }, - { - id: 4, - name: 'gemma', - gpu: { allocated: 1, utilization: '25%' }, - instances: { running: 1, pending: 0 } - }, - { - id: 5, - name: 'phi3', - gpu: { allocated: 2, utilization: '46%' }, - instances: { running: 1, pending: 0 } - } -]; - const projectData = [ { id: 1, @@ -130,11 +102,12 @@ const projectData = [ } ]; const ActiveTable = () => { + const data = useContext(DashboardContext).active_models || []; return ( - - + + { } right={false} /> - +
- - - - - Active Projects - - } - right={false} - /> - -
- + ); diff --git a/src/pages/dashboard/components/over-view.less b/src/pages/dashboard/components/over-view.less index 67bdaae2..b0fe020e 100644 --- a/src/pages/dashboard/components/over-view.less +++ b/src/pages/dashboard/components/over-view.less @@ -3,6 +3,8 @@ height: 110px; display: flex; justify-content: space-around; + box-shadow: var(--box-shadow-base); + border-radius: var(--ant-border-radius-lg); } } diff --git a/src/pages/dashboard/components/over-view.tsx b/src/pages/dashboard/components/over-view.tsx index ddd22dec..46cf5de0 100644 --- a/src/pages/dashboard/components/over-view.tsx +++ b/src/pages/dashboard/components/over-view.tsx @@ -1,7 +1,8 @@ -import ContentWrapper from '@/components/content-wrapper'; import { Card, Col, Row, Space } from 'antd'; -import React from 'react'; +import _ from 'lodash'; +import React, { useContext } from 'react'; import { overviewConfigs } from '../config'; +import { DashboardContext } from '../config/dashboard-context'; import '../styles/index.less'; import styles from './over-view.less'; @@ -24,23 +25,8 @@ const renderCardItem = (data: { ); }; -const Overview: React.FC = (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 Overview: React.FC = () => { + const data = useContext(DashboardContext).resource_counts || {}; const renderValue = ( value: @@ -63,19 +49,26 @@ const Overview: React.FC = (props) => { ); }; return ( - +
{overviewConfigs.map((config, index) => ( -
+ {renderCardItem({ label: config.label, - value: renderValue(data[config.key] || 0), + value: renderValue(_.get(data, config.key, 0)), bgColor: config.backgroundColor })} ))} - + ); }; diff --git a/src/pages/dashboard/components/resource-utilization.tsx b/src/pages/dashboard/components/resource-utilization.tsx index 3b7c94b2..33cbb2b8 100644 --- a/src/pages/dashboard/components/resource-utilization.tsx +++ b/src/pages/dashboard/components/resource-utilization.tsx @@ -1,68 +1,68 @@ import LineChart from '@/components/charts/line-chart'; -import { generateFluctuatingData } from '@/utils'; +import dayjs from 'dayjs'; import _ from 'lodash'; +import { useContext, useEffect, useState } from 'react'; +import { DashboardContext } from '../config/dashboard-context'; -const mockData = { - GPU: generateFluctuatingData({ - total: 17, - max: 80, - min: 20 - }), - CPU: generateFluctuatingData({ - total: 17, - max: 70, - min: 10 - }), - Memory: generateFluctuatingData({ - total: 17, - max: 60, - min: 20 - }), - VRAM: generateFluctuatingData({ - total: 17, - max: 90, - min: 15 - }) +const TypeKeyMap = { + cpu: 'CPU', + memory: 'Memory', + gpu: 'GPU', + gpu_memory: 'VRAM' }; + const UtilizationOvertime: React.FC = () => { - const timeList = [ - '08:00:00', - '09:00:00', - '10:00:00', - '11:00:00', - '12:00:00', - '13:00:00', - '14:00:00', - '15:00:00', - '16:00:00', - '17:00:00', - '18:00:00', - '19:00:00', - '20:00:00', - '21:00:00', - '22:00:00', - '23:00:00', - '24:00:00' - ]; - const typeList = ['GPU', 'CPU', 'Memory', 'VRAM']; - const generateData = () => { - const data = []; - for (let i = 0; i < timeList.length; i++) { - for (let j = 0; j < typeList.length; j++) { - data.push({ - time: timeList[i], - type: typeList[j], - value: _.get(mockData, typeList[j])[i] - }); - } + const data = useContext(DashboardContext)?.system_load?.history || {}; + const [result, setResult] = useState< + { time: string; value: number; type: string }[] + >([]); + + const typeList = ['gpu', 'cpu', 'memory', 'gpu_memory']; + const sliderConfig = { + y: false, + x: { + style: { + selectionFill: 'rgb(84, 204, 152)', + selectionFillOpacity: 0.1, + handleIconFill: 'rgb(84, 204, 152)', + handleIconFillOpacity: 0.15, + handleIconStrokeOpacity: 0, + sparklineType: 'line', + sparkline: true + }, + sparkline: true } - return data; }; - const data = generateData(); + + const labelFormatter = (value: any) => { + return `${value}%`; + }; + const generateData = () => { + const list: { value: number; time: string; type: string }[] = []; + _.each(typeList, (type: any) => { + const dataList = _.map(_.get(data, type, []), (item: any) => { + return { + value: _.round(item.value, 2) || 0, + time: dayjs(item.timestamp * 1000).format('HH:mm:ss'), + type: _.get(TypeKeyMap, type, '') + }; + }); + list.push(...dataList); + }); + setResult(list); + }; + + useEffect(() => { + generateData(); + }, [data]); return ( <> - + ); }; diff --git a/src/pages/dashboard/components/system-load.tsx b/src/pages/dashboard/components/system-load.tsx index d93a9481..3a2a541e 100644 --- a/src/pages/dashboard/components/system-load.tsx +++ b/src/pages/dashboard/components/system-load.tsx @@ -1,20 +1,43 @@ -import GaugeChart from '@/components/charts/gauge'; +import CardWrapper from '@/components/card-wrapper'; +import LiquidChart from '@/components/charts/liquid'; import PageTools from '@/components/page-tools'; -import { PageContainer } from '@ant-design/pro-components'; +import breakpoints from '@/config/breakpoints'; +import useWindowResize from '@/hooks/use-window-resize'; import { Col, DatePicker, Row } from 'antd'; +import _ from 'lodash'; +import { useContext, useEffect, useState } from 'react'; +import { DashboardContext } from '../config/dashboard-context'; import ResourceUtilization from './resource-utilization'; const SystemLoad = () => { - const handleSelectDate = (date: string) => { - console.log('dateString============', date); - }; + const colors = [ + 'linear-gradient(90deg, rgba(84, 204, 152,.8) 0%, rgba(84, 204, 152,0.5) 50%, rgba(84, 204, 152,.8) 100%)', + 'linear-gradient(90deg, rgba(255, 214, 102,.8) 0%, rgba(255, 214, 102,0.5) 50%, rgba(255, 214, 102,.8) 100%)', + 'linear-gradient(90deg, rgba(255, 120, 117,.8) 0%, rgba(255, 120, 117,0.5) 50%, rgba(255, 120, 117,.8) 100%)' + ]; + const data = useContext(DashboardContext)?.system_load?.current || {}; + const { size } = useWindowResize(); + const [paddingRight, setPaddingRight] = useState('20px'); + const [smallChartHeight, setSmallChartHeight] = useState(190); + const [largeChartHeight, setLargeChartHeight] = useState(400); + const thresholds = [0.5, 0.7, 1]; + const height = 400; + + const handleSelectDate = (date: string) => {}; + + useEffect(() => { + if (size.width < breakpoints.xl) { + setPaddingRight('0'); + } else { + setPaddingRight('20px'); + } + }, [size.width]); return ( - +
System Load @@ -24,51 +47,64 @@ const SystemLoad = () => { } /> - - -
- + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + - + ); }; diff --git a/src/pages/dashboard/components/usage.tsx b/src/pages/dashboard/components/usage.tsx index 7a89476e..77245dd1 100644 --- a/src/pages/dashboard/components/usage.tsx +++ b/src/pages/dashboard/components/usage.tsx @@ -1,10 +1,17 @@ +import CardWrapper from '@/components/card-wrapper'; import ColumnBar from '@/components/charts/column-bar'; import HBar from '@/components/charts/h-bar'; -import ContentWrapper from '@/components/content-wrapper'; import PageTools from '@/components/page-tools'; +import breakpoints from '@/config/breakpoints'; +import useWindowResize from '@/hooks/use-window-resize'; import { generateRandomArray } from '@/utils'; import { Col, DatePicker, Row } from 'antd'; +import dayjs from 'dayjs'; +import _ from 'lodash'; +import { useContext, useEffect, useState } from 'react'; +import { DashboardContext } from '../config/dashboard-context'; +const { RangePicker } = DatePicker; const times = [ 'june 1', 'june 2', @@ -86,64 +93,154 @@ const tokenUsage = TokensData.map((val, i) => { }); const Usage = () => { - const handleSelectDate = (dateString: string) => { - console.log('dateString============', dateString); + const { size } = useWindowResize(); + const [paddingRight, setPaddingRight] = useState('20px'); + const [requestData, setRequestData] = useState< + { time: string; value: number }[] + >([]); + const [tokenData, setTokenData] = useState<{ time: string; value: number }[]>( + [] + ); + const [userData, setUserData] = useState<{ name: string; value: number }[]>( + [] + ); + const data = useContext(DashboardContext)?.model_usage || {}; + + const handleSelectDate = (dateString: string) => {}; + + const generateData = () => { + const requestList: { time: string; value: number }[] = []; + const tokenList: { + time: string; + value: number; + name: string; + color: string; + }[] = []; + const userList: { + name: string; + value: number; + type: string; + color: string; + }[] = []; + + _.each(data.api_request_history, (item: any) => { + requestList.push({ + time: dayjs(item.timestamp * 1000).format('YYYY-MM-DD'), + value: item.value + }); + }); + + _.each(data.completion_token_history, (item: any) => { + tokenList.push({ + time: dayjs(item.timestamp * 1000).format('YYYY-MM-DD'), + name: 'completion_token', + color: + 'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgb(0, 168, 143,.7) 100%)', + value: item.value + }); + }); + _.each(data.prompt_token_history, (item: any) => { + tokenList.push({ + time: dayjs(item.timestamp * 1000).format('YYYY-MM-DD'), + name: 'prompt_token', + color: + 'linear-gradient(90deg,rgba(0, 170, 173, 0.8) 0%,rgba(0, 109, 193, 0.7) 100%)', + value: item.value + }); + }); + + _.each(data.top_users, (item: any) => { + userList.push({ + name: item.username, + type: 'completion_token', + color: + 'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgb(0, 168, 143,.7) 100%)', + value: item.completion_token_count + }); + userList.push({ + name: item.username, + type: 'prompt_token', + color: + 'linear-gradient(90deg,rgba(0, 170, 173, 0.8) 0%,rgba(0, 109, 193, 0.7) 100%)', + value: item.prompt_token_count + }); + }); + + setRequestData(requestList); + setTokenData(tokenList); + setUserData(userList); }; + + useEffect(() => { + if (size.width < breakpoints.xl) { + setPaddingRight('0'); + } else { + setPaddingRight('20px'); + } + }, [size.width]); + + useEffect(() => { + generateData(); + }, [data]); + return ( <> Usage} right={ - + } /> - - - - - + + + + + + + + + + + + - - - - - - - - - + + - - - - - - + diff --git a/src/pages/dashboard/config/dashboard-context.ts b/src/pages/dashboard/config/dashboard-context.ts new file mode 100644 index 00000000..6d18d54e --- /dev/null +++ b/src/pages/dashboard/config/dashboard-context.ts @@ -0,0 +1,8 @@ +import { createContext } from 'react'; +import { DashboardProps } from './types'; + +export const DashboardContext = createContext( + {} as DashboardProps +); + +export default DashboardContext; diff --git a/src/pages/dashboard/config/index.ts b/src/pages/dashboard/config/index.ts index b524fc4a..56aec962 100644 --- a/src/pages/dashboard/config/index.ts +++ b/src/pages/dashboard/config/index.ts @@ -1,32 +1,32 @@ export const overviewConfigs = [ { - key: 'workers', + key: 'workworker_count', label: 'Workers', - backgroundColor: - 'linear-gradient(180deg, rgb(0 139 188 / 20%) 0%, rgba(40,207,181,.2) 100%)' + // backgroundColor: 'linear-gradient(135deg, #ffffff, rgb(232 249 240 / 60%))' + backgroundColor: 'var(--color-white-1)' }, { - key: 'gpus', + key: 'gpu_count', label: 'Total GPUs', - backgroundColor: - 'linear-gradient(180deg, rgb(0 139 188 / 20%) 0%, rgba(40,207,181,.2) 100%)' + backgroundColor: 'var(--color-white-1)' + // backgroundColor: 'linear-gradient(135deg, #ffffff, rgb(232 249 240 / 60%))' }, { key: 'allocatedGpus', label: 'Allocated GPUs', - backgroundColor: - 'linear-gradient(180deg, rgb(0 139 188 / 20%) 0%, rgba(40,207,181,.2) 100%)' + backgroundColor: 'var(--color-white-1)' + // backgroundColor: 'linear-gradient(135deg, #ffffff, rgb(232 249 240 / 60%))' }, { - key: 'models', + key: 'model_count', label: 'Models', - backgroundColor: - 'linear-gradient(180deg, rgb(0 139 188 / 20%) 0%, rgba(40,207,181,.2) 100%)' + backgroundColor: 'var(--color-white-1)' + // backgroundColor: 'linear-gradient(135deg, #ffffff, rgb(232 249 240 / 60%))' }, { - key: 'instances', + key: 'model_instance_count', label: 'Instances', - backgroundColor: - 'linear-gradient(180deg, rgb(0 139 188 / 20%) 0%, rgba(40,207,181,.2) 100%)' + backgroundColor: 'var(--color-white-1)' + // backgroundColor: 'linear-gradient(135deg, #ffffff, rgb(232 249 240 / 60%))' } ]; diff --git a/src/pages/dashboard/config/types.ts b/src/pages/dashboard/config/types.ts new file mode 100644 index 00000000..8080b65b --- /dev/null +++ b/src/pages/dashboard/config/types.ts @@ -0,0 +1,62 @@ +export interface DashboardProps { + resource_counts: { + worker_count: number; + gpu_count: number; + model_count: number; + model_instance_count: number; + }; + system_load: { + current: { + cpu: { + total: number; + used: number; + utilization_rate: number; + }; + memory: { + total: number; + used: number; + utilization_rate: number; + }; + gpu: { + total: number; + used: number; + utilization_rate: number; + }; + gpu_memory: { + total: number; + used: number; + utilization_rate: number; + }; + }; + history: { + cpu: { + timestamp: number; + value: number; + }[]; + memory: { + timestamp: number; + value: number; + }[]; + gpu: { + timestamp: number; + value: number; + }[]; + gpu_memory: { + timestamp: number; + value: number; + }[]; + }; + }; + model_usage: { + api_request_history: any[]; + completion_token_history: any[]; + prompt_token_history: any[]; + top_users: { + user_id: number; + username: string; + prompt_token_count: number; + completion_token_count: number; + }[]; + }; + active_models: any[]; +} diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx index 6ebc44ce..8edbaf48 100644 --- a/src/pages/dashboard/index.tsx +++ b/src/pages/dashboard/index.tsx @@ -1,20 +1,40 @@ -import DividerLine from '@/components/divider-line'; +import { Spin } from 'antd'; +import { useEffect, useState } from 'react'; +import { queryDashboardData } from './apis'; import ActiveTable from './components/active-table'; import Overview from './components/over-view'; import SystemLoad from './components/system-load'; import Usage from './components/usage'; +import DashboardContext from './config/dashboard-context'; +import { DashboardProps } from './config/types'; const Dashboard: React.FC = () => { + const [data, setData] = useState({} as DashboardProps); + const [loading, setLoading] = useState(false); + + const getDashboardData = async () => { + try { + setLoading(true); + const res = await queryDashboardData(); + setData(res); + setLoading(false); + } catch (error) { + setLoading(false); + setData({} as DashboardProps); + } + }; + useEffect(() => { + getDashboardData(); + }, []); return ( - <> - - - - - - - - + + + + + + + + ); }; diff --git a/src/pages/llmodels/components/add-modal.tsx b/src/pages/llmodels/components/add-modal.tsx index 75eefcfd..16714687 100644 --- a/src/pages/llmodels/components/add-modal.tsx +++ b/src/pages/llmodels/components/add-modal.tsx @@ -26,8 +26,7 @@ type AddModalProps = { const sourceOptions = [ { label: 'Huggingface', value: 'huggingface', key: 'huggingface' }, - { label: 'Ollama', value: 'ollama_library', key: 'ollama_library' }, - { label: 'S3', value: 's3', key: 's3' } + { label: 'Ollama Library', value: 'ollama_library', key: 'ollama_library' } ]; const AddModal: React.FC = (props) => { diff --git a/src/pages/llmodels/index.tsx b/src/pages/llmodels/index.tsx index d7d1fa86..ff0b0f67 100644 --- a/src/pages/llmodels/index.tsx +++ b/src/pages/llmodels/index.tsx @@ -114,7 +114,7 @@ const Models: React.FC = () => { clearInterval(timer.current); timer.current = setInterval(() => { fetchData(true); - }, 3000); + }, 5000); }; const handleShowSizeChange = (page: number, size: number) => { diff --git a/src/pages/playground/components/chat-footer.tsx b/src/pages/playground/components/chat-footer.tsx index fa3cf394..d1e02365 100644 --- a/src/pages/playground/components/chat-footer.tsx +++ b/src/pages/playground/components/chat-footer.tsx @@ -18,11 +18,20 @@ interface ChatFooterProps { onView: () => void; disabled?: boolean; feedback?: React.ReactNode; + hasTokenResult?: boolean; } const ChatFooter: React.FC = (props) => { const intl = useIntl(); - const { onSubmit, onClear, onNewMessage, onView, feedback, disabled } = props; + const { + onSubmit, + onClear, + onNewMessage, + onView, + feedback, + disabled, + hasTokenResult + } = props; useHotkeys( HotKeys.SUBMIT.join(','), () => { @@ -34,7 +43,7 @@ const ChatFooter: React.FC = (props) => { return (
-
+ - {feedback} - + {feedback} +
); diff --git a/src/utils/index.ts b/src/utils/index.ts index e805792e..55b69441 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,4 @@ +import _ from 'lodash'; export const isNotEmptyValue = (value: any) => { if (Array.isArray(value)) { return value.length > 0;