@@ -32,6 +32,8 @@ import {
3232} from '../hooks/useRuntimeParameterSchema' ;
3333import DeploymentPresetDetailModal from './DeploymentPresetDetailModal' ;
3434import EnvVarFormList , { type EnvVarFormListValue } from './EnvVarFormList' ;
35+ import FolderCreateModalV2 from './FolderCreateModalV2' ;
36+ import { useFolderExplorerOpener } from './FolderExplorerOpener' ;
3537import ImageEnvironmentSelectFormItems , {
3638 type ImageEnvironmentFormInput ,
3739} from './ImageEnvironmentSelectFormItems' ;
@@ -46,7 +48,7 @@ import ResourceAllocationFormItems, {
4648import VFolderTableFormItem , {
4749 type VFolderTableFormValues ,
4850} from './VFolderTableFormItem' ;
49- import { InfoCircleOutlined } from '@ant-design/icons' ;
51+ import { InfoCircleOutlined , ReloadOutlined } from '@ant-design/icons' ;
5052import {
5153 Alert ,
5254 App ,
@@ -73,15 +75,18 @@ import {
7375 BAIModalProps ,
7476 BAIRuntimeVariantSelect ,
7577 BAIVFolderSelect ,
78+ BAIVFolderSelectRef ,
7679 convertToUUID ,
7780 safeDecodeUuid ,
7881 toGlobalId ,
7982 toLocalId ,
8083 useBAILogger ,
8184} from 'backend.ai-ui' ;
8285import * as _ from 'lodash-es' ;
86+ import { FolderOpenIcon , PlusIcon } from 'lucide-react' ;
8387import React , {
8488 Suspense ,
89+ startTransition ,
8590 useDeferredValue ,
8691 useEffect ,
8792 useEffectEvent ,
@@ -187,6 +192,15 @@ const DeploymentAddRevisionModal: React.FC<DeploymentAddRevisionModalProps> = ({
187192 // (ServiceLauncherPageContent, ModelCardDeployModal).
188193 const { id : currentProjectId } = useCurrentProjectValue ( ) ;
189194 const { logger } = useBAILogger ( ) ;
195+ const { open : openFolderExplorer } = useFolderExplorerOpener ( ) ;
196+
197+ // Refs to refetch each form's model folder select after creating a new
198+ // model-usage folder, or via the manual refresh button. Two refs because
199+ // the Preset and Custom forms each mount their own BAIVFolderSelect.
200+ const presetVFolderSelectRef = useRef < BAIVFolderSelectRef > ( null ) ;
201+ const customVFolderSelectRef = useRef < BAIVFolderSelectRef > ( null ) ;
202+ const [ isModelFolderCreateModalOpen , setIsModelFolderCreateModalOpen ] =
203+ useState ( false ) ;
190204
191205 // Defer `open` so the lazy query only fires once the modal has actually
192206 // committed to opening. `loading={deferredOpen !== open}` then lets the
@@ -1272,18 +1286,64 @@ const DeploymentAddRevisionModal: React.FC<DeploymentAddRevisionModalProps> = ({
12721286 </ Form . Item >
12731287
12741288 < Form . Item
1275- name = "modelFolderId"
12761289 label = { t ( 'deployment.ModelFolder' ) }
12771290 tooltip = { t ( 'deployment.ModelFolderTooltip' ) }
1278- rules = { [ { required : true } ] }
1291+ required
12791292 >
1280- < BAIVFolderSelect
1281- currentProjectId = { currentProjectId ?? undefined }
1282- disabled = { ! currentProjectId }
1283- excludeDeleted
1284- filter = 'usage_mode == "model"'
1285- style = { { width : '100%' } }
1286- />
1293+ < BAIFlex direction = "row" gap = "xs" >
1294+ < Form . Item
1295+ name = "modelFolderId"
1296+ noStyle
1297+ rules = { [ { required : true } ] }
1298+ >
1299+ < BAIVFolderSelect
1300+ ref = { presetVFolderSelectRef }
1301+ currentProjectId = { currentProjectId ?? undefined }
1302+ disabled = { ! currentProjectId }
1303+ excludeDeleted
1304+ filter = 'usage_mode == "model"'
1305+ style = { { flex : 1 } }
1306+ />
1307+ </ Form . Item >
1308+ < Form . Item dependencies = { [ 'modelFolderId' ] } noStyle >
1309+ { ( { getFieldValue } : FormInstance < PresetFormValues > ) => {
1310+ const modelFolderId = getFieldValue ( 'modelFolderId' ) ;
1311+ return (
1312+ < Space . Compact >
1313+ < Tooltip title = { t ( 'modelService.OpenFolder' ) } >
1314+ < Button
1315+ icon = { < FolderOpenIcon /> }
1316+ disabled = { ! modelFolderId }
1317+ onClick = { ( ) => {
1318+ if ( modelFolderId ) {
1319+ openFolderExplorer ( toLocalId ( modelFolderId ) ) ;
1320+ }
1321+ } }
1322+ />
1323+ </ Tooltip >
1324+ < Tooltip title = { t ( 'data.CreateANewStorageFolder' ) } >
1325+ < Button
1326+ icon = { < PlusIcon /> }
1327+ onClick = { ( ) =>
1328+ setIsModelFolderCreateModalOpen ( true )
1329+ }
1330+ />
1331+ </ Tooltip >
1332+ < Tooltip title = { t ( 'button.Refresh' ) } >
1333+ < Button
1334+ icon = { < ReloadOutlined /> }
1335+ onClick = { ( ) => {
1336+ startTransition ( ( ) => {
1337+ presetVFolderSelectRef . current ?. refetch ( ) ;
1338+ } ) ;
1339+ } }
1340+ />
1341+ </ Tooltip >
1342+ </ Space . Compact >
1343+ ) ;
1344+ } }
1345+ </ Form . Item >
1346+ </ BAIFlex >
12871347 </ Form . Item >
12881348 </ Form >
12891349 )
@@ -1329,18 +1389,62 @@ const DeploymentAddRevisionModal: React.FC<DeploymentAddRevisionModalProps> = ({
13291389
13301390 < SectionHeader > { t ( 'deployment.step.ModelAndRuntime' ) } </ SectionHeader >
13311391 < Form . Item
1332- name = "modelFolderId"
13331392 label = { t ( 'deployment.ModelFolder' ) }
13341393 tooltip = { t ( 'deployment.ModelFolderTooltip' ) }
1335- rules = { [ { required : true } ] }
1394+ required
13361395 >
1337- < BAIVFolderSelect
1338- currentProjectId = { currentProjectId ?? undefined }
1339- disabled = { ! currentProjectId }
1340- excludeDeleted
1341- filter = 'usage_mode == "model"'
1342- style = { { width : '100%' } }
1343- />
1396+ < BAIFlex direction = "row" gap = "xs" >
1397+ < Form . Item
1398+ name = "modelFolderId"
1399+ noStyle
1400+ rules = { [ { required : true } ] }
1401+ >
1402+ < BAIVFolderSelect
1403+ ref = { customVFolderSelectRef }
1404+ currentProjectId = { currentProjectId ?? undefined }
1405+ disabled = { ! currentProjectId }
1406+ excludeDeleted
1407+ filter = 'usage_mode == "model"'
1408+ style = { { flex : 1 } }
1409+ />
1410+ </ Form . Item >
1411+ < Form . Item dependencies = { [ 'modelFolderId' ] } noStyle >
1412+ { ( { getFieldValue } : FormInstance < FormValues > ) => {
1413+ const modelFolderId = getFieldValue ( 'modelFolderId' ) ;
1414+ return (
1415+ < Space . Compact >
1416+ < Tooltip title = { t ( 'modelService.OpenFolder' ) } >
1417+ < Button
1418+ icon = { < FolderOpenIcon /> }
1419+ disabled = { ! modelFolderId }
1420+ onClick = { ( ) => {
1421+ if ( modelFolderId ) {
1422+ openFolderExplorer ( toLocalId ( modelFolderId ) ) ;
1423+ }
1424+ } }
1425+ />
1426+ </ Tooltip >
1427+ < Tooltip title = { t ( 'data.CreateANewStorageFolder' ) } >
1428+ < Button
1429+ icon = { < PlusIcon /> }
1430+ onClick = { ( ) => setIsModelFolderCreateModalOpen ( true ) }
1431+ />
1432+ </ Tooltip >
1433+ < Tooltip title = { t ( 'button.Refresh' ) } >
1434+ < Button
1435+ icon = { < ReloadOutlined /> }
1436+ onClick = { ( ) => {
1437+ startTransition ( ( ) => {
1438+ customVFolderSelectRef . current ?. refetch ( ) ;
1439+ } ) ;
1440+ } }
1441+ />
1442+ </ Tooltip >
1443+ </ Space . Compact >
1444+ ) ;
1445+ } }
1446+ </ Form . Item >
1447+ </ BAIFlex >
13441448 </ Form . Item >
13451449 < Form . Item
13461450 name = "runtimeVariantId"
@@ -1630,6 +1734,38 @@ const DeploymentAddRevisionModal: React.FC<DeploymentAddRevisionModalProps> = ({
16301734 />
16311735 </ Suspense >
16321736 ) }
1737+ < FolderCreateModalV2
1738+ open = { isModelFolderCreateModalOpen }
1739+ initialValues = { { usage_mode : 'model' } }
1740+ onRequestClose = { ( result ) => {
1741+ setIsModelFolderCreateModalOpen ( false ) ;
1742+ if ( result ?. id ) {
1743+ // `createVfolderV2` returns a `VFolder` (Strawberry) global ID,
1744+ // but BAIVFolderSelect's value query reads from `vfolder_nodes`
1745+ // (`VirtualFolderNode`, Graphene). Both encode the same UUID
1746+ // but with different `__typename:` prefixes, so the select's
1747+ // option matching (`edge.node.id === value`) would fail if we
1748+ // set the raw mutation id directly. Re-encode to the
1749+ // VirtualFolderNode global ID form.
1750+ const newFolderUuid = safeDecodeUuid ( result . id ) ;
1751+ if ( ! newFolderUuid ) return ;
1752+ const newFolderGlobalId = toGlobalId (
1753+ 'VirtualFolderNode' ,
1754+ newFolderUuid ,
1755+ ) ;
1756+ const activeForm =
1757+ effectiveMode === 'preset' ? presetForm : customForm ;
1758+ const activeRef =
1759+ effectiveMode === 'preset'
1760+ ? presetVFolderSelectRef
1761+ : customVFolderSelectRef ;
1762+ activeForm . setFieldValue ( 'modelFolderId' , newFolderGlobalId ) ;
1763+ startTransition ( ( ) => {
1764+ activeRef . current ?. refetch ( ) ;
1765+ } ) ;
1766+ }
1767+ } }
1768+ />
16331769 </ BAIModal >
16341770 ) ;
16351771} ;
0 commit comments