jupyterlab-mlflow 0.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of jupyterlab-mlflow might be problematic. Click here for more details.
- jupyterlab_mlflow/__init__.py +63 -0
- jupyterlab_mlflow/_version.py +6 -0
- jupyterlab_mlflow/post_install.py +44 -0
- jupyterlab_mlflow/schema/plugin.json +17 -0
- jupyterlab_mlflow/serverextension/__init__.py +69 -0
- jupyterlab_mlflow/serverextension/handlers.py +570 -0
- jupyterlab_mlflow/serverextension/mlflow_server.py +214 -0
- jupyterlab_mlflow-0.4.1.data/data/etc/jupyter/jupyter_server_config.d/jupyterlab_mlflow.json +8 -0
- jupyterlab_mlflow-0.4.1.data/data/share/jupyter/labextensions/jupyterlab-mlflow/install.json +12 -0
- jupyterlab_mlflow-0.4.1.data/data/share/jupyter/labextensions/jupyterlab-mlflow/package.json +103 -0
- jupyterlab_mlflow-0.4.1.data/data/share/jupyter/labextensions/jupyterlab-mlflow/schema/plugin.json +17 -0
- jupyterlab_mlflow-0.4.1.data/data/share/jupyter/labextensions/jupyterlab-mlflow/schemas/jupyterlab-mlflow/package.json.orig +98 -0
- jupyterlab_mlflow-0.4.1.data/data/share/jupyter/labextensions/jupyterlab-mlflow/schemas/jupyterlab-mlflow/plugin.json +17 -0
- jupyterlab_mlflow-0.4.1.data/data/share/jupyter/labextensions/jupyterlab-mlflow/static/218.47b1285b67dde3db8969.js +1 -0
- jupyterlab_mlflow-0.4.1.data/data/share/jupyter/labextensions/jupyterlab-mlflow/static/665.f3ea36ea04224fd9c2f3.js +1 -0
- jupyterlab_mlflow-0.4.1.data/data/share/jupyter/labextensions/jupyterlab-mlflow/static/remoteEntry.121dc9414dda869fb1a6.js +1 -0
- jupyterlab_mlflow-0.4.1.data/data/share/jupyter/labextensions/jupyterlab-mlflow/static/style.js +4 -0
- jupyterlab_mlflow-0.4.1.data/data/share/jupyter/labextensions/jupyterlab-mlflow/static/third-party-licenses.json +16 -0
- jupyterlab_mlflow-0.4.1.dist-info/METADATA +273 -0
- jupyterlab_mlflow-0.4.1.dist-info/RECORD +23 -0
- jupyterlab_mlflow-0.4.1.dist-info/WHEEL +4 -0
- jupyterlab_mlflow-0.4.1.dist-info/entry_points.txt +2 -0
- jupyterlab_mlflow-0.4.1.dist-info/licenses/LICENSE +30 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MLflow local server management
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional, Dict, Any
|
|
11
|
+
import tempfile
|
|
12
|
+
|
|
13
|
+
# Global state for MLflow server process
|
|
14
|
+
_mlflow_process: Optional[subprocess.Popen] = None
|
|
15
|
+
_mlflow_port: int = 5000
|
|
16
|
+
_mlflow_tracking_uri: str = "sqlite:///mlflow.db"
|
|
17
|
+
_mlflow_artifact_uri: Optional[str] = None
|
|
18
|
+
_mlflow_backend_uri: Optional[str] = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def start_mlflow_server(
|
|
22
|
+
port: int = 5000,
|
|
23
|
+
tracking_uri: str = "sqlite:///mlflow.db",
|
|
24
|
+
artifact_uri: Optional[str] = None,
|
|
25
|
+
backend_uri: Optional[str] = None
|
|
26
|
+
) -> Dict[str, Any]:
|
|
27
|
+
"""
|
|
28
|
+
Start a local MLflow server.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
port : int
|
|
33
|
+
Port to run MLflow UI on (default: 5000)
|
|
34
|
+
tracking_uri : str
|
|
35
|
+
Tracking URI (default: sqlite:///mlflow.db)
|
|
36
|
+
artifact_uri : str, optional
|
|
37
|
+
Artifact URI (default: None, uses default)
|
|
38
|
+
backend_uri : str, optional
|
|
39
|
+
Backend store URI (default: None)
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
dict
|
|
44
|
+
Status information including server URL and process info
|
|
45
|
+
"""
|
|
46
|
+
global _mlflow_process, _mlflow_port, _mlflow_tracking_uri, _mlflow_artifact_uri, _mlflow_backend_uri
|
|
47
|
+
|
|
48
|
+
if _mlflow_process is not None:
|
|
49
|
+
# Check if process is still running
|
|
50
|
+
if _mlflow_process.poll() is None:
|
|
51
|
+
return {
|
|
52
|
+
"success": True,
|
|
53
|
+
"running": True,
|
|
54
|
+
"port": _mlflow_port,
|
|
55
|
+
"url": f"http://localhost:{_mlflow_port}",
|
|
56
|
+
"message": "MLflow server is already running"
|
|
57
|
+
}
|
|
58
|
+
else:
|
|
59
|
+
# Process died, clean up
|
|
60
|
+
_mlflow_process = None
|
|
61
|
+
|
|
62
|
+
# Prepare command
|
|
63
|
+
cmd = ["mlflow", "ui", "--port", str(port), "--host", "127.0.0.1"]
|
|
64
|
+
|
|
65
|
+
# Set tracking URI via environment variable
|
|
66
|
+
env = os.environ.copy()
|
|
67
|
+
env["MLFLOW_TRACKING_URI"] = tracking_uri
|
|
68
|
+
|
|
69
|
+
# Set artifact URI if provided
|
|
70
|
+
if artifact_uri:
|
|
71
|
+
env["MLFLOW_ARTIFACT_ROOT"] = artifact_uri
|
|
72
|
+
|
|
73
|
+
# Set backend URI if provided
|
|
74
|
+
if backend_uri:
|
|
75
|
+
env["MLFLOW_BACKEND_STORE_URI"] = backend_uri
|
|
76
|
+
|
|
77
|
+
# Create artifact directory if it doesn't exist
|
|
78
|
+
if artifact_uri:
|
|
79
|
+
artifact_path = Path(artifact_uri)
|
|
80
|
+
if not artifact_path.exists():
|
|
81
|
+
artifact_path.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
# Start MLflow server
|
|
85
|
+
_mlflow_process = subprocess.Popen(
|
|
86
|
+
cmd,
|
|
87
|
+
env=env,
|
|
88
|
+
stdout=subprocess.PIPE,
|
|
89
|
+
stderr=subprocess.PIPE,
|
|
90
|
+
start_new_session=True
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Store configuration
|
|
94
|
+
_mlflow_port = port
|
|
95
|
+
_mlflow_tracking_uri = tracking_uri
|
|
96
|
+
_mlflow_artifact_uri = artifact_uri
|
|
97
|
+
_mlflow_backend_uri = backend_uri
|
|
98
|
+
|
|
99
|
+
# Wait a bit to check if it started successfully
|
|
100
|
+
time.sleep(2)
|
|
101
|
+
|
|
102
|
+
if _mlflow_process.poll() is not None:
|
|
103
|
+
# Process died immediately
|
|
104
|
+
stdout, stderr = _mlflow_process.communicate()
|
|
105
|
+
error_msg = stderr.decode('utf-8', errors='ignore') if stderr else "Unknown error"
|
|
106
|
+
_mlflow_process = None
|
|
107
|
+
return {
|
|
108
|
+
"success": False,
|
|
109
|
+
"running": False,
|
|
110
|
+
"error": f"Failed to start MLflow server: {error_msg}"
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
"success": True,
|
|
115
|
+
"running": True,
|
|
116
|
+
"port": port,
|
|
117
|
+
"url": f"http://localhost:{port}",
|
|
118
|
+
"tracking_uri": tracking_uri,
|
|
119
|
+
"artifact_uri": artifact_uri,
|
|
120
|
+
"message": f"MLflow server started on http://localhost:{port}"
|
|
121
|
+
}
|
|
122
|
+
except Exception as e:
|
|
123
|
+
return {
|
|
124
|
+
"success": False,
|
|
125
|
+
"running": False,
|
|
126
|
+
"error": f"Failed to start MLflow server: {str(e)}"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def stop_mlflow_server() -> Dict[str, Any]:
|
|
131
|
+
"""
|
|
132
|
+
Stop the local MLflow server.
|
|
133
|
+
|
|
134
|
+
Returns
|
|
135
|
+
-------
|
|
136
|
+
dict
|
|
137
|
+
Status information
|
|
138
|
+
"""
|
|
139
|
+
global _mlflow_process
|
|
140
|
+
|
|
141
|
+
if _mlflow_process is None:
|
|
142
|
+
return {
|
|
143
|
+
"success": True,
|
|
144
|
+
"running": False,
|
|
145
|
+
"message": "MLflow server is not running"
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
# Terminate the process
|
|
150
|
+
_mlflow_process.terminate()
|
|
151
|
+
|
|
152
|
+
# Wait for it to terminate (with timeout)
|
|
153
|
+
try:
|
|
154
|
+
_mlflow_process.wait(timeout=5)
|
|
155
|
+
except subprocess.TimeoutExpired:
|
|
156
|
+
# Force kill if it doesn't terminate
|
|
157
|
+
_mlflow_process.kill()
|
|
158
|
+
_mlflow_process.wait()
|
|
159
|
+
|
|
160
|
+
_mlflow_process = None
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
"success": True,
|
|
164
|
+
"running": False,
|
|
165
|
+
"message": "MLflow server stopped"
|
|
166
|
+
}
|
|
167
|
+
except Exception as e:
|
|
168
|
+
return {
|
|
169
|
+
"success": False,
|
|
170
|
+
"error": f"Failed to stop MLflow server: {str(e)}"
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def get_mlflow_server_status() -> Dict[str, Any]:
|
|
175
|
+
"""
|
|
176
|
+
Get the status of the local MLflow server.
|
|
177
|
+
|
|
178
|
+
Returns
|
|
179
|
+
-------
|
|
180
|
+
dict
|
|
181
|
+
Status information
|
|
182
|
+
"""
|
|
183
|
+
global _mlflow_process, _mlflow_port, _mlflow_tracking_uri, _mlflow_artifact_uri
|
|
184
|
+
|
|
185
|
+
if _mlflow_process is None:
|
|
186
|
+
return {
|
|
187
|
+
"running": False,
|
|
188
|
+
"port": None,
|
|
189
|
+
"url": None,
|
|
190
|
+
"tracking_uri": None,
|
|
191
|
+
"artifact_uri": None
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# Check if process is still running
|
|
195
|
+
if _mlflow_process.poll() is not None:
|
|
196
|
+
# Process died
|
|
197
|
+
_mlflow_process = None
|
|
198
|
+
return {
|
|
199
|
+
"running": False,
|
|
200
|
+
"port": None,
|
|
201
|
+
"url": None,
|
|
202
|
+
"tracking_uri": None,
|
|
203
|
+
"artifact_uri": None,
|
|
204
|
+
"message": "MLflow server process has stopped"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
"running": True,
|
|
209
|
+
"port": _mlflow_port,
|
|
210
|
+
"url": f"http://localhost:{_mlflow_port}",
|
|
211
|
+
"tracking_uri": _mlflow_tracking_uri,
|
|
212
|
+
"artifact_uri": _mlflow_artifact_uri
|
|
213
|
+
}
|
|
214
|
+
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jupyterlab-mlflow",
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "A JupyterLab extension for browsing MLflow experiments, runs, models, and artifacts",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"jupyter",
|
|
7
|
+
"jupyterlab",
|
|
8
|
+
"jupyterlab-extension",
|
|
9
|
+
"mlflow"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/BioLM/jupyterlab-mlflow",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/BioLM/jupyterlab-mlflow/issues"
|
|
14
|
+
},
|
|
15
|
+
"license": "BSD-3-Clause",
|
|
16
|
+
"author": "",
|
|
17
|
+
"files": [
|
|
18
|
+
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
|
|
19
|
+
"schema/**/*.json",
|
|
20
|
+
"style/**/*.css",
|
|
21
|
+
"style/icons/*.png"
|
|
22
|
+
],
|
|
23
|
+
"main": "lib/index.js",
|
|
24
|
+
"types": "lib/index.d.ts",
|
|
25
|
+
"style": "style/index.css",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/BioLM/jupyterlab-mlflow.git"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "jlpm build:lib && jlpm build:labextension:dev",
|
|
32
|
+
"build:prod": "jlpm clean && jlpm build:lib && jlpm build:labextension",
|
|
33
|
+
"build:labextension": "jupyter labextension build .",
|
|
34
|
+
"build:labextension:dev": "jupyter labextension build --development .",
|
|
35
|
+
"build:lib": "tsc",
|
|
36
|
+
"build:lib:watch": "tsc --watch",
|
|
37
|
+
"clean": "jlpm clean:lib",
|
|
38
|
+
"clean:lib": "rimraf lib tsconfig.tsbuildinfo",
|
|
39
|
+
"clean:labextension": "rimraf jupyterlab_mlflow/labextension",
|
|
40
|
+
"clean:all": "jlpm clean:lib && jlpm clean:labextension",
|
|
41
|
+
"eslint": "eslint . --ext .ts,.tsx --fix",
|
|
42
|
+
"eslint:check": "eslint . --ext .ts,.tsx",
|
|
43
|
+
"prepack": "jlpm clean && jlpm build:prod",
|
|
44
|
+
"watch": "npm-run-all --parallel watch:src watch:labextension",
|
|
45
|
+
"watch:src": "tsc -w",
|
|
46
|
+
"watch:labextension": "jupyter labextension watch"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@jupyterlab/application": "^4.0.0",
|
|
50
|
+
"@jupyterlab/apputils": "^4.0.0",
|
|
51
|
+
"@jupyterlab/coreutils": "^6.0.0",
|
|
52
|
+
"@jupyterlab/mainmenu": "^4.0.0",
|
|
53
|
+
"@jupyterlab/services": "^4.0.0",
|
|
54
|
+
"@jupyterlab/settingregistry": "^4.0.0",
|
|
55
|
+
"@jupyterlab/statedb": "^4.0.0",
|
|
56
|
+
"@jupyterlab/translation": "^4.0.0",
|
|
57
|
+
"@jupyterlab/ui-components": "^4.0.0",
|
|
58
|
+
"@lumino/algorithm": "^2.0.0",
|
|
59
|
+
"@lumino/commands": "^2.0.0",
|
|
60
|
+
"@lumino/coreutils": "^2.0.0",
|
|
61
|
+
"@lumino/disposable": "^2.0.0",
|
|
62
|
+
"@lumino/messaging": "^2.0.0",
|
|
63
|
+
"@lumino/signaling": "^2.0.0",
|
|
64
|
+
"@lumino/widgets": "^2.0.0",
|
|
65
|
+
"react": "^18.2.0",
|
|
66
|
+
"react-dom": "^18.2.0"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@jupyterlab/builder": "^4.0.0",
|
|
70
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
71
|
+
"@semantic-release/git": "^10.0.1",
|
|
72
|
+
"@semantic-release/github": "^11.0.6",
|
|
73
|
+
"@semantic-release/npm": "^12.0.2",
|
|
74
|
+
"@semantic-release/release-notes-generator": "^12.1.0",
|
|
75
|
+
"@types/react": "^18.2.0",
|
|
76
|
+
"@types/react-dom": "^18.2.0",
|
|
77
|
+
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
|
78
|
+
"@typescript-eslint/parser": "^5.0.0",
|
|
79
|
+
"css-loader": "^6.0.0",
|
|
80
|
+
"eslint": "^8.0.0",
|
|
81
|
+
"eslint-config-prettier": "^8.0.0",
|
|
82
|
+
"eslint-plugin-prettier": "^4.0.0",
|
|
83
|
+
"npm-run-all": "^4.1.5",
|
|
84
|
+
"prettier": "^2.0.0",
|
|
85
|
+
"rimraf": "^5.0.0",
|
|
86
|
+
"semantic-release": "^24.2.9",
|
|
87
|
+
"style-loader": "^3.0.0",
|
|
88
|
+
"ts-loader": "^9.0.0",
|
|
89
|
+
"typescript": "~5.2.0",
|
|
90
|
+
"webpack": "^5.0.0"
|
|
91
|
+
},
|
|
92
|
+
"jupyterlab": {
|
|
93
|
+
"extension": true,
|
|
94
|
+
"outputDir": "jupyterlab_mlflow/labextension",
|
|
95
|
+
"schemaDir": "jupyterlab_mlflow/schema",
|
|
96
|
+
"_build": {
|
|
97
|
+
"load": "static/remoteEntry.121dc9414dda869fb1a6.js",
|
|
98
|
+
"extension": "./extension",
|
|
99
|
+
"style": "./style"
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"logo": "static/logo.png"
|
|
103
|
+
}
|
jupyterlab_mlflow-0.4.1.data/data/share/jupyter/labextensions/jupyterlab-mlflow/schema/plugin.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jupyter.lab.setting-icon": "mlflow:icon",
|
|
3
|
+
"jupyter.lab.setting-icon-label": "MLflow",
|
|
4
|
+
"title": "MLflow",
|
|
5
|
+
"description": "MLflow extension settings",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"mlflowTrackingUri": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"title": "MLflow Tracking URI",
|
|
11
|
+
"description": "URI of the MLflow tracking server (e.g., http://localhost:5000). Leave empty to use MLFLOW_TRACKING_URI environment variable.",
|
|
12
|
+
"default": ""
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"additionalProperties": false
|
|
16
|
+
}
|
|
17
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jupyterlab-mlflow",
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "A JupyterLab extension for browsing MLflow experiments, runs, models, and artifacts",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"jupyter",
|
|
7
|
+
"jupyterlab",
|
|
8
|
+
"jupyterlab-extension",
|
|
9
|
+
"mlflow"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/BioLM/jupyterlab-mlflow",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/BioLM/jupyterlab-mlflow/issues"
|
|
14
|
+
},
|
|
15
|
+
"license": "BSD-3-Clause",
|
|
16
|
+
"author": "",
|
|
17
|
+
"files": [
|
|
18
|
+
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
|
|
19
|
+
"schema/**/*.json",
|
|
20
|
+
"style/**/*.css",
|
|
21
|
+
"style/icons/*.png"
|
|
22
|
+
],
|
|
23
|
+
"main": "lib/index.js",
|
|
24
|
+
"types": "lib/index.d.ts",
|
|
25
|
+
"style": "style/index.css",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/BioLM/jupyterlab-mlflow.git"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "jlpm build:lib && jlpm build:labextension:dev",
|
|
32
|
+
"build:prod": "jlpm clean && jlpm build:lib && jlpm build:labextension",
|
|
33
|
+
"build:labextension": "jupyter labextension build .",
|
|
34
|
+
"build:labextension:dev": "jupyter labextension build --development .",
|
|
35
|
+
"build:lib": "tsc",
|
|
36
|
+
"build:lib:watch": "tsc --watch",
|
|
37
|
+
"clean": "jlpm clean:lib",
|
|
38
|
+
"clean:lib": "rimraf lib tsconfig.tsbuildinfo",
|
|
39
|
+
"clean:labextension": "rimraf jupyterlab_mlflow/labextension",
|
|
40
|
+
"clean:all": "jlpm clean:lib && jlpm clean:labextension",
|
|
41
|
+
"eslint": "eslint . --ext .ts,.tsx --fix",
|
|
42
|
+
"eslint:check": "eslint . --ext .ts,.tsx",
|
|
43
|
+
"prepack": "jlpm clean && jlpm build:prod",
|
|
44
|
+
"watch": "npm-run-all --parallel watch:src watch:labextension",
|
|
45
|
+
"watch:src": "tsc -w",
|
|
46
|
+
"watch:labextension": "jupyter labextension watch"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@jupyterlab/application": "^4.0.0",
|
|
50
|
+
"@jupyterlab/apputils": "^4.0.0",
|
|
51
|
+
"@jupyterlab/coreutils": "^6.0.0",
|
|
52
|
+
"@jupyterlab/mainmenu": "^4.0.0",
|
|
53
|
+
"@jupyterlab/services": "^4.0.0",
|
|
54
|
+
"@jupyterlab/settingregistry": "^4.0.0",
|
|
55
|
+
"@jupyterlab/statedb": "^4.0.0",
|
|
56
|
+
"@jupyterlab/translation": "^4.0.0",
|
|
57
|
+
"@jupyterlab/ui-components": "^4.0.0",
|
|
58
|
+
"@lumino/algorithm": "^2.0.0",
|
|
59
|
+
"@lumino/commands": "^2.0.0",
|
|
60
|
+
"@lumino/coreutils": "^2.0.0",
|
|
61
|
+
"@lumino/disposable": "^2.0.0",
|
|
62
|
+
"@lumino/messaging": "^2.0.0",
|
|
63
|
+
"@lumino/signaling": "^2.0.0",
|
|
64
|
+
"@lumino/widgets": "^2.0.0",
|
|
65
|
+
"react": "^18.2.0",
|
|
66
|
+
"react-dom": "^18.2.0"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@jupyterlab/builder": "^4.0.0",
|
|
70
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
71
|
+
"@semantic-release/git": "^10.0.1",
|
|
72
|
+
"@semantic-release/github": "^11.0.6",
|
|
73
|
+
"@semantic-release/npm": "^12.0.2",
|
|
74
|
+
"@semantic-release/release-notes-generator": "^12.1.0",
|
|
75
|
+
"@types/react": "^18.2.0",
|
|
76
|
+
"@types/react-dom": "^18.2.0",
|
|
77
|
+
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
|
78
|
+
"@typescript-eslint/parser": "^5.0.0",
|
|
79
|
+
"css-loader": "^6.0.0",
|
|
80
|
+
"eslint": "^8.0.0",
|
|
81
|
+
"eslint-config-prettier": "^8.0.0",
|
|
82
|
+
"eslint-plugin-prettier": "^4.0.0",
|
|
83
|
+
"npm-run-all": "^4.1.5",
|
|
84
|
+
"prettier": "^2.0.0",
|
|
85
|
+
"rimraf": "^5.0.0",
|
|
86
|
+
"semantic-release": "^24.2.9",
|
|
87
|
+
"style-loader": "^3.0.0",
|
|
88
|
+
"ts-loader": "^9.0.0",
|
|
89
|
+
"typescript": "~5.2.0",
|
|
90
|
+
"webpack": "^5.0.0"
|
|
91
|
+
},
|
|
92
|
+
"jupyterlab": {
|
|
93
|
+
"extension": true,
|
|
94
|
+
"outputDir": "jupyterlab_mlflow/labextension",
|
|
95
|
+
"schemaDir": "jupyterlab_mlflow/schema"
|
|
96
|
+
},
|
|
97
|
+
"logo": "static/logo.png"
|
|
98
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jupyter.lab.setting-icon": "mlflow:icon",
|
|
3
|
+
"jupyter.lab.setting-icon-label": "MLflow",
|
|
4
|
+
"title": "MLflow",
|
|
5
|
+
"description": "MLflow extension settings",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"mlflowTrackingUri": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"title": "MLflow Tracking URI",
|
|
11
|
+
"description": "URI of the MLflow tracking server (e.g., http://localhost:5000). Leave empty to use MLFLOW_TRACKING_URI environment variable.",
|
|
12
|
+
"default": ""
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"additionalProperties": false
|
|
16
|
+
}
|
|
17
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";(self.webpackChunkjupyterlab_mlflow=self.webpackChunkjupyterlab_mlflow||[]).push([[218],{218:(e,t,l)=>{l.r(t),l.d(t,{default:()=>I});var a=l(986),n=l(560),s=l(177),o=l(424),i=l(860),r=l(297),c=l(694),m=l(256),d=l(345),u=l.n(d),p=l(628),f=l.n(p);function w(e){const{mlflowClient:t,onOpenObject:l}=e,[a,n]=(0,d.useState)([]),[s,o]=(0,d.useState)([]),[r,c]=(0,d.useState)(!0),[m,p]=(0,d.useState)(null),[f,w]=(0,d.useState)("experiments"),g=(0,d.useCallback)(async()=>{try{c(!0),p(null);const e=(await t.getExperiments()).map(e=>({id:e.experiment_id,label:e.name||e.experiment_id,type:"experiment",expanded:!1,children:[],data:e}));n(e)}catch(e){p(e instanceof Error?e.message:"Failed to load experiments")}finally{c(!1)}},[t]),v=(0,d.useCallback)(async()=>{try{c(!0),p(null);const e=(await t.getModels()).map(e=>({id:e.name,label:e.name,type:"model",expanded:!1,children:[],data:e}));o(e)}catch(e){p(e instanceof Error?e.message:" Failed to load models")}finally{c(!1)}},[t]);(0,d.useEffect)(()=>{g(),v()},[g,v]);const h=async(e,l,a)=>{var n;if(e.expanded)e.expanded=!1,a([...l]);else{if(e.loading)return;if(0===e.children.length){e.expanded=!0,e.loading=!0,a([...l]);try{if("experiment"===e.type){const l=await t.getRuns(e.id);e.children=l.map(e=>({id:e.run_id,label:e.run_name||e.run_id,type:"run",expanded:!1,children:[],data:e}))}else if("run"===e.type){const l=await t.getArtifacts(e.id);e.children=l.artifacts.map(t=>({id:`${e.id}/${t.path}`,label:t.path.split("/").pop()||t.path,type:"artifact",expanded:!1,children:[],data:{path:t.path,is_dir:Boolean(t.is_dir),file_size:t.file_size,runId:e.id}}))}else if("artifact"===e.type&&Boolean(null===(n=e.data)||void 0===n?void 0:n.is_dir)){const l=await t.getArtifacts(e.data.runId,e.data.path);e.children=l.artifacts.filter(e=>!e.is_dir).map(t=>({id:`${e.data.runId}/${t.path}`,label:t.path.split("/").pop()||t.path,type:"artifact",expanded:!1,children:[],data:{path:t.path,is_dir:!1,file_size:t.file_size,runId:e.data.runId}}))}else if("model"===e.type){const l=await t.getModel(e.id);e.children=l.latest_versions.map(t=>({id:`${e.id}/${t.version}`,label:`Version ${t.version} (${t.stage})`,type:"version",expanded:!1,children:[],data:{...t,modelName:e.id}}))}}catch(e){p(e instanceof Error?e.message:"Failed to load children")}finally{e.loading=!1,a([...l])}}else e.expanded=!0,a([...l])}},E=(e,a,n,s)=>{var o,r,c;const m=20*a,d="experiment"===e.type||"run"===e.type||"model"===e.type;let p,f;"artifact"===e.type?Boolean(null===(o=e.data)||void 0===o?void 0:o.is_dir)?(p=e.expanded?"🔽":"▶️",f=!0):(p="🔹",f=!1):(f=d,p=f?e.expanded?"▼":"▶":"experiment"===e.type?"📁":"run"===e.type?"▶️":"model"===e.type?"🤖":"🔢");const w="artifact"===e.type&&!Boolean(null===(r=e.data)||void 0===r?void 0:r.is_dir);return u().createElement("div",{key:e.id},u().createElement("div",{className:"mlflow-tree-node",style:{paddingLeft:`${m}px`},onClick:()=>(async(e,l,a)=>{"artifact"!==e.type?await h(e,l,a):e.data.is_dir||async function(e,t,l){var a;try{const n=await l.downloadArtifact(e,t),s=(null===(a=t.split(".").pop())||void 0===a?void 0:a.toLowerCase())||"",o=t.split("/").pop()||`artifact_${e}`,i=URL.createObjectURL(n);if(["png","jpg","jpeg","gif","svg"].includes(s)){const e=window.open("","_blank");e&&e.document.write(`\n <html>\n <head><title>${o}</title></head>\n <body style="margin:0;padding:20px;text-align:center;">\n <img src="${i}" style="max-width:100%;height:auto;" />\n </body>\n </html>\n `)}else if("json"===s){const e=await n.text(),t=window.open("","_blank");t&&t.document.write(`\n <html>\n <head>\n <title>${o}</title>\n <style>\n body { font-family: monospace; padding: 20px; background: #f5f5f5; }\n pre { background: white; padding: 15px; border-radius: 5px; overflow: auto; }\n </style>\n </head>\n <body>\n <h2>${o}</h2>\n <pre>${JSON.stringify(JSON.parse(e),null,2)}</pre>\n </body>\n </html>\n `)}else if("csv"===s){const e=await n.text(),t=window.open("","_blank");if(t){const l=e.split("\n").map(e=>"<tr>"+e.split(",").map(e=>`<td>${e}</td>`).join("")+"</tr>").join("");t.document.write(`\n <html>\n <head>\n <title>${o}</title>\n <style>\n body { font-family: sans-serif; padding: 20px; }\n table { border-collapse: collapse; width: 100%; }\n th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }\n th { background-color: #f2f2f2; }\n </style>\n </head>\n <body>\n <h2>${o}</h2>\n <table>${l}</table>\n </body>\n </html>\n `)}}else{const e=await n.text(),t=window.open("","_blank");t&&t.document.write(`\n <html>\n <head>\n <title>${o}</title>\n <style>\n body { font-family: monospace; padding: 20px; white-space: pre-wrap; }\n </style>\n </head>\n <body>${e}</body>\n </html>\n `)}setTimeout(()=>URL.revokeObjectURL(i),1e3)}catch(e){console.error("Failed to open artifact:",e),alert(`Failed to open artifact: ${e instanceof Error?e.message:"Unknown error"}`)}}(e.data.runId,e.data.path,t)})(e,n,s)},u().createElement("span",{className:"mlflow-tree-icon",onClick:t=>f&&(async(e,t,l,a)=>{e.stopPropagation(),await h(t,l,a)})(t,e,n,s),style:{cursor:f?"pointer":"default",display:"inline-block",minWidth:"16px",fontSize:"12px",lineHeight:"1",userSelect:"none"},title:"artifact"===e.type?Boolean(null===(c=e.data)||void 0===c?void 0:c.is_dir)?"Directory":"File":""},p||" "),u().createElement("span",{className:"mlflow-loading-container"},e.loading&&u().createElement("span",{className:"mlflow-loading"},"⏳")),u().createElement("span",{className:"mlflow-tree-label",title:"run"===e.type?e.id:e.label},"run"===e.type&&e.id.length>20?function(e,t=20){return e.length<=t?e:`${e.substring(0,t-3)}...`}(e.label):e.label),u().createElement("div",{style:{display:"flex",gap:"2px",marginLeft:"4px",alignItems:"center"}},w&&u().createElement("button",{className:"mlflow-download-button",onClick:l=>{l.preventDefault(),l.stopPropagation(),(async e=>{var l,a;if("artifact"===e.type)try{const n=(null===(l=e.data)||void 0===l?void 0:l.runId)||e.id.split("/")[0],s=(null===(a=e.data)||void 0===a?void 0:a.path)||e.label,o=s.split("/").pop()||`artifact_${n}`,r=await t.downloadArtifact(n,s),c=await new Promise((e,t)=>{const l=new FileReader;l.onloadend=()=>{const t=l.result.split(",")[1];e(t)},l.onerror=t,l.readAsDataURL(r)}),m=t.getServerSettings(),d=new i.ContentsManager({serverSettings:m});await d.save(o,{type:"file",format:"base64",content:c}),alert(`Artifact saved to: ${o}`)}catch(e){console.error("Failed to download artifact:",e);const t=e instanceof Error?e.message:"Unknown error";t.includes("is a directory")||t.includes("Is a directory")?alert("Cannot download directories. Expand the directory to see files inside, then download individual files."):alert(`Failed to download: ${t}`)}})(e)},title:"Download artifact",style:{fontSize:"14px",padding:"2px 6px",opacity:.7,cursor:"pointer"}},"⬇"),("model"===e.type||"run"===e.type||"experiment"===e.type||"version"===e.type)&&u().createElement("button",{className:"mlflow-open-button",onClick:t=>{t.stopPropagation(),(e=>{l&&("experiment"===e.type?l("experiment",e.id,e.data):"run"===e.type?l("run",e.id,e.data):"model"===e.type?l("model",e.id,e.data):"version"===e.type?l("version",e.data.version,{...e.data,modelName:e.data.modelName}):"artifact"===e.type&&l("artifact",e.data.path,e.data))})(e)},title:"Open in Details View",style:{fontSize:"11px",padding:"2px 6px",opacity:.7,cursor:"pointer"}},"Open"))),e.expanded&&e.children.map(t=>E(t,a+1,e.children,t=>{e.children=t,s([...n])})))};return u().createElement("div",{className:"mlflow-tree-view"},u().createElement("div",{className:"mlflow-tabs"},u().createElement("button",{className:"mlflow-tab "+("experiments"===f?"active":""),onClick:()=>w("experiments")},"Experiments"),u().createElement("button",{className:"mlflow-tab "+("models"===f?"active":""),onClick:()=>w("models")},"Models")),m&&u().createElement("div",{className:"mlflow-error"},"Error: ",m,u().createElement("button",{onClick:()=>"experiments"===f?g():v()},"Retry")),r&&0===a.length&&0===s.length?u().createElement("div",{className:"mlflow-loading"},"Loading..."):u().createElement("div",{className:"mlflow-tree-content"},"experiments"===f&&a.map(e=>E(e,0,a,n)),"models"===f&&s.map(e=>E(e,0,s,o))))}async function g(e){try{if(navigator.clipboard&&navigator.clipboard.writeText)return await navigator.clipboard.writeText(e),!0;{const t=document.createElement("textarea");t.value=e,t.style.position="fixed",t.style.left="-999999px",t.style.top="-999999px",document.body.appendChild(t),t.focus(),t.select();try{const e=document.execCommand("copy");return document.body.removeChild(t),e}catch(e){return document.body.removeChild(t),!1}}}catch(e){return console.error("Failed to copy to clipboard:",e),!1}}async function v(e){await g(e)?h("Code copied to clipboard!"):h("Failed to copy code","error")}function h(e,t="success"){const l=document.querySelector(".mlflow-toast");l&&l.remove();const a=document.createElement("div");a.className="mlflow-toast",a.textContent=e;const n="success"===t?"var(--jp-success-color1, #4caf50)":"var(--jp-error-color1, #f44336)";a.style.cssText=`\n position: fixed;\n bottom: 20px;\n right: 20px;\n background: ${n};\n color: white;\n padding: 12px 24px;\n border-radius: 4px;\n box-shadow: 0 2px 8px rgba(0,0,0,0.2);\n z-index: 10000;\n font-size: 13px;\n animation: mlflow-slideIn 0.3s ease-out;\n `,document.body.appendChild(a),setTimeout(()=>{a.style.animation="mlflow-slideOut 0.3s ease-out",setTimeout(()=>{a.parentNode&&document.body.removeChild(a)},300)},2e3)}async function E(e){return g(e)}function y(e){return`run = mlflow.get_run(run_id="${e}")\nprint(f"Status: {run.info.status}")\nprint(f"Metrics: {run.data.metrics}")\nprint(f"Parameters: {run.data.params}")\n`}function b(e){const{mlflowClient:t,app:l,initialSelection:a,onObjectSelect:n}=e,[s,o]=(0,d.useState)("experiments"),[i,r]=(0,d.useState)(null),[c,m]=(0,d.useState)([]),[p,f]=(0,d.useState)([]),[w,h]=(0,d.useState)([]),[b,N]=(0,d.useState)([]),[x,_]=(0,d.useState)([]),[k,S]=(0,d.useState)(!0),[C,L]=(0,d.useState)(null),M=(0,d.useRef)(!1),T=(0,d.useCallback)(async()=>{try{S(!0),L(null);const e=await t.getExperiments();m(e)}catch(e){L(e instanceof Error?e.message:"Failed to load experiments")}finally{S(!1)}},[t]),U=(0,d.useCallback)(async()=>{try{S(!0),L(null);const e=await t.getModels();N(e)}catch(e){L(e instanceof Error?e.message:"Failed to load models")}finally{S(!1)}},[t]),I=(0,d.useCallback)(async e=>{try{S(!0),L(null);const l=await t.getExperiment(e);r({type:"experiment",data:l,parent:null});const a=await t.getRuns(e);f(a)}catch(e){L(e instanceof Error?e.message:"Failed to load experiment")}finally{S(!1)}},[t]),R=(0,d.useCallback)(async e=>{try{S(!0),L(null);const l=await t.getRun(e);r({type:"run",data:l,parent:i||void 0});const a=await t.getArtifacts(e);h(a.artifacts||[])}catch(e){L(e instanceof Error?e.message:"Failed to load run")}finally{S(!1)}},[t,i]),$=(0,d.useCallback)(async e=>{try{S(!0),L(null);const l=await t.getModel(e);r({type:"model",data:l,parent:null}),_(l.latest_versions||[])}catch(e){L(e instanceof Error?e.message:"Failed to load model")}finally{S(!1)}},[t]),j=(0,d.useCallback)(async(e,l)=>{try{S(!0),L(null);const a=(await t.getModel(e)).latest_versions.find(e=>e.version===l);if(a){const t=i||void 0;r({type:"version",data:{...a,modelName:e},parent:t||void 0})}}catch(e){L(e instanceof Error?e.message:"Failed to load model version")}finally{S(!1)}},[t,i]),A=(0,d.useCallback)((e,t,l)=>{n&&n(e,t),"experiment"===e?I(t):"run"===e?R(t):"model"===e?$(t):"version"===e&&(null==l?void 0:l.modelName)?j(l.modelName,t):"artifact"===e&&l&&r(e=>({type:"artifact",data:l,parent:e||void 0}))},[n,I,R,$,j]),O=(0,d.useCallback)(()=>{r(e=>(null==e?void 0:e.parent)?("experiment"===e.parent.type?t.getRuns(e.parent.data.experiment_id).then(f).catch(console.error):"model"===e.parent.type&&t.getModel(e.parent.data.name).then(e=>{_(e.latest_versions||[])}).catch(console.error),e.parent):("experiments"===s?T():U(),null))},[s,t,T,U]);(0,d.useEffect)(()=>{a&&!M.current?(M.current=!0,A(a.type,a.id,a.data)):i||a||("experiments"===s?T():U())},[s,A,T,U,a,i]),(0,d.useEffect)(()=>{a&&a.id&&!M.current&&(M.current=!0,A(a.type,a.id,a.data))},[null==a?void 0:a.id,null==a?void 0:a.type,A]);const D=e=>e<1024?`${e} B`:e<1048576?`${(e/1024).toFixed(1)} KB`:`${(e/1048576).toFixed(1)} MB`,F=e=>e.length>6?e.slice(-6):e,P=e=>{if(!l)return void alert("JupyterLab app not available");const t=l.shell.currentWidget;if(!t)return void alert("Please open a notebook first");const a=t.content;if(!a||!a.activeCell){const t=l.shell.widgets("main");for(const a of t){const t=a.content;if(t&&t.activeCell){const n=t.activeCell;if(n.model){if(n.model.sharedModel)return n.model.sharedModel.setSource(e),void l.shell.activateById(a.id);if(n.model.value)return n.model.value.text=e,void l.shell.activateById(a.id)}}}return void alert("Could not find active notebook cell. Please make sure a notebook is open and a cell is selected.")}const n=a.activeCell;n&&n.model?(n.model.sharedModel?n.model.sharedModel.setSource(e):n.model.value&&(n.model.value.text=e),l.shell.activateById(t.id)):alert("Please select a cell in the notebook")};return u().createElement("div",{className:"mlflow-details-view"},u().createElement("div",{className:"mlflow-tabs"},u().createElement("button",{className:"mlflow-tab "+("experiments"===s?"active":""),onClick:()=>{o("experiments"),r(null),T()}},"Experiments"),u().createElement("button",{className:"mlflow-tab "+("models"===s?"active":""),onClick:()=>{o("models"),r(null),U()}},"Models")),C&&u().createElement("div",{className:"mlflow-error"},"Error: ",C,u().createElement("button",{onClick:()=>{L(null),"experiments"===s?T():U()}},"Retry")),k&&!i&&u().createElement("div",{className:"mlflow-loading"},"Loading..."),u().createElement("div",{className:"mlflow-details-content"},i&&u().createElement("div",{className:"mlflow-details-navigation"},u().createElement("button",{className:"mlflow-button",onClick:O},"← Back")),(()=>{if(!i)return null;const{type:e,data:t}=i;if("experiment"===e)return u().createElement("div",{className:"mlflow-details-metadata"},u().createElement("h3",{className:"mlflow-details-title"},"Experiment: ",t.name||t.experiment_id),u().createElement("div",{className:"mlflow-details-grid"},u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Experiment ID:"),u().createElement("span",{className:"mlflow-details-value"},t.experiment_id)),u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Name:"),u().createElement("span",{className:"mlflow-details-value"},t.name||"(unnamed)")),u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Artifact Location:"),u().createElement("span",{className:"mlflow-details-value"},t.artifact_location)),u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Lifecycle Stage:"),u().createElement("span",{className:"mlflow-details-value"},t.lifecycle_stage)),t.tags&&Object.keys(t.tags).length>0&&u().createElement("div",{className:"mlflow-details-item mlflow-details-item-full"},u().createElement("span",{className:"mlflow-details-label"},"Tags:"),u().createElement("div",{className:"mlflow-details-tags"},Object.entries(t.tags).map(([e,t])=>u().createElement("span",{key:e,className:"mlflow-tag"},e,": ",t))))));if("run"===e){const e=t.end_time&&t.start_time?`${((t.end_time-t.start_time)/1e3).toFixed(1)}s`:"Running...";return u().createElement("div",{className:"mlflow-details-metadata"},u().createElement("h3",{className:"mlflow-details-title"},"Run: ",t.run_name||t.run_id),u().createElement("div",{className:"mlflow-details-grid"},u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Run ID:"),u().createElement("span",{className:"mlflow-details-value"},t.run_id)),u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Status:"),u().createElement("span",{className:"mlflow-details-value"},t.status)),u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"User:"),u().createElement("span",{className:"mlflow-details-value"},t.user_id)),u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Start Time:"),u().createElement("span",{className:"mlflow-details-value"},new Date(t.start_time).toLocaleString())),t.end_time&&u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"End Time:"),u().createElement("span",{className:"mlflow-details-value"},new Date(t.end_time).toLocaleString())),u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Duration:"),u().createElement("span",{className:"mlflow-details-value"},e)),u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Artifact URI:"),u().createElement("span",{className:"mlflow-details-value"},t.artifact_uri)),t.metrics&&Object.keys(t.metrics).length>0&&u().createElement("div",{className:"mlflow-details-item mlflow-details-item-full"},u().createElement("span",{className:"mlflow-details-label"},"Metrics:"),u().createElement("div",{className:"mlflow-details-metrics"},Object.entries(t.metrics).map(([e,t])=>u().createElement("span",{key:e,className:"mlflow-metric"},e,": ",t)))),t.params&&Object.keys(t.params).length>0&&u().createElement("div",{className:"mlflow-details-item mlflow-details-item-full"},u().createElement("span",{className:"mlflow-details-label"},"Parameters:"),u().createElement("div",{className:"mlflow-details-params"},Object.entries(t.params).map(([e,t])=>u().createElement("span",{key:e,className:"mlflow-param"},e,": ",t)))),t.tags&&Object.keys(t.tags).length>0&&u().createElement("div",{className:"mlflow-details-item mlflow-details-item-full"},u().createElement("span",{className:"mlflow-details-label"},"Tags:"),u().createElement("div",{className:"mlflow-details-tags"},Object.entries(t.tags).map(([e,t])=>u().createElement("span",{key:e,className:"mlflow-tag"},e,": ",t))))))}return"model"===e?u().createElement("div",{className:"mlflow-details-metadata"},u().createElement("h3",{className:"mlflow-details-title"},"Model: ",t.name),u().createElement("div",{className:"mlflow-details-grid"},u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Name:"),u().createElement("span",{className:"mlflow-details-value"},t.name)),u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Created:"),u().createElement("span",{className:"mlflow-details-value"},new Date(t.creation_timestamp).toLocaleString())),u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Last Updated:"),u().createElement("span",{className:"mlflow-details-value"},new Date(t.last_updated_timestamp).toLocaleString())),t.description&&u().createElement("div",{className:"mlflow-details-item mlflow-details-item-full"},u().createElement("span",{className:"mlflow-details-label"},"Description:"),u().createElement("span",{className:"mlflow-details-value"},t.description)),t.tags&&Object.keys(t.tags).length>0&&u().createElement("div",{className:"mlflow-details-item mlflow-details-item-full"},u().createElement("span",{className:"mlflow-details-label"},"Tags:"),u().createElement("div",{className:"mlflow-details-tags"},Object.entries(t.tags).map(([e,t])=>u().createElement("span",{key:e,className:"mlflow-tag"},e,": ",t)))))):"version"===e?u().createElement("div",{className:"mlflow-details-metadata"},u().createElement("h3",{className:"mlflow-details-title"},"Model Version: ",t.version),u().createElement("div",{className:"mlflow-details-grid"},u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Version:"),u().createElement("span",{className:"mlflow-details-value"},t.version)),u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Stage:"),u().createElement("span",{className:"mlflow-details-value"},t.stage)),u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Status:"),u().createElement("span",{className:"mlflow-details-value"},t.status)),u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Run ID:"),u().createElement("span",{className:"mlflow-details-value"},t.run_id)),u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Created:"),u().createElement("span",{className:"mlflow-details-value"},new Date(t.creation_timestamp).toLocaleString())),t.description&&u().createElement("div",{className:"mlflow-details-item mlflow-details-item-full"},u().createElement("span",{className:"mlflow-details-label"},"Description:"),u().createElement("span",{className:"mlflow-details-value"},t.description)))):"artifact"===e?u().createElement("div",{className:"mlflow-details-metadata"},u().createElement("h3",{className:"mlflow-details-title"},"Artifact: ",t.path),u().createElement("div",{className:"mlflow-details-grid"},u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Path:"),u().createElement("span",{className:"mlflow-details-value"},t.path)),u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Type:"),u().createElement("span",{className:"mlflow-details-value"},t.is_dir?"Directory":"File")),null!==t.file_size&&u().createElement("div",{className:"mlflow-details-item"},u().createElement("span",{className:"mlflow-details-label"},"Size:"),u().createElement("span",{className:"mlflow-details-value"},D(t.file_size))))):null})(),(()=>{if(!i)return"experiments"===s?u().createElement("div",{className:"mlflow-details-table-section"},u().createElement("h4",{className:"mlflow-details-section-title"},"Experiments"),u().createElement("table",{className:"mlflow-table"},u().createElement("thead",null,u().createElement("tr",null,u().createElement("th",null,"ID"),u().createElement("th",null,"Name"),u().createElement("th",null,"Actions"))),u().createElement("tbody",null,c.map(e=>u().createElement("tr",{key:e.experiment_id,onClick:()=>A("experiment",e.experiment_id,e)},u().createElement("td",null,e.experiment_id),u().createElement("td",null,e.name||"(unnamed)"),u().createElement("td",null,u().createElement("button",{className:"mlflow-button-small",onClick:t=>{t.stopPropagation(),async function(e){g(e)}(e.experiment_id)}},"Copy ID"))))))):u().createElement("div",{className:"mlflow-details-table-section"},u().createElement("h4",{className:"mlflow-details-section-title"},"Models"),u().createElement("table",{className:"mlflow-table"},u().createElement("thead",null,u().createElement("tr",null,u().createElement("th",null,"Name"),u().createElement("th",null,"Versions"),u().createElement("th",null,"Last Updated"),u().createElement("th",null,"Actions"))),u().createElement("tbody",null,b.map(e=>u().createElement("tr",{key:e.name,onClick:()=>A("model",e.name,e)},u().createElement("td",null,e.name),u().createElement("td",null,e.latest_versions.length),u().createElement("td",null,new Date(e.last_updated_timestamp).toLocaleString()),u().createElement("td",null,u().createElement("button",{className:"mlflow-button-small",onClick:t=>{t.stopPropagation(),async function(e){g(e)}(e.name)}},"Copy Name")))))));const{type:e}=i;return"experiment"===e?u().createElement("div",{className:"mlflow-details-table-section"},u().createElement("h4",{className:"mlflow-details-section-title"},"Runs"),u().createElement("table",{className:"mlflow-table"},u().createElement("thead",null,u().createElement("tr",null,u().createElement("th",null,"Run ID"),u().createElement("th",null,"Name"),u().createElement("th",null,"Status"),u().createElement("th",null,"Actions"))),u().createElement("tbody",null,p.map(e=>u().createElement("tr",{key:e.run_id,onClick:t=>{"BUTTON"!==t.target.tagName&&A("run",e.run_id,e)}},u().createElement("td",null,u().createElement("button",{className:"mlflow-run-id-button",onClick:t=>{t.stopPropagation(),E(e.run_id)},title:e.run_id},F(e.run_id))),u().createElement("td",null,e.run_name||"-"),u().createElement("td",null,e.status),u().createElement("td",null,u().createElement("div",{className:"mlflow-actions-group",onClick:e=>e.stopPropagation()},u().createElement("button",{className:"mlflow-button-small",onClick:()=>v(y(e.run_id)),title:"Copy code: Get run"},"📋"),l&&u().createElement("button",{className:"mlflow-button-small",onClick:()=>P(y(e.run_id)),title:"Insert code: Get run"},"➕")))))))):"run"===e?u().createElement("div",{className:"mlflow-details-table-section"},u().createElement("h4",{className:"mlflow-details-section-title"},"Artifacts"),u().createElement("table",{className:"mlflow-table"},u().createElement("thead",null,u().createElement("tr",null,u().createElement("th",null,"Path"),u().createElement("th",null,"Type"),u().createElement("th",null,"Size"))),u().createElement("tbody",null,w.map(e=>u().createElement("tr",{key:e.path,onClick:()=>A("artifact",e.path,e)},u().createElement("td",null,e.path),u().createElement("td",null,e.is_dir?"Directory":"File"),u().createElement("td",null,null!==e.file_size?D(e.file_size):"-")))))):"model"===e?u().createElement("div",{className:"mlflow-details-table-section"},u().createElement("h4",{className:"mlflow-details-section-title"},"Versions"),u().createElement("table",{className:"mlflow-table"},u().createElement("thead",null,u().createElement("tr",null,u().createElement("th",null,"Version"),u().createElement("th",null,"Stage"),u().createElement("th",null,"Status"),u().createElement("th",null,"Run ID"),u().createElement("th",null,"Created"),u().createElement("th",null,"Actions"))),u().createElement("tbody",null,x.map(e=>u().createElement("tr",{key:e.version,onClick:t=>{"BUTTON"!==t.target.tagName&&A("version",e.version,{...e,modelName:i.data.name})}},u().createElement("td",null,e.version),u().createElement("td",null,e.stage),u().createElement("td",null,e.status),u().createElement("td",null,u().createElement("button",{className:"mlflow-run-id-button",onClick:t=>{t.stopPropagation(),E(e.run_id)},title:e.run_id},F(e.run_id))),u().createElement("td",null,new Date(e.creation_timestamp).toLocaleString()),u().createElement("td",null,u().createElement("div",{className:"mlflow-actions-group",onClick:e=>e.stopPropagation()},u().createElement("button",{className:"mlflow-button-small",onClick:()=>v(y(e.run_id)),title:"Copy code: Get run"},"📋"),l&&u().createElement("button",{className:"mlflow-button-small",onClick:()=>P(y(e.run_id)),title:"Insert code: Get run"},"➕")))))))):null})()))}function N(e){const{mlflowClient:t,trackingUri:l,onTrackingUriChange:a,onClose:n}=e,[s,o]=(0,d.useState)(l),[i,r]=(0,d.useState)(!1),[c,m]=(0,d.useState)(null),[p,f]=(0,d.useState)(null),[w,g]=(0,d.useState)(5e3),[v,h]=(0,d.useState)("sqlite:///mlflow.db"),[E,y]=(0,d.useState)("./mlruns"),[b,N]=(0,d.useState)(!1),[x,_]=(0,d.useState)(!1),k=(0,d.useCallback)(async()=>{try{const e=await t.getLocalServerStatus();f(e),e.running&&e.url&&o(e.url)}catch(e){console.error("Failed to load local server status:",e)}},[t]);return(0,d.useEffect)(()=>{k();const e=setInterval(k,5e3);return()=>clearInterval(e)},[k]),u().createElement("div",{className:"mlflow-settings-panel"},u().createElement("div",{className:"mlflow-settings-header"},u().createElement("h3",null,"MLflow Settings"),u().createElement("button",{className:"mlflow-button-close",onClick:n},"×")),u().createElement("div",{className:"mlflow-settings-content"},u().createElement("div",{className:"mlflow-settings-field"},u().createElement("label",{htmlFor:"tracking-uri"},"MLflow Tracking URI"),u().createElement("input",{id:"tracking-uri",type:"text",value:s,onChange:e=>o(e.target.value),placeholder:"http://localhost:5000 or leave empty for MLFLOW_TRACKING_URI env var",className:"mlflow-input"}),u().createElement("div",{className:"mlflow-settings-help"},"Leave empty to use MLFLOW_TRACKING_URI environment variable")),u().createElement("div",{className:"mlflow-settings-actions"},u().createElement("button",{className:"mlflow-button mlflow-button-primary",onClick:async()=>{r(!0),m(null);try{const e=await t.testConnection(s);m(e)}catch(e){m({success:!1,error:e instanceof Error?e.message:"Unknown error"})}finally{r(!1)}},disabled:i},i?"Testing...":"Test Connection"),c&&u().createElement("div",{className:"mlflow-test-result "+(c.success?"success":"error")},c.success?u().createElement("span",null,"✓ ",c.message||"Connection successful"):u().createElement("span",null,"✗ ",c.error||"Connection failed"))),u().createElement("div",{className:"mlflow-settings-actions"},u().createElement("button",{className:"mlflow-button mlflow-button-primary",onClick:()=>{a(s),n()}},"Save"),u().createElement("button",{className:"mlflow-button",onClick:n},"Cancel")),u().createElement("div",{className:"mlflow-settings-divider"}),u().createElement("div",{className:"mlflow-settings-section"},u().createElement("h4",{className:"mlflow-settings-section-title"},"Local MLflow Server"),u().createElement("div",{className:"mlflow-settings-help",style:{marginBottom:"12px"}},"Launch a local MLflow server for development and testing"),(null==p?void 0:p.running)&&u().createElement("div",{className:"mlflow-test-result success",style:{marginBottom:"12px"}},"✓ MLflow server running on ",p.url),u().createElement("div",{className:"mlflow-settings-field"},u().createElement("label",{htmlFor:"local-server-port"},"Port"),u().createElement("input",{id:"local-server-port",type:"number",value:w,onChange:e=>g(parseInt(e.target.value)||5e3),className:"mlflow-input",disabled:null==p?void 0:p.running})),u().createElement("div",{className:"mlflow-settings-field"},u().createElement("label",{htmlFor:"local-server-tracking-uri"},"Tracking URI"),u().createElement("input",{id:"local-server-tracking-uri",type:"text",value:v,onChange:e=>h(e.target.value),placeholder:"sqlite:///mlflow.db",className:"mlflow-input",disabled:null==p?void 0:p.running}),u().createElement("div",{className:"mlflow-settings-help"},"Default: sqlite:///mlflow.db (SQLite database)")),u().createElement("div",{className:"mlflow-settings-field"},u().createElement("label",{htmlFor:"local-server-artifact-uri"},"Artifact URI"),u().createElement("input",{id:"local-server-artifact-uri",type:"text",value:E,onChange:e=>y(e.target.value),placeholder:"./mlruns",className:"mlflow-input",disabled:null==p?void 0:p.running}),u().createElement("div",{className:"mlflow-settings-help"},"Default: ./mlruns (local directory)")),u().createElement("div",{className:"mlflow-settings-actions"},(null==p?void 0:p.running)?u().createElement("button",{className:"mlflow-button",onClick:async()=>{_(!0);try{await t.stopLocalServer(),await k()}catch(e){console.error("Failed to stop local server:",e),alert(`Failed to stop local MLflow server: ${e instanceof Error?e.message:"Unknown error"}`)}finally{_(!1)}},disabled:x},x?"Stopping...":"Stop Server"):u().createElement("button",{className:"mlflow-button mlflow-button-primary",onClick:async()=>{N(!0);try{const e=await t.startLocalServer(w,v,E);e.success&&e.url&&(o(e.url),a(e.url)),await k()}catch(e){console.error("Failed to start local server:",e),alert(`Failed to start local MLflow server: ${e instanceof Error?e.message:"Unknown error"}`)}finally{N(!1)}},disabled:b},b?"Starting...":"Start Local Server")))))}l(665);const x=[{title:"Import MLflow",description:"Import the MLflow library",code:"import mlflow\n",category:"Setup"},{title:"Set Tracking URI",description:"Set the MLflow tracking server URI",code:'mlflow.set_tracking_uri("http://localhost:5000")\n',category:"Setup"},{title:"Create Experiment",description:"Create a new MLflow experiment",code:'experiment_id = mlflow.create_experiment(\n name="my_experiment"\n)\nprint(f"Created experiment: {experiment_id}")\n',category:"Experiments"},{title:"Get Experiment",description:"Get experiment details by ID",code:'experiment = mlflow.get_experiment(experiment_id="0")\nprint(f"Name: {experiment.name}")\nprint(f"Artifact Location: {experiment.artifact_location}")\n',category:"Experiments"},{title:"Set Active Experiment",description:"Set the active experiment for logging",code:'mlflow.set_experiment("my_experiment")\n',category:"Experiments"},{title:"List Experiments",description:"List all experiments",code:'experiments = mlflow.search_experiments()\nfor exp in experiments:\n print(f"{exp.experiment_id}: {exp.name}")\n',category:"Experiments"},{title:"Start Run",description:"Start a new MLflow run",code:'with mlflow.start_run() as run:\n print(f"Run ID: {run.info.run_id}")\n # Your code here\n',category:"Runs"},{title:"Start Run with Name",description:"Start a named run",code:'with mlflow.start_run(run_name="my_run") as run:\n print(f"Run ID: {run.info.run_id}")\n # Your code here\n',category:"Runs"},{title:"Get Run",description:"Get run details by run ID",code:'run = mlflow.get_run(run_id="your-run-id")\nprint(f"Status: {run.info.status}")\nprint(f"Metrics: {run.data.metrics}")\nprint(f"Parameters: {run.data.params}")\n',category:"Runs"},{title:"Search Runs",description:"Search runs in an experiment",code:'runs = mlflow.search_runs(experiment_ids=["0"])\nprint(runs.head())\n',category:"Runs"},{title:"Log Parameter",description:"Log a parameter value",code:'mlflow.log_param("learning_rate", 0.01)\n',category:"Logging"},{title:"Log Multiple Parameters",description:"Log multiple parameters at once",code:'mlflow.log_params({\n "learning_rate": 0.01,\n "batch_size": 32,\n "epochs": 10\n})\n',category:"Logging"},{title:"Log Metric",description:"Log a metric value",code:'mlflow.log_metric("accuracy", 0.95)\n',category:"Logging"},{title:"Log Multiple Metrics",description:"Log multiple metrics at once",code:'mlflow.log_metrics({\n "accuracy": 0.95,\n "f1_score": 0.92,\n "loss": 0.05\n})\n',category:"Logging"},{title:"Log Metric Over Time",description:"Log a metric at a specific step",code:'for step in range(10):\n mlflow.log_metric("loss", 1.0 / (step + 1), step=step)\n',category:"Logging"},{title:"Log Artifact",description:"Log a file as an artifact",code:'mlflow.log_artifact("path/to/file.txt")\n',category:"Logging"},{title:"Log Artifacts Directory",description:"Log all files in a directory",code:'mlflow.log_artifacts("path/to/directory")\n',category:"Logging"},{title:"Log Text",description:"Log text as an artifact",code:'mlflow.log_text("Some text content", "output.txt")\n',category:"Logging"},{title:"Log JSON",description:"Log a dictionary as JSON artifact",code:'import json\n\ndata = {"key": "value"}\nmlflow.log_dict(data, "data.json")\n',category:"Logging"},{title:"Log Image",description:"Log an image as an artifact",code:'from PIL import Image\n\nimg = Image.open("image.png")\nmlflow.log_image(img, "image.png")\n',category:"Logging"},{title:"Log Figure",description:"Log a matplotlib figure",code:'import matplotlib.pyplot as plt\n\nfig, ax = plt.subplots()\nax.plot([1, 2, 3], [1, 4, 9])\nmlflow.log_figure(fig, "plot.png")\n',category:"Logging"},{title:"Log Model (sklearn)",description:"Log a scikit-learn model",code:'from sklearn.ensemble import RandomForestClassifier\n\nmodel = RandomForestClassifier()\nmlflow.sklearn.log_model(model, "model")\n',category:"Models"},{title:"Log Model (PyTorch)",description:"Log a PyTorch model",code:'import torch\n\nmodel = torch.nn.Linear(10, 1)\nmlflow.pytorch.log_model(model, "model")\n',category:"Models"},{title:"Log Model (TensorFlow)",description:"Log a TensorFlow/Keras model",code:'import tensorflow as tf\n\nmodel = tf.keras.Sequential([...])\nmlflow.tensorflow.log_model(model, "model")\n',category:"Models"},{title:"Log Model (XGBoost)",description:"Log an XGBoost model",code:'import xgboost as xgb\n\nmodel = xgb.XGBClassifier()\nmlflow.xgboost.log_model(model, "model")\n',category:"Models"},{title:"Load Model",description:"Load a logged model",code:'model = mlflow.pyfunc.load_model("runs:/run-id/model")\n',category:"Models"},{title:"Load Model from Registry",description:"Load a model from the model registry",code:'model = mlflow.pyfunc.load_model("models:/model-name/1")\n',category:"Models"},{title:"Register Model",description:"Register a model in the model registry",code:'mlflow.register_model(\n model_uri="runs:/run-id/model",\n name="my_model"\n)\n',category:"Models"},{title:"List Registered Models",description:"List all registered models",code:'models = mlflow.search_registered_models()\nfor model in models:\n print(f"{model.name}: {model.latest_versions}")\n',category:"Model Registry"},{title:"Get Model Version",description:"Get details of a specific model version",code:'model_version = mlflow.get_model_version(\n name="my_model",\n version=1\n)\nprint(f"Stage: {model_version.current_stage}")\n',category:"Model Registry"},{title:"Transition Model Stage",description:"Transition a model version to a new stage",code:'mlflow.transition_model_version_stage(\n name="my_model",\n version=1,\n stage="Production"\n)\n',category:"Model Registry"},{title:"Download Artifacts",description:"Download artifacts from a run",code:'artifact_path = mlflow.artifacts.download_artifacts(\n run_id="run-id",\n artifact_path="model"\n)\nprint(f"Downloaded to: {artifact_path}")\n',category:"Artifacts"},{title:"Load Artifact as Text",description:"Load an artifact as text",code:'text = mlflow.artifacts.load_text("runs:/run-id/artifact.txt")\nprint(text)\n',category:"Artifacts"},{title:"Load Artifact as JSON",description:"Load an artifact as JSON",code:'import json\n\ntext = mlflow.artifacts.load_text("runs:/run-id/data.json")\ndata = json.loads(text)\nprint(data)\n',category:"Artifacts"},{title:"List Artifacts",description:"List artifacts in a run",code:'artifacts = mlflow.artifacts.list_artifacts("runs:/run-id")\nfor artifact in artifacts:\n print(f"{artifact.path} ({artifact.file_size} bytes)")\n',category:"Artifacts"}];function _(e){const{app:t}=e,l=x.reduce((e,t)=>(e[t.category]||(e[t.category]=[]),e[t.category].push(t),e),{}),a=["Setup","Experiments","Runs","Logging"],n=Object.keys(l).filter(e=>!a.includes(e)).sort(),s=[...a.filter(e=>l[e]),...n];return u().createElement("div",{className:"mlflow-shortcuts-panel"},u().createElement("div",{className:"mlflow-shortcuts-header"},u().createElement("h3",null,"MLflow Shortcuts"),u().createElement("p",{className:"mlflow-shortcuts-description"},"Click on any shortcut to insert it into the active notebook cell")),u().createElement("div",{className:"mlflow-shortcuts-content"},s.map(e=>u().createElement("div",{key:e,className:"mlflow-shortcuts-category"},u().createElement("h4",{className:"mlflow-shortcuts-category-title"},e),u().createElement("div",{className:"mlflow-shortcuts-list"},l[e].map((l,a)=>u().createElement("div",{key:`${e}-${a}`,className:"mlflow-shortcut-item",onClick:()=>(e=>{const l=t.shell.currentWidget;if(!l)return void alert("Please open a notebook first");const a=l.content;if(!a||!a.activeCell){const l=t.shell.widgets("main");for(const a of l){const l=a.content;if(l&&l.activeCell){const n=l.activeCell;if(n.model){if(n.model.sharedModel)return n.model.sharedModel.setSource(e),void t.shell.activateById(a.id);if(n.model.value)return n.model.value.text=e,void t.shell.activateById(a.id)}}}return void alert("Could not find active notebook cell. Please make sure a notebook is open and a cell is selected.")}const n=a.activeCell;n&&n.model?(n.model.sharedModel?n.model.sharedModel.setSource(e):n.model.value&&(n.model.value.text=e),t.shell.activateById(l.id)):alert("Please select a cell in the notebook")})(l.code),title:l.description},u().createElement("div",{className:"mlflow-shortcut-title"},l.title),u().createElement("div",{className:"mlflow-shortcut-description"},l.description),u().createElement("div",{className:"mlflow-shortcut-code-preview"},u().createElement("code",null,l.code.split("\n")[0],l.code.split("\n").length>1?"...":"")))))))))}const k=new c.LabIcon({name:"jupyterlab-mlflow:icon",svgstr:'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>Mlflow SVG Icon</title><path fill="currentColor" d="M11.883.002a12.044 12.044 0 0 0-9.326 19.463l3.668-2.694A7.573 7.573 0 0 1 12.043 4.45v2.867l6.908-5.14A12 12 0 0 0 11.883.002m9.562 4.533L17.777 7.23a7.573 7.573 0 0 1-5.818 12.322v-2.867l-6.908 5.14a12.046 12.046 0 0 0 16.394-17.29"/></svg>'});function S(e){const{settings:t,mlflowClient:l,app:n}=e,[s,o]=(0,d.useState)("tree"),[i,r]=(0,d.useState)(!1),[c,p]=(0,d.useState)(""),[f,g]=(0,d.useState)(null);return(0,d.useEffect)(()=>{t.getTrackingUri().then(e=>{p(e),e?l.setTrackingUri(e):r(!0)})},[t,l]),u().createElement("div",{className:"mlflow-panel"},u().createElement("div",{className:"mlflow-panel-header"},u().createElement("div",{className:"mlflow-panel-title"},"MLflow"),u().createElement("div",{className:"mlflow-panel-controls"},u().createElement("button",{className:"mlflow-button "+("tree"===s?"active":""),onClick:()=>o("tree"),title:"Tree View"},"📁"),u().createElement("button",{className:"mlflow-button "+("details"===s?"active":""),onClick:()=>{o("details"),g(null)},title:"Details View"},"📋"),u().createElement("button",{className:"mlflow-button "+("shortcuts"===s?"active":""),onClick:()=>o("shortcuts"),title:"MLflow Shortcuts"},"⚡"),u().createElement("button",{className:"mlflow-button",onClick:async()=>{const e=c||await t.getTrackingUri();if(!e)return void r(!0);const l=e.replace(/\/$/,""),s=new m.Widget;s.addClass("mlflow-iframe-container"),s.node.innerHTML=`\n <div class="mlflow-iframe-header">\n <a href="${l}" target="_blank" rel="noopener noreferrer" \n class="mlflow-pop-out-link" title="Open MLflow UI in new browser tab">\n 🔗 Open in new tab\n </a>\n </div>\n <iframe src="${l}" \n sandbox="allow-same-origin allow-scripts allow-popups allow-forms"\n class="mlflow-iframe"\n referrerpolicy="no-referrer">\n </iframe>\n `;const o=new a.MainAreaWidget({content:s});o.id="mlflow-ui-widget",o.title.label="MLflow UI",o.title.icon=k,o.title.closable=!0,n.shell.add(o,"main",{activate:!0})},title:"Open MLflow UI in new tab"},"🌐"),u().createElement("button",{className:"mlflow-button",onClick:()=>r(!i),title:"Settings"},"⚙️"))),i&&u().createElement("div",{className:"mlflow-settings-container"},u().createElement(N,{settings:t,mlflowClient:l,trackingUri:c,onTrackingUriChange:async e=>{p(e),e&&l.setTrackingUri(e),await t.setTrackingUri(e)},onClose:()=>r(!1)})),u().createElement("div",{className:"mlflow-panel-content"},"tree"===s?u().createElement(w,{mlflowClient:l,onOpenObject:(e,t,l)=>{o("details"),g({type:e,id:t,data:l})}}):"details"===s?u().createElement(b,{mlflowClient:l,app:n,initialSelection:f||void 0,onObjectSelect:(e,t)=>{e&&g({type:e,id:t})}}):u().createElement(_,{app:n})))}class C extends m.Widget{constructor(e,t,l){super(),this._settings=e,this._mlflowClient=t,this._app=l,this.addClass("mlflow-widget"),this.id="mlflow-widget",this.title.closable=!0}onAfterAttach(){this._render()}onBeforeDetach(){f().unmountComponentAtNode(this.node)}_render(){f().render(u().createElement(S,{settings:this._settings,mlflowClient:this._mlflowClient,app:this._app}),this.node)}}class L{constructor(e,t){this._registry=e,this._settings=null}async initialize(){const e="jupyterlab-mlflow:plugin";try{this._settings=await this._registry.load(e)}catch(t){console.error(`Failed to load settings for ${e}:`,t)}}async getTrackingUri(){if(this._settings){const e=this._settings.get("mlflowTrackingUri").composite;if(e&&""!==e.trim())return e.trim()}return""}async setTrackingUri(e){if(!this._settings)throw new Error("Settings not initialized");await this._settings.set("mlflowTrackingUri",e)}getSettings(){return this._settings?{mlflowTrackingUri:this._settings.get("mlflowTrackingUri").composite}:{mlflowTrackingUri:""}}get isLoaded(){return null!==this._settings}}class M{constructor(e){this._trackingUri="",this._serverSettings=e||i.ServerConnection.makeSettings()}setTrackingUri(e){this._trackingUri=e}getBaseUrl(){return this._serverSettings.baseUrl}getServerSettings(){return this._serverSettings}getApiUrl(e){const t=this._serverSettings.baseUrl,l=r.URLExt.join(t,"mlflow","api",e);if(this._trackingUri){const e=l.includes("?")?"&":"?";return`${l}${e}tracking_uri=${encodeURIComponent(this._trackingUri)}`}return l}async request(e,t={}){const l=this.getApiUrl(e),a=await i.ServerConnection.makeRequest(l,{...t,headers:{...t.headers,"Content-Type":"application/json"}},this._serverSettings);if(!a.ok){const e=await a.json().catch(()=>({error:a.statusText}));throw new Error(e.error||`HTTP ${a.status}: ${a.statusText}`)}return a.json()}async testConnection(e){const t=this.getApiUrl("connection/test"),l=await i.ServerConnection.makeRequest(t,{method:"POST",body:JSON.stringify({tracking_uri:e}),headers:{"Content-Type":"application/json"}},this._serverSettings);return l.ok?l.json():{success:!1,error:(await l.json().catch(()=>({error:l.statusText}))).error||`HTTP ${l.status}: ${l.statusText}`}}async getExperiments(){return(await this.request("experiments")).experiments}async getExperiment(e){return this.request(`experiments/${e}`)}async getRuns(e){return(await this.request(`experiments/${e}/runs`)).runs}async getRun(e){return this.request(`runs/${e}`)}async getArtifacts(e,t){const l=t?`runs/${e}/artifacts?path=${encodeURIComponent(t)}`:`runs/${e}/artifacts`;return this.request(l)}async downloadArtifact(e,t){const l=this.getApiUrl(`runs/${e}/artifacts/download?path=${encodeURIComponent(t)}`);console.log("Downloading artifact from URL:",l);const a=await i.ServerConnection.makeRequest(l,{},this._serverSettings);if(console.log("Download response status:",a.status,a.statusText),console.log("Download response headers:",a.headers),!a.ok){let e=a.statusText;try{e=(await a.json()).error||e}catch(t){try{e=await a.text()||e}catch(e){}}throw new Error(`HTTP ${a.status}: ${e}`)}const n=await a.blob();if(console.log("Downloaded blob size:",n.size,"type:",n.type),0===n.size)throw new Error("Downloaded file is empty");return n}async getModels(){return(await this.request("models")).models}async getModel(e){return this.request(`models/${encodeURIComponent(e)}`)}async getLocalServerStatus(){const e=this._serverSettings.baseUrl,t=r.URLExt.join(e,"mlflow","api","local-server"),l=await i.ServerConnection.makeRequest(t,{method:"GET"},this._serverSettings);if(!l.ok){const e=await l.json().catch(()=>({error:l.statusText}));throw new Error(e.error||`HTTP ${l.status}: ${l.statusText}`)}return l.json()}async startLocalServer(e=5e3,t="sqlite:///mlflow.db",l,a){const n=this._serverSettings.baseUrl,s=r.URLExt.join(n,"mlflow","api","local-server"),o=JSON.stringify({port:e,tracking_uri:t,artifact_uri:l,backend_uri:a}),c=await i.ServerConnection.makeRequest(s,{method:"POST",body:o,headers:{"Content-Type":"application/json"}},this._serverSettings);if(!c.ok){const e=await c.json().catch(()=>({error:c.statusText}));throw new Error(e.error||`HTTP ${c.status}: ${c.statusText}`)}return c.json()}async stopLocalServer(){const e=this._serverSettings.baseUrl,t=r.URLExt.join(e,"mlflow","api","local-server"),l=await i.ServerConnection.makeRequest(t,{method:"DELETE"},this._serverSettings);if(!l.ok){const e=await l.json().catch(()=>({error:l.statusText}));throw new Error(e.error||`HTTP ${l.status}: ${l.statusText}`)}return l.json()}}var T;!function(e){e.open="mlflow:open",e.toggle="mlflow:toggle",e.openUI="mlflow:open-ui"}(T||(T={}));const U=new c.LabIcon({name:"jupyterlab-mlflow:icon",svgstr:'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>Mlflow SVG Icon</title><path fill="currentColor" d="M11.883.002a12.044 12.044 0 0 0-9.326 19.463l3.668-2.694A7.573 7.573 0 0 1 12.043 4.45v2.867l6.908-5.14A12 12 0 0 0 11.883.002m9.562 4.533L17.777 7.23a7.573 7.573 0 0 1-5.818 12.322v-2.867l-6.908 5.14a12.046 12.046 0 0 0 16.394-17.29"/></svg>'}),I={id:"jupyterlab-mlflow:plugin",autoStart:!0,requires:[n.ISettingRegistry,o.ITranslator],optional:[a.ICommandPalette,s.IMainMenu],activate:async(e,t,l,n,s)=>{console.log("JupyterLab extension jupyterlab-mlflow is activated!");const o=new L(t,l);await o.initialize();const c=i.ServerConnection.makeSettings(),d=new M(c);try{const e=c.baseUrl,t=r.URLExt.join(e,"mlflow","api","health"),l=await i.ServerConnection.makeRequest(t,{},c);l.ok||console.warn("⚠️ jupyterlab-mlflow: Server extension may not be loaded. Health check returned status:",l.status,"\nPlease ensure the server extension is enabled: jupyter server extension enable jupyterlab_mlflow.serverextension")}catch(e){console.error("❌ jupyterlab-mlflow: Server extension not loaded or not accessible. Error checking health endpoint:",e,"\nPlease ensure the server extension is enabled: jupyter server extension enable jupyterlab_mlflow.serverextension")}const u=await o.getTrackingUri();u&&d.setTrackingUri(u);const p=new C(o,d,e);p.id="mlflow-widget",p.title.icon=U,e.shell.add(p,"left",{rank:1e3});const{commands:f}=e;f.addCommand(T.open,{label:"Open MLflow",execute:()=>{p.isAttached||e.shell.add(p,"left",{rank:1e3}),e.shell.activateById(p.id)}}),f.addCommand(T.toggle,{label:"Toggle MLflow",execute:()=>{p.isAttached?p.dispose():(e.shell.add(p,"left",{rank:1e3}),e.shell.activateById(p.id))}}),f.addCommand(T.openUI,{label:"Open MLflow UI",execute:async()=>{const t=await o.getTrackingUri();if(!t)return p.isAttached||e.shell.add(p,"left",{rank:1e3}),void e.shell.activateById(p.id);const l=t.replace(/\/$/,""),n=new m.Widget;n.addClass("mlflow-iframe-container"),n.node.innerHTML=`\n <div class="mlflow-iframe-header">\n <a href="${l}" target="_blank" rel="noopener noreferrer" \n class="mlflow-pop-out-link" title="Open MLflow UI in new browser tab">\n 🔗 Open in new tab\n </a>\n </div>\n <iframe src="${l}" \n sandbox="allow-same-origin allow-scripts allow-popups allow-forms"\n class="mlflow-iframe"\n referrerpolicy="no-referrer">\n </iframe>\n `;const s=new a.MainAreaWidget({content:n});s.id="mlflow-ui-widget",s.title.label="MLflow UI",s.title.icon=U,s.title.closable=!0,e.shell.add(s,"main",{activate:!0})}}),n&&(n.addItem({command:T.open,category:"MLflow"}),n.addItem({command:T.openUI,category:"MLflow"})),s&&s.viewMenu.addGroup([{command:T.toggle}])}}}}]);
|