@@ -82,6 +82,8 @@ const getNodeColor = (type: FEAST_FCO_TYPES) => {
8282 return "#cc0000" ; // Red
8383 case FEAST_FCO_TYPES . mlflowRun :
8484 return "#0194e2" ; // MLflow brand blue
85+ case FEAST_FCO_TYPES . mlflowModel :
86+ return "#7b2d8e" ; // Purple
8587 default :
8688 return "#666666" ; // Gray
8789 }
@@ -99,6 +101,8 @@ const getLightNodeColor = (type: FEAST_FCO_TYPES) => {
99101 return "#ffe6e6" ; // Light red
100102 case FEAST_FCO_TYPES . mlflowRun :
101103 return "#e6f6fd" ; // Light MLflow blue
104+ case FEAST_FCO_TYPES . mlflowModel :
105+ return "#f3e6f9" ; // Light purple
102106 default :
103107 return "#f0f0f0" ; // Light gray
104108 }
@@ -116,6 +120,8 @@ const getNodeIcon = (type: FEAST_FCO_TYPES) => {
116120 return "◆" ; // Diamond for data source
117121 case FEAST_FCO_TYPES . mlflowRun :
118122 return "⬡" ; // Hexagon for MLflow run
123+ case FEAST_FCO_TYPES . mlflowModel :
124+ return "⬢" ; // Filled hexagon for registered model
119125 default :
120126 return "●" ; // Default circle
121127 }
@@ -132,7 +138,11 @@ const CustomNode = ({ data }: { data: NodeData }) => {
132138 const hasVersion = data . versionNumber != null && data . versionNumber > 1 ;
133139
134140 const handleClick = ( ) => {
135- if ( data . type === FEAST_FCO_TYPES . mlflowRun && data . metadata ?. mlflow_url ) {
141+ if (
142+ ( data . type === FEAST_FCO_TYPES . mlflowRun ||
143+ data . type === FEAST_FCO_TYPES . mlflowModel ) &&
144+ data . metadata ?. mlflow_url
145+ ) {
136146 window . open ( data . metadata . mlflow_url , "_blank" , "noopener,noreferrer" ) ;
137147 return ;
138148 }
@@ -194,7 +204,8 @@ const CustomNode = ({ data }: { data: NodeData }) => {
194204 zIndex : 5 ,
195205 } }
196206 >
197- { data . type === FEAST_FCO_TYPES . mlflowRun
207+ { data . type === FEAST_FCO_TYPES . mlflowRun ||
208+ data . type === FEAST_FCO_TYPES . mlflowModel
198209 ? "Open in MLflow ↗"
199210 : "View Details" }
200211 </ div >
@@ -412,6 +423,7 @@ const getLayoutedElements = (
412423 [ FEAST_FCO_TYPES . featureView ] : [ ] ,
413424 [ FEAST_FCO_TYPES . featureService ] : [ ] ,
414425 [ FEAST_FCO_TYPES . mlflowRun ] : [ ] ,
426+ [ FEAST_FCO_TYPES . mlflowModel ] : [ ] ,
415427 } ;
416428
417429 isolatedNodes . forEach ( ( node ) => {
@@ -469,6 +481,7 @@ const Legend = () => {
469481 { type : FEAST_FCO_TYPES . entity , label : "Entity" } ,
470482 { type : FEAST_FCO_TYPES . dataSource , label : "Data Source" } ,
471483 { type : FEAST_FCO_TYPES . mlflowRun , label : "MLflow Run" } ,
484+ { type : FEAST_FCO_TYPES . mlflowModel , label : "Registered Model" } ,
472485 ] ;
473486
474487 const isDarkMode = colorMode === "dark" ;
@@ -805,6 +818,52 @@ const registryToFlow = (
805818 } ) ;
806819 }
807820 }
821+
822+ if ( run . registered_models && run . registered_models . length > 0 ) {
823+ run . registered_models . forEach ( ( model ) => {
824+ const modelNodeId = `model-${ model . model_name } -v${ model . version } ` ;
825+ const modelExists = nodes . some ( ( n ) => n . id === modelNodeId ) ;
826+ if ( ! modelExists ) {
827+ nodes . push ( {
828+ id : modelNodeId ,
829+ type : "custom" ,
830+ data : {
831+ label : `${ model . model_name } v${ model . version } ` ,
832+ type : FEAST_FCO_TYPES . mlflowModel ,
833+ metadata : {
834+ mlflow_url : model . mlflow_url ,
835+ model_name : model . model_name ,
836+ version : model . version ,
837+ stage : model . stage ,
838+ } ,
839+ } ,
840+ position : { x : 0 , y : 0 } ,
841+ } ) ;
842+ }
843+
844+ edges . push ( {
845+ id : `edge-model-${ run . run_id } -${ model . model_name } -v${ model . version } ` ,
846+ source : `mlflow-${ run . run_id } ` ,
847+ sourceHandle : "source" ,
848+ target : modelNodeId ,
849+ targetHandle : "target" ,
850+ animated : true ,
851+ style : {
852+ strokeWidth : 3 ,
853+ stroke : "#7b2d8e" ,
854+ strokeDasharray : "10 5" ,
855+ animation : "dataflow 2s linear infinite" ,
856+ } ,
857+ type : "smoothstep" ,
858+ markerEnd : {
859+ type : MarkerType . ArrowClosed ,
860+ width : 20 ,
861+ height : 20 ,
862+ color : "#7b2d8e" ,
863+ } ,
864+ } ) ;
865+ } ) ;
866+ }
808867 } ) ;
809868 }
810869
@@ -823,6 +882,8 @@ const getNodePrefix = (type: FEAST_FCO_TYPES) => {
823882 return "ds" ;
824883 case FEAST_FCO_TYPES . mlflowRun :
825884 return "mlflow" ;
885+ case FEAST_FCO_TYPES . mlflowModel :
886+ return "model" ;
826887 default :
827888 return "unknown" ;
828889 }
0 commit comments