fix(lms-web): load modules from platform environment profiles
This commit is contained in:
@@ -84,6 +84,11 @@ type IdeActivity = WorkspaceActivity
|
||||
|
||||
const PUBLIC_GUIDE_COURSE_SLUG = 'autonomy-studio-lms-guide'
|
||||
|
||||
function isRemoteEnvironmentProfile(profile: EnvironmentProfile): boolean {
|
||||
const target = profile.config?.target
|
||||
return target === 'remote-jetson' || target === 'platform'
|
||||
}
|
||||
|
||||
function resolveModuleRuntimeImages(
|
||||
profiles: EnvironmentProfile[],
|
||||
remoteWorkspace: boolean,
|
||||
@@ -92,7 +97,7 @@ function resolveModuleRuntimeImages(
|
||||
if (!remoteWorkspace) return activeRuntimeImages
|
||||
|
||||
const images = profiles
|
||||
.filter((profile) => ['real_slam', 'real_slam_3d', 'real_navigation'].includes(profile.profileType.toLowerCase()))
|
||||
.filter(isRemoteEnvironmentProfile)
|
||||
.flatMap((profile) => Array.isArray(profile.config.runtimeImages) ? profile.config.runtimeImages : [])
|
||||
.filter((image): image is RuntimeImageArtifact => (
|
||||
typeof image === 'object'
|
||||
@@ -740,7 +745,7 @@ function LocalPcWorkspaceMockup({
|
||||
const runtimeModules = useMemo(() => resolveRuntimeModules(
|
||||
curriculumWeek.modules,
|
||||
remoteWorkspace
|
||||
? visualizationProfiles.filter((profile) => ['real_slam', 'real_slam_3d', 'real_navigation'].includes(profile.profileType.toLowerCase()))
|
||||
? visualizationProfiles.filter(isRemoteEnvironmentProfile)
|
||||
: activeEnvironmentProfile,
|
||||
remoteWorkspace ? 'platform' : 'local'
|
||||
), [activeEnvironmentProfile, curriculumWeek.modules, remoteWorkspace, visualizationProfiles])
|
||||
|
||||
@@ -50,7 +50,9 @@ export function useWorkspaceModuleList({
|
||||
const [modulesLoading, setModulesLoading] = useState(false)
|
||||
const [moduleListError, setModuleListError] = useState<string | null>(null)
|
||||
const remoteTarget = useMemo(() => parseRemoteWorkspaceTarget(remoteWorkspaceTargetKey), [remoteWorkspaceTargetKey])
|
||||
const moduleInputsRef = useRef({ curriculumModules, applyModuleParameterOverrides })
|
||||
const loadSequenceRef = useRef(0)
|
||||
const moduleLoadInFlightRef = useRef(false)
|
||||
const pendingModuleStatesRef = useRef(new Map<string, { state: ModuleRow['state']; expiresAt: number }>())
|
||||
|
||||
const moduleStateKey = (module: ModuleRow): string => module.folderName ?? module.name
|
||||
@@ -89,9 +91,13 @@ export function useWorkspaceModuleList({
|
||||
}))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
moduleInputsRef.current = { curriculumModules, applyModuleParameterOverrides }
|
||||
}, [applyModuleParameterOverrides, curriculumModuleKey, curriculumModules])
|
||||
|
||||
const loadRemoteModules = useCallback(async (): Promise<{ modules: ModuleRow[] }> => {
|
||||
if (!remoteTarget) return { modules: [] }
|
||||
const result = await window.remoteWorkspace.modules.list(remoteTarget, curriculumModules ?? [])
|
||||
if (!remoteTarget) throw new Error('원격 플랫폼 SSH 주소를 확인하지 못했습니다.')
|
||||
const result = await window.remoteWorkspace.modules.list(remoteTarget, moduleInputsRef.current.curriculumModules ?? [])
|
||||
return {
|
||||
modules: result.modules.map((module) => {
|
||||
const rawModule = module as ModuleRow & { defaultParameters?: unknown }
|
||||
@@ -106,38 +112,42 @@ export function useWorkspaceModuleList({
|
||||
}, [curriculumModuleKey, remoteTarget])
|
||||
|
||||
const loadAvailableModules = useCallback(async ({ silent = false }: LoadAvailableModulesOptions = {}): Promise<void> => {
|
||||
// Remote inventory probes open SSH sessions. Keep one request in flight so
|
||||
// polling cannot continuously replace a slow response with a newer one.
|
||||
if (moduleLoadInFlightRef.current) return
|
||||
moduleLoadInFlightRef.current = true
|
||||
const requestSequence = ++loadSequenceRef.current
|
||||
if (!silent) setModulesLoading(true)
|
||||
try {
|
||||
const { curriculumModules: currentCurriculumModules, applyModuleParameterOverrides: applyOverrides } = moduleInputsRef.current
|
||||
if (remoteWorkspace) {
|
||||
const result = await loadRemoteModules()
|
||||
if (requestSequence !== loadSequenceRef.current) return
|
||||
setModuleListError(null)
|
||||
setAvailableModules(applyPendingModuleStates(applyModuleParameterOverrides(result.modules).sort(compareModulePriority)))
|
||||
setAvailableModules(applyPendingModuleStates(applyOverrides(result.modules).sort(compareModulePriority)))
|
||||
return
|
||||
}
|
||||
|
||||
const result = await window.localWorkspace.modules.list(clonePathPreview, curriculumModules ?? [])
|
||||
const result = await window.localWorkspace.modules.list(clonePathPreview, currentCurriculumModules ?? [])
|
||||
if (requestSequence !== loadSequenceRef.current) return
|
||||
setModuleListError(null)
|
||||
setAvailableModules(applyPendingModuleStates(applyModuleParameterOverrides(result.modules).sort(compareModulePriority)))
|
||||
setAvailableModules(applyPendingModuleStates(applyOverrides(result.modules).sort(compareModulePriority)))
|
||||
} catch (error) {
|
||||
if (requestSequence !== loadSequenceRef.current) return
|
||||
console.error('[modules] list failed', error)
|
||||
const message = error instanceof Error ? error.message : '모듈 목록을 불러오지 못했습니다.'
|
||||
if (!silent) {
|
||||
setModuleListError(message)
|
||||
setAvailableModules(makeUninstalledModuleRows(curriculumModules))
|
||||
setAvailableModules(makeUninstalledModuleRows(moduleInputsRef.current.curriculumModules))
|
||||
}
|
||||
} finally {
|
||||
moduleLoadInFlightRef.current = false
|
||||
if (!silent && requestSequence === loadSequenceRef.current) setModulesLoading(false)
|
||||
}
|
||||
}, [
|
||||
applyModuleParameterOverrides,
|
||||
applyPendingModuleStates,
|
||||
clonePathPreview,
|
||||
curriculumModuleKey,
|
||||
curriculumModules,
|
||||
loadRemoteModules,
|
||||
remoteWorkspace
|
||||
])
|
||||
@@ -151,10 +161,10 @@ export function useWorkspaceModuleList({
|
||||
|
||||
const intervalId = window.setInterval(() => {
|
||||
void loadAvailableModules({ silent: true })
|
||||
}, 5000)
|
||||
}, remoteWorkspace ? 10_000 : 5_000)
|
||||
|
||||
return () => window.clearInterval(intervalId)
|
||||
}, [loadAvailableModules, pollingActive])
|
||||
}, [loadAvailableModules, pollingActive, remoteWorkspace])
|
||||
|
||||
return {
|
||||
availableModules,
|
||||
|
||||
@@ -36,14 +36,14 @@ function resolveNav2Mode(profile: EnvironmentProfile | null): Nav2VisualizationM
|
||||
return profile?.profileType.toLowerCase() === 'real_navigation' ? 'navigation' : 'slam'
|
||||
}
|
||||
|
||||
function isRealPlatformProfile(profile: EnvironmentProfile | null): boolean {
|
||||
const type = profile?.profileType.toLowerCase()
|
||||
return type === 'real_slam' || type === 'real_navigation'
|
||||
function isRemoteEnvironmentProfile(profile: EnvironmentProfile | null): boolean {
|
||||
const target = profile?.config?.target
|
||||
return target === 'remote-jetson' || target === 'platform'
|
||||
}
|
||||
|
||||
function selectRuntimeProfiles(profiles: EnvironmentProfile[], remoteWorkspace: boolean): EnvironmentProfile[] {
|
||||
const matchingProfiles = profiles.filter((profile) => (
|
||||
remoteWorkspace ? isRealPlatformProfile(profile) : !isRealPlatformProfile(profile)
|
||||
remoteWorkspace ? isRemoteEnvironmentProfile(profile) : !isRemoteEnvironmentProfile(profile)
|
||||
))
|
||||
return matchingProfiles.length ? matchingProfiles : profiles
|
||||
}
|
||||
@@ -121,7 +121,7 @@ export function useWorkspaceVisualization({
|
||||
const activeEnvironmentAgentUrl = typeof activeEnvironmentAgent?.url === 'string'
|
||||
? activeEnvironmentAgent.url.replace(/\/+$/, '')
|
||||
: ''
|
||||
const visualizationRemote = activeEnvironmentConfig.target === 'remote-jetson' && activeEnvironmentAgentUrl
|
||||
const visualizationRemote = isRemoteEnvironmentProfile(activeEnvironmentProfile) && activeEnvironmentAgentUrl
|
||||
const visualizationRemoteBridgeHttpUrl = (() => {
|
||||
if (!visualizationRemote) return ''
|
||||
try {
|
||||
@@ -138,7 +138,7 @@ export function useWorkspaceVisualization({
|
||||
: 'ws://localhost:8765'
|
||||
const visualizationBackendUrl = visualizationBridgeHttpUrl
|
||||
const visualizationViewerUrl = visualizationStack === 'slam' || visualizationStack === 'nav2'
|
||||
? `/slam-viewer/index.html?directBridge=1&mode=${nav2VisualizationMode}&runtime=${isRealPlatformProfile(activeEnvironmentProfile) ? 'real' : 'simulation'}&bridgeWs=${encodeURIComponent(`${visualizationBridgeUrl}/ws/state`)}&bridgeHttp=${encodeURIComponent(visualizationBridgeHttpUrl)}&mapKey=${encodeURIComponent(mapKey ?? 'nav2-turtlebot3-world')}&layoutVersion=${worldLayoutVersion ?? 0}`
|
||||
? `/slam-viewer/index.html?directBridge=1&mode=${nav2VisualizationMode}&runtime=${isRemoteEnvironmentProfile(activeEnvironmentProfile) ? 'real' : 'simulation'}&bridgeWs=${encodeURIComponent(`${visualizationBridgeUrl}/ws/state`)}&bridgeHttp=${encodeURIComponent(visualizationBridgeHttpUrl)}&mapKey=${encodeURIComponent(mapKey ?? 'nav2-turtlebot3-world')}&layoutVersion=${worldLayoutVersion ?? 0}`
|
||||
: `/autoware-viewer/index.html?source=ros&stack=${visualizationStack}&bridge=${encodeURIComponent(visualizationBridgeUrl)}&backend=${encodeURIComponent(visualizationBackendUrl)}`
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user