workbench 0.8.224__py3-none-any.whl → 0.8.234__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.
Files changed (61) hide show
  1. workbench/__init__.py +1 -0
  2. workbench/algorithms/dataframe/__init__.py +2 -0
  3. workbench/algorithms/dataframe/smart_aggregator.py +161 -0
  4. workbench/algorithms/sql/column_stats.py +0 -1
  5. workbench/algorithms/sql/correlations.py +0 -1
  6. workbench/algorithms/sql/descriptive_stats.py +0 -1
  7. workbench/api/meta.py +0 -1
  8. workbench/cached/cached_meta.py +0 -1
  9. workbench/cached/cached_model.py +37 -7
  10. workbench/core/artifacts/endpoint_core.py +12 -2
  11. workbench/core/artifacts/feature_set_core.py +66 -8
  12. workbench/core/cloud_platform/cloud_meta.py +0 -1
  13. workbench/model_script_utils/model_script_utils.py +30 -0
  14. workbench/model_script_utils/uq_harness.py +0 -1
  15. workbench/model_scripts/chemprop/chemprop.template +3 -0
  16. workbench/model_scripts/chemprop/generated_model_script.py +3 -3
  17. workbench/model_scripts/chemprop/model_script_utils.py +30 -0
  18. workbench/model_scripts/custom_models/chem_info/mol_descriptors.py +0 -1
  19. workbench/model_scripts/custom_models/chem_info/molecular_descriptors.py +0 -1
  20. workbench/model_scripts/custom_models/chem_info/morgan_fingerprints.py +0 -1
  21. workbench/model_scripts/pytorch_model/generated_model_script.py +50 -32
  22. workbench/model_scripts/pytorch_model/model_script_utils.py +30 -0
  23. workbench/model_scripts/pytorch_model/pytorch.template +47 -29
  24. workbench/model_scripts/pytorch_model/uq_harness.py +0 -1
  25. workbench/model_scripts/script_generation.py +0 -1
  26. workbench/model_scripts/xgb_model/model_script_utils.py +30 -0
  27. workbench/model_scripts/xgb_model/uq_harness.py +0 -1
  28. workbench/themes/dark/custom.css +85 -8
  29. workbench/themes/dark/plotly.json +6 -6
  30. workbench/themes/light/custom.css +172 -70
  31. workbench/themes/light/plotly.json +9 -9
  32. workbench/themes/midnight_blue/custom.css +48 -29
  33. workbench/themes/midnight_blue/plotly.json +1 -1
  34. workbench/utils/aws_utils.py +0 -1
  35. workbench/utils/chem_utils/mol_descriptors.py +0 -1
  36. workbench/utils/chem_utils/vis.py +137 -27
  37. workbench/utils/clientside_callbacks.py +41 -0
  38. workbench/utils/markdown_utils.py +61 -0
  39. workbench/utils/pipeline_utils.py +0 -1
  40. workbench/utils/plot_utils.py +8 -110
  41. workbench/web_interface/components/experiments/outlier_plot.py +0 -1
  42. workbench/web_interface/components/model_plot.py +2 -0
  43. workbench/web_interface/components/plugin_unit_test.py +0 -1
  44. workbench/web_interface/components/plugins/ag_table.py +2 -4
  45. workbench/web_interface/components/plugins/confusion_matrix.py +3 -6
  46. workbench/web_interface/components/plugins/model_details.py +28 -11
  47. workbench/web_interface/components/plugins/scatter_plot.py +56 -43
  48. workbench/web_interface/components/settings_menu.py +2 -1
  49. workbench/web_interface/page_views/main_page.py +0 -1
  50. {workbench-0.8.224.dist-info → workbench-0.8.234.dist-info}/METADATA +31 -29
  51. {workbench-0.8.224.dist-info → workbench-0.8.234.dist-info}/RECORD +55 -59
  52. {workbench-0.8.224.dist-info → workbench-0.8.234.dist-info}/WHEEL +1 -1
  53. workbench/themes/quartz/base_css.url +0 -1
  54. workbench/themes/quartz/custom.css +0 -117
  55. workbench/themes/quartz/plotly.json +0 -642
  56. workbench/themes/quartz_dark/base_css.url +0 -1
  57. workbench/themes/quartz_dark/custom.css +0 -131
  58. workbench/themes/quartz_dark/plotly.json +0 -642
  59. {workbench-0.8.224.dist-info → workbench-0.8.234.dist-info}/entry_points.txt +0 -0
  60. {workbench-0.8.224.dist-info → workbench-0.8.234.dist-info}/licenses/LICENSE +0 -0
  61. {workbench-0.8.224.dist-info → workbench-0.8.234.dist-info}/top_level.txt +0 -0
