feat: paste multiple lines in parameters input

This commit is contained in:
jialin
2026-03-17 16:35:55 +08:00
committed by jialin
parent 556c4a434d
commit 4f02bcd40c
11 changed files with 76 additions and 58 deletions
+11 -1
View File
@@ -7,6 +7,7 @@ interface HintInputProps {
label?: string;
onChange: (value: string) => void;
onBlur?: (e: any) => void;
onPaste?: (e: any) => void;
placeholder?: string;
trim?: boolean;
sourceOptions?: Global.HintOptions[];
@@ -15,7 +16,15 @@ interface HintInputProps {
const matchReg = /[^=]+=[^=]*$/;
const HintInput: React.FC<HintInputProps> = (props) => {
const { value, label, onChange, onBlur, sourceOptions, trim = true } = props;
const {
value,
label,
onChange,
onBlur,
onPaste,
sourceOptions,
trim = true
} = props;
const cursorPosRef = React.useRef(0);
const contextBeforeCursorRef = React.useRef('');
const [options, setOptions] = React.useState<
@@ -92,6 +101,7 @@ const HintInput: React.FC<HintInputProps> = (props) => {
options={options}
trim={trim}
style={{ flex: 1 }}
onPaste={onPaste}
/>
);
};
+36
View File
@@ -79,6 +79,41 @@ const ListInput: React.FC<ListInputProps> = (props) => {
setList(values);
};
const handleOnPaste = (e: any, index: number) => {
const pastedText = e.clipboardData?.getData('text');
if (!pastedText) return;
const lines = pastedText.split(/\r?\n/).filter((line: string) => {
const trimmedLine = trim ? line.trim() : line;
return trimmedLine.length > 0;
});
if (lines.length <= 1) {
// if there's only one line, let the default paste behavior handle it
return;
}
e.preventDefault();
const values = _.cloneDeep(list);
// replace the current item with the first line of the pasted text
values[index].value = trim ? lines[0].trim() : lines[0];
// create new list items for the remaining lines
for (let i = 1; i < lines.length; i++) {
updateCountRef();
values.splice(index + i, 0, {
value: trim ? lines[i].trim() : lines[i],
uid: countRef.current
});
}
const valueList = _.map(values, 'value').filter((val: string) => !!val);
setList(values);
onChange(valueList);
};
React.useEffect(() => {
const valueList = _.map(list, 'value').filter((val: string) => !!val);
if (!_.isEqual(valueList, dataList)) {
@@ -122,6 +157,7 @@ const ListInput: React.FC<ListInputProps> = (props) => {
onBlur={(e) => onBlur?.(e, index)}
onRemove={() => handleOnRemove(index)}
onChange={(val) => handleOnChange(val, index)}
onPaste={(e) => handleOnPaste(e, index)}
trim={trim}
renderItem={renderItem}
/>
+6 -1
View File
@@ -8,11 +8,13 @@ interface LabelItemProps {
onRemove: () => void;
onChange: (value: string) => void;
onBlur?: (e: any) => void;
onPaste?: (e: any) => void;
renderItem?: (
data: any,
props: {
onChange: (value: string) => void;
onBlur?: (e: any) => void;
onPaste?: (e: any) => void;
}
) => React.ReactNode;
value: string;
@@ -29,6 +31,7 @@ const ListItem: React.FC<LabelItemProps> = (props) => {
onRemove,
onChange,
onBlur,
onPaste,
label,
value,
options,
@@ -47,13 +50,15 @@ const ListItem: React.FC<LabelItemProps> = (props) => {
{renderItem ? (
renderItem(data, {
onChange: handleOnChange,
onBlur
onBlur,
onPaste
})
) : (
<HintInput
value={value}
onChange={handleOnChange}
onBlur={onBlur}
onPaste={onPaste}
label={label}
sourceOptions={options}
trim={trim}
@@ -12,6 +12,7 @@ const Link = Typography.Link;
const SealAutoComplete: React.FC<
AutoCompleteProps &
SealFormItemProps & {
onPaste?: (e: any) => void;
onInput?: (e: Event) => void;
clearSpaceOnBlur?: boolean;
}
@@ -148,6 +149,7 @@ const SealAutoComplete: React.FC<
onChange={handleChange}
popupRender={popupRender}
onInput={handleOnInput}
onPaste={props.onPaste}
></AutoComplete>
</Wrapper>
</SelectWrapper>
@@ -46,27 +46,19 @@ const ClusterDetailModal = () => {
key: 'workers',
label: `Workers`,
icon: <IconFont type="icon-resources" />,
children: (
<WorkerList
clusterId={id}
showAddButton={false}
showSelect={false}
widths={{ input: 360 }}
sourceType="cluster"
/>
)
children: <WorkerList />
},
{
key: 'deployments',
label: `Deployments`,
icon: <IconFont type="icon-rocket-launch1" />,
children: <Deployments clusterId={Number(id)}></Deployments>
children: <Deployments></Deployments>
},
{
key: 'gpus',
label: `GPUs`,
icon: <IconFont type="icon-gpu1" />,
children: <GPUList clusterId={Number(id)} widths={{ input: 360 }} />
children: <GPUList />
}
]}
/>
@@ -9,7 +9,7 @@ import { tableSorter } from '@/config/settings';
import GrafanaIcon from '@/pages/_components/grafana-icon';
import { StarFilled } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Tooltip } from 'antd';
import { Tooltip, Typography } from 'antd';
import dayjs from 'dayjs';
import { useAtomValue } from 'jotai';
import { useMemo } from 'react';
@@ -100,9 +100,9 @@ const useClusterColumns = (
span: 3,
render: (text: string, record: ClusterListItem) => (
<>
<AutoTooltip ghost title={text}>
{text}
</AutoTooltip>
<Typography.Link onClick={() => onCellClick?.(record, 'name')}>
{record.name}
</Typography.Link>
{record.is_default && (
<Tooltip
title={intl.formatMessage({
@@ -311,7 +311,7 @@ const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
}}
options={modelFilesSortOptions.current}
size="middle"
style={{ width: '120px' }}
style={{ width: '120px', fontWeight: 400 }}
></BaseSelect>
</TitleWrapper>
{dataSource.loading && (
+3 -3
View File
@@ -8,7 +8,7 @@ import useUpdateChunkedList from '@/hooks/use-update-chunk-list';
import { useMemoizedFn } from 'ahooks';
import _ from 'lodash';
import qs from 'query-string';
import React, { useEffect, useRef, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import {
MODELS_API,
MODEL_INSTANCE_API,
@@ -18,7 +18,7 @@ import {
import TableList from './components/table-list';
import { ListItem } from './config/types';
const Models: React.FC<{ clusterId?: number }> = ({ clusterId }) => {
const Models = () => {
const { pagination, setPagination } = usePaginationStatus(
PaginationKey.Deployments
);
@@ -49,7 +49,7 @@ const Models: React.FC<{ clusterId?: number }> = ({ clusterId }) => {
page: 1,
perPage: 10,
search: '',
cluster_id: clusterId || 0,
cluster_id: 0,
categories: [],
state: '',
sort_by: '',
+4 -11
View File
@@ -8,15 +8,12 @@ import { useQueryClusterList } from '@/pages/cluster-management/services/use-que
import { useIntl, useSearchParams } from '@umijs/max';
import { ConfigProvider, Table } from 'antd';
import _ from 'lodash';
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { GPU_DEVICES_API, queryGpuDevicesList } from '../apis';
import { GPUDeviceItem } from '../config/types';
import useGPUColumns from '../hooks/use-gpu-columns';
const GPUList: React.FC<{ clusterId?: number; widths?: { input: number } }> = ({
clusterId,
widths
}) => {
const GPUList = () => {
const {
dataSource,
queryParams,
@@ -31,10 +28,7 @@ const GPUList: React.FC<{ clusterId?: number; widths?: { input: number } }> = ({
key: PaginationKey.GPUs,
fetchAPI: queryGpuDevicesList,
polling: true,
API: GPU_DEVICES_API,
defaultQueryParams: {
cluster_id: clusterId
}
API: GPU_DEVICES_API
});
const [searchParams] = useSearchParams();
const page = searchParams.get('page');
@@ -108,8 +102,7 @@ const GPUList: React.FC<{ clusterId?: number; widths?: { input: number } }> = ({
handleInputChange={handleNameChange}
handleSelectChange={handleClusterChange}
selectOptions={clusterList}
showSelect={page !== 'clusters'}
widths={{ input: widths?.input || 200 }}
showSelect={true}
></FilterBar>
<ConfigProvider renderEmpty={renderEmpty}>
<Table
+5 -22
View File
@@ -12,7 +12,7 @@ import useGranfanaLink from '@/pages/resources/hooks/use-grafana-link';
import { useIntl } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { ConfigProvider, Table, message } from 'antd';
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import {
WORKERS_API,
deleteWorker,
@@ -27,19 +27,7 @@ import UpdateLabels from './update-labels';
import WorkerDetailModal from './worker-detail-modal';
import WorkerRightActions from './worker-right-actions';
const Workers: React.FC<{
clusterId?: string | number | null;
showSelect?: boolean;
showAddButton?: boolean;
widths?: { input: number };
sourceType?: string;
}> = ({
clusterId,
showSelect = true,
showAddButton = true,
widths = { input: 200 },
sourceType = 'resources'
}) => {
const Workers = () => {
const {
dataSource,
rowSelection,
@@ -63,10 +51,7 @@ const Workers: React.FC<{
contentForDelete: 'resources.worker',
watch: true,
API: WORKERS_API,
updateManually: true,
defaultQueryParams: {
cluster_id: clusterId
}
updateManually: true
});
const { goToGrafana, ActionButton } = useGranfanaLink({
type: 'worker'
@@ -255,8 +240,7 @@ const Workers: React.FC<{
loadend: dataSource.loadend,
firstLoad: extraStatus.firstLoad,
sortOrder,
handleSelect,
sourceType
handleSelect
});
useEffect(() => {
@@ -268,7 +252,7 @@ const Workers: React.FC<{
<>
<PageBox>
<FilterBar
showSelect={showSelect}
showSelect={true}
selectHolder={intl.formatMessage({ id: 'clusters.filterBy.cluster' })}
marginBottom={22}
marginTop={30}
@@ -280,7 +264,6 @@ const Workers: React.FC<{
handleInputChange={handleNameChange}
rowSelection={rowSelection}
selectOptions={clusterData.list}
widths={widths}
right={
<WorkerRightActions
handleDeleteByBatch={handleDeleteBatch}
@@ -235,7 +235,6 @@ const useWorkerColumns = ({
loadend,
firstLoad,
sortOrder,
sourceType,
handleSelect
}: {
clusterData: {
@@ -245,7 +244,6 @@ const useWorkerColumns = ({
loadend: boolean;
firstLoad: boolean;
sortOrder: string[];
sourceType: string;
handleSelect: (action: string, record: ListItem) => void;
}): ColumnsType<ListItem> => {
const intl = useIntl();
@@ -316,7 +314,6 @@ const useWorkerColumns = ({
{
title: intl.formatMessage({ id: 'clusters.title' }),
dataIndex: 'cluster_id',
hidden: sourceType === 'cluster',
render: (id: number) => (
<AutoTooltip ghost maxWidth={240}>
{_.get(clusterData.data, id, '')}
@@ -440,7 +437,7 @@ const useWorkerColumns = ({
)
}
],
[intl, sourceType, sortOrder, clusterData, loadend, firstLoad, handleSelect]
[intl, sortOrder, clusterData, loadend, firstLoad, handleSelect]
);
};