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