@@ -3,14 +3,19 @@
3
3
  from typing import Union
4
4
 
5
5
  # Dash Imports
6
- from dash import html, callback, dcc, Input, Output, State
6
+ from dash import html, callback, dcc, no_update, Input, Output, State
7
7
 
8
8
  # Workbench Imports
9
9
  from workbench.api import ModelType, ParameterStore
10
10
  from workbench.cached.cached_model import CachedModel
11
- from workbench.utils.markdown_utils import health_tag_markdown
11
+ from workbench.utils.markdown_utils import (
12
+ health_tag_markdown,
13
+ tags_to_markdown,
14
+ dict_to_markdown,
15
+ dict_to_collapsible_html,
16
+ df_to_html_table,
17
+ )
12
18
  from workbench.web_interface.components.plugin_interface import PluginInterface, PluginPage, PluginInputType
13
- from workbench.utils.markdown_utils import tags_to_markdown, dict_to_markdown, dict_to_collapsible_html
14
19
 
15
20
 
16
21
  class ModelDetails(PluginInterface):
@@ -44,7 +49,7 @@ class ModelDetails(PluginInterface):
44
49
  dcc.Markdown(id=f"{self.component_id}-summary", dangerously_allow_html=True),
45
50
  html.H5(children="Inference Metrics", style={"marginTop": "20px"}),
46
51
  dcc.Dropdown(id=f"{self.component_id}-dropdown", className="dropdown"),
47
- dcc.Markdown(id=f"{self.component_id}-metrics"),
52
+ dcc.Markdown(id=f"{self.component_id}-metrics", dangerously_allow_html=True),
48
53
  ],
49
54
  )
50
55
 
@@ -66,7 +71,9 @@ class ModelDetails(PluginInterface):
66
71
 
67
72
  Args:
68
73
  model (CachedModel): An instantiated CachedModel object
