alchemist-nrel 0.3.0__tar.gz → 0.3.1__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.0 → alchemist_nrel-0.3.1}/MANIFEST.in +4 -4
- alchemist_nrel-0.3.1/PKG-INFO +185 -0
- alchemist_nrel-0.3.1/README.md +126 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/package.json +1 -1
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/App.tsx +89 -23
- alchemist_nrel-0.3.1/alchemist-web/src/hooks/useLockStatus.ts +164 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_core/acquisition/botorch_acquisition.py +1 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_core/data/experiment_manager.py +15 -8
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_core/models/sklearn_model.py +30 -2
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_core/session.py +208 -51
- alchemist_nrel-0.3.1/alchemist_nrel.egg-info/PKG-INFO +185 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_nrel.egg-info/SOURCES.txt +8 -7
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_nrel.egg-info/entry_points.txt +1 -1
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_nrel.egg-info/top_level.txt +0 -1
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/example_client.py +7 -2
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/main.py +2 -1
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/models/requests.py +24 -1
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/models/responses.py +23 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/routers/acquisition.py +25 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/routers/experiments.py +19 -3
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/routers/sessions.py +111 -2
- alchemist_nrel-0.3.1/api/routers/websocket.py +132 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1/api}/run_api.py +8 -7
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/services/session_store.py +75 -0
- alchemist_nrel-0.3.0/api/static/assets/index-C0_glioA.js → alchemist_nrel-0.3.1/api/static/assets/index-DWfIKU9j.js +172 -162
- alchemist_nrel-0.3.0/api/static/assets/index-CB4V1LI5.css → alchemist_nrel-0.3.1/api/static/assets/index-sMIa_1hV.css +1 -1
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/static/index.html +2 -2
- alchemist_nrel-0.3.1/build_tools/__init__.py +1 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1/build_tools}/setup.py +1 -1
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/pyproject.toml +5 -4
- alchemist_nrel-0.3.0/PKG-INFO +0 -223
- alchemist_nrel-0.3.0/README.md +0 -164
- alchemist_nrel-0.3.0/RELEASE_NOTES_v0.3.0.md +0 -154
- alchemist_nrel-0.3.0/alchemist_core/models/ax_model.py +0 -159
- alchemist_nrel-0.3.0/alchemist_nrel.egg-info/PKG-INFO +0 -223
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/LICENSE +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/eslint.config.js +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/index.html +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/package-lock.json +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/postcss.config.js +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/public/NEW_ICON.ico +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/public/NEW_ICON.png +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/public/NEW_LOGO_DARK.png +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/public/NEW_LOGO_LIGHT.png +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/public/vite.svg +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/App.css +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/api/client.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/api/endpoints/acquisition.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/api/endpoints/experiments.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/api/endpoints/models.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/api/endpoints/sessions.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/api/endpoints/variables.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/api/endpoints/visualizations.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/api/types.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/assets/react.svg +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/AddPointDialog.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/AuditLockDialog.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/PendingSuggestionsPanel.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/SessionMetadata.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/SessionMetadataDialog.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/api.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/session-components.md +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/ui/CollapsibleSection.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/ui/FormField.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/ui/SegmentedButton.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/ui/TabView.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/ui/index.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/visualizations/CalibrationCurve.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/visualizations/ContourPlot.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/visualizations/ContourPlotSimple.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/visualizations/HyperparametersDisplay.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/visualizations/MetricsPlot.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/visualizations/ParityPlot.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/visualizations/QQPlot.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/visualizations/VisualizationsPanel.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/components/visualizations/index.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/features/acquisition/AcquisitionPanel.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/features/acquisition/index.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/features/experiments/ExperimentsPanel.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/features/experiments/InitialDesignPanel.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/features/models/GPRPanel.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/features/models/README.md +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/features/models/index.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/features/monitoring/MonitoringDashboard.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/features/variables/VariableForm.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/features/variables/VariableList.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/features/variables/VariablesPanel.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/hooks/api/useAcquisition.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/hooks/api/useDeleteVariable.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/hooks/api/useExperiments.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/hooks/api/useFileOperations.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/hooks/api/useModels.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/hooks/api/useSessions.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/hooks/api/useVariables.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/hooks/api/useVisualizations.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/hooks/useTheme.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/index.css +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/lib/utils.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/main.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/providers/QueryProvider.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/src/providers/VisualizationProvider.tsx +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/tailwind.config.js +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/tsconfig.app.json +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/tsconfig.json +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/tsconfig.node.json +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist-web/vite.config.ts +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_core/__init__.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_core/acquisition/__init__.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_core/acquisition/base_acquisition.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_core/acquisition/skopt_acquisition.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_core/audit_log.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_core/config.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_core/data/__init__.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_core/data/search_space.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_core/events.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_core/models/__init__.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_core/models/base_model.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_core/models/botorch_model.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_core/utils/__init__.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_core/utils/doe.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_nrel.egg-info/dependency_links.txt +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/alchemist_nrel.egg-info/requires.txt +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/__init__.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/dependencies.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/middleware/__init__.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/middleware/error_handlers.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/models/__init__.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/routers/__init__.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/routers/models.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/routers/variables.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/routers/visualizations.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/services/__init__.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/static/NEW_ICON.ico +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/static/NEW_ICON.png +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/static/NEW_LOGO_DARK.png +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/static/NEW_LOGO_LIGHT.png +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/static/assets/api-vcoXEqyq.js +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/api/static/vite.svg +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1/build_tools}/build_hooks.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/main.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/setup.cfg +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/ui/__init__.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/ui/acquisition_panel.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/ui/custom_widgets.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/ui/experiment_logger.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/ui/gpr_panel.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/ui/notifications.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/ui/pool_viz.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/ui/ui.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/ui/ui_utils.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/ui/utils.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/ui/variables_setup.py +0 -0
- {alchemist_nrel-0.3.0 → alchemist_nrel-0.3.1}/ui/visualizations.py +0 -0
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
include README.md
|
|
5
5
|
include LICENSE
|
|
6
6
|
include RELEASE_BUILD_GUIDE.md
|
|
7
|
-
include RELEASE_NOTES_*.md
|
|
8
7
|
|
|
9
|
-
# Include build
|
|
10
|
-
include build_hooks.py
|
|
8
|
+
# Include build tools
|
|
9
|
+
include build_tools/build_hooks.py
|
|
10
|
+
include build_tools/setup.py
|
|
11
|
+
include build_tools/__init__.py
|
|
11
12
|
|
|
12
13
|
# Include frontend source (for building from sdist) - but NOT node_modules or build artifacts
|
|
13
14
|
recursive-include alchemist-web/src *
|
|
@@ -23,7 +24,6 @@ include alchemist-web/eslint.config.js
|
|
|
23
24
|
include alchemist-web/index.html
|
|
24
25
|
|
|
25
26
|
# Include run scripts
|
|
26
|
-
include run_api.py
|
|
27
27
|
include main.py
|
|
28
28
|
|
|
29
29
|
# Exclude unnecessary files from sdist
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: alchemist-nrel
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Active learning and optimization toolkit for chemical and materials research
|
|
5
|
+
Author-email: Caleb Coatney <caleb.coatney@nrel.gov>
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/NREL/ALchemist
|
|
8
|
+
Project-URL: Documentation, https://nrel.github.io/ALchemist/
|
|
9
|
+
Project-URL: Source, https://github.com/NREL/ALchemist
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/NREL/ALchemist/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/NREL/ALchemist/releases
|
|
12
|
+
Keywords: active learning,bayesian optimization,gaussian processes,materials science,chemistry
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: numpy
|
|
26
|
+
Requires-Dist: pandas
|
|
27
|
+
Requires-Dist: scipy
|
|
28
|
+
Requires-Dist: matplotlib
|
|
29
|
+
Requires-Dist: mplcursors
|
|
30
|
+
Requires-Dist: scikit-learn
|
|
31
|
+
Requires-Dist: scikit-optimize
|
|
32
|
+
Requires-Dist: botorch
|
|
33
|
+
Requires-Dist: torch
|
|
34
|
+
Requires-Dist: gpytorch
|
|
35
|
+
Requires-Dist: ax-platform
|
|
36
|
+
Requires-Dist: customtkinter
|
|
37
|
+
Requires-Dist: tksheet
|
|
38
|
+
Requires-Dist: tabulate
|
|
39
|
+
Requires-Dist: ctkmessagebox
|
|
40
|
+
Requires-Dist: joblib
|
|
41
|
+
Requires-Dist: fastapi>=0.109.0
|
|
42
|
+
Requires-Dist: uvicorn[standard]>=0.27.0
|
|
43
|
+
Requires-Dist: pydantic>=2.5.0
|
|
44
|
+
Requires-Dist: python-multipart>=0.0.6
|
|
45
|
+
Requires-Dist: requests
|
|
46
|
+
Provides-Extra: test
|
|
47
|
+
Requires-Dist: pytest>=8.0.0; extra == "test"
|
|
48
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "test"
|
|
49
|
+
Requires-Dist: pytest-anyio>=0.0.0; extra == "test"
|
|
50
|
+
Requires-Dist: httpx>=0.25.0; extra == "test"
|
|
51
|
+
Requires-Dist: requests>=2.31.0; extra == "test"
|
|
52
|
+
Provides-Extra: dev
|
|
53
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
54
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
55
|
+
Requires-Dist: pytest-anyio>=0.0.0; extra == "dev"
|
|
56
|
+
Requires-Dist: httpx>=0.25.0; extra == "dev"
|
|
57
|
+
Requires-Dist: requests>=2.31.0; extra == "dev"
|
|
58
|
+
Dynamic: license-file
|
|
59
|
+
|
|
60
|
+
<img src="docs/assets/NEW_LOGO_LIGHT.png" alt="ALchemist" width="50%" />
|
|
61
|
+
|
|
62
|
+
**ALchemist: Active Learning Toolkit for Chemical and Materials Research**
|
|
63
|
+
|
|
64
|
+
ALchemist is a modular Python toolkit that brings active learning and Bayesian optimization to experimental design in chemical and materials research. It is designed for scientists and engineers who want to efficiently explore or optimize high-dimensional variable spaces—using intuitive graphical interfaces, programmatic APIs, or autonomous optimization workflows.
|
|
65
|
+
|
|
66
|
+
**NLR Software Record:** SWR-25-102
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Documentation
|
|
71
|
+
|
|
72
|
+
Full user guide and documentation:
|
|
73
|
+
[https://nrel.github.io/ALchemist/](https://nrel.github.io/ALchemist/)
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Overview
|
|
78
|
+
|
|
79
|
+
**Key Features:**
|
|
80
|
+
|
|
81
|
+
- **Flexible variable space definition**: Real, integer, and categorical variables with bounds or discrete values
|
|
82
|
+
- **Probabilistic surrogate modeling**: Gaussian process regression via BoTorch or scikit-learn backends
|
|
83
|
+
- **Advanced acquisition strategies**: Efficient sampling using qEI, qPI, qUCB, and qNegIntegratedPosteriorVariance
|
|
84
|
+
- **Modern web interface**: React-based UI with FastAPI backend for seamless active learning workflows
|
|
85
|
+
- **Desktop GUI**: CustomTkinter desktop application for offline optimization
|
|
86
|
+
- **Session management**: Save/load optimization sessions with audit logs for reproducibility
|
|
87
|
+
- **Multiple interfaces**: No-code GUI, Python Session API, or REST API for different use cases
|
|
88
|
+
- **Autonomous optimization**: Human-out-of-the-loop operation for real-time process control
|
|
89
|
+
- **Experiment tracking**: CSV logging, reproducible random seeds, and comprehensive audit trails
|
|
90
|
+
- **Extensibility**: Abstract interfaces for models and acquisition functions enable future backend and workflow expansion
|
|
91
|
+
**Architecture:**
|
|
92
|
+
|
|
93
|
+
ALchemist is built on a clean, modular architecture:
|
|
94
|
+
|
|
95
|
+
- **Core Session API**: Headless Bayesian optimization engine (`alchemist_core`) that powers all interfaces
|
|
96
|
+
- **Desktop Application**: CustomTkinter GUI using the Core Session API, designed for human-in-the-loop and offline optimization
|
|
97
|
+
- **REST API**: FastAPI server providing a thin wrapper around the Core Session API for remote access
|
|
98
|
+
- **Web Application**: React UI consuming the REST API, supporting both interactive and autonomous optimization workflows
|
|
99
|
+
|
|
100
|
+
Session files (JSON format) are fully interoperable between desktop and web applications, enabling seamless workflow transitions.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Use Cases
|
|
105
|
+
|
|
106
|
+
- **Interactive Optimization**: Use desktop or web GUI for manual experiment design and human-in-the-loop optimization
|
|
107
|
+
- **Programmatic Workflows**: Import the Session API in Python scripts or Jupyter notebooks for batch processing
|
|
108
|
+
- **Autonomous Optimization**: Use the REST API to integrate ALchemist with automated laboratory equipment for real-time process control
|
|
109
|
+
- **Remote Monitoring**: Web dashboard provides read-only monitoring mode when ALchemist is being remote-controlled
|
|
110
|
+
|
|
111
|
+
For detailed application examples, see [Use Cases](https://nrel.github.io/ALchemist/use_cases/) in the documentation.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Installation
|
|
116
|
+
|
|
117
|
+
**Requirements:** Python 3.11 or higher
|
|
118
|
+
|
|
119
|
+
**Recommended (Optional):** We recommend using [Anaconda](https://www.anaconda.com/products/distribution) to manage Python environments:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
conda create -n alchemist-env python=3.11
|
|
123
|
+
conda activate alchemist-env
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Basic Installation:**
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
pip install alchemist-nrel
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**From GitHub:**
|
|
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
|
+
|
|
135
|
+
```bash
|
|
136
|
+
pip install git+https://github.com/NREL/ALchemist.git
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
For advanced installation options, Docker deployment, and development setup, see the [Advanced Installation Guide](https://nrel.github.io/ALchemist/#advanced-installation) in the documentation.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Running ALchemist
|
|
144
|
+
|
|
145
|
+
**Web Application:**
|
|
146
|
+
```bash
|
|
147
|
+
alchemist-web
|
|
148
|
+
```
|
|
149
|
+
Opens at [http://localhost:8000/app](http://localhost:8000/app)
|
|
150
|
+
|
|
151
|
+
**Desktop Application:**
|
|
152
|
+
```bash
|
|
153
|
+
alchemist
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
For detailed usage instructions, see [Getting Started](https://nrel.github.io/ALchemist/) in the documentation.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Development Status
|
|
161
|
+
|
|
162
|
+
ALchemist is under active development at NLR as part of the DataHub project within the ChemCatBio consortium.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Issues & Troubleshooting
|
|
167
|
+
|
|
168
|
+
If you encounter any issues or have questions, please [open an issue on GitHub](https://github.com/NREL/ALchemist/issues) or contact ccoatney@nrel.gov.
|
|
169
|
+
|
|
170
|
+
For the latest known issues and troubleshooting tips, see the [Issues & Troubleshooting Log](docs/ISSUES_LOG.md).
|
|
171
|
+
|
|
172
|
+
We appreciate your feedback and bug reports to help improve ALchemist!
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
This project is licensed under the BSD 3-Clause License. See the [LICENSE](LICENSE) file for details.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Repository
|
|
183
|
+
|
|
184
|
+
[https://github.com/NREL/ALchemist](https://github.com/NREL/ALchemist)
|
|
185
|
+
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<img src="docs/assets/NEW_LOGO_LIGHT.png" alt="ALchemist" width="50%" />
|
|
2
|
+
|
|
3
|
+
**ALchemist: Active Learning Toolkit for Chemical and Materials Research**
|
|
4
|
+
|
|
5
|
+
ALchemist is a modular Python toolkit that brings active learning and Bayesian optimization to experimental design in chemical and materials research. It is designed for scientists and engineers who want to efficiently explore or optimize high-dimensional variable spaces—using intuitive graphical interfaces, programmatic APIs, or autonomous optimization workflows.
|
|
6
|
+
|
|
7
|
+
**NLR Software Record:** SWR-25-102
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Documentation
|
|
12
|
+
|
|
13
|
+
Full user guide and documentation:
|
|
14
|
+
[https://nrel.github.io/ALchemist/](https://nrel.github.io/ALchemist/)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Overview
|
|
19
|
+
|
|
20
|
+
**Key Features:**
|
|
21
|
+
|
|
22
|
+
- **Flexible variable space definition**: Real, integer, and categorical variables with bounds or discrete values
|
|
23
|
+
- **Probabilistic surrogate modeling**: Gaussian process regression via BoTorch or scikit-learn backends
|
|
24
|
+
- **Advanced acquisition strategies**: Efficient sampling using qEI, qPI, qUCB, and qNegIntegratedPosteriorVariance
|
|
25
|
+
- **Modern web interface**: React-based UI with FastAPI backend for seamless active learning workflows
|
|
26
|
+
- **Desktop GUI**: CustomTkinter desktop application for offline optimization
|
|
27
|
+
- **Session management**: Save/load optimization sessions with audit logs for reproducibility
|
|
28
|
+
- **Multiple interfaces**: No-code GUI, Python Session API, or REST API for different use cases
|
|
29
|
+
- **Autonomous optimization**: Human-out-of-the-loop operation for real-time process control
|
|
30
|
+
- **Experiment tracking**: CSV logging, reproducible random seeds, and comprehensive audit trails
|
|
31
|
+
- **Extensibility**: Abstract interfaces for models and acquisition functions enable future backend and workflow expansion
|
|
32
|
+
**Architecture:**
|
|
33
|
+
|
|
34
|
+
ALchemist is built on a clean, modular architecture:
|
|
35
|
+
|
|
36
|
+
- **Core Session API**: Headless Bayesian optimization engine (`alchemist_core`) that powers all interfaces
|
|
37
|
+
- **Desktop Application**: CustomTkinter GUI using the Core Session API, designed for human-in-the-loop and offline optimization
|
|
38
|
+
- **REST API**: FastAPI server providing a thin wrapper around the Core Session API for remote access
|
|
39
|
+
- **Web Application**: React UI consuming the REST API, supporting both interactive and autonomous optimization workflows
|
|
40
|
+
|
|
41
|
+
Session files (JSON format) are fully interoperable between desktop and web applications, enabling seamless workflow transitions.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Use Cases
|
|
46
|
+
|
|
47
|
+
- **Interactive Optimization**: Use desktop or web GUI for manual experiment design and human-in-the-loop optimization
|
|
48
|
+
- **Programmatic Workflows**: Import the Session API in Python scripts or Jupyter notebooks for batch processing
|
|
49
|
+
- **Autonomous Optimization**: Use the REST API to integrate ALchemist with automated laboratory equipment for real-time process control
|
|
50
|
+
- **Remote Monitoring**: Web dashboard provides read-only monitoring mode when ALchemist is being remote-controlled
|
|
51
|
+
|
|
52
|
+
For detailed application examples, see [Use Cases](https://nrel.github.io/ALchemist/use_cases/) in the documentation.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
**Requirements:** Python 3.11 or higher
|
|
59
|
+
|
|
60
|
+
**Recommended (Optional):** We recommend using [Anaconda](https://www.anaconda.com/products/distribution) to manage Python environments:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
conda create -n alchemist-env python=3.11
|
|
64
|
+
conda activate alchemist-env
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Basic Installation:**
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install alchemist-nrel
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**From GitHub:**
|
|
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
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install git+https://github.com/NREL/ALchemist.git
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
For advanced installation options, Docker deployment, and development setup, see the [Advanced Installation Guide](https://nrel.github.io/ALchemist/#advanced-installation) in the documentation.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Running ALchemist
|
|
85
|
+
|
|
86
|
+
**Web Application:**
|
|
87
|
+
```bash
|
|
88
|
+
alchemist-web
|
|
89
|
+
```
|
|
90
|
+
Opens at [http://localhost:8000/app](http://localhost:8000/app)
|
|
91
|
+
|
|
92
|
+
**Desktop Application:**
|
|
93
|
+
```bash
|
|
94
|
+
alchemist
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
For detailed usage instructions, see [Getting Started](https://nrel.github.io/ALchemist/) in the documentation.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Development Status
|
|
102
|
+
|
|
103
|
+
ALchemist is under active development at NLR as part of the DataHub project within the ChemCatBio consortium.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Issues & Troubleshooting
|
|
108
|
+
|
|
109
|
+
If you encounter any issues or have questions, please [open an issue on GitHub](https://github.com/NREL/ALchemist/issues) or contact ccoatney@nrel.gov.
|
|
110
|
+
|
|
111
|
+
For the latest known issues and troubleshooting tips, see the [Issues & Troubleshooting Log](docs/ISSUES_LOG.md).
|
|
112
|
+
|
|
113
|
+
We appreciate your feedback and bug reports to help improve ALchemist!
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
This project is licensed under the BSD 3-Clause License. See the [LICENSE](LICENSE) file for details.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Repository
|
|
124
|
+
|
|
125
|
+
[https://github.com/NREL/ALchemist](https://github.com/NREL/ALchemist)
|
|
126
|
+
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
useImportSession,
|
|
12
12
|
useUpdateSessionMetadata
|
|
13
13
|
} from './hooks/api/useSessions';
|
|
14
|
+
import { useLockStatus } from './hooks/useLockStatus';
|
|
14
15
|
import { VariablesPanel } from './features/variables/VariablesPanel';
|
|
15
16
|
import { ExperimentsPanel } from './features/experiments/ExperimentsPanel';
|
|
16
17
|
import { InitialDesignPanel } from './features/experiments/InitialDesignPanel';
|
|
@@ -21,13 +22,15 @@ import { VisualizationsPanel } from './components/visualizations';
|
|
|
21
22
|
import { TabView } from './components/ui';
|
|
22
23
|
import { SessionMetadataDialog } from './components/SessionMetadataDialog';
|
|
23
24
|
import { useTheme } from './hooks/useTheme';
|
|
24
|
-
import { Sun, Moon, X } from 'lucide-react';
|
|
25
|
+
import { Sun, Moon, X, Copy, Check } from 'lucide-react';
|
|
25
26
|
import './index.css';
|
|
26
27
|
|
|
27
28
|
function AppContent() {
|
|
28
29
|
const [sessionId, setSessionId] = useState<string | null>(null);
|
|
29
30
|
const [isMonitoringMode, setIsMonitoringMode] = useState<boolean>(false);
|
|
30
31
|
const [showMetadataDialog, setShowMetadataDialog] = useState(false);
|
|
32
|
+
const [copiedSessionId, setCopiedSessionId] = useState(false);
|
|
33
|
+
const [sessionFromUrl, setSessionFromUrl] = useState<boolean>(false);
|
|
31
34
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
32
35
|
|
|
33
36
|
const createSession = useCreateSession();
|
|
@@ -37,6 +40,12 @@ function AppContent() {
|
|
|
37
40
|
const { data: session, error: sessionError } = useSession(sessionId);
|
|
38
41
|
const { theme, toggleTheme } = useTheme();
|
|
39
42
|
const { isVisualizationOpen, closeVisualization, sessionId: vizSessionId } = useVisualization();
|
|
43
|
+
|
|
44
|
+
// Monitor lock status and auto-switch to monitoring mode
|
|
45
|
+
const { lockStatus } = useLockStatus(sessionId, 5000, (locked) => {
|
|
46
|
+
setIsMonitoringMode(locked);
|
|
47
|
+
});
|
|
48
|
+
|
|
40
49
|
// Global staged suggestions to mirror desktop main_app.pending_suggestions
|
|
41
50
|
const [pendingSuggestions, setPendingSuggestions] = useState<any[]>([]);
|
|
42
51
|
const [loadedFromFile, setLoadedFromFile] = useState<boolean>(false);
|
|
@@ -76,30 +85,47 @@ function AppContent() {
|
|
|
76
85
|
restorePendingSuggestions();
|
|
77
86
|
}, [sessionId]);
|
|
78
87
|
|
|
79
|
-
// Check for monitoring mode
|
|
88
|
+
// Check for URL parameters (session ID and monitoring mode)
|
|
80
89
|
useEffect(() => {
|
|
81
90
|
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
|
+
const urlSessionId = urlParams.get('session');
|
|
95
|
+
console.log('Session ID from URL:', urlSessionId);
|
|
96
|
+
|
|
97
|
+
if (urlSessionId) {
|
|
98
|
+
setSessionId(urlSessionId);
|
|
99
|
+
setSessionFromUrl(true); // Mark that this session came from URL
|
|
100
|
+
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
|
+
}
|
|
109
|
+
|
|
110
|
+
// Check for monitoring mode
|
|
82
111
|
const monitorParam = urlParams.get('mode');
|
|
112
|
+
console.log('Monitoring mode from URL:', monitorParam);
|
|
83
113
|
if (monitorParam === 'monitor') {
|
|
84
114
|
setIsMonitoringMode(true);
|
|
115
|
+
console.log('✓ Monitoring mode enabled');
|
|
85
116
|
}
|
|
86
117
|
}, []);
|
|
87
118
|
|
|
88
|
-
//
|
|
119
|
+
// Auto-clear invalid session (but not if it came from URL - let user see the error)
|
|
89
120
|
useEffect(() => {
|
|
90
|
-
|
|
91
|
-
if (storedId) {
|
|
92
|
-
setSessionId(storedId);
|
|
93
|
-
}
|
|
94
|
-
}, []);
|
|
95
|
-
|
|
96
|
-
// Auto-clear invalid session
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
if (sessionError && sessionId) {
|
|
121
|
+
if (sessionError && sessionId && !sessionFromUrl) {
|
|
99
122
|
toast.error('Session expired or not found. Please create a new session.');
|
|
100
123
|
handleClearSession();
|
|
124
|
+
} else if (sessionError && sessionFromUrl) {
|
|
125
|
+
// Show error but don't clear - might be loading or user wants to see the issue
|
|
126
|
+
console.warn('Session from URL not found or error loading:', sessionError);
|
|
101
127
|
}
|
|
102
|
-
}, [sessionError, sessionId]);
|
|
128
|
+
}, [sessionError, sessionId, sessionFromUrl]);
|
|
103
129
|
|
|
104
130
|
// Prompt to save on page close/reload (desktop _quit behavior)
|
|
105
131
|
useEffect(() => {
|
|
@@ -199,7 +225,6 @@ function AppContent() {
|
|
|
199
225
|
}
|
|
200
226
|
}
|
|
201
227
|
};
|
|
202
|
-
|
|
203
228
|
// Handle audit log export (matches desktop File → Export Audit Log)
|
|
204
229
|
const handleExportAuditLog = async () => {
|
|
205
230
|
if (!sessionId) return;
|
|
@@ -224,6 +249,26 @@ function AppContent() {
|
|
|
224
249
|
}
|
|
225
250
|
};
|
|
226
251
|
|
|
252
|
+
// Handle copy session ID to clipboard
|
|
253
|
+
const handleCopySessionId = async () => {
|
|
254
|
+
if (!sessionId) return;
|
|
255
|
+
try {
|
|
256
|
+
await navigator.clipboard.writeText(sessionId);
|
|
257
|
+
setCopiedSessionId(true);
|
|
258
|
+
toast.success('Session ID copied to clipboard!');
|
|
259
|
+
setTimeout(() => setCopiedSessionId(false), 2000);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
toast.error('Failed to copy session ID');
|
|
262
|
+
console.error('Error copying session ID:', error);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// Debug: Log state before render
|
|
267
|
+
console.log('=== Render State ===');
|
|
268
|
+
console.log('sessionId:', sessionId);
|
|
269
|
+
console.log('isMonitoringMode:', isMonitoringMode);
|
|
270
|
+
console.log('Should show monitoring:', isMonitoringMode && sessionId);
|
|
271
|
+
|
|
227
272
|
return (
|
|
228
273
|
<div className="h-screen bg-background flex flex-col overflow-hidden">
|
|
229
274
|
{/* Monitoring Mode - Show dedicated dashboard */}
|
|
@@ -264,15 +309,36 @@ function AppContent() {
|
|
|
264
309
|
{/* Session Controls */}
|
|
265
310
|
{sessionId ? (
|
|
266
311
|
<div className="flex items-center gap-3">
|
|
267
|
-
<div className="
|
|
268
|
-
<
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
312
|
+
<div className="flex items-center gap-2">
|
|
313
|
+
<div className="text-sm text-muted-foreground">
|
|
314
|
+
<code className="bg-muted px-2 py-1 rounded text-xs">
|
|
315
|
+
{sessionId.substring(0, 8)}
|
|
316
|
+
</code>
|
|
317
|
+
{session && (
|
|
318
|
+
<span className="ml-2">
|
|
319
|
+
{session.variable_count}V · {session.experiment_count}E
|
|
320
|
+
</span>
|
|
321
|
+
)}
|
|
322
|
+
{lockStatus?.locked && (
|
|
323
|
+
<span className="ml-2 inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs bg-blue-500/10 text-blue-500 border border-blue-500/20">
|
|
324
|
+
<svg className="h-3 w-3" fill="currentColor" viewBox="0 0 20 20">
|
|
325
|
+
<path fillRule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clipRule="evenodd" />
|
|
326
|
+
</svg>
|
|
327
|
+
{lockStatus.locked_by}
|
|
328
|
+
</span>
|
|
329
|
+
)}
|
|
330
|
+
</div>
|
|
331
|
+
<button
|
|
332
|
+
onClick={handleCopySessionId}
|
|
333
|
+
className="p-1.5 rounded hover:bg-accent transition-colors"
|
|
334
|
+
title="Copy full session ID to clipboard"
|
|
335
|
+
>
|
|
336
|
+
{copiedSessionId ? (
|
|
337
|
+
<Check className="h-3.5 w-3.5 text-green-500" />
|
|
338
|
+
) : (
|
|
339
|
+
<Copy className="h-3.5 w-3.5 text-muted-foreground hover:text-foreground" />
|
|
340
|
+
)}
|
|
341
|
+
</button>
|
|
276
342
|
</div>
|
|
277
343
|
<button
|
|
278
344
|
onClick={() => setShowMetadataDialog(true)}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { useEffect, useState, useCallback, useRef } from 'react';
|
|
2
|
+
import { toast } from 'sonner';
|
|
3
|
+
|
|
4
|
+
interface LockStatus {
|
|
5
|
+
locked: boolean;
|
|
6
|
+
locked_by: string | null;
|
|
7
|
+
locked_at: string | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface UseLockStatusReturn {
|
|
11
|
+
lockStatus: LockStatus | null;
|
|
12
|
+
isConnected: boolean;
|
|
13
|
+
error: Error | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Hook to monitor session lock status via WebSocket for real-time updates.
|
|
18
|
+
*
|
|
19
|
+
* When a session is locked by an external controller (e.g., Qt app),
|
|
20
|
+
* this hook receives instant push notifications and can trigger automatic monitor mode.
|
|
21
|
+
*
|
|
22
|
+
* @param sessionId - The session ID to monitor
|
|
23
|
+
* @param _pollingInterval - Deprecated parameter (kept for backwards compatibility)
|
|
24
|
+
* @param onLockStateChange - Callback when lock state changes
|
|
25
|
+
*/
|
|
26
|
+
export function useLockStatus(
|
|
27
|
+
sessionId: string | null,
|
|
28
|
+
_pollingInterval?: number, // Kept for backwards compatibility but unused
|
|
29
|
+
onLockStateChange?: (locked: boolean, lockedBy: string | null) => void
|
|
30
|
+
): UseLockStatusReturn {
|
|
31
|
+
const [lockStatus, setLockStatus] = useState<LockStatus | null>(null);
|
|
32
|
+
const [isConnected, setIsConnected] = useState<boolean>(false);
|
|
33
|
+
const [error, setError] = useState<Error | null>(null);
|
|
34
|
+
const wsRef = useRef<WebSocket | null>(null);
|
|
35
|
+
const reconnectTimeoutRef = useRef<number | null>(null);
|
|
36
|
+
const previousLockStateRef = useRef<boolean | null>(null);
|
|
37
|
+
|
|
38
|
+
const connect = useCallback(() => {
|
|
39
|
+
if (!sessionId) return;
|
|
40
|
+
|
|
41
|
+
// Clean up existing connection
|
|
42
|
+
if (wsRef.current) {
|
|
43
|
+
wsRef.current.close();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Create WebSocket connection
|
|
48
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
49
|
+
const host = window.location.hostname;
|
|
50
|
+
const port = '8000'; // FastAPI backend port
|
|
51
|
+
const ws = new WebSocket(`${protocol}//${host}:${port}/api/v1/ws/sessions/${sessionId}`);
|
|
52
|
+
|
|
53
|
+
ws.onopen = () => {
|
|
54
|
+
console.log('✓ WebSocket connected for session lock status');
|
|
55
|
+
setIsConnected(true);
|
|
56
|
+
setError(null);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
ws.onmessage = (event) => {
|
|
60
|
+
try {
|
|
61
|
+
const data = JSON.parse(event.data);
|
|
62
|
+
|
|
63
|
+
if (data.event === 'connected') {
|
|
64
|
+
console.log('✓ WebSocket connection confirmed');
|
|
65
|
+
} else if (data.event === 'lock_status_changed') {
|
|
66
|
+
const newStatus: LockStatus = {
|
|
67
|
+
locked: data.locked,
|
|
68
|
+
locked_by: data.locked_by,
|
|
69
|
+
locked_at: data.locked_at,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
setLockStatus(newStatus);
|
|
73
|
+
|
|
74
|
+
// Show toast notification only on state changes (not initial connection)
|
|
75
|
+
const previousLockState = previousLockStateRef.current;
|
|
76
|
+
if (previousLockState !== null && previousLockState !== data.locked) {
|
|
77
|
+
if (data.locked) {
|
|
78
|
+
toast.info(`External controller connected: ${data.locked_by}`, {
|
|
79
|
+
duration: 5000,
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
toast.info('External controller disconnected - resuming interactive mode', {
|
|
83
|
+
duration: 3000,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Update previous state
|
|
89
|
+
previousLockStateRef.current = data.locked;
|
|
90
|
+
|
|
91
|
+
// Trigger callback
|
|
92
|
+
if (onLockStateChange) {
|
|
93
|
+
onLockStateChange(data.locked, data.locked_by);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.error('Failed to parse WebSocket message:', err);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
ws.onerror = (event) => {
|
|
102
|
+
console.error('WebSocket error:', event);
|
|
103
|
+
setError(new Error('WebSocket connection error'));
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
ws.onclose = () => {
|
|
107
|
+
console.log('WebSocket disconnected');
|
|
108
|
+
setIsConnected(false);
|
|
109
|
+
wsRef.current = null;
|
|
110
|
+
|
|
111
|
+
// Attempt to reconnect after 5 seconds
|
|
112
|
+
if (sessionId) {
|
|
113
|
+
reconnectTimeoutRef.current = setTimeout(() => {
|
|
114
|
+
console.log('Attempting to reconnect WebSocket...');
|
|
115
|
+
connect();
|
|
116
|
+
}, 5000);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
wsRef.current = ws;
|
|
121
|
+
} catch (err) {
|
|
122
|
+
const error = err instanceof Error ? err : new Error('Failed to create WebSocket');
|
|
123
|
+
setError(error);
|
|
124
|
+
console.error('Error creating WebSocket:', error);
|
|
125
|
+
}
|
|
126
|
+
}, [sessionId, onLockStateChange]);
|
|
127
|
+
|
|
128
|
+
// Connect on mount or when sessionId changes
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
if (!sessionId) {
|
|
131
|
+
// Clean up if no session
|
|
132
|
+
if (wsRef.current) {
|
|
133
|
+
wsRef.current.close();
|
|
134
|
+
wsRef.current = null;
|
|
135
|
+
}
|
|
136
|
+
if (reconnectTimeoutRef.current) {
|
|
137
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
138
|
+
}
|
|
139
|
+
setIsConnected(false);
|
|
140
|
+
setLockStatus(null);
|
|
141
|
+
previousLockStateRef.current = null;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
connect();
|
|
146
|
+
|
|
147
|
+
// Cleanup on unmount
|
|
148
|
+
return () => {
|
|
149
|
+
if (wsRef.current) {
|
|
150
|
+
wsRef.current.close();
|
|
151
|
+
wsRef.current = null;
|
|
152
|
+
}
|
|
153
|
+
if (reconnectTimeoutRef.current) {
|
|
154
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}, [sessionId, connect]);
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
lockStatus,
|
|
161
|
+
isConnected,
|
|
162
|
+
error,
|
|
163
|
+
};
|
|
164
|
+
}
|