chore: npu ux for add worker
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user