Skip to content

Commit c68eddb

Browse files
committed
feat(FR-2801): pre-populate deployment launcher form fields from deployment preset (#7224)
Resolves #7223 ([FR-2801](https://lablup.atlassian.net/browse/FR-2801)) ## Summary - Add `launcherFormValues` field to `QuickDeployInput` in `useDeploymentLauncher`, serialized to the `formValues` URL parameter when navigating to the launcher - Extend the GraphQL fragment/query in `ModelCardDrawer` and `VFolderDeployModal` to fetch `runtimeVariant`, `execution`, `cluster`, and `deploymentDefaults` fields from deployment presets - Pass all preset values (startup command, runtime variant, cluster mode, cluster size, replica count, open-to-public) via `launcherFormValues` when the user clicks "Configure and Deploy" from either entry point [FR-2801]: https://lablup.atlassian.net/browse/FR-2801?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
1 parent 923fa09 commit c68eddb

3 files changed

Lines changed: 154 additions & 4 deletions

File tree

react/src/components/ModelCardDrawer.tsx

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,21 @@ const ModelCardDrawer: React.FC<ModelCardDrawerProps> = ({
102102
description
103103
rank
104104
runtimeVariantId
105+
runtimeVariant {
106+
name
107+
}
108+
execution {
109+
imageId
110+
startupCommand
111+
}
112+
cluster {
113+
clusterMode
114+
clusterSize
115+
}
116+
deploymentDefaults {
117+
openToPublic
118+
replicaCount
119+
}
105120
}
106121
}
107122
}
@@ -181,12 +196,41 @@ const ModelCardDrawer: React.FC<ModelCardDrawerProps> = ({
181196
{
182197
key: 'configure',
183198
label: t('modelStore.QuickDeployDetailed'),
199+
// TODO(FR-2762): Always uses the top-ranked preset
200+
// (presets[0]). If a model card has multiple presets
201+
// with different runtime variants, the launcher is
202+
// always pre-filled with the first one (e.g. vllm).
203+
// Consider letting the user pick a preset when
204+
// multiple are available.
184205
onClick: () => {
185206
const modelFolderId = toLocalId(
186207
modelCard.vfolder?.id ?? '',
187208
);
188209
if (!modelFolderId) return;
189-
openLauncher({ modelFolderId });
210+
openLauncher({
211+
modelFolderId,
212+
launcherFormValues: {
213+
imageId:
214+
presets[0]?.execution?.imageId ?? undefined,
215+
startCommand:
216+
presets[0]?.execution?.startupCommand ??
217+
undefined,
218+
runtimeVariant:
219+
presets[0]?.runtimeVariant?.name ?? undefined,
220+
runtimeVariantId:
221+
presets[0]?.runtimeVariantId ?? undefined,
222+
clusterMode:
223+
presets[0]?.cluster?.clusterMode ?? undefined,
224+
clusterSize:
225+
presets[0]?.cluster?.clusterSize ?? undefined,
226+
desiredReplicaCount:
227+
presets[0]?.deploymentDefaults?.replicaCount ??
228+
undefined,
229+
openToPublic:
230+
presets[0]?.deploymentDefaults?.openToPublic ??
231+
undefined,
232+
},
233+
});
190234
},
191235
},
192236
],

react/src/components/VFolderDeployModal.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,21 @@ const VFolderDeployModalContent: React.FC<VFolderDeployModalContentProps> = ({
134134
description
135135
rank
136136
runtimeVariantId
137+
runtimeVariant {
138+
name
139+
}
140+
execution {
141+
imageId
142+
startupCommand
143+
}
144+
cluster {
145+
clusterMode
146+
clusterSize
147+
}
148+
deploymentDefaults {
149+
openToPublic
150+
replicaCount
151+
}
137152
...DeploymentPresetDetailContentFragment
138153
}
139154
}
@@ -468,10 +483,34 @@ const VFolderDeployModalContent: React.FC<VFolderDeployModalContentProps> = ({
468483
key: 'configure',
469484
label: t('modelStore.QuickDeployDetailed'),
470485
onClick: () => {
486+
const selectedPreset = availablePresets.find(
487+
(p) => toLocalId(p.id) === effectivePresetId,
488+
);
471489
openLauncher({
472490
modelFolderId: vfolderId,
473491
resourceGroup: effectiveResourceGroup,
474492
revisionPresetId: effectivePresetId,
493+
launcherFormValues: {
494+
imageId:
495+
selectedPreset?.execution?.imageId ?? undefined,
496+
startCommand:
497+
selectedPreset?.execution?.startupCommand ??
498+
undefined,
499+
runtimeVariant:
500+
selectedPreset?.runtimeVariant?.name ?? undefined,
501+
runtimeVariantId:
502+
selectedPreset?.runtimeVariantId ?? undefined,
503+
clusterMode:
504+
selectedPreset?.cluster?.clusterMode ?? undefined,
505+
clusterSize:
506+
selectedPreset?.cluster?.clusterSize ?? undefined,
507+
desiredReplicaCount:
508+
selectedPreset?.deploymentDefaults?.replicaCount ??
509+
undefined,
510+
openToPublic:
511+
selectedPreset?.deploymentDefaults?.openToPublic ??
512+
undefined,
513+
},
475514
});
476515
},
477516
},

react/src/hooks/useDeploymentLauncher.ts

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
44
*/
55
import { useDeploymentLauncherDeployVFolderMutation } from '../__generated__/useDeploymentLauncherDeployVFolderMutation.graphql';
6+
import { useDeploymentLauncherImageCanonicalNameQuery } from '../__generated__/useDeploymentLauncherImageCanonicalNameQuery.graphql';
67
import { useSuspendedBackendaiClient, useWebUINavigate } from '../hooks';
78
import { useSetBAINotification } from '../hooks/useBAINotification';
89
import {
@@ -12,7 +13,12 @@ import {
1213
import { useBAILogger } from 'backend.ai-ui';
1314
import { useState } from 'react';
1415
import { useTranslation } from 'react-i18next';
15-
import { graphql, useMutation } from 'react-relay';
16+
import {
17+
fetchQuery,
18+
graphql,
19+
useMutation,
20+
useRelayEnvironment,
21+
} from 'react-relay';
1622

1723
export interface QuickDeployInput {
1824
/** Virtual folder (model folder) id that backs the deployment. */
@@ -25,6 +31,22 @@ export interface QuickDeployInput {
2531
replicas?: number;
2632
/** Public endpoint toggle (default: false). */
2733
openToPublic?: boolean;
34+
/** Pre-populate form fields in the launcher from a preset (create mode only). */
35+
launcherFormValues?: {
36+
startCommand?: string;
37+
runtimeVariant?: string;
38+
runtimeVariantId?: string;
39+
clusterMode?: string;
40+
clusterSize?: number;
41+
desiredReplicaCount?: number;
42+
openToPublic?: boolean;
43+
/**
44+
* Image UUID from the preset's execution spec. Resolved to the canonical
45+
* name string (registry/ns:tag@arch) before being serialized into
46+
* `environments.version` in the URL — the launcher's form field name.
47+
*/
48+
imageId?: string;
49+
};
2850
}
2951

3052
export interface DeployInstantlyResult {
@@ -42,9 +64,19 @@ export interface DeployInstantlyResult {
4264
* - `openLauncher`: navigates to `/deployments/start?model=<folderId>` so the
4365
* user can configure a deployment in the full launcher UI.
4466
*/
67+
const imageCanonicalNameQuery = graphql`
68+
query useDeploymentLauncherImageCanonicalNameQuery($id: ID!) {
69+
imageV2(id: $id) {
70+
identity {
71+
canonicalName
72+
}
73+
}
74+
}
75+
`;
76+
4577
export const useDeploymentLauncher = (): {
4678
deployInstantly: (input: QuickDeployInput) => Promise<DeployInstantlyResult>;
47-
openLauncher: (input: QuickDeployInput) => void;
79+
openLauncher: (input: QuickDeployInput) => Promise<void>;
4880
isDeploying: boolean;
4981
supportsQuickDeploy: boolean;
5082
} => {
@@ -58,6 +90,8 @@ export const useDeploymentLauncher = (): {
5890
const { upsertNotification } = useSetBAINotification();
5991
const { logger } = useBAILogger();
6092

93+
const relayEnvironment = useRelayEnvironment();
94+
6195
const [isDeploying, setIsDeploying] = useState<boolean>(false);
6296

6397
const [commitDeployVFolder] =
@@ -185,11 +219,44 @@ export const useDeploymentLauncher = (): {
185219
});
186220
};
187221

188-
const openLauncher = (input: QuickDeployInput): void => {
222+
const openLauncher = async (input: QuickDeployInput): Promise<void> => {
189223
const params = new URLSearchParams({ model: input.modelFolderId });
190224
if (input.resourceGroup) params.set('resourceGroup', input.resourceGroup);
191225
if (input.revisionPresetId)
192226
params.set('revisionPresetId', input.revisionPresetId);
227+
228+
if (
229+
input.launcherFormValues &&
230+
Object.keys(input.launcherFormValues).length > 0
231+
) {
232+
const { imageId, ...restFormValues } = input.launcherFormValues;
233+
const urlFormValues: Record<string, unknown> = { ...restFormValues };
234+
235+
if (imageId) {
236+
try {
237+
const result =
238+
await fetchQuery<useDeploymentLauncherImageCanonicalNameQuery>(
239+
relayEnvironment,
240+
imageCanonicalNameQuery,
241+
{ id: imageId },
242+
{ fetchPolicy: 'store-or-network' },
243+
).toPromise();
244+
const canonicalName = result?.imageV2?.identity?.canonicalName;
245+
if (canonicalName) {
246+
urlFormValues.environments = { version: canonicalName };
247+
}
248+
} catch (e) {
249+
logger.warn(
250+
'[useDeploymentLauncher] Failed to resolve imageId to canonical name',
251+
e,
252+
);
253+
}
254+
}
255+
256+
if (Object.keys(urlFormValues).length > 0)
257+
params.set('formValues', JSON.stringify(urlFormValues));
258+
}
259+
193260
navigate(`/deployments/start?${params.toString()}`);
194261
};
195262

0 commit comments

Comments
 (0)