alchemist-nrel 0.3.1__tar.gz → 0.3.2__tar.gz
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.
- {alchemist_nrel-0.3.1/alchemist_nrel.egg-info → alchemist_nrel-0.3.2}/PKG-INFO +13 -13
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/README.md +7 -7
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/package.json +1 -1
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/App.tsx +193 -59
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/api/endpoints/experiments.ts +40 -2
- alchemist_nrel-0.3.2/alchemist-web/src/components/TargetColumnDialog.tsx +203 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/acquisition/AcquisitionPanel.tsx +21 -6
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/experiments/ExperimentsPanel.tsx +81 -7
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/experiments/InitialDesignPanel.tsx +83 -9
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/api/useExperiments.ts +15 -2
- alchemist_nrel-0.3.2/alchemist-web/src/hooks/useSessionEvents.ts +186 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/__init__.py +2 -2
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/acquisition/botorch_acquisition.py +83 -126
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/data/experiment_manager.py +181 -12
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/models/botorch_model.py +292 -63
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/models/sklearn_model.py +145 -13
- alchemist_nrel-0.3.2/alchemist_core/session.py +4651 -0
- alchemist_nrel-0.3.2/alchemist_core/utils/__init__.py +9 -0
- alchemist_nrel-0.3.2/alchemist_core/utils/acquisition_utils.py +60 -0
- alchemist_nrel-0.3.2/alchemist_core/visualization/__init__.py +45 -0
- alchemist_nrel-0.3.2/alchemist_core/visualization/helpers.py +130 -0
- alchemist_nrel-0.3.2/alchemist_core/visualization/plots.py +1449 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2/alchemist_nrel.egg-info}/PKG-INFO +13 -13
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_nrel.egg-info/SOURCES.txt +9 -2
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/main.py +1 -1
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/models/requests.py +52 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/models/responses.py +79 -2
- alchemist_nrel-0.3.2/api/routers/experiments.py +607 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/routers/sessions.py +84 -9
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/routers/visualizations.py +6 -4
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/routers/websocket.py +2 -2
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/services/session_store.py +295 -71
- alchemist_nrel-0.3.2/api/static/assets/index-B6Cf6s_b.css +1 -0
- alchemist_nrel-0.3.1/api/static/assets/index-DWfIKU9j.js → alchemist_nrel-0.3.2/api/static/assets/index-B7njvc9r.js +201 -196
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/static/index.html +2 -2
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/build_tools/build_hooks.py +2 -2
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/pyproject.toml +6 -6
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/gpr_panel.py +11 -5
- alchemist_nrel-0.3.2/ui/target_column_dialog.py +299 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/ui.py +52 -5
- alchemist_nrel-0.3.1/alchemist_core/session.py +0 -1352
- alchemist_nrel-0.3.1/alchemist_core/utils/__init__.py +0 -7
- alchemist_nrel-0.3.1/api/routers/experiments.py +0 -282
- alchemist_nrel-0.3.1/api/static/assets/index-sMIa_1hV.css +0 -1
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/LICENSE +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/MANIFEST.in +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/eslint.config.js +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/index.html +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/package-lock.json +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/postcss.config.js +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/public/NEW_ICON.ico +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/public/NEW_ICON.png +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/public/NEW_LOGO_DARK.png +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/public/NEW_LOGO_LIGHT.png +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/public/vite.svg +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/App.css +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/api/client.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/api/endpoints/acquisition.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/api/endpoints/models.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/api/endpoints/sessions.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/api/endpoints/variables.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/api/endpoints/visualizations.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/api/types.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/assets/react.svg +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/AddPointDialog.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/AuditLockDialog.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/PendingSuggestionsPanel.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/SessionMetadata.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/SessionMetadataDialog.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/api.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/session-components.md +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/ui/CollapsibleSection.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/ui/FormField.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/ui/SegmentedButton.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/ui/TabView.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/ui/index.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/visualizations/CalibrationCurve.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/visualizations/ContourPlot.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/visualizations/ContourPlotSimple.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/visualizations/HyperparametersDisplay.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/visualizations/MetricsPlot.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/visualizations/ParityPlot.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/visualizations/QQPlot.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/visualizations/VisualizationsPanel.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/visualizations/index.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/acquisition/index.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/models/GPRPanel.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/models/README.md +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/models/index.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/monitoring/MonitoringDashboard.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/variables/VariableForm.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/variables/VariableList.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/variables/VariablesPanel.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/api/useAcquisition.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/api/useDeleteVariable.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/api/useFileOperations.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/api/useModels.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/api/useSessions.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/api/useVariables.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/api/useVisualizations.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/useLockStatus.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/useTheme.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/index.css +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/lib/utils.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/main.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/providers/QueryProvider.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/providers/VisualizationProvider.tsx +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/tailwind.config.js +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/tsconfig.app.json +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/tsconfig.json +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/tsconfig.node.json +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/vite.config.ts +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/acquisition/__init__.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/acquisition/base_acquisition.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/acquisition/skopt_acquisition.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/audit_log.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/config.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/data/__init__.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/data/search_space.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/events.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/models/__init__.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/models/base_model.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/utils/doe.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_nrel.egg-info/dependency_links.txt +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_nrel.egg-info/entry_points.txt +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_nrel.egg-info/requires.txt +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_nrel.egg-info/top_level.txt +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/__init__.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/dependencies.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/example_client.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/middleware/__init__.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/middleware/error_handlers.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/models/__init__.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/routers/__init__.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/routers/acquisition.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/routers/models.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/routers/variables.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/run_api.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/services/__init__.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/static/NEW_ICON.ico +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/static/NEW_ICON.png +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/static/NEW_LOGO_DARK.png +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/static/NEW_LOGO_LIGHT.png +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/static/assets/api-vcoXEqyq.js +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/static/vite.svg +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/build_tools/__init__.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/build_tools/setup.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/main.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/setup.cfg +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/__init__.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/acquisition_panel.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/custom_widgets.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/experiment_logger.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/notifications.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/pool_viz.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/ui_utils.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/utils.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/variables_setup.py +0 -0
- {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/visualizations.py +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alchemist-nrel
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Active learning and optimization toolkit for chemical and materials research
|
|
5
5
|
Author-email: Caleb Coatney <caleb.coatney@nrel.gov>
|
|
6
6
|
License: BSD-3-Clause
|
|
7
|
-
Project-URL: Homepage, https://github.com/
|
|
8
|
-
Project-URL: Documentation, https://
|
|
9
|
-
Project-URL: Source, https://github.com/
|
|
10
|
-
Project-URL: Bug Tracker, https://github.com/
|
|
11
|
-
Project-URL: Changelog, https://github.com/
|
|
7
|
+
Project-URL: Homepage, https://github.com/NatLabRockies/ALchemist
|
|
8
|
+
Project-URL: Documentation, https://natlabrockies.github.io/ALchemist/
|
|
9
|
+
Project-URL: Source, https://github.com/NatLabRockies/ALchemist
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/NatLabRockies/ALchemist/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/NatLabRockies/ALchemist/releases
|
|
12
12
|
Keywords: active learning,bayesian optimization,gaussian processes,materials science,chemistry
|
|
13
13
|
Classifier: Development Status :: 4 - Beta
|
|
14
14
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -70,7 +70,7 @@ ALchemist is a modular Python toolkit that brings active learning and Bayesian o
|
|
|
70
70
|
## Documentation
|
|
71
71
|
|
|
72
72
|
Full user guide and documentation:
|
|
73
|
-
[https://
|
|
73
|
+
[https://natlabrockies.github.io/ALchemist/](https://natlabrockies.github.io/ALchemist/)
|
|
74
74
|
|
|
75
75
|
---
|
|
76
76
|
|
|
@@ -108,7 +108,7 @@ Session files (JSON format) are fully interoperable between desktop and web appl
|
|
|
108
108
|
- **Autonomous Optimization**: Use the REST API to integrate ALchemist with automated laboratory equipment for real-time process control
|
|
109
109
|
- **Remote Monitoring**: Web dashboard provides read-only monitoring mode when ALchemist is being remote-controlled
|
|
110
110
|
|
|
111
|
-
For detailed application examples, see [Use Cases](https://
|
|
111
|
+
For detailed application examples, see [Use Cases](https://natlabrockies.github.io/ALchemist/use_cases/) in the documentation.
|
|
112
112
|
|
|
113
113
|
---
|
|
114
114
|
|
|
@@ -133,10 +133,10 @@ pip install alchemist-nrel
|
|
|
133
133
|
> *Note: This installs the latest unreleased version. The web application is not pre-built with this method because static build files are not included in the repository.*
|
|
134
134
|
|
|
135
135
|
```bash
|
|
136
|
-
pip install git+https://github.com/
|
|
136
|
+
pip install git+https://github.com/NatLabRockies/ALchemist.git
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
-
For advanced installation options, Docker deployment, and development setup, see the [Advanced Installation Guide](https://
|
|
139
|
+
For advanced installation options, Docker deployment, and development setup, see the [Advanced Installation Guide](https://natlabrockies.github.io/ALchemist/#advanced-installation) in the documentation.
|
|
140
140
|
|
|
141
141
|
---
|
|
142
142
|
|
|
@@ -153,7 +153,7 @@ Opens at [http://localhost:8000/app](http://localhost:8000/app)
|
|
|
153
153
|
alchemist
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
-
For detailed usage instructions, see [Getting Started](https://
|
|
156
|
+
For detailed usage instructions, see [Getting Started](https://natlabrockies.github.io/ALchemist/) in the documentation.
|
|
157
157
|
|
|
158
158
|
---
|
|
159
159
|
|
|
@@ -165,7 +165,7 @@ ALchemist is under active development at NLR as part of the DataHub project with
|
|
|
165
165
|
|
|
166
166
|
## Issues & Troubleshooting
|
|
167
167
|
|
|
168
|
-
If you encounter any issues or have questions, please [open an issue on GitHub](https://github.com/
|
|
168
|
+
If you encounter any issues or have questions, please [open an issue on GitHub](https://github.com/NatLabRockies/ALchemist/issues) or contact ccoatney@nrel.gov.
|
|
169
169
|
|
|
170
170
|
For the latest known issues and troubleshooting tips, see the [Issues & Troubleshooting Log](docs/ISSUES_LOG.md).
|
|
171
171
|
|
|
@@ -181,5 +181,5 @@ This project is licensed under the BSD 3-Clause License. See the [LICENSE](LICEN
|
|
|
181
181
|
|
|
182
182
|
## Repository
|
|
183
183
|
|
|
184
|
-
[https://github.com/
|
|
184
|
+
[https://github.com/NatLabRockies/ALchemist](https://github.com/NatLabRockies/ALchemist)
|
|
185
185
|
|
|
@@ -11,7 +11,7 @@ ALchemist is a modular Python toolkit that brings active learning and Bayesian o
|
|
|
11
11
|
## Documentation
|
|
12
12
|
|
|
13
13
|
Full user guide and documentation:
|
|
14
|
-
[https://
|
|
14
|
+
[https://natlabrockies.github.io/ALchemist/](https://natlabrockies.github.io/ALchemist/)
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -49,7 +49,7 @@ Session files (JSON format) are fully interoperable between desktop and web appl
|
|
|
49
49
|
- **Autonomous Optimization**: Use the REST API to integrate ALchemist with automated laboratory equipment for real-time process control
|
|
50
50
|
- **Remote Monitoring**: Web dashboard provides read-only monitoring mode when ALchemist is being remote-controlled
|
|
51
51
|
|
|
52
|
-
For detailed application examples, see [Use Cases](https://
|
|
52
|
+
For detailed application examples, see [Use Cases](https://natlabrockies.github.io/ALchemist/use_cases/) in the documentation.
|
|
53
53
|
|
|
54
54
|
---
|
|
55
55
|
|
|
@@ -74,10 +74,10 @@ pip install alchemist-nrel
|
|
|
74
74
|
> *Note: This installs the latest unreleased version. The web application is not pre-built with this method because static build files are not included in the repository.*
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
|
-
pip install git+https://github.com/
|
|
77
|
+
pip install git+https://github.com/NatLabRockies/ALchemist.git
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
For advanced installation options, Docker deployment, and development setup, see the [Advanced Installation Guide](https://
|
|
80
|
+
For advanced installation options, Docker deployment, and development setup, see the [Advanced Installation Guide](https://natlabrockies.github.io/ALchemist/#advanced-installation) in the documentation.
|
|
81
81
|
|
|
82
82
|
---
|
|
83
83
|
|
|
@@ -94,7 +94,7 @@ Opens at [http://localhost:8000/app](http://localhost:8000/app)
|
|
|
94
94
|
alchemist
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
For detailed usage instructions, see [Getting Started](https://
|
|
97
|
+
For detailed usage instructions, see [Getting Started](https://natlabrockies.github.io/ALchemist/) in the documentation.
|
|
98
98
|
|
|
99
99
|
---
|
|
100
100
|
|
|
@@ -106,7 +106,7 @@ ALchemist is under active development at NLR as part of the DataHub project with
|
|
|
106
106
|
|
|
107
107
|
## Issues & Troubleshooting
|
|
108
108
|
|
|
109
|
-
If you encounter any issues or have questions, please [open an issue on GitHub](https://github.com/
|
|
109
|
+
If you encounter any issues or have questions, please [open an issue on GitHub](https://github.com/NatLabRockies/ALchemist/issues) or contact ccoatney@nrel.gov.
|
|
110
110
|
|
|
111
111
|
For the latest known issues and troubleshooting tips, see the [Issues & Troubleshooting Log](docs/ISSUES_LOG.md).
|
|
112
112
|
|
|
@@ -122,5 +122,5 @@ This project is licensed under the BSD 3-Clause License. See the [LICENSE](LICEN
|
|
|
122
122
|
|
|
123
123
|
## Repository
|
|
124
124
|
|
|
125
|
-
[https://github.com/
|
|
125
|
+
[https://github.com/NatLabRockies/ALchemist](https://github.com/NatLabRockies/ALchemist)
|
|
126
126
|
|
|
@@ -3,7 +3,6 @@ import { Toaster, toast } from 'sonner';
|
|
|
3
3
|
import { QueryProvider } from './providers/QueryProvider';
|
|
4
4
|
import { VisualizationProvider, useVisualization } from './providers/VisualizationProvider';
|
|
5
5
|
import {
|
|
6
|
-
getStoredSessionId,
|
|
7
6
|
clearStoredSessionId,
|
|
8
7
|
useCreateSession,
|
|
9
8
|
useSession,
|
|
@@ -11,7 +10,7 @@ import {
|
|
|
11
10
|
useImportSession,
|
|
12
11
|
useUpdateSessionMetadata
|
|
13
12
|
} from './hooks/api/useSessions';
|
|
14
|
-
import {
|
|
13
|
+
import { useSessionEvents } from './hooks/useSessionEvents';
|
|
15
14
|
import { VariablesPanel } from './features/variables/VariablesPanel';
|
|
16
15
|
import { ExperimentsPanel } from './features/experiments/ExperimentsPanel';
|
|
17
16
|
import { InitialDesignPanel } from './features/experiments/InitialDesignPanel';
|
|
@@ -31,7 +30,11 @@ function AppContent() {
|
|
|
31
30
|
const [showMetadataDialog, setShowMetadataDialog] = useState(false);
|
|
32
31
|
const [copiedSessionId, setCopiedSessionId] = useState(false);
|
|
33
32
|
const [sessionFromUrl, setSessionFromUrl] = useState<boolean>(false);
|
|
33
|
+
const [showRecoveryBanner, setShowRecoveryBanner] = useState(false);
|
|
34
|
+
const [recoverySession, setRecoverySession] = useState<any>(null);
|
|
34
35
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
36
|
+
const urlSessionRef = useRef<string | null>(null);
|
|
37
|
+
const [joinSessionId, setJoinSessionId] = useState('');
|
|
35
38
|
|
|
36
39
|
const createSession = useCreateSession();
|
|
37
40
|
const exportSession = useExportSession();
|
|
@@ -41,25 +44,43 @@ function AppContent() {
|
|
|
41
44
|
const { theme, toggleTheme } = useTheme();
|
|
42
45
|
const { isVisualizationOpen, closeVisualization, sessionId: vizSessionId } = useVisualization();
|
|
43
46
|
|
|
44
|
-
// Monitor lock status
|
|
45
|
-
const { lockStatus } =
|
|
47
|
+
// Monitor session events (lock status, experiment updates, model training) via WebSocket
|
|
48
|
+
const { lockStatus } = useSessionEvents(sessionId, (locked) => {
|
|
46
49
|
setIsMonitoringMode(locked);
|
|
47
50
|
});
|
|
48
51
|
|
|
49
52
|
// Global staged suggestions to mirror desktop main_app.pending_suggestions
|
|
50
53
|
const [pendingSuggestions, setPendingSuggestions] = useState<any[]>([]);
|
|
51
|
-
const [loadedFromFile, setLoadedFromFile] = useState<boolean>(false);
|
|
52
54
|
|
|
53
|
-
// Restore pending suggestions from
|
|
55
|
+
// Restore pending suggestions from staged experiments API on session load
|
|
54
56
|
useEffect(() => {
|
|
55
57
|
if (!sessionId) return;
|
|
56
58
|
|
|
57
|
-
async function
|
|
59
|
+
async function restoreStagedExperiments() {
|
|
58
60
|
try {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
// First try the staged experiments API (preferred)
|
|
62
|
+
const stagedResponse = await fetch(`/api/v1/sessions/${sessionId}/experiments/staged`);
|
|
63
|
+
if (stagedResponse.ok) {
|
|
64
|
+
const stagedData = await stagedResponse.json();
|
|
65
|
+
if (stagedData.experiments && stagedData.experiments.length > 0) {
|
|
66
|
+
// API now returns clean experiments + reason separately
|
|
67
|
+
// Tag each experiment with the reason for the Add Point dialog
|
|
68
|
+
const reason = stagedData.reason || 'Staged';
|
|
69
|
+
const taggedExperiments = stagedData.experiments.map((exp: any) => ({
|
|
70
|
+
...exp,
|
|
71
|
+
_reason: reason // UI-only metadata for dialog auto-fill
|
|
72
|
+
}));
|
|
73
|
+
setPendingSuggestions(taggedExperiments);
|
|
74
|
+
console.log(`✓ Restored ${stagedData.experiments.length} staged experiments from API (reason: ${reason})`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Fallback: check audit log for backward compatibility
|
|
80
|
+
const auditResponse = await fetch(`/api/v1/sessions/${sessionId}/audit?entry_type=acquisition_locked`);
|
|
81
|
+
if (!auditResponse.ok) return;
|
|
61
82
|
|
|
62
|
-
const data = await
|
|
83
|
+
const data = await auditResponse.json();
|
|
63
84
|
if (data.entries && data.entries.length > 0) {
|
|
64
85
|
// Get latest acquisition entry
|
|
65
86
|
const latestAcq = data.entries[data.entries.length - 1];
|
|
@@ -74,52 +95,53 @@ function AppContent() {
|
|
|
74
95
|
Iteration: latestAcq.parameters?.iteration
|
|
75
96
|
}));
|
|
76
97
|
setPendingSuggestions(taggedSuggestions);
|
|
77
|
-
console.log(`✓ Restored ${suggestions.length} pending suggestions from audit log`);
|
|
98
|
+
console.log(`✓ Restored ${suggestions.length} pending suggestions from audit log (fallback)`);
|
|
78
99
|
}
|
|
79
100
|
}
|
|
80
101
|
} catch (e) {
|
|
81
|
-
console.error('Failed to restore
|
|
102
|
+
console.error('Failed to restore staged experiments:', e);
|
|
82
103
|
}
|
|
83
104
|
}
|
|
84
105
|
|
|
85
|
-
|
|
106
|
+
restoreStagedExperiments();
|
|
86
107
|
}, [sessionId]);
|
|
87
108
|
|
|
88
|
-
// Check
|
|
109
|
+
// Check URL parameters on mount (always takes priority over recovery)
|
|
89
110
|
useEffect(() => {
|
|
90
111
|
const urlParams = new URLSearchParams(window.location.search);
|
|
91
|
-
console.log('URL search params:', window.location.search);
|
|
92
|
-
|
|
93
|
-
// Check for session ID in URL
|
|
94
112
|
const urlSessionId = urlParams.get('session');
|
|
95
|
-
console.log('Session ID from URL:', urlSessionId);
|
|
96
|
-
|
|
97
113
|
if (urlSessionId) {
|
|
114
|
+
urlSessionRef.current = urlSessionId;
|
|
98
115
|
setSessionId(urlSessionId);
|
|
99
|
-
setSessionFromUrl(true);
|
|
116
|
+
setSessionFromUrl(true);
|
|
100
117
|
console.log(`✓ Loaded session from URL: ${urlSessionId}`);
|
|
101
|
-
} else {
|
|
102
|
-
// Fallback to localStorage if no URL session
|
|
103
|
-
const storedId = getStoredSessionId();
|
|
104
|
-
if (storedId) {
|
|
105
|
-
setSessionId(storedId);
|
|
106
|
-
console.log(`✓ Loaded session from localStorage: ${storedId}`);
|
|
107
|
-
}
|
|
108
118
|
}
|
|
109
|
-
|
|
110
|
-
// Check for monitoring mode
|
|
111
119
|
const monitorParam = urlParams.get('mode');
|
|
112
|
-
console.log('Monitoring mode from URL:', monitorParam);
|
|
113
120
|
if (monitorParam === 'monitor') {
|
|
114
121
|
setIsMonitoringMode(true);
|
|
115
122
|
console.log('✓ Monitoring mode enabled');
|
|
116
123
|
}
|
|
117
124
|
}, []);
|
|
118
125
|
|
|
126
|
+
// Check for recovery sessions on startup — skip if URL session is present
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (urlSessionRef.current) return; // URL intent takes priority
|
|
129
|
+
fetch('/api/v1/recovery/list')
|
|
130
|
+
.then(res => res.json())
|
|
131
|
+
.then(data => {
|
|
132
|
+
if (data.recoveries && data.recoveries.length > 0) {
|
|
133
|
+
setRecoverySession(data.recoveries[0]);
|
|
134
|
+
setShowRecoveryBanner(true);
|
|
135
|
+
console.log('✓ Found recovery session:', data.recoveries[0]);
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
.catch(err => console.warn('Failed to check for recovery sessions:', err));
|
|
139
|
+
}, []);
|
|
140
|
+
|
|
119
141
|
// Auto-clear invalid session (but not if it came from URL - let user see the error)
|
|
120
142
|
useEffect(() => {
|
|
121
143
|
if (sessionError && sessionId && !sessionFromUrl) {
|
|
122
|
-
toast.error('Session
|
|
144
|
+
toast.error('Session not found. Please create a new session.');
|
|
123
145
|
handleClearSession();
|
|
124
146
|
} else if (sessionError && sessionFromUrl) {
|
|
125
147
|
// Show error but don't clear - might be loading or user wants to see the issue
|
|
@@ -127,24 +149,25 @@ function AppContent() {
|
|
|
127
149
|
}
|
|
128
150
|
}, [sessionError, sessionId, sessionFromUrl]);
|
|
129
151
|
|
|
130
|
-
//
|
|
152
|
+
// Silent auto-backup every 30 seconds for crash recovery
|
|
131
153
|
useEffect(() => {
|
|
132
154
|
if (!sessionId) return;
|
|
133
155
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
e.returnValue = 'You have unsaved changes. Are you sure you want to leave?';
|
|
139
|
-
return e.returnValue;
|
|
140
|
-
}
|
|
141
|
-
};
|
|
156
|
+
// Only backup if session has data
|
|
157
|
+
if (!session || (session.variable_count === 0 && session.experiment_count === 0)) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
142
160
|
|
|
143
|
-
|
|
144
|
-
|
|
161
|
+
const interval = setInterval(() => {
|
|
162
|
+
fetch(`/api/v1/sessions/${sessionId}/backup`, { method: 'POST' })
|
|
163
|
+
.then(() => console.debug('Recovery backup saved'))
|
|
164
|
+
.catch(err => console.warn('Recovery backup failed:', err));
|
|
165
|
+
}, 30000); // Every 30 seconds
|
|
166
|
+
|
|
167
|
+
return () => clearInterval(interval);
|
|
145
168
|
}, [sessionId, session]);
|
|
146
169
|
|
|
147
|
-
// Prompt to save on page close/reload
|
|
170
|
+
// Prompt to save on page close/reload if session has data
|
|
148
171
|
useEffect(() => {
|
|
149
172
|
if (!sessionId) return;
|
|
150
173
|
|
|
@@ -166,7 +189,6 @@ function AppContent() {
|
|
|
166
189
|
try {
|
|
167
190
|
const newSession = await createSession.mutateAsync({ ttl_hours: 24 });
|
|
168
191
|
setSessionId(newSession.session_id);
|
|
169
|
-
setLoadedFromFile(false);
|
|
170
192
|
toast.success('Session created successfully!');
|
|
171
193
|
// Show metadata dialog for new session
|
|
172
194
|
setShowMetadataDialog(true);
|
|
@@ -180,7 +202,6 @@ function AppContent() {
|
|
|
180
202
|
const handleClearSession = () => {
|
|
181
203
|
clearStoredSessionId();
|
|
182
204
|
setSessionId(null);
|
|
183
|
-
setLoadedFromFile(false);
|
|
184
205
|
toast.info('Session cleared');
|
|
185
206
|
};
|
|
186
207
|
|
|
@@ -188,15 +209,16 @@ function AppContent() {
|
|
|
188
209
|
const handleExportSession = async () => {
|
|
189
210
|
if (!sessionId) return;
|
|
190
211
|
try {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
212
|
+
await exportSession.mutateAsync({ sessionId, serverSide: false });
|
|
213
|
+
|
|
214
|
+
// Clear recovery backup after successful save
|
|
215
|
+
fetch(`/api/v1/sessions/${sessionId}/backup`, { method: 'DELETE' })
|
|
216
|
+
.then(() => console.log('Recovery backup cleared after save'))
|
|
217
|
+
.catch(err => console.warn('Failed to clear recovery backup:', err));
|
|
218
|
+
|
|
219
|
+
toast.success('Session saved to Downloads folder!');
|
|
198
220
|
} catch (error: any) {
|
|
199
|
-
toast.error('Failed to
|
|
221
|
+
toast.error('Failed to save session');
|
|
200
222
|
console.error('Error exporting session:', error);
|
|
201
223
|
}
|
|
202
224
|
};
|
|
@@ -212,8 +234,6 @@ function AppContent() {
|
|
|
212
234
|
try {
|
|
213
235
|
const newSession = await importSession.mutateAsync(file);
|
|
214
236
|
setSessionId(newSession.session_id);
|
|
215
|
-
// Mark that this session was created from a file upload/import
|
|
216
|
-
setLoadedFromFile(true);
|
|
217
237
|
toast.success('Session imported successfully!');
|
|
218
238
|
// Reset file input
|
|
219
239
|
if (fileInputRef.current) {
|
|
@@ -225,6 +245,46 @@ function AppContent() {
|
|
|
225
245
|
}
|
|
226
246
|
}
|
|
227
247
|
};
|
|
248
|
+
|
|
249
|
+
// Handle recovery session restore
|
|
250
|
+
const handleRestoreRecovery = async () => {
|
|
251
|
+
if (!recoverySession) return;
|
|
252
|
+
try {
|
|
253
|
+
const response = await fetch(`/api/v1/recovery/${recoverySession.session_id}/restore`, {
|
|
254
|
+
method: 'POST'
|
|
255
|
+
});
|
|
256
|
+
if (!response.ok) throw new Error('Failed to restore recovery session');
|
|
257
|
+
|
|
258
|
+
const data = await response.json();
|
|
259
|
+
setSessionId(data.session_id);
|
|
260
|
+
setShowRecoveryBanner(false);
|
|
261
|
+
setRecoverySession(null);
|
|
262
|
+
toast.success('Session restored from recovery backup!');
|
|
263
|
+
} catch (error: any) {
|
|
264
|
+
toast.error('Failed to restore recovery session');
|
|
265
|
+
console.error('Error restoring recovery:', error);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// Handle recovery dismissal
|
|
270
|
+
const handleDismissRecovery = async () => {
|
|
271
|
+
if (!recoverySession) return;
|
|
272
|
+
try {
|
|
273
|
+
// Delete the recovery backup
|
|
274
|
+
await fetch(`/api/v1/sessions/${recoverySession.session_id}/backup`, {
|
|
275
|
+
method: 'DELETE'
|
|
276
|
+
});
|
|
277
|
+
setShowRecoveryBanner(false);
|
|
278
|
+
setRecoverySession(null);
|
|
279
|
+
toast.info('Recovery session discarded');
|
|
280
|
+
} catch (error: any) {
|
|
281
|
+
console.error('Error dismissing recovery:', error);
|
|
282
|
+
// Still hide banner even if delete fails
|
|
283
|
+
setShowRecoveryBanner(false);
|
|
284
|
+
setRecoverySession(null);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
228
288
|
// Handle audit log export (matches desktop File → Export Audit Log)
|
|
229
289
|
const handleExportAuditLog = async () => {
|
|
230
290
|
if (!sessionId) return;
|
|
@@ -263,6 +323,15 @@ function AppContent() {
|
|
|
263
323
|
}
|
|
264
324
|
};
|
|
265
325
|
|
|
326
|
+
// Handle joining an existing session by ID
|
|
327
|
+
const handleJoinSession = () => {
|
|
328
|
+
const trimmed = joinSessionId.trim();
|
|
329
|
+
if (!trimmed) return;
|
|
330
|
+
setSessionId(trimmed);
|
|
331
|
+
setSessionFromUrl(true); // Prevent auto-clear on 404
|
|
332
|
+
setJoinSessionId('');
|
|
333
|
+
};
|
|
334
|
+
|
|
266
335
|
// Debug: Log state before render
|
|
267
336
|
console.log('=== Render State ===');
|
|
268
337
|
console.log('sessionId:', sessionId);
|
|
@@ -271,6 +340,51 @@ function AppContent() {
|
|
|
271
340
|
|
|
272
341
|
return (
|
|
273
342
|
<div className="h-screen bg-background flex flex-col overflow-hidden">
|
|
343
|
+
{/* Recovery Banner */}
|
|
344
|
+
{showRecoveryBanner && recoverySession && (
|
|
345
|
+
<div className="bg-yellow-50 dark:bg-yellow-900/20 border-b border-yellow-200 dark:border-yellow-800">
|
|
346
|
+
<div className="max-w-7xl mx-auto px-6 py-4">
|
|
347
|
+
<div className="flex items-start justify-between">
|
|
348
|
+
<div className="flex-1">
|
|
349
|
+
<div className="flex items-center gap-2 mb-2">
|
|
350
|
+
<svg className="w-5 h-5 text-yellow-600 dark:text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
351
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
352
|
+
</svg>
|
|
353
|
+
<h3 className="text-sm font-semibold text-yellow-900 dark:text-yellow-100">
|
|
354
|
+
Unsaved work detected
|
|
355
|
+
</h3>
|
|
356
|
+
</div>
|
|
357
|
+
<p className="text-sm text-yellow-800 dark:text-yellow-200 mb-2">
|
|
358
|
+
We found a session from {new Date(recoverySession.backup_time).toLocaleString()} that wasn't saved.
|
|
359
|
+
Would you like to restore it?
|
|
360
|
+
</p>
|
|
361
|
+
<div className="text-xs text-yellow-700 dark:text-yellow-300 space-y-1">
|
|
362
|
+
<div><strong>Session:</strong> {recoverySession.session_name || 'Untitled Session'}</div>
|
|
363
|
+
<div>
|
|
364
|
+
<strong>Data:</strong> {recoverySession.n_variables} variables, {recoverySession.n_experiments} experiments
|
|
365
|
+
{recoverySession.model_trained && ', trained model'}
|
|
366
|
+
</div>
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
<div className="flex gap-2 ml-4">
|
|
370
|
+
<button
|
|
371
|
+
onClick={handleRestoreRecovery}
|
|
372
|
+
className="px-4 py-2 bg-yellow-600 hover:bg-yellow-700 text-white text-sm font-medium rounded-md transition-colors"
|
|
373
|
+
>
|
|
374
|
+
Restore Session
|
|
375
|
+
</button>
|
|
376
|
+
<button
|
|
377
|
+
onClick={handleDismissRecovery}
|
|
378
|
+
className="px-4 py-2 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200 text-sm font-medium rounded-md transition-colors"
|
|
379
|
+
>
|
|
380
|
+
Discard
|
|
381
|
+
</button>
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
)}
|
|
387
|
+
|
|
274
388
|
{/* Monitoring Mode - Show dedicated dashboard */}
|
|
275
389
|
{isMonitoringMode && sessionId ? (
|
|
276
390
|
<MonitoringDashboard sessionId={sessionId} pollingInterval={90000} />
|
|
@@ -330,13 +444,13 @@ function AppContent() {
|
|
|
330
444
|
</div>
|
|
331
445
|
<button
|
|
332
446
|
onClick={handleCopySessionId}
|
|
333
|
-
className="p-1.5 rounded hover:bg-accent transition-colors"
|
|
447
|
+
className="p-1.5 rounded hover:bg-accent transition-colors border border-transparent hover:border-border"
|
|
334
448
|
title="Copy full session ID to clipboard"
|
|
335
449
|
>
|
|
336
450
|
{copiedSessionId ? (
|
|
337
|
-
<Check className="h-
|
|
451
|
+
<Check className="h-4 w-4 text-green-500" />
|
|
338
452
|
) : (
|
|
339
|
-
<Copy className="h-
|
|
453
|
+
<Copy className="h-4 w-4 text-muted-foreground hover:text-foreground" />
|
|
340
454
|
)}
|
|
341
455
|
</button>
|
|
342
456
|
</div>
|
|
@@ -391,6 +505,23 @@ function AppContent() {
|
|
|
391
505
|
onChange={handleFileSelected}
|
|
392
506
|
className="hidden"
|
|
393
507
|
/>
|
|
508
|
+
<div className="flex gap-2 items-center ml-4 pl-4 border-l border-border">
|
|
509
|
+
<input
|
|
510
|
+
type="text"
|
|
511
|
+
value={joinSessionId}
|
|
512
|
+
onChange={(e) => setJoinSessionId(e.target.value)}
|
|
513
|
+
onKeyDown={(e) => e.key === 'Enter' && handleJoinSession()}
|
|
514
|
+
placeholder="Paste session ID..."
|
|
515
|
+
className="text-sm px-3 py-2 rounded border border-input bg-background w-64"
|
|
516
|
+
/>
|
|
517
|
+
<button
|
|
518
|
+
onClick={handleJoinSession}
|
|
519
|
+
disabled={!joinSessionId.trim()}
|
|
520
|
+
className="text-sm bg-accent text-accent-foreground px-4 py-2 rounded hover:bg-accent/80 disabled:opacity-50"
|
|
521
|
+
>
|
|
522
|
+
Join
|
|
523
|
+
</button>
|
|
524
|
+
</div>
|
|
394
525
|
</div>
|
|
395
526
|
)}
|
|
396
527
|
</div>
|
|
@@ -407,7 +538,10 @@ function AppContent() {
|
|
|
407
538
|
pendingSuggestions={pendingSuggestions}
|
|
408
539
|
onStageSuggestions={(s:any[])=>setPendingSuggestions(s)}
|
|
409
540
|
/>
|
|
410
|
-
<InitialDesignPanel
|
|
541
|
+
<InitialDesignPanel
|
|
542
|
+
sessionId={sessionId}
|
|
543
|
+
onStageSuggestions={(s:any[])=>setPendingSuggestions(s)}
|
|
544
|
+
/>
|
|
411
545
|
</div>
|
|
412
546
|
|
|
413
547
|
{/* CENTER - Visualization Area (expandable) */}
|
{alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/api/endpoints/experiments.ts
RENAMED
|
@@ -42,13 +42,51 @@ export const createExperimentBatch = async (
|
|
|
42
42
|
*/
|
|
43
43
|
export const uploadExperimentsCSV = async (
|
|
44
44
|
sessionId: string,
|
|
45
|
-
file: File
|
|
45
|
+
file: File,
|
|
46
|
+
targetColumns?: string | string[]
|
|
46
47
|
): Promise<{ message: string; n_experiments: number }> => {
|
|
47
48
|
const formData = new FormData();
|
|
48
49
|
formData.append('file', file);
|
|
49
50
|
|
|
51
|
+
// Build URL with target_columns as query parameter
|
|
52
|
+
const targetParam = targetColumns
|
|
53
|
+
? (Array.isArray(targetColumns) ? targetColumns.join(',') : targetColumns)
|
|
54
|
+
: undefined;
|
|
55
|
+
|
|
56
|
+
const url = targetParam
|
|
57
|
+
? `/sessions/${sessionId}/experiments/upload?target_columns=${encodeURIComponent(targetParam)}`
|
|
58
|
+
: `/sessions/${sessionId}/experiments/upload`;
|
|
59
|
+
|
|
60
|
+
const response = await apiClient.post(
|
|
61
|
+
url,
|
|
62
|
+
formData,
|
|
63
|
+
{
|
|
64
|
+
headers: {
|
|
65
|
+
'Content-Type': 'multipart/form-data',
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
return response.data;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Preview CSV columns before upload to check for target columns
|
|
74
|
+
*/
|
|
75
|
+
export const previewCSVColumns = async (
|
|
76
|
+
sessionId: string,
|
|
77
|
+
file: File
|
|
78
|
+
): Promise<{
|
|
79
|
+
columns: string[];
|
|
80
|
+
available_targets: string[];
|
|
81
|
+
has_output: boolean;
|
|
82
|
+
recommended_target: string | null;
|
|
83
|
+
n_rows: number;
|
|
84
|
+
}> => {
|
|
85
|
+
const formData = new FormData();
|
|
86
|
+
formData.append('file', file);
|
|
87
|
+
|
|
50
88
|
const response = await apiClient.post(
|
|
51
|
-
`/sessions/${sessionId}/experiments/
|
|
89
|
+
`/sessions/${sessionId}/experiments/preview`,
|
|
52
90
|
formData,
|
|
53
91
|
{
|
|
54
92
|
headers: {
|