chore: npu ux for add worker

main
jialin 10 months ago
parent ade1db5215
commit fe8ff2b180

@ -280,3 +280,29 @@
.color-white-light-4 {
color: var(--color-white-light-4);
}
textarea {
padding-inline: 6px 0;
padding-block: 0;
background-color: transparent;
}
textarea::-webkit-scrollbar {
width: var(--scrollbar-size);
}
textarea::-webkit-scrollbar-track {
background-color: transparent;
}
textarea::-webkit-scrollbar-thumb {
background-color: transparent;
border-radius: 6px;
}
textarea:hover {
&::-webkit-scrollbar-thumb {
background-color: var(--color-scrollbar-thumb);
border-radius: 6px;
}
}

@ -9,8 +9,8 @@
.editor-header {
display: flex;
height: 40px;
padding: 0 10px;
padding-block: 0;
padding-inline: 12px 10px;
justify-content: space-between;
align-items: center;
background-color: rgb(56, 56, 56);

@ -1,33 +1,40 @@
import { Select } from 'antd';
import React from 'react';
import styled from 'styled-components';
import CopyButton from '../copy-button';
import SegmentLine from '../segment-line';
import './index.less';
const Wrapper = styled.div<{ $height: number }>`
height: ${(props) => props.$height}px;
`;
interface EditorwrapProps {
headerHeight?: number;
header?: React.ReactNode;
children: React.ReactNode;
showHeader?: boolean;
copyText: string;
defaultValue?: string;
langOptions?: { label: string; value: string }[];
langOptions?: Global.BaseOption<string | number>[];
styles?: {
wrapper?: React.CSSProperties;
header?: React.CSSProperties;
content?: React.CSSProperties;
};
onChangeLang?: (value: string) => void;
onChangeLang?: (value: string | number) => void;
}
const EditorWrap: React.FC<EditorwrapProps> = ({
headerHeight = 40,
header,
children,
copyText,
langOptions,
langOptions = [],
onChangeLang,
defaultValue,
styles = {},
showHeader = true
}) => {
const handleChangeLang = (value: string) => {
const handleChangeLang = (value: string | number) => {
onChangeLang?.(value);
};
const renderHeader = () => {
@ -36,15 +43,14 @@ const EditorWrap: React.FC<EditorwrapProps> = ({
}
if (showHeader) {
return (
<div className="editor-header">
<Select
<Wrapper className="editor-header" $height={headerHeight}>
<SegmentLine
height={headerHeight}
defaultValue={defaultValue}
style={{ width: 120 }}
size="middle"
variant="filled"
size="small"
options={langOptions}
onChange={handleChangeLang}
></Select>
></SegmentLine>
<CopyButton
text={copyText}
size="small"
@ -52,7 +58,7 @@ const EditorWrap: React.FC<EditorwrapProps> = ({
color: 'rgba(255,255,255,.7)'
}}
/>
</div>
</Wrapper>
);
}
return null;

@ -107,4 +107,7 @@ export default {
Password: SealPassword,
Number: SealInputNumber,
Search: SealInputSearch
} as Record<string, React.FC<InputProps & SealFormItemProps>>;
} as Record<
string,
React.FC<InputProps & SealFormItemProps & { scaleSize?: boolean }>
>;

@ -1,11 +1,28 @@
import { Form, Input } from 'antd';
import type { TextAreaProps } from 'antd/es/input/TextArea';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState
} from 'react';
import styled from 'styled-components';
import { SealFormItemProps } from './types';
import Wrapper from './wrapper';
import InputWrapper from './wrapper/input';
const SealTextArea: React.FC<TextAreaProps & SealFormItemProps> = (props) => {
const LabelWrapper = styled.div`
background-color: var(--color-white-1);
`;
interface InputTextareaProps extends TextAreaProps {
scaleSize?: boolean;
}
const SealTextArea: React.FC<InputTextareaProps & SealFormItemProps> = (
props
) => {
const {
label,
placeholder,
@ -21,6 +38,7 @@ const SealTextArea: React.FC<TextAreaProps & SealFormItemProps> = (props) => {
extra,
addAfter,
trim,
scaleSize,
...rest
} = props;
const [isFocus, setIsFocus] = useState(false);
@ -37,6 +55,16 @@ const SealTextArea: React.FC<TextAreaProps & SealFormItemProps> = (props) => {
}
}, [props.value]);
const autoSize = useMemo(() => {
const focusRows = props.autoSize || { minRows: 2, maxRows: 5 };
if (scaleSize) {
return isFocus ? focusRows : { minRows: 1, maxRows: 1 };
}
return focusRows;
}, [props.autoSize, isFocus, scaleSize]);
const handleClickWrapper = useCallback(() => {
if (!props.disabled && !isFocus) {
inputRef.current?.focus?.({
@ -68,7 +96,7 @@ const SealTextArea: React.FC<TextAreaProps & SealFormItemProps> = (props) => {
onBlur?.(e);
}
},
[onBlur]
[onBlur, scaleSize]
);
const handleInput = useCallback(
@ -82,7 +110,7 @@ const SealTextArea: React.FC<TextAreaProps & SealFormItemProps> = (props) => {
<InputWrapper>
<Wrapper
status={status}
label={label}
label={<LabelWrapper>{label}</LabelWrapper>}
isFocus={isFocus}
required={required}
description={description}
@ -94,7 +122,7 @@ const SealTextArea: React.FC<TextAreaProps & SealFormItemProps> = (props) => {
>
<Input.TextArea
{...rest}
autoSize={rest.autoSize || { minRows: 2, maxRows: 5 }}
autoSize={autoSize}
ref={inputRef}
style={{ minHeight: '80px', ...style }}
className="seal-textarea"

@ -143,7 +143,7 @@ export const Label = styled.div.attrs<{
}
&.blur-no-value {
top: 21px;
top: 20px;
transition: all 0.2s var(--seal-transition-func);
}

@ -126,7 +126,6 @@ const InputWrapper = styled.div`
}
.seal-textarea-wrapper {
height: auto;
padding-bottom: 10px;
padding-right: 10px;
}
`;

@ -0,0 +1,84 @@
import { Segmented, type SegmentedProps } from 'antd';
import React from 'react';
import styled from 'styled-components';
interface SegmentLineProps extends SegmentedProps {
height?: number;
}
const SegmentedQwrapper = styled.div<{ $height: number }>`
.ant-segmented.segment-line {
padding: 0;
background-color: transparent;
color: var(--color-white-tertiary);
border: none;
box-shadow: none;
height: ${(props) => props.$height}px;
display: flex;
align-items: center;
.ant-segmented-group {
gap: 16px;
height: 100%;
}
.ant-segmented-item:hover {
color: var(--color-white-secondary);
}
.ant-segmented-thumb {
height: 2px;
padding: 0px;
bottom: 0px;
top: unset;
}
.ant-segmented-item-label {
display: flex;
align-items: center;
padding: 0;
font-size: var(--font-size-small);
}
.ant-segmented-item {
background-color: transparent;
padding-bottom: 2px;
display: flex;
align-items: center;
&::after {
background-color: var(--color-white-primary) !important;
width: 100%;
height: 0px;
border-radius: 2px;
bottom: 0px;
top: unset;
}
}
.ant-segmented-item-selected {
background-color: transparent;
color: var(--color-white-primary);
&::after {
height: 2px;
}
}
}
`;
const SegmentLine: React.FC<SegmentLineProps> = (props) => {
const {
height = 32,
size = 'small',
options = [],
className = 'segment-line',
...rest
} = props;
return (
<SegmentedQwrapper $height={height}>
<Segmented
{...rest}
size={size}
options={options}
className={className}
/>
</SegmentedQwrapper>
);
};
SegmentLine.displayName = 'SegmentLine';
export default SegmentLine;

@ -21,8 +21,7 @@ export default {
'model.form.ollama.model': 'Ollama Model',
'model.form.ollamaholder': 'Please select or input model name',
'model.deploy.sort': 'Sort',
'model.deploy.search.placeholder':
'Type <kbd>/</kbd> to search models from {source}',
'model.deploy.search.placeholder': 'Type <kbd>/</kbd> to search models',
'model.form.ollamatips':
'Tip: The following are the preconfigured Ollama models in GPUStack. Please select the model you want, or directly enter the model you wish to deploy in the 【{name}】 input box on the right.',
'models.sort.name': 'Name',

@ -21,8 +21,7 @@ export default {
'model.form.ollama.model': 'Ollamaモデル',
'model.form.ollamaholder': 'モデル名を選択または入力してください',
'model.deploy.sort': '並び替え',
'model.deploy.search.placeholder':
'Type <kbd>/</kbd> to search models from {source}',
'model.deploy.search.placeholder': 'Type <kbd>/</kbd> to search models',
'model.form.ollamatips':
'ヒント: 以下はGPUStackで事前設定されたOllamaモデルです。希望するモデルを選択するか、右側の【{name}】入力ボックスにデプロイしたいモデルを直接入力してください。',
'models.sort.name': '名前',

@ -21,8 +21,7 @@ export default {
'model.form.ollama.model': 'Модель Ollama',
'model.form.ollamaholder': 'Выберите или введите название модели',
'model.deploy.sort': 'Сортировка',
'model.deploy.search.placeholder':
'Type <kbd>/</kbd> to search models from {source}',
'model.deploy.search.placeholder': 'Type <kbd>/</kbd> to search models',
'model.form.ollamatips':
'Подсказка: ниже представлены предустановленные модели Ollama в GPUStack. Выберите нужную или введите модель для развертывания в поле 【{name}】 справа.',
'models.sort.name': 'По имени',

@ -23,7 +23,7 @@ export default {
'model.form.ollama.model': 'Ollama 模型',
'model.form.ollamaholder': '请选择或输入模型名称',
'model.deploy.sort': '排序',
'model.deploy.search.placeholder': '按 <kbd>/</kbd> 开始从 {source} 搜索模型',
'model.deploy.search.placeholder': '按 <kbd>/</kbd> 开始搜索模型',
'model.form.ollamatips':
'提示:以下为 GPUStack 预设的 Ollama 模型,请选择你想要的模型或者直接在右侧表单 【{name}】 输入框中输入你要部署的模型。',
'models.sort.name': '名称',

@ -25,7 +25,8 @@ interface CompatibilityAlertProps {
const DivWrapper = styled.div`
position: relative;
padding-inline: 8px;
padding-inline: 24px;
padding-block: 10px 0;
&:hover {
.close-wrapper {
display: block;
@ -36,11 +37,10 @@ const DivWrapper = styled.div`
const CloseWrapper = styled.div`
display: none;
position: absolute;
top: 6px;
right: 12px;
top: 14px;
right: 28px;
line-height: 1;
cursor: pointer;
background-color: var(--ant-color-warning-bg);
`;
const MessageWrapper = styled.div`
@ -104,7 +104,7 @@ const CompatibilityAlert: React.FC<CompatibilityAlertProps> = (props) => {
type={type || 'warning'}
icon={renderIcon}
></AlertBlockInfo>
{showClose && !['transition', 'success'].includes(type) && (
{showClose && !['transition'].includes(type) && (
<CloseWrapper className="close-wrapper">
<Button
onClick={onClose}

@ -325,6 +325,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
</Form.Item>
<Form.Item<FormData> name="description">
<SealInput.TextArea
scaleSize={true}
label={intl.formatMessage({
id: 'common.table.description'
})}

@ -320,13 +320,7 @@ const AddModal: FC<AddModalProps> = (props) => {
>
<FormWrapper>
<ColumnWrapper
paddingBottom={
warningStatus.show
? Array.isArray(warningStatus.message)
? 150
: 125
: 50
}
paddingBottom={warningStatus.show ? 170 : 50}
footer={
<>
<CompatibilityAlert

@ -16,6 +16,7 @@ const CompatibleTag = styled(Tag)`
margin-right: 0;
font-size: var(--font-size-base);
background: transparent !important;
padding-inline: 0;
`;
const ClaimTag = styled(Tag)`

@ -432,11 +432,7 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
<ColumnWrapper
maxHeight={550}
paddingBottom={
warningStatus.show
? Array.isArray(warningStatus.message)
? 100
: 70
: 0
warningStatus.show ? (warningStatus.isDefault ? 50 : 100) : 0
}
footer={
<>
@ -584,6 +580,7 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
</Form.Item>
<Form.Item<FormData> name="description">
<SealInput.TextArea
scaleSize={true}
label={intl.formatMessage({
id: 'common.table.description'
})}

@ -1,5 +1,5 @@
.hf-model-item {
height: 80px;
min-height: 82px;
display: flex;
flex-direction: column;
justify-content: space-between;

@ -32,7 +32,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
const { title, open, onCancel, viewCodeContent } = props || {};
const intl = useIntl();
const [lang, setLang] = useState(langMap.shell);
const [lang, setLang] = useState<string>(langMap.shell);
const codeValue = useMemo(() => {
if (lang === langMap.shell) {
@ -47,8 +47,8 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
return '';
}, [lang, viewCodeContent]);
const handleOnChangeLang = (value: string) => {
setLang(value);
const handleOnChangeLang = (value: string | number) => {
setLang(value as string);
};
const handleClose = () => {

@ -1,5 +1,6 @@
import { GPUStackVersionAtom } from '@/atoms/user';
import { getAtomStorage } from '@/atoms/utils';
import EditorWrap from '@/components/editor-wrap';
import HighlightCode from '@/components/highlight-code';
import { WarningOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
@ -12,11 +13,17 @@ type ViewModalProps = {
token: string;
};
const npuOptions = [
{ label: '910B', value: 'npu' },
{ label: '310P', value: 'npu310p' }
];
const AddWorker: React.FC<ViewModalProps> = (props) => {
const intl = useIntl();
const origin = window.location.origin;
const [activeKey, setActiveKey] = React.useState('cuda');
const [npuKey, setNpuKey] = React.useState('npu');
const versionInfo = getAtomStorage(GPUStackVersionAtom);
const code = React.useMemo(() => {
@ -25,7 +32,11 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
version = 'main';
}
const commandCode = addWorkerGuide[activeKey];
let commandCode = addWorkerGuide[activeKey];
if (npuKey === 'npu310p') {
commandCode = addWorkerGuide[npuKey];
}
if (activeKey === 'cuda') {
return commandCode?.registerWorker({
@ -41,20 +52,15 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
token: '${token}',
workerip: '${workerip}'
});
}, [versionInfo, activeKey, props.token]);
}, [versionInfo, activeKey, props.token, npuKey]);
const handleOnChange = (value: string | number) => {
setNpuKey(value as string);
};
return (
<div className="container-install">
<ul className="notes">
<li>
<span>
{intl.formatMessage({ id: 'resources.worker.container.supported' })}
</span>
<WarningOutlined
style={{ color: 'var(--ant-color-warning)' }}
className="font-size-14 m-l-5"
/>
</li>
<li>
{intl.formatMessage(
{ id: 'resources.worker.current.version' },
@ -125,7 +131,31 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
}}
></div>
)}
<HighlightCode theme="dark" code={code} lang="bash"></HighlightCode>
{activeKey === 'npu' ? (
<EditorWrap
headerHeight={32}
copyText={code}
langOptions={npuOptions}
defaultValue="npu"
showHeader={true}
onChangeLang={handleOnChange}
styles={{
wrapper: {
backgroundColor: 'var(--color-editor-dark)'
}
}}
>
<HighlightCode
theme="dark"
code={code}
lang="bash"
copyable={false}
height={160}
></HighlightCode>
</EditorWrap>
) : (
<HighlightCode theme="dark" code={code} lang="bash"></HighlightCode>
)}
<h3 className="m-b-0 m-t-10 font-size-14 font-600">
3. {intl.formatMessage({ id: 'resources.worker.add.step3' })}
</h3>

Loading…
Cancel
Save