| @@ -1 +1 @@ | |||
| v18.16.0 | |||
| v18.20.7 | |||
| @@ -30,6 +30,7 @@ function CodeSelect({ | |||
| onChange, | |||
| ...rest | |||
| }: CodeSelectProps) { | |||
| // 选择代码配置 | |||
| const selectResource = () => { | |||
| const { close } = openAntdModal(CodeSelectorModal, { | |||
| onOk: (res) => { | |||
| @@ -59,6 +60,11 @@ function CodeSelect({ | |||
| }); | |||
| }; | |||
| // 删除 | |||
| const handleRemove = () => { | |||
| onChange?.(undefined); | |||
| }; | |||
| return ( | |||
| <div className={classNames('kf-code-select', className)} style={style}> | |||
| <ParameterInput | |||
| @@ -68,6 +74,7 @@ function CodeSelect({ | |||
| value={value} | |||
| onChange={onChange} | |||
| onClick={selectResource} | |||
| onRemove={handleRemove} | |||
| ></ParameterInput> | |||
| <Button | |||
| className="kf-code-select__button" | |||
| @@ -6,7 +6,7 @@ | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { CloseOutlined } from '@ant-design/icons'; | |||
| import { Form, Input } from 'antd'; | |||
| import { ConfigProvider, Form, Input } from 'antd'; | |||
| import { RuleObject } from 'antd/es/form'; | |||
| import classNames from 'classnames'; | |||
| import './index.less'; | |||
| @@ -67,7 +67,7 @@ function ParameterInput({ | |||
| allowClear, | |||
| className, | |||
| style, | |||
| size = 'middle', | |||
| size, | |||
| disabled = false, | |||
| id, | |||
| ...rest | |||
| @@ -81,10 +81,17 @@ function ParameterInput({ | |||
| const placeholder = valueObj?.placeholder || rest?.placeholder; | |||
| const InputComponent = textArea ? Input.TextArea : Input; | |||
| const { status } = Form.Item.useStatus(); | |||
| const { componentSize } = ConfigProvider.useConfig(); | |||
| const mySize = size || componentSize; | |||
| // 删除 | |||
| const handleRemove = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => { | |||
| e.stopPropagation(); | |||
| if (onRemove) { | |||
| onRemove(); | |||
| return; | |||
| } | |||
| onChange?.({ | |||
| ...valueObj, | |||
| value: undefined, | |||
| @@ -94,7 +101,6 @@ function ParameterInput({ | |||
| expandedKeys: [], | |||
| checkedKeys: [], | |||
| }); | |||
| onRemove?.(); | |||
| }; | |||
| return ( | |||
| @@ -104,8 +110,8 @@ function ParameterInput({ | |||
| id={id} | |||
| className={classNames( | |||
| 'parameter-input', | |||
| { 'parameter-input--large': size === 'large' }, | |||
| { 'parameter-input--small': size === 'small' }, | |||
| { 'parameter-input--large': mySize === 'large' }, | |||
| { 'parameter-input--small': mySize === 'small' }, | |||
| { [`parameter-input--${status}`]: status }, | |||
| className, | |||
| )} | |||
| @@ -128,7 +134,7 @@ function ParameterInput({ | |||
| <InputComponent | |||
| {...rest} | |||
| id={id} | |||
| size={size} | |||
| size={mySize} | |||
| className={className} | |||
| style={style} | |||
| placeholder={placeholder} | |||
| @@ -1,21 +1,10 @@ | |||
| import { filterResourceStandard, resourceFieldNames } from '@/hooks/resource'; | |||
| import { ServiceData } from '@/pages/ModelDeployment/types'; | |||
| import { getDatasetList, getModelList } from '@/services/dataset/index.js'; | |||
| import { getServiceListReq } from '@/services/modelDeployment'; | |||
| import { getComputingResourceReq } from '@/services/pipeline'; | |||
| import { ComputingResource } from '@/types'; | |||
| import { type SelectProps } from 'antd'; | |||
| import { pick } from 'lodash'; | |||
| // 过滤资源规格 | |||
| const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = ( | |||
| input: string, | |||
| option?: ComputingResource, | |||
| ) => { | |||
| return ( | |||
| option?.computing_resource?.toLocaleLowerCase()?.includes(input.toLocaleLowerCase()) ?? false | |||
| ); | |||
| }; | |||
| // id 从 number 转换为 string | |||
| const convertId = (item: any) => ({ | |||
| ...item, | |||
| @@ -86,17 +75,10 @@ export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||
| }, | |||
| resource: { | |||
| getOptions: async () => { | |||
| const res = await getComputingResourceReq({ | |||
| page: 0, | |||
| size: 1000, | |||
| resource_type: '', | |||
| }); | |||
| return res?.data?.content ?? []; | |||
| }, | |||
| fieldNames: { | |||
| label: 'description', | |||
| value: 'standard', | |||
| // 不需要这个函数 | |||
| return []; | |||
| }, | |||
| fieldNames: resourceFieldNames, | |||
| filterOption: filterResourceStandard as SelectProps['filterOption'], | |||
| }, | |||
| }; | |||
| @@ -4,6 +4,7 @@ | |||
| * @Description: 参数下拉选择组件,支持资源规格、数据集、模型、服务 | |||
| */ | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import { to } from '@/utils/promise'; | |||
| import { Select, type SelectProps } from 'antd'; | |||
| import { useEffect, useState } from 'react'; | |||
| @@ -20,7 +21,7 @@ export interface ParameterSelectProps extends SelectProps { | |||
| dataType: 'dataset' | 'model' | 'service' | 'resource'; | |||
| /** 是否只是展示信息 */ | |||
| display?: boolean; | |||
| /** 值 */ | |||
| /** 值,支持对象,对象必须包含 value */ | |||
| value?: string | ParameterSelectObject; | |||
| /** 修改后回调 */ | |||
| onChange?: (value: string | ParameterSelectObject) => void; | |||
| @@ -34,9 +35,10 @@ function ParameterSelect({ | |||
| onChange, | |||
| ...rest | |||
| }: ParameterSelectProps) { | |||
| const [options, setOptions] = useState([]); | |||
| const [options, setOptions] = useState<SelectProps['options']>([]); | |||
| const propsConfig = paramSelectConfig[dataType]; | |||
| const valueText = typeof value === 'object' && value !== null ? value.value : value; | |||
| const [resourceStandardList] = useComputingResource(); | |||
| useEffect(() => { | |||
| // 获取下拉数据 | |||
| @@ -54,6 +56,8 @@ function ParameterSelect({ | |||
| getSelectOptions(); | |||
| }, [propsConfig]); | |||
| const selectOptions = dataType === 'resource' ? resourceStandardList : options; | |||
| const handleChange = (text: string) => { | |||
| if (typeof value === 'object' && value !== null) { | |||
| onChange?.({ | |||
| @@ -71,7 +75,7 @@ function ParameterSelect({ | |||
| <FormInfo | |||
| select | |||
| value={valueText} | |||
| options={options} | |||
| options={selectOptions} | |||
| fieldNames={propsConfig?.fieldNames} | |||
| ></FormInfo> | |||
| ); | |||
| @@ -81,7 +85,7 @@ function ParameterSelect({ | |||
| <Select | |||
| {...rest} | |||
| filterOption={propsConfig?.filterOption} | |||
| options={options} | |||
| options={selectOptions} | |||
| fieldNames={propsConfig?.fieldNames} | |||
| optionFilterProp={propsConfig?.optionFilterProp} | |||
| value={valueText} | |||
| @@ -11,10 +11,9 @@ import ResourceSelectorModal, { | |||
| selectorTypeConfig, | |||
| } from '@/components/ResourceSelectorModal'; | |||
| import { openAntdModal } from '@/utils/modal'; | |||
| import { Button } from 'antd'; | |||
| import { Button, ConfigProvider } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { pick } from 'lodash'; | |||
| import { useEffect, useState } from 'react'; | |||
| import ParameterInput, { type ParameterInputProps } from '../ParameterInput'; | |||
| import './index.less'; | |||
| @@ -46,43 +45,40 @@ function ResourceSelect({ | |||
| onChange, | |||
| ...rest | |||
| }: ResourceSelectProps) { | |||
| const [selectedResource, setSelectedResource] = useState<ResourceSelectorResponse | undefined>( | |||
| undefined, | |||
| ); | |||
| useEffect(() => { | |||
| if ( | |||
| value && | |||
| typeof value === 'object' && | |||
| value.activeTab && | |||
| value.id && | |||
| value.name && | |||
| value.version && | |||
| value.path && | |||
| (type === ResourceSelectorType.Mirror || (value.identifier && value.owner)) | |||
| ) { | |||
| const originResource = pick(value, [ | |||
| 'activeTab', | |||
| 'id', | |||
| 'identifier', | |||
| 'name', | |||
| 'owner', | |||
| 'version', | |||
| 'path', | |||
| ]) as ResourceSelectorResponse; | |||
| setSelectedResource(originResource); | |||
| } | |||
| }, [value, type]); | |||
| const { componentSize } = ConfigProvider.useConfig(); | |||
| const mySize = size || componentSize; | |||
| let selectedResource: ResourceSelectorResponse | undefined = undefined; | |||
| if ( | |||
| value && | |||
| typeof value === 'object' && | |||
| value.activeTab && | |||
| value.id && | |||
| value.name && | |||
| value.version && | |||
| value.path && | |||
| (type === ResourceSelectorType.Mirror || (value.identifier && value.owner)) | |||
| ) { | |||
| selectedResource = pick(value, [ | |||
| 'activeTab', | |||
| 'id', | |||
| 'identifier', | |||
| 'name', | |||
| 'owner', | |||
| 'version', | |||
| 'path', | |||
| ]) as ResourceSelectorResponse; | |||
| } | |||
| // 选择数据集、模型、镜像 | |||
| const selectResource = () => { | |||
| const resource = selectedResource; | |||
| const { close } = openAntdModal(ResourceSelectorModal, { | |||
| type, | |||
| defaultExpandedKeys: resource ? [resource.id] : [], | |||
| defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [], | |||
| defaultActiveTab: resource?.activeTab, | |||
| defaultExpandedKeys: selectedResource ? [selectedResource.id] : [], | |||
| defaultCheckedKeys: selectedResource | |||
| ? [`${selectedResource.id}-${selectedResource.version}`] | |||
| : [], | |||
| defaultActiveTab: selectedResource?.activeTab, | |||
| onOk: (res) => { | |||
| setSelectedResource(res); | |||
| if (res) { | |||
| const { activeTab, id, name, version, path, identifier, owner } = res; | |||
| if (type === ResourceSelectorType.Mirror) { | |||
| @@ -116,32 +112,32 @@ function ResourceSelect({ | |||
| }); | |||
| } | |||
| } else { | |||
| onChange?.({ | |||
| value: undefined, | |||
| showValue: undefined, | |||
| fromSelect: false, | |||
| activeTab: undefined, | |||
| }); | |||
| onChange?.(undefined); | |||
| } | |||
| close(); | |||
| }, | |||
| }); | |||
| }; | |||
| // 删除 | |||
| const handleRemove = () => { | |||
| onChange?.(undefined); | |||
| }; | |||
| return ( | |||
| <div className={classNames('kf-resource-select', className)} style={style}> | |||
| <ParameterInput | |||
| {...rest} | |||
| disabled={disabled} | |||
| value={value} | |||
| size={size} | |||
| size={mySize} | |||
| onChange={onChange} | |||
| onRemove={() => setSelectedResource(undefined)} | |||
| onRemove={handleRemove} | |||
| onClick={selectResource} | |||
| ></ParameterInput> | |||
| <Button | |||
| className="kf-resource-select__button" | |||
| size={size} | |||
| size={mySize} | |||
| type="link" | |||
| icon={getSelectBtnIcon(type)} | |||
| disabled={disabled} | |||
| @@ -30,7 +30,6 @@ function DatasetConfig() { | |||
| type={ResourceSelectorType.Dataset} | |||
| placeholder="请选择数据集" | |||
| canInput={false} | |||
| size="large" | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| @@ -431,7 +431,12 @@ function ExecuteConfig() { | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item label="是否打乱" name="shuffle" tooltip="拆分数据前是否打乱顺序"> | |||
| <Form.Item | |||
| label="是否打乱" | |||
| name="shuffle" | |||
| tooltip="拆分数据前是否打乱顺序" | |||
| valuePropName="checked" | |||
| > | |||
| <Switch /> | |||
| </Form.Item> | |||
| </Col> | |||
| @@ -33,7 +33,7 @@ function LogGroup({ | |||
| status, | |||
| }: LogGroupProps) { | |||
| const [collapse, setCollapse] = useState(true); | |||
| const [logList, setLogList, logListRef] = useStateRef<Log[]>([]); | |||
| const [logList, setLogList] = useState<Log[]>([]); | |||
| const [completed, setCompleted] = useState(false); | |||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | |||
| const [_isMouseDown, setIsMouseDown, isMouseDownRef] = useStateRef(false); | |||
| @@ -126,6 +126,14 @@ function LogGroup({ | |||
| socketRef.current = socket; | |||
| }; | |||
| // 关闭 socket | |||
| const closeSocket = () => { | |||
| if (socketRef.current) { | |||
| socketRef.current.close(1000, 'completed'); | |||
| socketRef.current = undefined; | |||
| } | |||
| }; | |||
| if (status === ExperimentStatus.Running) { | |||
| setupSockect(); | |||
| } | |||
| @@ -133,7 +141,7 @@ function LogGroup({ | |||
| return () => { | |||
| closeSocket(); | |||
| }; | |||
| }, [status, start_time, pod_name, isMouseDownRef, setLogList]); | |||
| }, [status, start_time, pod_name, isMouseDownRef]); | |||
| // 鼠标拖到中不滚动到底部 | |||
| useEffect(() => { | |||
| @@ -153,8 +161,8 @@ function LogGroup({ | |||
| // 请求日志 | |||
| const requestExperimentPodsLog = async () => { | |||
| const list = logListRef.current; | |||
| const startTime = list.length > 0 ? list[list.length - 1].start_time : start_time; | |||
| const last = logList[logList.length - 1]; | |||
| const startTime = last ? last.start_time : start_time; | |||
| const params = { | |||
| pod_name, | |||
| start_time: startTime, | |||
| @@ -201,14 +209,6 @@ function LogGroup({ | |||
| requestExperimentPodsLog(); | |||
| }; | |||
| // 关闭 socket | |||
| const closeSocket = () => { | |||
| if (socketRef.current) { | |||
| socketRef.current.close(1000, 'completed'); | |||
| socketRef.current = undefined; | |||
| } | |||
| }; | |||
| // 滚动到底部 | |||
| const scrollToBottom = (smooth: boolean = true) => { | |||
| // const element = document.getElementById(listId); | |||
| @@ -305,7 +305,6 @@ function CreateServiceVersion() { | |||
| placeholder="请选择模型" | |||
| disabled={disabled} | |||
| canInput={false} | |||
| size="large" | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| @@ -327,7 +326,6 @@ function CreateServiceVersion() { | |||
| type={ResourceSelectorType.Mirror} | |||
| placeholder="请选择镜像" | |||
| canInput={false} | |||
| size="large" | |||
| disabled={disabled} | |||
| /> | |||
| </Form.Item> | |||
| @@ -24,7 +24,7 @@ const EditPipeline = () => { | |||
| const propsRef = useRef(); | |||
| const [paramsDrawerOpen, openParamsDrawer, closeParamsDrawer] = useVisible(false); | |||
| const [globalParam, setGlobalParam, globalParamRef] = useStateRef([]); | |||
| const [workflowInfo, setWorkflowInfo] = useStateRef(undefined); | |||
| const [workflowInfo, setWorkflowInfo] = useState(undefined); | |||
| const { message } = App.useApp(); | |||
| let sourceAnchorIdx, targetAnchorIdx, dropAnchorIdx; | |||
| let dragSourceNode; | |||
| @@ -41,6 +41,7 @@ export const Primary: Story = { | |||
| canInput: false, | |||
| textArea: false, | |||
| size: 'large', | |||
| placeholder: '请选择代码配置', | |||
| style: { width: 400 }, | |||
| }, | |||
| render: function Render(args) { | |||
| @@ -1,5 +1,7 @@ | |||
| import ParameterInput, { ParameterInputValue } from '@/components/ParameterInput'; | |||
| import { action } from '@storybook/addon-actions'; | |||
| import type { Meta, StoryObj } from '@storybook/react'; | |||
| import { fn } from '@storybook/test'; | |||
| import { Button } from 'antd'; | |||
| import { useState } from 'react'; | |||
| @@ -18,7 +20,7 @@ const meta = { | |||
| // backgroundColor: { control: 'color' }, | |||
| }, | |||
| // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args | |||
| // args: { onClick: fn() }, | |||
| args: { onChange: fn() }, | |||
| } satisfies Meta<typeof ParameterInput>; | |||
| export default meta; | |||
| @@ -37,45 +39,6 @@ export const Input: Story = { | |||
| }; | |||
| export const Select: Story = { | |||
| args: { | |||
| placeholder: '请输入工作目录', | |||
| style: { width: 300 }, | |||
| value: 'storybook', | |||
| canInput: false, | |||
| size: 'large', | |||
| }, | |||
| }; | |||
| export const SelectWithObjctValue: Story = { | |||
| args: { | |||
| placeholder: '请输入工作目录', | |||
| style: { width: 300 }, | |||
| value: { | |||
| value: 'storybook', | |||
| showValue: 'storybook', | |||
| fromSelect: true, | |||
| }, | |||
| canInput: true, | |||
| size: 'large', | |||
| }, | |||
| }; | |||
| export const Disabled: Story = { | |||
| args: { | |||
| placeholder: '请输入工作目录', | |||
| style: { width: 300 }, | |||
| value: { | |||
| value: 'storybook', | |||
| showValue: 'storybook', | |||
| fromSelect: true, | |||
| }, | |||
| canInput: true, | |||
| size: 'large', | |||
| disabled: true, | |||
| }, | |||
| }; | |||
| export const Application: Story = { | |||
| args: { | |||
| placeholder: '请输入工作目录', | |||
| style: { width: 300 }, | |||
| @@ -86,18 +49,24 @@ export const Application: Story = { | |||
| const [value, setValue] = useState<ParameterInputValue | undefined>(''); | |||
| const onClick = () => { | |||
| setValue({ | |||
| const value = { | |||
| value: 'storybook', | |||
| showValue: 'storybook', | |||
| fromSelect: true, | |||
| }); | |||
| otherValue: 'others', | |||
| }; | |||
| setValue(value); | |||
| action('onChange')(value); | |||
| }; | |||
| return ( | |||
| <> | |||
| <ParameterInput | |||
| {...args} | |||
| value={value} | |||
| onChange={(value) => setValue(value)} | |||
| onChange={(value) => { | |||
| setValue(value); | |||
| action('onChange')(value); | |||
| }} | |||
| ></ParameterInput> | |||
| <Button type="primary" style={{ display: 'block', marginTop: 10 }} onClick={onClick}> | |||
| 模拟从全局参数选择 | |||
| @@ -106,3 +75,18 @@ export const Application: Story = { | |||
| ); | |||
| }, | |||
| }; | |||
| export const Disabled: Story = { | |||
| args: { | |||
| placeholder: '请输入工作目录', | |||
| style: { width: 300 }, | |||
| value: { | |||
| value: 'storybook', | |||
| showValue: 'storybook', | |||
| fromSelect: true, | |||
| }, | |||
| canInput: true, | |||
| size: 'large', | |||
| disabled: true, | |||
| }, | |||
| }; | |||
| @@ -76,6 +76,7 @@ export const Primary: Story = { | |||
| canInput: false, | |||
| textArea: false, | |||
| size: 'large', | |||
| placeholder: '请选择数据集', | |||
| style: { width: 400 }, | |||
| }, | |||
| render: function Render(args) { | |||
| @@ -120,7 +121,6 @@ export const InForm: Story = { | |||
| type={ResourceSelectorType.Dataset} | |||
| placeholder="请选择" | |||
| canInput={false} | |||
| size="large" | |||
| onChange={onChange} | |||
| /> | |||
| </Form.Item> | |||
| @@ -133,7 +133,6 @@ export const InForm: Story = { | |||
| type={ResourceSelectorType.Model} | |||
| placeholder="请选择" | |||
| canInput={false} | |||
| size="large" | |||
| onChange={onChange} | |||
| /> | |||
| </Form.Item> | |||
| @@ -146,7 +145,6 @@ export const InForm: Story = { | |||
| type={ResourceSelectorType.Mirror} | |||
| placeholder="请选择" | |||
| canInput={false} | |||
| size="large" | |||
| onChange={onChange} | |||
| /> | |||
| </Form.Item> | |||