From cbc000d35a2471280032aaf9fc92c345520f629e Mon Sep 17 00:00:00 2001 From: jialin Date: Wed, 12 Nov 2025 14:54:31 +0800 Subject: [PATCH] fix: playground formref is invalid --- src/atoms/clusters.ts | 7 + src/components/icon-font/index.tsx | 2 +- src/components/segment-line/index.tsx | 44 +- .../_components/terminal-tabs/footer.less | 2 +- src/pages/backends/components/add-modal.tsx | 7 +- src/pages/cluster-management/clusters.tsx | 45 +- .../components/add-worker-step.tsx | 13 +- .../components/cluster-item.tsx | 390 ------------------ .../components/cluster-steps.tsx | 13 - src/pages/cluster-management/credentials.tsx | 3 +- .../llmodels/components/deploy-modal.tsx | 2 +- src/pages/llmodels/forms/index.tsx | 12 +- .../playground/components/ground-left.tsx | 2 +- .../playground/hooks/use-chat-completion.ts | 2 - src/pages/playground/hooks/use-init-meta.ts | 2 + src/pages/playground/index.tsx | 9 - src/pages/resources/components/workers.tsx | 4 + 17 files changed, 110 insertions(+), 449 deletions(-) delete mode 100644 src/pages/cluster-management/components/cluster-item.tsx diff --git a/src/atoms/clusters.ts b/src/atoms/clusters.ts index ad8c2c76..9bfb5be3 100644 --- a/src/atoms/clusters.ts +++ b/src/atoms/clusters.ts @@ -54,3 +54,10 @@ export const allRegionInstanceTypeListAtom = atom< >([]); export const fromClusterCreationAtom = atom(false); + +/** + * for temporary cluster session storage during creation/editing + */ +export const clusterSessionAtom = atom<{ + firstAddWorker: boolean; +} | null>(null); diff --git a/src/components/icon-font/index.tsx b/src/components/icon-font/index.tsx index a97d2e79..b91e1c14 100644 --- a/src/components/icon-font/index.tsx +++ b/src/components/icon-font/index.tsx @@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons'; // import './iconfont/iconfont.js'; const IconFont = createFromIconfontCN({ - scriptUrl: '//at.alicdn.com/t/c/font_4613488_ga8perwfeom.js' + scriptUrl: '//at.alicdn.com/t/c/font_4613488_9h4e8ttfsau.js' }); export default IconFont; diff --git a/src/components/segment-line/index.tsx b/src/components/segment-line/index.tsx index 4c10e83c..bab2a133 100644 --- a/src/components/segment-line/index.tsx +++ b/src/components/segment-line/index.tsx @@ -2,15 +2,40 @@ import { Segmented, type SegmentedProps } from 'antd'; import React from 'react'; import styled from 'styled-components'; +interface ThemeType { + color: string; + itemColorHover: string; + itemSelectedColor: string; + thumbBgColor: string; + fontWeight: number; +} interface SegmentLineProps extends SegmentedProps { height?: number; + theme: 'dark' | 'light'; } -const SegmentedQwrapper = styled.div<{ $height: number }>` +const darkTheme: ThemeType = { + color: 'var(--color-white-tertiary)', + itemColorHover: 'var(--color-white-secondary)', + itemSelectedColor: 'var(--color-white-primary)', + thumbBgColor: 'var(--color-white-primary)', + fontWeight: 400 +}; + +const lightTheme: ThemeType = { + color: 'var(--ant-segmented-item-color)', + itemColorHover: 'var(--ant-segmented-item-hover-color)', + itemSelectedColor: 'var(--ant-segmented-item-selected-color)', + thumbBgColor: 'var(--ant-color-primary)', + fontWeight: 500 +}; + +const SegmentedQwrapper = styled.div<{ $height: number; $theme: ThemeType }>` .ant-segmented.segment-line { padding: 0; background-color: transparent; - color: var(--color-white-tertiary); + color: ${(props) => props.$theme.color}; + font-weight: ${(props) => props.$theme.fontWeight}; border: none; box-shadow: none; height: ${(props) => props.$height}px; @@ -21,14 +46,14 @@ const SegmentedQwrapper = styled.div<{ $height: number }>` height: 100%; } .ant-segmented-item:hover { - color: var(--color-white-secondary); + color: ${(props) => props.$theme.itemColorHover}; } .ant-segmented-thumb { height: 2px; padding: 0px; bottom: 0px; top: unset; - background-color: var(--color-white-primary) !important; + background-color: ${(props) => props.$theme.thumbBgColor} !important; } .ant-segmented-item-label { display: flex; @@ -42,7 +67,7 @@ const SegmentedQwrapper = styled.div<{ $height: number }>` display: flex; align-items: center; &::after { - background-color: var(--color-white-primary) !important; + background-color: ${(props) => props.$theme.thumbBgColor} !important; width: 100%; height: 0px; border-radius: 2px; @@ -53,7 +78,8 @@ const SegmentedQwrapper = styled.div<{ $height: number }>` } .ant-segmented-item-selected { background-color: transparent; - color: var(--color-white-primary); + box-shadow: none; + color: ${(props) => props.$theme.itemSelectedColor}; &::after { height: 2px; opacity: 1; @@ -68,11 +94,15 @@ const SegmentLine: React.FC = (props) => { size = 'small', options = [], className = 'segment-line', + theme = 'dark', ...rest } = props; return ( - + = (props) => { footer={false} > - setActiveKey(value as string)} options={[ { diff --git a/src/pages/cluster-management/clusters.tsx b/src/pages/cluster-management/clusters.tsx index 63773cbe..033196dc 100644 --- a/src/pages/cluster-management/clusters.tsx +++ b/src/pages/cluster-management/clusters.tsx @@ -1,4 +1,4 @@ -import { expandKeysAtom } from '@/atoms/clusters'; +import { clusterSessionAtom, expandKeysAtom } from '@/atoms/clusters'; import DeleteModal from '@/components/delete-modal'; import IconFont from '@/components/icon-font'; import { FilterBar } from '@/components/page-tools'; @@ -10,7 +10,7 @@ import useExpandedRowKeys from '@/hooks/use-expanded-row-keys'; import useTableFetch from '@/hooks/use-table-fetch'; import useWatchList from '@/hooks/use-watch-list'; import AddWorker from '@/pages/cluster-management/components/add-worker'; -import { useIntl, useNavigate, useSearchParams } from '@umijs/max'; +import { useIntl, useNavigate } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { Button, message } from 'antd'; import { useAtom } from 'jotai'; @@ -32,7 +32,11 @@ import AddCluster from './components/add-cluster'; import AddPool from './components/add-pool'; import PoolRows from './components/pool-rows'; import RegisterCluster from './components/register-cluster'; -import { ProviderType, ProviderValueMap } from './config'; +import { + ClusterStatusValueMap, + ProviderType, + ProviderValueMap +} from './config'; import { ClusterListItem, ClusterFormData as FormData, @@ -61,15 +65,10 @@ const Clusters: React.FC = () => { contentForDelete: 'menu.clusterManagement.clusters' }); const { watchDataList: allWorkerPoolList } = useWatchList(WORKER_POOLS_API); - const [expandAtom, setExpandAtom] = useAtom(expandKeysAtom); - const { - handleExpandChange, - handleExpandAll, - updateExpandedRowKeys, - removeExpandedRowKey, - expandedRowKeys - } = useExpandedRowKeys(expandAtom); - const [searchParams, setSearchParams] = useSearchParams(); + const [expandAtom] = useAtom(expandKeysAtom); + const [clusterSession, setClusterSession] = useAtom(clusterSessionAtom); + const { handleExpandChange, handleExpandAll, expandedRowKeys } = + useExpandedRowKeys(expandAtom); const navigate = useNavigate(); const intl = useIntl(); const [registerClusterStatus, setRegisterClusterStatus] = useState<{ @@ -309,6 +308,28 @@ const Clusters: React.FC = () => { fetchCredentialList(); }, []); + /** + * add worker from workers page redirect + */ + useEffect(() => { + if ( + clusterSession?.firstAddWorker && + dataSource.loadend && + dataSource.dataList?.length > 0 + ) { + const targetCluster = dataSource.dataList.find( + (cluster) => + cluster.provider === ProviderValueMap.Docker && + cluster.state === ClusterStatusValueMap.Ready + ); + if (targetCluster) { + handleAddWorker(targetCluster); + // reset session + setClusterSession(null); + } + } + }, [clusterSession, dataSource.loadend, dataSource.dataList]); + const renderChildren = ( list: any, options: { parent?: any; [key: string]: any } diff --git a/src/pages/cluster-management/components/add-worker-step.tsx b/src/pages/cluster-management/components/add-worker-step.tsx index 7fa2b4e5..c6926085 100644 --- a/src/pages/cluster-management/components/add-worker-step.tsx +++ b/src/pages/cluster-management/components/add-worker-step.tsx @@ -29,17 +29,16 @@ const NotesWrapper = styled.ol` margin: 0 !important; padding: 0 !important; line-height: 1.25; + counter-reset: main 2 sub 0; li { - margin-left: 12px !important; + counter-increment: sub; + margin-left: 20px !important; + &::marker { + content: counters(main, '.') '.' counter(sub) ' '; + } } `; -const Title = styled.div` - font-size: 16px; - font-weight: 600; - margin-bottom: 16px; -`; - const Line = styled.div` position: relative; margin: 24px 0; diff --git a/src/pages/cluster-management/components/cluster-item.tsx b/src/pages/cluster-management/components/cluster-item.tsx deleted file mode 100644 index b33c6c64..00000000 --- a/src/pages/cluster-management/components/cluster-item.tsx +++ /dev/null @@ -1,390 +0,0 @@ -import DropdownButtons from '@/components/drop-down-buttons'; -import GaugeChart from '@/components/echarts/gauge'; -import IconFont from '@/components/icon-font'; -import StatusTag from '@/components/status-tag'; -import ThemeTag from '@/components/tags-wrapper/theme-tag'; -import Card from '@/components/templates/card'; -import { PageAction } from '@/config'; -import { Card as ACard, Col, Collapse, Row } from 'antd'; -import React, { useMemo } from 'react'; -import styled from 'styled-components'; -import { queryClusterToken } from '../apis'; -import { - ClusterStatus, - ClusterStatusLabelMap, - ProviderLabelMap, - ProviderValueMap, - clusterActionList -} from '../config'; -import { ClusterListItem as ListItem, NodePoolListItem } from '../config/types'; -import AddPool from './add-pool'; -import RegisterCluster from './register-cluster'; -import WorkerPools from './worker-pools'; - -const Content = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - .chart-wrapper { - flex: 1; - } -`; - -const CardWrapper = styled(ACard)` - text-align: center; - box-shadow: none !important; - flex: 1; - .ant-card { - box-shadow: none; - } - .ant-card-body { - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - padding: 6px 10px; - } - - .label { - font-weight: 400; - margin-bottom: 12px; - font-size: var(--font-size-middle); - color: var(--ant-color-text-secondary); - } - .value { - font-weight: 600; - color: var(--ant-color-text); - font-size: var(--font-size-large); - } -`; - -const CardBox = styled.div` - display: flex; - gap: 16px; - flex: 1; -`; - -const Inner = styled.div` - display: flex; - flex-direction: column; - height: 100%; - width: 100%; - cursor: default; - - .title { - margin-bottom: 12px; - display: flex; - align-items: center; - justify-content: space-between; - padding-bottom: 8px; - - .text { - display: flex; - align-items: center; - font-size: var(--font-size-middle); - font-weight: 500; - color: var(--ant-color-text); - } - } - - .content { - display: flex; - flex-direction: column; - justify-content: space-between; - flex: 1; - } -`; - -const CollapseWrapper = styled(Collapse)` - width: 100%; - border: none; - border-radius: 0; - border-top: 1px solid var(--ant-color-split); - background-color: var(--ant-color-fill-quaternary); - .ant-collapse-content { - border-top: 1px solid var(--ant-color-split); - } - .ant-collapse-header { - font-size: var(--font-size-middle); - padding-block: 10px !important; - .ant-collapse-expand-icon { - margin-left: 0 !important; - } - } - table .ant-table-thead tr { - background-color: transparent !important; - th { - border-bottom: 1px solid var(--ant-color-split) !important; - font-weight: 500 !important; - } - } -`; - -const SubTitle = styled.div` - font-size: var(--font-size-middle); - font-weight: 500; - color: var(--ant-color-text); - margin-block: 24px 16px; -`; - -interface CardProps { - data: ListItem; - onSelect?: (key: string, row: ListItem) => void; -} - -const gaugeConfig = { - radius: '100%', - progress: { - show: true, - roundCap: false, - width: 8 - }, - axisLine: { - roundCap: false, - lineStyle: { - width: 8, - color: [ - [0.5, 'rgba(84, 204, 152, 80%)'], - [0.8, 'rgba(250, 173, 20, 80%)'], - [1, 'rgba(255, 77, 79, 80%)'] - ] - } - } -}; - -const CardItem: React.FC = (props) => { - const chartHeight = 160; - const { data, onSelect } = props; - const [show, setShow] = React.useState(false); - const [addPoolStatus, setAddPoolStatus] = React.useState({ - open: false, - action: PageAction.CREATE, - title: '', - provider: ProviderValueMap.DigitalOcean - }); - const [registerClusterStatus, setRegisterClusterStatus] = React.useState<{ - open: boolean; - registrationInfo: { - token: string; - image: string; - server_url: string; - cluster_id: number; - }; - }>({ - open: false, - registrationInfo: { - token: '', - image: '', - server_url: '', - cluster_id: 0 - } - }); - - const handleRegisterCluster = async () => { - try { - const info = await queryClusterToken({ id: data.id }); - setRegisterClusterStatus({ - open: true, - registrationInfo: { - ...info, - cluster_id: data.id - } - }); - } catch (error) {} - }; - - // cluster action handler - const handleOnSelect = (key: string) => { - if (key === 'addPool') { - setAddPoolStatus({ - open: true, - action: PageAction.CREATE, - title: 'Add Worker Pool', - provider: data.provider - }); - return; - } - - if (key === 'register_cluster') { - handleRegisterCluster(); - return; - } - onSelect?.(key, data); - }; - - // pool action handler - const handleOnAction = (action: string, record: NodePoolListItem) => { - if (action === 'edit') { - setAddPoolStatus({ - open: true, - action: PageAction.CREATE, - title: 'Edit Worker Pool', - provider: data.provider - }); - } - }; - - const actions = useMemo(() => { - return clusterActionList.filter((item) => { - if (item.provider) { - return item.provider === data.provider; - } - return true; - }); - }, [data.provider]); - - return ( - setShow(!show)} - expandIconPosition="end" - expandIcon={({ isActive }) => ( - - )} - items={[ - { - key: '1', - label: 'More Information', - children: ( - <> -
- - - - - - - - - - - - - - -
- {data.provider === ProviderValueMap.DigitalOcean && ( - <> - Worker Pools - - - )} - - ) - } - ]} - > - } - > - -
- - {data.name} - - {ProviderLabelMap[data.provider]} - - - - - - -
- - - -
Workers
-
- {data.ready_workers} / {data.workers} -
-
- -
GPUs
-
{data.gpus}
-
- -
Deployments
-
{data.models}
-
-
-
-
- { - setAddPoolStatus({ - open: false, - action: PageAction.CREATE, - title: '', - provider: 'digitalocean' - }); - }} - onOk={() => { - setAddPoolStatus({ - open: false, - action: addPoolStatus.action, - title: '', - provider: 'digitalocean' - }); - }} - > - { - setRegisterClusterStatus({ - open: false, - registrationInfo: { - token: '', - image: '', - server_url: '', - cluster_id: 0 - } - }); - }} - > -
- ); -}; - -export default CardItem; diff --git a/src/pages/cluster-management/components/cluster-steps.tsx b/src/pages/cluster-management/components/cluster-steps.tsx index 7333a164..30fd2add 100644 --- a/src/pages/cluster-management/components/cluster-steps.tsx +++ b/src/pages/cluster-management/components/cluster-steps.tsx @@ -13,19 +13,6 @@ const Wrapper = styled.div` max-width: 300px !important; color: var(--ant-color-text-description) !important; } - .ant-steps-item-content > .ant-steps-item-title { - // font-weight: 600; - } - .ant-steps-item { - .ant-steps-item-container { - // .ant-steps-item-icon { - // margin-inline-start: 0 !important; - // } - // .ant-steps-item-tail { - // margin-inline-start: 0 !important; - // } - } - } `; const ClusterSteps: React.FC<{ diff --git a/src/pages/cluster-management/credentials.tsx b/src/pages/cluster-management/credentials.tsx index eb018429..cbc3f5d9 100644 --- a/src/pages/cluster-management/credentials.tsx +++ b/src/pages/cluster-management/credentials.tsx @@ -60,7 +60,7 @@ const Credentials: React.FC = () => { deleteAPI: deleteCredential, contentForDelete: 'menu.clusterManagement.credentials' }); - const [isFromCluster] = useAtom(fromClusterCreationAtom); + const [isFromCluster, setIsFromCluster] = useAtom(fromClusterCreationAtom); const intl = useIntl(); const navigate = useNavigate(); const [openModalStatus, setOpenModalStatus] = useState<{ @@ -110,6 +110,7 @@ const Credentials: React.FC = () => { navigate(-1); } } + setIsFromCluster(false); fetchData(); setOpenModalStatus({ ...openModalStatus, open: false }); message.success(intl.formatMessage({ id: 'common.message.success' })); diff --git a/src/pages/llmodels/components/deploy-modal.tsx b/src/pages/llmodels/components/deploy-modal.tsx index e783670f..8a8dcf33 100644 --- a/src/pages/llmodels/components/deploy-modal.tsx +++ b/src/pages/llmodels/components/deploy-modal.tsx @@ -454,6 +454,7 @@ const AddModal: FC = (props) => { form.current?.form?.setFieldsValue({ ...props.initialValues }); + console.log('handleOnOpen - modelFiles:', props.initialValues); handleOnValuesChange?.({ changedValues: {}, allValues: { @@ -530,7 +531,6 @@ const AddModal: FC = (props) => { closeIcon={false} maskClosable={false} keyboard={false} - zIndex={2000} styles={{ body: { height: 'calc(100vh - 57px)', diff --git a/src/pages/llmodels/forms/index.tsx b/src/pages/llmodels/forms/index.tsx index 03b9a20c..fd08a231 100644 --- a/src/pages/llmodels/forms/index.tsx +++ b/src/pages/llmodels/forms/index.tsx @@ -1,3 +1,5 @@ +import IconFont from '@/components/icon-font'; +import SegmentLine from '@/components/segment-line'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; import CollapsePanel from '@/pages/_components/collapse-panel'; @@ -52,7 +54,8 @@ const SegmentedHeader = styled.div<{ $top?: number }>` position: sticky; top: ${(props) => props.$top || 0}px; z-index: 10; - padding-bottom: 16px; + margin-bottom: 16px; + border-bottom: 1px solid var(--ant-color-split); background-color: var(--ant-color-bg-elevated); `; @@ -112,21 +115,25 @@ const DataForm: React.FC = forwardRef((props, ref) => { { value: TABKeysMap.BASIC, label: intl.formatMessage({ id: 'common.title.basicInfo' }), + icon: , field: 'name' }, { value: TABKeysMap.SCHEDULING, label: intl.formatMessage({ id: 'models.form.scheduling' }), + icon: , field: 'scheduleType' }, { value: TABKeysMap.PERFORMANCE, label: intl.formatMessage({ id: 'models.form.performance' }), + icon: , field: 'extended_kv_cache.enabled' }, { value: TABKeysMap.ADVANCED, label: intl.formatMessage({ id: 'resources.form.advanced' }), + icon: , field: 'categories' } ]; @@ -404,7 +411,8 @@ const DataForm: React.FC = forwardRef((props, ref) => { }} > - = forwardRef((props, ref) => { handleAddNewMessage, handleClear, setMessageList, - formRef, tokenResult, messageList, loading } = useChatCompletion(scroller); const { handleOnValuesChange, + formRef, paramsRef, paramsConfig, initialValues, diff --git a/src/pages/playground/hooks/use-chat-completion.ts b/src/pages/playground/hooks/use-chat-completion.ts index 02ce0a6e..b248bbb1 100644 --- a/src/pages/playground/hooks/use-chat-completion.ts +++ b/src/pages/playground/hooks/use-chat-completion.ts @@ -23,7 +23,6 @@ export default function useChatCompletion( const currentMessageRef = useRef(null); const messageListLengthCache = useRef(0); const reasonContentRef = useRef(''); - const formRef = useRef(null); const setMessageId = () => { messageId.current = messageId.current + 1; @@ -217,7 +216,6 @@ export default function useChatCompletion( loading, tokenResult, messageList, - formRef, setMessageId, setMessageList, handleClear, diff --git a/src/pages/playground/hooks/use-init-meta.ts b/src/pages/playground/hooks/use-init-meta.ts index 3913d7a5..a8844979 100644 --- a/src/pages/playground/hooks/use-init-meta.ts +++ b/src/pages/playground/hooks/use-init-meta.ts @@ -52,6 +52,7 @@ export const useInitLLmMeta = ( defaultValues = {}, defaultParamsConfig = [] } = options; + const formRef = useRef(null); const [searchParams] = useSearchParams(); const [modelMeta, setModelMeta] = useState({}); const [initialValues, setInitialValues] = useState({ @@ -194,6 +195,7 @@ export const useInitLLmMeta = ( setInitialValues, setParams, setParamsConfig, + formRef, paramsConfig, initialValues, parameters, diff --git a/src/pages/playground/index.tsx b/src/pages/playground/index.tsx index d40edf17..19ab31a9 100644 --- a/src/pages/playground/index.tsx +++ b/src/pages/playground/index.tsx @@ -11,7 +11,6 @@ import classNames from 'classnames'; import _ from 'lodash'; import { useEffect, useMemo, useRef, useState } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; -import styled from 'styled-components'; import { PageContainerInner } from '../_components/page-box'; import { queryModelsList } from './apis'; import GroundLeft from './components/ground-left'; @@ -19,14 +18,6 @@ import MultipleChat from './components/multiple-chat'; import ViewCodeButtons from './components/view-code-buttons'; import './style/play-ground.less'; -const Header = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - margin-top: 16px; - padding-inline: 32px; -`; - const Playground: React.FC = () => { const intl = useIntl(); const { size } = useWindowResize(); diff --git a/src/pages/resources/components/workers.tsx b/src/pages/resources/components/workers.tsx index 30e02a78..ac85d5dc 100644 --- a/src/pages/resources/components/workers.tsx +++ b/src/pages/resources/components/workers.tsx @@ -1,3 +1,4 @@ +import { clusterSessionAtom } from '@/atoms/clusters'; import DeleteModal from '@/components/delete-modal'; import IconFont from '@/components/icon-font'; import { FilterBar } from '@/components/page-tools'; @@ -7,6 +8,7 @@ import { queryClusterList } from '@/pages/cluster-management/apis'; import { useIntl, useNavigate } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { Button, ConfigProvider, Table, message } from 'antd'; +import { useAtom } from 'jotai'; import React, { useEffect, useState } from 'react'; import NoResult from '../../_components/no-result'; import TerminalTabs from '../../_components/terminal-tabs'; @@ -44,6 +46,7 @@ const Workers: React.FC = () => { watch: true, API: WORKERS_API }); + const [, setClusterSession] = useAtom(clusterSessionAtom); const navigate = useNavigate(); const intl = useIntl(); @@ -178,6 +181,7 @@ const Workers: React.FC = () => { }); const handleAddWorker = () => { + setClusterSession({ firstAddWorker: true }); navigate('/cluster-management/clusters/list'); };