69
- **kwargs: Additional keyword arguments (unused)
74
+ **kwargs: Additional keyword arguments
75
+ - inference_run: Current inference run selection (to preserve user's choice)
76
+ - previous_model_name: Name of the previously selected model
70
77
 
71
78
  Returns:
72
79
  list: A list of the updated property values for the plugin
@@ -80,10 +87,21 @@ class ModelDetails(PluginInterface):
80
87
 
81
88
  # Populate the inference runs dropdown
82
89
  inference_runs, default_run = self.get_inference_runs()
83
- metrics = self.inference_metrics(default_run)
84
90
 
85
- # Return the updated property values for the plugin
86
- return [header, details, inference_runs, default_run, metrics]
91
+ # Check if the model changed
92
+ previous_model_name = kwargs.get("previous_model_name")
93
+ current_inference_run = kwargs.get("inference_run")
94
+ model_changed = previous_model_name != model.name
95
+
96
+ # Only preserve the inference run if the model hasn't changed AND the selection is valid
97
+ if not model_changed and current_inference_run and current_inference_run in inference_runs:
98
+ # Same model, preserve the user's selection - use no_update for dropdown value
99
+ metrics = self.inference_metrics(current_inference_run)
100
+ return [header, details, inference_runs, no_update, metrics]
101
+ else:
102
+ # New model or invalid selection - use default
103
+ metrics = self.inference_metrics(default_run)
104
+ return [header, details, inference_runs, default_run, metrics]
87
105
 
88
106
  def register_internal_callbacks(self):
89
107
  @callback(
@@ -175,15 +193,14 @@ class ModelDetails(PluginInterface):
175
193
  markdown += " \nNo Data \n"
176
194
  else:
177
195
  markdown += " \n"
178
- metrics = metrics.round(3)
179
196
 
180
197
  # If the model is a classification model, have the index sorting match the class labels
181
198
  if self.current_model.model_type == ModelType.CLASSIFIER:
182
- # Sort the metrics by the class labels (if they match)
183
199
  class_labels = self.current_model.class_labels()
184
200
  if set(metrics.index) == set(class_labels):
185
201
  metrics = metrics.reindex(class_labels)
186
- markdown += metrics.to_markdown()
202
+
203
+ markdown += df_to_html_table(metrics)
187
204
 
188
205
  # Get additional inference metrics if they exist
189
206
  model_name = self.current_model.name
@@ -1,6 +1,7 @@
1
1
  import base64
2
+ import numpy as np
2
3
  import pandas as pd
3
- from dash import dcc, html, callback, Input, Output, no_update
4
+ from dash import dcc, html, callback, clientside_callback, Input, Output, no_update
4
5
  import plotly.graph_objects as go
5
6
  import plotly.express as px
6
7
  from dash.exceptions import PreventUpdate
@@ -8,7 +9,9 @@ from dash.exceptions import PreventUpdate
8
9
  # Workbench Imports
9
10
  from workbench.web_interface.components.plugin_interface import PluginInterface, PluginPage, PluginInputType
10
11
  from workbench.utils.theme_manager import ThemeManager
11
- from workbench.utils.plot_utils import prediction_intervals, molecule_hover_tooltip
12
+ from workbench.utils.plot_utils import prediction_intervals
13
+ from workbench.utils.chem_utils.vis import molecule_hover_tooltip
14
+ from workbench.utils.clientside_callbacks import circle_overlay_callback
12
15
 
13
16
 
14
17
  class ScatterPlot(PluginInterface):
@@ -35,10 +38,10 @@ class ScatterPlot(PluginInterface):
35
38
  self.df = None
36
39
  self.show_axes = show_axes
37
40
  self.theme_manager = ThemeManager()
38
- self.colorscale = self.theme_manager.colorscale()
39
41
  self.has_smiles = False # Track if dataframe has smiles column for molecule hover
40
42
  self.smiles_column = None
41
43
  self.id_column = None
44
+ self.hover_background = None # Cached background color for molecule hover tooltip
42
45
 
43
46
  # Call the parent class constructor
44
47
  super().__init__()
@@ -78,7 +81,7 @@ class ScatterPlot(PluginInterface):
78
81
  id=f"{component_id}-graph",
79
82
  figure=self.display_text("Waiting for Data..."),
80
83
  config={"scrollZoom": True},
81
- style={"height": "100%"},
84
+ style={"height": "500px", "width": "100%"},
82
85
  clear_on_unhover=True,
83
86
  ),
84
87
  # Controls: X, Y, Color, Label Dropdowns, and Regression Line Checkbox
@@ -178,6 +181,10 @@ class ScatterPlot(PluginInterface):
178
181
  list: A list of updated property values (figure, x options, y options, color options,
179
182
  x default, y default, color default).
180
183
  """
184
+ # Get the colorscale and background color from the current theme
185
+ self.colorscale = self.theme_manager.colorscale()
186
+ self.hover_background = self.theme_manager.background()
187
+
181
188
  # Get the limit for the number of rows to plot
182
189
  limit = kwargs.get("limit", 20000)
183
190
 
@@ -198,7 +205,10 @@ class ScatterPlot(PluginInterface):
198
205
 
199
206
  # Check if the dataframe has smiles/id columns for molecule hover rendering
200
207
  self.smiles_column = next((col for col in self.df.columns if col.lower() == "smiles"), None)
201
- self.id_column = kwargs.get("id_column") or next((col for col in self.df.columns if col.lower() == "id"), None)
208
+ # Use provided id_column, or auto-detect "id" column, or fall back to first column
209
+ self.id_column = kwargs.get("id_column") or next(
210
+ (col for col in self.df.columns if col.lower() == "id"), self.df.columns[0]
211
+ )
202
212
  self.has_smiles = self.smiles_column is not None
203
213
 
204
214
  # Identify numeric columns
@@ -206,10 +216,11 @@ class ScatterPlot(PluginInterface):
206
216
  if len(numeric_columns) < 3:
207
217
  raise ValueError("At least three numeric columns are required for x, y, and color.")
208
218
 
209
- # Default x, y, and color (for color, default to a numeric column)
219
+ # Default x, y, and color (for color, prefer 'confidence' if it exists)
210
220
  x_default = kwargs.get("x", numeric_columns[0])
211
221
  y_default = kwargs.get("y", numeric_columns[1])
212
- color_default = kwargs.get("color", numeric_columns[2])
222
+ default_color = "confidence" if "confidence" in self.df.columns else numeric_columns[2]
223
+ color_default = kwargs.get("color", default_color)
213
224
  regression_line = kwargs.get("regression_line", False)
214
225
 
215
226
  # Create the default scatter plot
@@ -238,7 +249,6 @@ class ScatterPlot(PluginInterface):
238
249
  y_col: str,
239
250
  color_col: str,
240
251
  regression_line: bool = False,
241
- marker_size: int = 15,
242
252
  ) -> go.Figure:
243
253
  """Create a Plotly Scatter Plot figure.
244
254
 
@@ -248,12 +258,20 @@ class ScatterPlot(PluginInterface):
248
258
  y_col (str): The column to use for the y-axis.
249
259
  color_col (str): The column to use for the color scale.
250
260
  regression_line (bool): Whether to include a regression line.
251
- marker_size (int): Size of the markers. Default is 15.
252
261
 
253
262
  Returns:
254
263
  go.Figure: A Plotly Figure object.
255
264
  """
256
265
 
266
+ # If aggregation_count is present, sort so largest counts are drawn first (underneath)
267
+ # and compute marker sizes using square root (between log and linear)
268
+ if "aggregation_count" in df.columns:
269
+ df = df.sort_values("aggregation_count", ascending=False).reset_index(drop=True)
270
+ # Scale: base_size (15) + (sqrt(count) - 1) * factor, so count=1 stays at base_size
271
+ marker_sizes = 15 + (np.sqrt(df["aggregation_count"]) - 1) * 3
272
+ else:
273
+ marker_sizes = 15
274
+
257
275
  # Helper to generate hover text for each point.
258
276
  def generate_hover_text(row):
259
277
  return "<br>".join([f"{col}: {row[col]}" for col in self.hover_columns])
@@ -295,11 +313,11 @@ class ScatterPlot(PluginInterface):
295
313
  hovertemplate=hovertemplate,
296
314
  customdata=df[custom_data_cols] if custom_data_cols else None,
297
315
  marker=dict(
298
- size=marker_size,
316
+ size=marker_sizes,
299
317
  color=marker_color,
300
318
  colorscale=self.colorscale,
301
319
  colorbar=colorbar,
302
- opacity=0.8,
320
+ opacity=0.9,
303
321
  line=dict(color="rgba(0,0,0,0.25)", width=1),
304
322
  ),
305
323
  )
@@ -315,6 +333,15 @@ class ScatterPlot(PluginInterface):
315
333
  for i, cat in enumerate(categories):
316
334
  sub_df = df[df[color_col] == cat]
317
335
  sub_hovertext = hovertext.loc[sub_df.index] if hovertext is not None else None
336
+ # Get marker sizes for this subset (handles both array and scalar)
337
+ if isinstance(marker_sizes, (pd.Series, np.ndarray)):
338
+ sub_marker_sizes = (
339
+ marker_sizes.loc[sub_df.index]
340
+ if isinstance(marker_sizes, pd.Series)
341
+ else marker_sizes[sub_df.index]
342
+ )
343
+ else:
344
+ sub_marker_sizes = marker_sizes
318
345
  trace = go.Scattergl(
319
346
  x=sub_df[x_col],
320
347
  y=sub_df[y_col],
@@ -325,7 +352,7 @@ class ScatterPlot(PluginInterface):
325
352
  hovertemplate=hovertemplate,
326
353
  customdata=sub_df[custom_data_cols] if custom_data_cols else None,
327
354
  marker=dict(
328
- size=marker_size,
355
+ size=sub_marker_sizes,
329
356
  color=discrete_colors[i % len(discrete_colors)],
330
357
  opacity=0.8,
331
358
  line=dict(color="rgba(0,0,0,0.25)", width=1),
@@ -405,35 +432,14 @@ class ScatterPlot(PluginInterface):
405
432
 
406
433
  raise PreventUpdate
407
434
 
408
- @callback(
435
+ # Clientside callback for circle overlay - runs in browser, no server round trip
436
+ clientside_callback(
437
+ circle_overlay_callback(self._circle_data_uri),
409
438
  Output(f"{self.component_id}-overlay", "show"),
410
439
  Output(f"{self.component_id}-overlay", "bbox"),
411
440
  Output(f"{self.component_id}-overlay", "children"),
412
441
  Input(f"{self.component_id}-graph", "hoverData"),
413
442
  )
414
- def _scatter_circle_overlay(hover_data):
415
- """Show white circle overlay centered on the hovered point."""
416
- if hover_data is None:
417
- return False, no_update, no_update
418
-
419
- # Extract bounding box from hoverData
420
- bbox = hover_data["points"][0]["bbox"]
421
-
422
- # Use pre-computed circle SVG
423
- svg_image = html.Img(src=self._circle_data_uri, style={"width": "100px", "height": "100px"})
424
-
425
- # Get the center of the bounding box
426
- center_x = (bbox["x0"] + bbox["x1"]) / 2
427
- center_y = (bbox["y0"] + bbox["y1"]) / 2
428
-
429
- # The tooltip should be centered on the point
430
- adjusted_bbox = {
431
- "x0": center_x - 50,
432
- "x1": center_x + 50,
433
- "y0": center_y - 162,
434
- "y1": center_y - 62,
435
- }
436
- return True, adjusted_bbox, [svg_image]
437
443
 
438
444
  @callback(
439
445
  Output(f"{self.component_id}-molecule-tooltip", "show"),
@@ -459,17 +465,24 @@ class ScatterPlot(PluginInterface):
459
465
  smiles = customdata
460
466
  mol_id = None
461
467
 
462
- # Generate molecule tooltip with ID header
468
+ # Generate molecule tooltip with ID header (use cached background color)
463
469
  mol_width, mol_height = 300, 200
464
- children = molecule_hover_tooltip(smiles, mol_id=mol_id, width=mol_width, height=mol_height)
470
+ children = molecule_hover_tooltip(
471
+ smiles, mol_id=mol_id, width=mol_width, height=mol_height, background=self.hover_background
472
+ )
465
473
 
466
- # Extract bounding box and offset the molecule tooltip to the right of the point
474
+ # Position molecule tooltip above and slightly right of the point
467
475
  bbox = hover_data["points"][0]["bbox"]
476
+ center_x = (bbox["x0"] + bbox["x1"]) / 2
477
+ center_y = (bbox["y0"] + bbox["y1"]) / 2
478
+ x_offset = 5 # Slight offset to the right
479
+ y_offset = mol_height + 50 # Above the point
480
+
468
481
  adjusted_bbox = {
469
- "x0": bbox["x0"] + 15,
470
- "x1": bbox["x1"] + mol_width + 15,
471
- "y0": bbox["y0"] - (2 * mol_height + 60),
472
- "y1": bbox["y1"] - (mol_height + 60),
482
+ "x0": center_x + x_offset,
483
+ "x1": center_x + x_offset + mol_width,
484
+ "y0": center_y - mol_height - y_offset,
485
+ "y1": center_y - y_offset,
473
486
  }
474
487
  return True, adjusted_bbox, children
475
488
 
@@ -75,8 +75,9 @@ class SettingsMenu:
75
75
  style={"display": "flex", "flexDirection": "column", "alignItems": "center", "justifyContent": "center"},
76
76
  )
77
77
 
78
- # Build menu items: Status, License, divider, Themes submenu
78
+ # Build menu items: Home, Status, License, divider, Themes submenu
79
79
  menu_items = [
80
+ dbc.DropdownMenuItem("Home", href="/"),
80
81
  dbc.DropdownMenuItem("Status", href="/status", external_link=True, target="_blank"),
81
82
  dbc.DropdownMenuItem("License", href="/license", external_link=True, target="_blank"),
82
83
  dbc.DropdownMenuItem(divider=True),
@@ -3,7 +3,6 @@
3
3
  import pandas as pd
4
4
  from typing import Optional, Tuple
5
5
 
6
-
7
6
  # Workbench Imports
8
7
  from workbench.web_interface.page_views.page_view import PageView
9
8
  from workbench.utils.symbols import tag_symbols
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workbench
3
- Version: 0.8.224
3
+ Version: 0.8.234
4
4
  Summary: Workbench: A Dashboard and Python API for creating and deploying AWS SageMaker Model Pipelines
5
5
  Author-email: SuperCowPowers LLC <support@supercowpowers.com>
6
6
  License: MIT License
@@ -60,7 +60,7 @@ Requires-Dist: plotly>=6.0.0; extra == "ui"
60
60
  Requires-Dist: dash>=3.0.0; extra == "ui"
61
61
  Requires-Dist: dash-bootstrap-components>=1.6.0; extra == "ui"
62
62
  Requires-Dist: dash-bootstrap-templates>=1.3.0; extra == "ui"
63
- Requires-Dist: dash_ag_grid; extra == "ui"
63
+ Requires-Dist: dash_ag_grid>=33.3.3; extra == "ui"
64
64
  Requires-Dist: tabulate>=0.9.0; extra == "ui"
65
65
  Requires-Dist: matplotlib>=3.9.2; extra == "ui"
66
66
  Provides-Extra: dev
@@ -77,51 +77,49 @@ Requires-Dist: workbench[dev]; extra == "all"
77
77
  Dynamic: license-file
78
78
 
79
79
 
80
- # Recent News
80
+ ## Live Dashboard Demo
81
+ You can explore a live demo of the Workbench Dashboard at: [Workbench Dashboard Demo](https://workbench-dashboard.com)
81
82
 
82
- ### Themes
83
+ ## Recent News
84
+ **Chemprop Models!** All the rage for the Open ADMET Challenge.
83
85
 
84
- Everyone knows that good data science requires... **Some Awesome Themes!**
86
+ Workbench now supports:
87
+ - Single Task Chemprop Models
88
+ - Multi Task Chemprop Models
89
+ - Chemprop Hybrid Models (MPNN + Descriptors)
90
+ - Foundation Chemprop Models (CheMeleon Pretrained)
91
+
92
+ Examples:
93
+
94
+ - [Deploying Chemprop Models](examples/models/chemprop.py)
95
+ - [Deploying Foundation Chemprop Models](examples/models/chemprop_foundation.py)
96
+
97
+ **References**
98
+ - [Open ADMET Challenge](https://huggingface.co/spaces/openadmet/OpenADMET-ExpansionRx-Challenge)
99
+ - **ChemProp:** Yang et al. "Analyzing Learned Molecular Representations for Property Prediction" *J. Chem. Inf. Model.* 2019 — [GitHub](https://github.com/chemprop/chemprop) | [Paper](https://pubs.acs.org/doi/10.1021/acs.jcim.9b00237)
100
+ - [CheMeleon Github](https://github.com/JacksonBurns/chemeleon)
101
+
102
+ ### Chemprop Action Shots!
85
103
 
86
104
  <table>
87
105
  <tr>
88
106
  <td>
89
- <a href="https://github.com/user-attachments/assets/82ab4eab-0688-4b93-ad8e-9b954564777b">
90
- <img width="400" alt="theme_dark" src="https://github.com/user-attachments/assets/82ab4eab-0688-4b93-ad8e-9b954564777b" />
91
- </a>
92
- </td>
93
- <td>
94
- <a href="https://github.com/user-attachments/assets/b63a0789-c144-4048-afb6-f03e3d993680">
95
- <img width="400" alt="theme_light" src="https://github.com/user-attachments/assets/b63a0789-c144-4048-afb6-f03e3d993680" />
107
+ <a href="https://github.com/user-attachments/assets/a36c6eff-c464-4c9a-9859-a45cd7e35145">
108
+ <img width="800" alt="theme_dark" src="https://github.com/user-attachments/assets/a36c6eff-c464-4c9a-9859-a45cd7e35145" />
96
109
  </a>
97
110
  </td>
98
111
  </tr>
99
112
  <tr>
100
113
  <td>
101
- <a href="https://github.com/user-attachments/assets/8a59be19-0c5d-42c6-9922-feafb1a1eecd">
102
- <img width="400" alt="theme_quartz" src="https://github.com/user-attachments/assets/8a59be19-0c5d-42c6-9922-feafb1a1eecd" />
103
- </a>
104
- </td>
105
- <td>
106
- <a href="https://github.com/user-attachments/assets/5b01ec64-8d56-43bf-96c5-7da8ec48f527">
107
- <img width="400" alt="theme_quartz_dark" src="https://github.com/user-attachments/assets/5b01ec64-8d56-43bf-96c5-7da8ec48f527" />
114
+ <a href="https://github.com/user-attachments/assets/d65ec1da-e04e-44fe-8782-4da0fb50588a">
115
+ <img width="800" alt="theme_quartz" src="https://github.com/user-attachments/assets/d65ec1da-e04e-44fe-8782-4da0fb50588a" />
108
116
  </a>
109
117
  </td>
110
118
  </tr>
111
119
  </table>
112
120
 
113
- All of the Dashboard pages, subpages, and plugins use our new `ThemeManager()` class. See [Workbench Themes](https://supercowpowers.github.io/workbench/themes/), also big thanks to our friends at [Dash Bootstrap Templates](https://github.com/AnnMarieW/dash-bootstrap-templates)
114
-
115
121
 
116
122
 
117
- ### Workbench up on the AWS Marketplace
118
-
119
- Powered by AWS® to accelerate your Machine Learning Pipelines development with our new [Dashboard for ML Pipelines](https://aws.amazon.com/marketplace/pp/prodview-5idedc7uptbqo). Getting started with Workbench is a snap and can be billed through AWS.
120
-
121
- ### Coming Soon: `v0.9`
122
-
123
- We're getting ready for our `v0.9` release. Here's the road map: [Workbench RoadMaps](https://supercowpowers.github.io/workbench/road_maps/0_9_0/)
124
-
125
123
  # Welcome to Workbench
126
124
  The Workbench framework makes AWS® both easier to use and more powerful. Workbench handles all the details around updating and managing a complex set of AWS Services. With a simple-to-use Python API and a beautiful set of web interfaces, Workbench makes creating AWS ML pipelines a snap. It also dramatically improves both the usability and visibility across the entire spectrum of services: Glue Job, Athena, Feature Store, Models, and Endpoints, Workbench makes it easy to build production ready, AWS powered, machine learning pipelines.
127
125
 
@@ -159,6 +157,10 @@ For the full instructions for connecting your AWS Account see:
159
157
  - One time AWS Onboarding: [AWS Setup](https://supercowpowers.github.io/workbench/aws_setup/core_stack/)
160
158
 
161
159
 
160
+ ### Workbench up on the AWS Marketplace
161
+
162
+ Powered by AWS® to accelerate your Machine Learning Pipelines development with our new [Dashboard for ML Pipelines](https://aws.amazon.com/marketplace/pp/prodview-5idedc7uptbqo). Getting started with Workbench is a snap and can be billed through AWS.
163
+
162
164
  ### Workbench Presentations
163
165
  Even though Workbench makes AWS easier, it's taking something very complex (the full set of AWS ML Pipelines/Services) and making it less complex. Workbench has a depth and breadth of functionality so we've provided higher level conceptual documentation See: [Workbench Presentations](https://supercowpowers.github.io/workbench/presentations/)
164
166