chore: add english comment

This commit is contained in:
jialin
2024-08-28 14:51:20 +08:00
parent 9a0aeb491a
commit 9bd6dedd90
28 changed files with 353 additions and 145 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ import { RequestConfig, history } from '@umijs/max';
const loginPath = '/login';
// 运行时配置
// runtime configuration
export async function getInitialState(): Promise<{
fetchUserInfo: () => Promise<Global.UserInfo>;
currentUser?: Global.UserInfo;
+28
View File
@@ -0,0 +1,28 @@
import { marked, Tokens } from 'marked';
import React from 'react';
interface MarkdownViewerProps {
content: string;
height?: string;
}
const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
content,
height = 'auto'
}) => {
const renderer = new marked.Renderer();
renderer.link = ({ href, title, text }: Tokens.Link) => {
return `<a href="${href}" title="${title || ''}" target="_blank" rel="noopener noreferrer">${text}</a>`;
};
return (
<div style={{ height, overflow: 'auto' }}>
<div
dangerouslySetInnerHTML={{ __html: marked(content, { renderer }) }}
/>
</div>
);
};
export default React.memo(MarkdownViewer);
+16 -68
View File
@@ -1,75 +1,27 @@
import { platformCall } from '@/utils';
import { SearchOutlined } from '@ant-design/icons';
import { Input, Table, Tag } from 'antd';
import _ from 'lodash';
import React from 'react';
import IconFont from '../icon-font';
import './index.less';
const dataSource = [
{
scope: 'playground',
command: 'New Message',
keybindingWin: 'Ctrl + N',
keybindingMac: 'N'
},
{
scope: 'models',
span: {
rowSpan: 3,
colSpan: 1
},
command: 'deploy model from Hugging Face',
keybindingWin: 'Ctrl + 1',
keybindingMac: '1'
},
{
scope: 'models',
command: 'deploy model from Ollama Library',
keybindingWin: 'Ctrl + 2',
keybindingMac: '2',
span: {
rowSpan: 0,
colSpan: 0
}
},
{
scope: 'models',
command: '从 Hugging Face 搜索模型',
keybindingWin: 'Ctrl + K',
keybindingMac: 'K',
span: {
rowSpan: 0,
colSpan: 0
}
}
];
import KeyMapConfig from './keymap';
const ShortCuts: React.FC<{ intl: any }> = ({ intl }) => {
const platform = platformCall();
const [dataList, setDataList] = React.useState<any[]>(dataSource);
const [dataList, setDataList] = React.useState<any[]>(KeyMapConfig);
const columns = [
{
title: 'Scope',
dataIndex: 'scope',
key: 'scope',
width: 120,
onCell: (row: any, index: number) => {
if (row.span) {
return row.span;
}
return {
rowSpan: 1,
colSpan: 1
};
}
width: 160
},
{
title: 'Command',
title: 'Action',
dataIndex: 'command',
key: 'command'
key: 'command',
render: (text: string, row: any) => {
return <span>{intl.formatMessage({ id: text })}</span>;
}
},
{
title: 'Keybinding',
@@ -77,21 +29,14 @@ const ShortCuts: React.FC<{ intl: any }> = ({ intl }) => {
key: 'keybinding',
width: 180,
render: (text: string, row: any) => {
if (platform.isMac) {
return (
<Tag>
<IconFont type="icon-command"></IconFont> + {row.keybindingMac}
</Tag>
);
}
return <Tag>{row.keybindingWin}</Tag>;
return <Tag>{row.keybinding}</Tag>;
}
}
];
const handleInputChange = (e: any) => {
const value = e.target.value;
const list = _.filter(dataSource, (item: any) => {
const list = _.filter(KeyMapConfig, (item: any) => {
return (
item.command.toLowerCase().includes(value.toLowerCase()) ||
item.scope.toLowerCase().includes(value.toLowerCase())
@@ -103,10 +48,12 @@ const ShortCuts: React.FC<{ intl: any }> = ({ intl }) => {
const debounceHandleInputChange = _.debounce(handleInputChange, 300);
return (
<div className="short-cuts">
<h3 style={{ marginBottom: 20 }}>GPUStack </h3>
<h3 style={{ marginBottom: 20, fontWeight: 'var(--font-weight-bold)' }}>
{intl.formatMessage({ id: 'shortcuts.title' })}
</h3>
<Input
allowClear
placeholder="Search keybindings"
placeholder={intl.formatMessage({ id: 'shortcuts.search.placeholder' })}
style={{ marginBottom: 16 }}
onChange={debounceHandleInputChange}
prefix={
@@ -125,6 +72,7 @@ const ShortCuts: React.FC<{ intl: any }> = ({ intl }) => {
columns={columns}
dataSource={dataList}
pagination={false}
scroll={{ y: 450 }}
></Table>
</div>
);
@@ -138,7 +86,7 @@ export const modalConfig = {
style: {
top: '10%'
},
width: 660
width: 700
};
export default React.memo(ShortCuts);
+60
View File
@@ -0,0 +1,60 @@
import { KeyMap } from '@/config/hotkeys';
export default [
{
scope: 'playground',
command: 'shortcuts.playground.newmessage',
keybinding: KeyMap.CREATE.iconKeybinding
},
{
scope: 'playground',
command: 'shortcuts.playground.clearmessage',
keybinding: KeyMap.CLEAR.iconKeybinding
},
{
scope: 'playground',
command: 'shortcuts.playground.toggleparams',
keybinding: KeyMap.RIGHT.iconKeybinding
},
{
scope: 'models',
// span: {
// rowSpan: 3,
// colSpan: 1
// },
command: 'shortcuts.models.newmodelHF',
keybinding: KeyMap.NEW1.iconKeybinding
},
{
scope: 'models',
command: 'shortcuts.models.newmodelLM',
keybinding: KeyMap.NEW2.iconKeybinding
// span: {
// rowSpan: 0,
// colSpan: 0
// }
},
{
scope: 'models',
command: 'shortcuts.models.search',
keybinding: KeyMap.SEARCH.iconKeybinding
// span: {
// rowSpan: 0,
// colSpan: 0
// }
},
{
scope: 'resources',
command: 'shortcuts.resources.addworker',
keybinding: KeyMap.CREATE.iconKeybinding
},
{
scope: 'API keys',
command: 'shortcuts.apikeys.new',
keybinding: KeyMap.CREATE.iconKeybinding
},
{
scope: 'users',
command: 'shortcuts.users.new',
keybinding: KeyMap.CREATE.iconKeybinding
}
];
+55 -12
View File
@@ -1,17 +1,60 @@
export default {
CREATE: ['ctrl+alt+n', 'option+meta+n'],
SAVE: ['ctrl+s', 'meta+s'],
import { platformCall } from '@/utils';
const platform = platformCall();
const KeybindingsMap = {
CREATE: ['alt+ctrl+N', 'alt+meta+N'],
CLEAR: ['alt+ctrl+W', 'alt+meta+W'],
RIGHT: ['ctrl+RIGHT', 'meta+RIGHT'],
SAVE: ['ctrl+S', 'meta+S'],
SUBMIT: ['ctrl+enter', 'meta+enter'],
SAVEAS: ['ctrl+shift+s', 'meta+shift+s'],
OPEN: ['ctrl+o', 'meta+o'],
CANCEL: ['ctrl+w', 'meta+w'],
SAVEAS: ['alt+ctrl+S', 'alt+meta+S'],
OPEN: ['alt+ctrl+O', 'alt+meta+O'],
CANCEL: ['ctrl+W', 'meta+W'],
DELETE: ['delete'],
COPY: ['ctrl+c', 'meta+c'],
REFRESH: ['ctrl+r', 'meta+r'],
EDIT: ['ctrl+e', 'meta+e'],
SEARCH: ['ctrl+f', 'meta+f'],
RESET: ['ctrl+shift+r', 'meta+shift+r'],
INPUT: ['ctrl+k', 'meta+k'],
COPY: ['ctrl+C', 'meta+C'],
REFRESH: ['ctrl+R', 'meta+R'],
EDIT: ['ctrl+E', 'meta+E'],
SEARCH: ['ctrl+K', 'meta+K'],
RESET: ['alt+ctrl+R', 'alt+meta+R'],
INPUT: ['ctrl+K', 'meta+K'],
NEW1: ['ctrl+1', 'meta+1'],
NEW2: ['ctrl+2', 'meta+2']
};
type KeyBindingType = keyof typeof KeybindingsMap;
type KeybindingValue = {
keybinding: string;
command: KeyBindingType;
textKeybinding: string;
iconKeybinding: string;
};
const KeybiningList: KeybindingValue[] = Object.entries(KeybindingsMap).map(
([key, value]) => {
const keybinding = platform.isMac ? value[1] || value[0] : value[0];
return {
keybinding: keybinding,
command: key,
textKeybinding: platform.isMac
? keybinding.replace('meta', 'Command').replace('alt', 'Option')
: keybinding.replace('ctrl', 'Ctrl'),
iconKeybinding: platform.isMac
? keybinding.replace('meta', '⌘').replace('alt', '⌥')
: keybinding.replace('ctrl', 'Ctrl')
} as KeybindingValue;
}
);
const KeyMap: Record<KeyBindingType, KeybindingValue> = KeybiningList.reduce(
(acc: any, item) => {
acc[item.command] = item;
return acc;
},
{}
);
console.log('KeyMap=========', KeyMap);
export { KeyMap, KeybiningList };
export default KeybindingsMap;
+1 -1
View File
@@ -1,4 +1,4 @@
// 应用前置、全局运行的逻辑时 会在这里执行
// Logic that runs globally and before the application will be executed here
import dayjs from 'dayjs';
import localizedFormat from 'dayjs/plugin/localizedFormat';
+15 -4
View File
@@ -1,23 +1,27 @@
@media screen and (max-width: 480px) {
/* 在小屏幕的时候可以有更好的体验 */
.umi-plugin-layout-container {
width: 100% !important;
}
.umi-plugin-layout-container > * {
border-radius: 0 !important;
}
}
.umi-plugin-layout-menu .anticon {
margin-right: 8px;
}
.umi-plugin-layout-menu .ant-dropdown-menu-item {
min-width: 160px;
}
.umi-plugin-layout-right {
display: flex !important;
height: 100%;
overflow: hidden;
}
.umi-plugin-layout-right .umi-plugin-layout-action {
display: flex;
align-items: center;
@@ -26,25 +30,32 @@
cursor: pointer;
transition: all 0.3s;
}
.umi-plugin-layout-right .umi-plugin-layout-action > i {
color: rgba(255, 255, 255, 0.85);
color: rgba(255, 255, 255, 85%);
vertical-align: middle;
}
.umi-plugin-layout-right .umi-plugin-layout-action:hover {
background: rgba(0, 0, 0, 0.025);
background: rgba(0, 0, 0, 2.5%);
}
.umi-plugin-layout-right .umi-plugin-layout-action.opened {
background: rgba(0, 0, 0, 0.025);
background: rgba(0, 0, 0, 2.5%);
}
.umi-plugin-layout-right .umi-plugin-layout-search {
padding: 0 12px;
}
.umi-plugin-layout-right .umi-plugin-layout-search:hover {
background: transparent;
}
.umi-plugin-layout-name {
margin-left: 8px;
}
.umi-plugin-layout-name.umi-plugin-layout-hide-avatar-img {
margin-left: 0;
}
-2
View File
@@ -1,6 +1,4 @@
// @ts-nocheck
// This file is generated by Umi automatically
// DO NOT CHANGE IT MANUALLY!
import GpustackLogo from '@/assets/images/gpustack-logo.png';
import SmallLogo from '@/assets/images/small-logo-200x200.png';
import React from 'react';
+4 -5
View File
@@ -35,7 +35,7 @@ type InitialStateType = {
currentUser?: Global.UserInfo;
};
// 过滤出需要显示的路由, 这里的filterFn 指 不希望显示的层级
// Filter out the routes that need to be displayed, where filterFn indicates the levels that should not be shown
const filterRoutes = (
routes: IRoute[],
filterFn: (route: IRoute) => boolean
@@ -99,8 +99,6 @@ export default (props: any) => {
setInitialState: null
};
// initialState: InitialStateType
const { initialState, loading, setInitialState } = initialInfo;
const userConfig = {
@@ -189,6 +187,8 @@ export default (props: any) => {
const { location } = history;
console.log('onPageChange', userInfo, initialState);
// if user is not change password, redirect to change password page
if (
location.pathname !== loginPath &&
userInfo?.require_password_change
@@ -198,8 +198,7 @@ export default (props: any) => {
return;
}
// 如果没有登录,重定向到 login
// if user is not logged in, redirect to login page
if (!initialState?.currentUser && location.pathname !== loginPath) {
history.push(loginPath);
} else if (location.pathname === '/') {
-3
View File
@@ -1,6 +1,4 @@
// @ts-nocheck
// This file is generated by Umi automatically
// DO NOT CHANGE IT MANUALLY!
import React from 'react';
import icons from './icons';
@@ -13,7 +11,6 @@ function formatIcon(name: string) {
}
export function patchRoutes({ routes, initialState }) {
console.log('patchRoutes99999', routes);
Object.keys(routes).forEach((key) => {
const { icon } = routes[key];
if (icon && typeof icon === 'string') {
-2
View File
@@ -1,5 +1,3 @@
// This file is generated by Umi automatically
// DO NOT CHANGE IT MANUALLY!
import type { RunTimeLayoutConfig } from './types';
export interface IRuntimeConfig {
layout?: RunTimeLayoutConfig;
+3 -6
View File
@@ -1,8 +1,5 @@
// This file is generated by Umi automatically
// DO NOT CHANGE IT MANUALLY!
/// <reference types="@ant-design/pro-components" />
import type InitialStateType from '@@/plugin-initialState/@@initialState';
import '@ant-design/pro-components';
import type { HeaderProps, ProLayoutProps } from '@ant-design/pro-components';
type InitDataType = ReturnType<typeof InitialStateType>;
@@ -26,12 +23,12 @@ export type RunTimeLayoutConfig = (initData: InitDataType) => Omit<
loading: InitDataType['loading'];
initialState: InitDataType['initialState'];
setInitialState: InitDataType['setInitialState'];
},
}
) => JSX.Element)
| false;
rightRender?: (
initialState: InitDataType['initialState'],
setInitialState: InitDataType['setInitialState'],
runtimeConfig: RunTimeLayoutConfig,
runtimeConfig: RunTimeLayoutConfig
) => JSX.Element;
};
+3 -1
View File
@@ -5,6 +5,7 @@ import menu from './en-US/menu';
import models from './en-US/models';
import playground from './en-US/playground';
import resources from './en-US/resources';
import shortcuts from './en-US/shortcuts';
import usage from './en-US/usage';
import users from './en-US/users';
@@ -17,5 +18,6 @@ export default {
...apikeys,
...users,
...dashboard,
...usage
...usage,
...shortcuts
};
+13
View File
@@ -0,0 +1,13 @@
export default {
'shortcuts.search.placeholder': 'Search keybindings',
'shortcuts.title': 'Keyboard shortcuts',
'shortcuts.playground.newmessage': 'New message',
'shortcuts.playground.clearmessage': 'Clear messages',
'shortcuts.playground.toggleparams': 'Collapse/Expand parameters',
'shortcuts.models.newmodelHF': 'Deploy Hugging Face model',
'shortcuts.models.newmodelLM': 'Deploy Ollama model',
'shortcuts.models.search': 'Search models from Hugging Face',
'shortcuts.resources.addworker': 'Add worker',
'shortcuts.apikeys.new': 'New API key',
'shortcuts.users.new': 'New user'
};
+3 -1
View File
@@ -5,6 +5,7 @@ import menu from './zh-CN/menu';
import models from './zh-CN/models';
import playground from './zh-CN/playground';
import resources from './zh-CN/resources';
import shortcuts from './zh-CN/shortcuts';
import usage from './zh-CN/usage';
import users from './zh-CN/users';
@@ -17,5 +18,6 @@ export default {
...apikeys,
...users,
...dashboard,
...usage
...usage,
...shortcuts
};
+14
View File
@@ -0,0 +1,14 @@
// keyborad shortcuts
export default {
'shortcuts.title': '快捷键',
'shortcuts.search.placeholder': '搜索快捷键',
'shortcuts.playground.newmessage': '新建消息',
'shortcuts.playground.clearmessage': '清空消息',
'shortcuts.playground.toggleparams': '收起/展开参数',
'shortcuts.models.newmodelHF': '部署 Hugging Face 模型',
'shortcuts.models.newmodelLM': '部署 Ollama 模型',
'shortcuts.models.search': '从 Hugging Face 搜索模型',
'shortcuts.resources.addworker': '添加节点',
'shortcuts.apikeys.new': '新建 API 密钥',
'shortcuts.users.new': '新建用户'
};
+2 -2
View File
@@ -8,11 +8,11 @@ const AccessPage: React.FC = () => {
<PageContainer
ghost
header={{
title: '权限示例'
title: 'permission example'
}}
>
<Access accessible={access.canSeeAdmin}>
<Button> Admin </Button>
<Button>Only Admin can see this button</Button>
</Access>
</PageContainer>
);
+12
View File
@@ -1,6 +1,7 @@
import DeleteModal from '@/components/delete-modal';
import PageTools from '@/components/page-tools';
import { PageAction } from '@/config';
import HotKeys from '@/config/hotkeys';
import type { PageActionType } from '@/config/types';
import useTableRowSelection from '@/hooks/use-table-row-selection';
import useTableSort from '@/hooks/use-table-sort';
@@ -12,6 +13,7 @@ import { Button, Input, Space, Table, Tooltip } from 'antd';
import dayjs from 'dayjs';
import _ from 'lodash';
import { useEffect, useRef, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { deleteApisKey, queryApisKeysList } from './apis';
import AddAPIKeyModal from './components/add-apikey';
import { ListItem } from './config/types';
@@ -140,6 +142,16 @@ const APIKeys: React.FC = () => {
fetchData();
}, [queryParams]);
useHotkeys(
HotKeys.CREATE,
() => {
handleAddUser();
},
{
enabled: !openAddModal
}
);
return (
<>
<PageContainer
@@ -1,5 +1,4 @@
import hotkeys from '@/config/hotkeys';
import { platformCall } from '@/utils';
import { SearchOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Input } from 'antd';
@@ -13,7 +12,6 @@ const SearchInput: React.FC<{
const intl = useIntl();
const [isFocus, setIsFocus] = useState(false);
const inputRef = useRef<any>(null);
const platform = platformCall();
useHotkeys(hotkeys.INPUT.join(','), () => {
inputRef.current?.focus?.();
@@ -10,7 +10,6 @@ const Separator: React.FC = () => {
style={{ height: 'calc(100vh - 89px)', marginInline: '0px' }}
></Divider>
<span className="shape"></span>
{/* <span className="shape-s"></span> */}
</div>
);
};
+1 -11
View File
@@ -3,6 +3,7 @@
.shape {
position: absolute;
border-radius: 0 2px 0 0;
top: 10px;
left: -10px;
width: 22px;
@@ -14,15 +15,4 @@
background-color: var(--color-white-1);
z-index: 100;
}
.shape-s {
position: absolute;
top: 50%;
left: 4px;
width: 30px;
height: 30px;
border: 16px solid transparent;
border-left: 16px solid var(--color-fill-sider);
z-index: 100;
}
}
@@ -239,6 +239,30 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
}
);
useHotkeys(
HotKeys.CREATE.join(','),
() => {
handleNewMessage();
},
{
enabled: !loading,
enableOnFormTags: !loading,
preventDefault: true
}
);
useHotkeys(
HotKeys.CLEAR.join(','),
() => {
handleClear();
},
{
enabled: !loading,
enableOnFormTags: !loading,
preventDefault: true
}
);
return (
<div className="ground-left">
<div
+14
View File
@@ -1,9 +1,11 @@
import IconFont from '@/components/icon-font';
import HotKeys from '@/config/hotkeys';
import { PageContainer } from '@ant-design/pro-components';
import { useIntl, useSearchParams } from '@umijs/max';
import { Button, Divider, Space } from 'antd';
import classNames from 'classnames';
import { useRef, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import GroundLeft from './components/ground-left';
import ParamsSettings from './components/params-settings';
import './style/play-ground.less';
@@ -20,6 +22,18 @@ const Playground: React.FC = () => {
groundLeftRef.current?.viewCode?.();
};
useHotkeys(
HotKeys.RIGHT.join(','),
() => {
setCollapse((pre) => {
return !pre;
});
},
{
preventDefault: true
}
);
return (
<PageContainer
ghost
@@ -2,6 +2,7 @@ import DeleteModal from '@/components/delete-modal';
import PageTools from '@/components/page-tools';
import ProgressBar from '@/components/progress-bar';
import StatusTag from '@/components/status-tag';
import Hotkeys from '@/config/hotkeys';
import useTableRowSelection from '@/hooks/use-table-row-selection';
import useTableSort from '@/hooks/use-table-sort';
import { convertFileSize, handleBatchRequest } from '@/utils';
@@ -15,6 +16,7 @@ import { useIntl } from '@umijs/max';
import { Button, Input, Space, Table, Tooltip } from 'antd';
import _ from 'lodash';
import { memo, useCallback, useEffect, useRef, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { deleteWorker, queryWorkersList } from '../apis';
import { WorkerStatusMapValue, status } from '../config';
import { Filesystem, GPUDeviceItem, ListItem } from '../config/types';
@@ -166,6 +168,10 @@ const Resources: React.FC = () => {
fetchData();
}, [queryParams]);
useHotkeys(Hotkeys.CREATE.join(','), handleAddWorker, {
enabled: !open
});
return (
<>
<PageTools
+12
View File
@@ -2,6 +2,7 @@ import DeleteModal from '@/components/delete-modal';
import DropdownButtons from '@/components/drop-down-buttons';
import PageTools from '@/components/page-tools';
import { PageAction } from '@/config';
import HotKeys from '@/config/hotkeys';
import type { PageActionType } from '@/config/types';
import useTableRowSelection from '@/hooks/use-table-row-selection';
import useTableSort from '@/hooks/use-table-sort';
@@ -20,6 +21,7 @@ import { Button, Input, Space, Table, message } from 'antd';
import dayjs from 'dayjs';
import _ from 'lodash';
import { useEffect, useRef, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { createUser, deleteUser, queryUsersList, updateUser } from './apis';
import AddModal from './components/add-modal';
import { FormData, ListItem } from './config/types';
@@ -197,6 +199,16 @@ const Users: React.FC = () => {
fetchData();
}, [queryParams]);
useHotkeys(
HotKeys.CREATE,
() => {
handleAddUser();
},
{
enabled: !openAddModal
}
);
return (
<>
<PageContainer
+18 -19
View File
@@ -48,9 +48,8 @@ export const generateRandomArray = (config?: {
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] 范围内
let newValue = prevValue + Math.floor(Math.random() * 21) - offset; // Fluctuation range [-10, 10]
newValue = Math.max(min, Math.min(max, newValue)); // Ensure within [10, 100]
data.push(newValue);
prevValue = newValue;
}
@@ -68,15 +67,15 @@ export const generateFluctuatingData2 = ({
const y = [];
for (let i = 0; i < total; i++) {
// 生成一个基本的线性趋势,使用正弦函数
// Generate a basic linear trend using a sine function
const phaseShift = Math.random() * 2 * Math.PI;
const trend =
max * Math.sin((3 * Math.PI * i + phaseShift) / total) + max / 2;
// 生成噪声
// Generate noise
const noise = (Math.random() * 2 - 1) * noiseLevel;
// 叠加趋势和噪声
// Add trend and noise
const value = trend + noise;
x.push(i);
y.push(Math.max(min, value));
@@ -94,19 +93,19 @@ export const generateFluctuatingData = ({
min = 0
}) => {
/**
* 生成一组用于折线图的数据,具有自然且美观的趋势。
* Generate a set of data for a line chart with a natural and aesthetically pleasing trend.
*
* 参数:
* total (number): 生成数据点的数量
* trendType (string): 数据趋势类型, 可选'linear', 'sine', 'exponential'
* max (number): 波动幅度
* f (number): 波动频率
* phase (number): 波动相位
* min (number): 数据的最小值
* Parameters:
* total (number): Number of data points to generate
* trendType (string): Type of data trend, options are 'linear', 'sine', 'exponential'
* max (number): Fluctuation amplitude
* f (number): Fluctuation frequency
* phase (number): Fluctuation phase
* min (number): Minimum value of the data
*
* 返回:
* x (number[]): x轴数据
* y (number[]): y轴数据
* Returns:
* x (number[]): x-axis data
* y (number[]): y-axis data
*/
const x = Array.from({ length: total }, (_, i) => (i * 10) / (total - 1));
@@ -131,11 +130,11 @@ export const generateFluctuatingData = ({
break;
default:
throw new Error(
'无效的trendType参数,请选择"linear", "sine""exponential".'
'Invalid trendType parameter. Please choose "linear", "sine", or "exponential".'
);
}
// 将数据调整到最小值
// Adjust the data to the minimum value
const minY = Math.min(...y);
y = y.map((val) => _.round(val - minY + min, 2));