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.
Files changed (159) hide show
  1. {alchemist_nrel-0.3.1/alchemist_nrel.egg-info → alchemist_nrel-0.3.2}/PKG-INFO +13 -13
  2. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/README.md +7 -7
  3. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/package.json +1 -1
  4. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/App.tsx +193 -59
  5. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/api/endpoints/experiments.ts +40 -2
  6. alchemist_nrel-0.3.2/alchemist-web/src/components/TargetColumnDialog.tsx +203 -0
  7. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/acquisition/AcquisitionPanel.tsx +21 -6
  8. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/experiments/ExperimentsPanel.tsx +81 -7
  9. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/experiments/InitialDesignPanel.tsx +83 -9
  10. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/api/useExperiments.ts +15 -2
  11. alchemist_nrel-0.3.2/alchemist-web/src/hooks/useSessionEvents.ts +186 -0
  12. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/__init__.py +2 -2
  13. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/acquisition/botorch_acquisition.py +83 -126
  14. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/data/experiment_manager.py +181 -12
  15. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/models/botorch_model.py +292 -63
  16. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/models/sklearn_model.py +145 -13
  17. alchemist_nrel-0.3.2/alchemist_core/session.py +4651 -0
  18. alchemist_nrel-0.3.2/alchemist_core/utils/__init__.py +9 -0
  19. alchemist_nrel-0.3.2/alchemist_core/utils/acquisition_utils.py +60 -0
  20. alchemist_nrel-0.3.2/alchemist_core/visualization/__init__.py +45 -0
  21. alchemist_nrel-0.3.2/alchemist_core/visualization/helpers.py +130 -0
  22. alchemist_nrel-0.3.2/alchemist_core/visualization/plots.py +1449 -0
  23. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2/alchemist_nrel.egg-info}/PKG-INFO +13 -13
  24. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_nrel.egg-info/SOURCES.txt +9 -2
  25. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/main.py +1 -1
  26. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/models/requests.py +52 -0
  27. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/models/responses.py +79 -2
  28. alchemist_nrel-0.3.2/api/routers/experiments.py +607 -0
  29. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/routers/sessions.py +84 -9
  30. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/routers/visualizations.py +6 -4
  31. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/routers/websocket.py +2 -2
  32. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/services/session_store.py +295 -71
  33. alchemist_nrel-0.3.2/api/static/assets/index-B6Cf6s_b.css +1 -0
  34. alchemist_nrel-0.3.1/api/static/assets/index-DWfIKU9j.js → alchemist_nrel-0.3.2/api/static/assets/index-B7njvc9r.js +201 -196
  35. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/static/index.html +2 -2
  36. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/build_tools/build_hooks.py +2 -2
  37. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/pyproject.toml +6 -6
  38. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/gpr_panel.py +11 -5
  39. alchemist_nrel-0.3.2/ui/target_column_dialog.py +299 -0
  40. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/ui.py +52 -5
  41. alchemist_nrel-0.3.1/alchemist_core/session.py +0 -1352
  42. alchemist_nrel-0.3.1/alchemist_core/utils/__init__.py +0 -7
  43. alchemist_nrel-0.3.1/api/routers/experiments.py +0 -282
  44. alchemist_nrel-0.3.1/api/static/assets/index-sMIa_1hV.css +0 -1
  45. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/LICENSE +0 -0
  46. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/MANIFEST.in +0 -0
  47. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/eslint.config.js +0 -0
  48. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/index.html +0 -0
  49. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/package-lock.json +0 -0
  50. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/postcss.config.js +0 -0
  51. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/public/NEW_ICON.ico +0 -0
  52. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/public/NEW_ICON.png +0 -0
  53. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/public/NEW_LOGO_DARK.png +0 -0
  54. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/public/NEW_LOGO_LIGHT.png +0 -0
  55. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/public/vite.svg +0 -0
  56. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/App.css +0 -0
  57. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/api/client.ts +0 -0
  58. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/api/endpoints/acquisition.ts +0 -0
  59. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/api/endpoints/models.ts +0 -0
  60. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/api/endpoints/sessions.ts +0 -0
  61. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/api/endpoints/variables.ts +0 -0
  62. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/api/endpoints/visualizations.ts +0 -0
  63. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/api/types.ts +0 -0
  64. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/assets/react.svg +0 -0
  65. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/AddPointDialog.tsx +0 -0
  66. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/AuditLockDialog.tsx +0 -0
  67. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/PendingSuggestionsPanel.tsx +0 -0
  68. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/SessionMetadata.tsx +0 -0
  69. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/SessionMetadataDialog.tsx +0 -0
  70. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/api.ts +0 -0
  71. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/session-components.md +0 -0
  72. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/ui/CollapsibleSection.tsx +0 -0
  73. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/ui/FormField.tsx +0 -0
  74. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/ui/SegmentedButton.tsx +0 -0
  75. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/ui/TabView.tsx +0 -0
  76. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/ui/index.ts +0 -0
  77. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/visualizations/CalibrationCurve.tsx +0 -0
  78. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/visualizations/ContourPlot.tsx +0 -0
  79. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/visualizations/ContourPlotSimple.tsx +0 -0
  80. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/visualizations/HyperparametersDisplay.tsx +0 -0
  81. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/visualizations/MetricsPlot.tsx +0 -0
  82. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/visualizations/ParityPlot.tsx +0 -0
  83. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/visualizations/QQPlot.tsx +0 -0
  84. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/visualizations/VisualizationsPanel.tsx +0 -0
  85. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/components/visualizations/index.ts +0 -0
  86. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/acquisition/index.ts +0 -0
  87. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/models/GPRPanel.tsx +0 -0
  88. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/models/README.md +0 -0
  89. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/models/index.ts +0 -0
  90. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/monitoring/MonitoringDashboard.tsx +0 -0
  91. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/variables/VariableForm.tsx +0 -0
  92. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/variables/VariableList.tsx +0 -0
  93. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/features/variables/VariablesPanel.tsx +0 -0
  94. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/api/useAcquisition.ts +0 -0
  95. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/api/useDeleteVariable.ts +0 -0
  96. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/api/useFileOperations.ts +0 -0
  97. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/api/useModels.ts +0 -0
  98. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/api/useSessions.ts +0 -0
  99. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/api/useVariables.ts +0 -0
  100. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/api/useVisualizations.ts +0 -0
  101. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/useLockStatus.ts +0 -0
  102. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/hooks/useTheme.tsx +0 -0
  103. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/index.css +0 -0
  104. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/lib/utils.ts +0 -0
  105. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/main.tsx +0 -0
  106. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/providers/QueryProvider.tsx +0 -0
  107. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/src/providers/VisualizationProvider.tsx +0 -0
  108. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/tailwind.config.js +0 -0
  109. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/tsconfig.app.json +0 -0
  110. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/tsconfig.json +0 -0
  111. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/tsconfig.node.json +0 -0
  112. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist-web/vite.config.ts +0 -0
  113. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/acquisition/__init__.py +0 -0
  114. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/acquisition/base_acquisition.py +0 -0
  115. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/acquisition/skopt_acquisition.py +0 -0
  116. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/audit_log.py +0 -0
  117. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/config.py +0 -0
  118. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/data/__init__.py +0 -0
  119. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/data/search_space.py +0 -0
  120. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/events.py +0 -0
  121. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/models/__init__.py +0 -0
  122. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/models/base_model.py +0 -0
  123. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_core/utils/doe.py +0 -0
  124. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_nrel.egg-info/dependency_links.txt +0 -0
  125. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_nrel.egg-info/entry_points.txt +0 -0
  126. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_nrel.egg-info/requires.txt +0 -0
  127. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/alchemist_nrel.egg-info/top_level.txt +0 -0
  128. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/__init__.py +0 -0
  129. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/dependencies.py +0 -0
  130. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/example_client.py +0 -0
  131. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/middleware/__init__.py +0 -0
  132. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/middleware/error_handlers.py +0 -0
  133. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/models/__init__.py +0 -0
  134. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/routers/__init__.py +0 -0
  135. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/routers/acquisition.py +0 -0
  136. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/routers/models.py +0 -0
  137. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/routers/variables.py +0 -0
  138. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/run_api.py +0 -0
  139. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/services/__init__.py +0 -0
  140. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/static/NEW_ICON.ico +0 -0
  141. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/static/NEW_ICON.png +0 -0
  142. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/static/NEW_LOGO_DARK.png +0 -0
  143. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/static/NEW_LOGO_LIGHT.png +0 -0
  144. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/static/assets/api-vcoXEqyq.js +0 -0
  145. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/api/static/vite.svg +0 -0
  146. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/build_tools/__init__.py +0 -0
  147. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/build_tools/setup.py +0 -0
  148. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/main.py +0 -0
  149. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/setup.cfg +0 -0
  150. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/__init__.py +0 -0
  151. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/acquisition_panel.py +0 -0
  152. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/custom_widgets.py +0 -0
  153. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/experiment_logger.py +0 -0
  154. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/notifications.py +0 -0
  155. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/pool_viz.py +0 -0
  156. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/ui_utils.py +0 -0
  157. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/utils.py +0 -0
  158. {alchemist_nrel-0.3.1 → alchemist_nrel-0.3.2}/ui/variables_setup.py +0 -0
  159. {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.1
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/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
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://nrel.github.io/ALchemist/](https://nrel.github.io/ALchemist/)
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://nrel.github.io/ALchemist/use_cases/) in the documentation.
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/NREL/ALchemist.git
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://nrel.github.io/ALchemist/#advanced-installation) in the documentation.
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://nrel.github.io/ALchemist/) in the documentation.
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/NREL/ALchemist/issues) or contact ccoatney@nrel.gov.
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/NREL/ALchemist](https://github.com/NREL/ALchemist)
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://nrel.github.io/ALchemist/](https://nrel.github.io/ALchemist/)
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://nrel.github.io/ALchemist/use_cases/) in the documentation.
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/NREL/ALchemist.git
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://nrel.github.io/ALchemist/#advanced-installation) in the documentation.
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://nrel.github.io/ALchemist/) in the documentation.
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/NREL/ALchemist/issues) or contact ccoatney@nrel.gov.
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/NREL/ALchemist](https://github.com/NREL/ALchemist)
125
+ [https://github.com/NatLabRockies/ALchemist](https://github.com/NatLabRockies/ALchemist)
126
126
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "alchemist-web",
3
3
  "private": true,
4
- "version": "0.3.1",
4
+ "version": "0.3.2",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -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 { useLockStatus } from './hooks/useLockStatus';
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 and auto-switch to monitoring mode
45
- const { lockStatus } = useLockStatus(sessionId, 5000, (locked) => {
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 audit log on session load (desktop workflow)
55
+ // Restore pending suggestions from staged experiments API on session load
54
56
  useEffect(() => {
55
57
  if (!sessionId) return;
56
58
 
57
- async function restorePendingSuggestions() {
59
+ async function restoreStagedExperiments() {
58
60
  try {
59
- const response = await fetch(`/api/v1/sessions/${sessionId}/audit?entry_type=acquisition_locked`);
60
- if (!response.ok) return;
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 response.json();
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 pending suggestions:', e);
102
+ console.error('Failed to restore staged experiments:', e);
82
103
  }
83
104
  }
84
105
 
85
- restorePendingSuggestions();
106
+ restoreStagedExperiments();
86
107
  }, [sessionId]);
87
108
 
88
- // Check for URL parameters (session ID and monitoring mode)
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); // Mark that this session came from URL
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 expired or not found. Please create a new 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
- // Prompt to save on page close/reload (desktop _quit behavior)
152
+ // Silent auto-backup every 30 seconds for crash recovery
131
153
  useEffect(() => {
132
154
  if (!sessionId) return;
133
155
 
134
- const handleBeforeUnload = (e: BeforeUnloadEvent) => {
135
- // Only prompt if session has data
136
- if (session && (session.variable_count > 0 || session.experiment_count > 0)) {
137
- e.preventDefault();
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
- window.addEventListener('beforeunload', handleBeforeUnload);
144
- return () => window.removeEventListener('beforeunload', handleBeforeUnload);
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 (desktop _quit behavior)
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
- // If this session was loaded via the file upload dialog, persist server-side
192
- if (loadedFromFile) {
193
- await exportSession.mutateAsync({ sessionId, serverSide: true });
194
- } else {
195
- await exportSession.mutateAsync({ sessionId, serverSide: false });
196
- }
197
- toast.success('Session exported successfully!');
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 export session');
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-3.5 w-3.5 text-green-500" />
451
+ <Check className="h-4 w-4 text-green-500" />
338
452
  ) : (
339
- <Copy className="h-3.5 w-3.5 text-muted-foreground hover:text-foreground" />
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 sessionId={sessionId} />
541
+ <InitialDesignPanel
542
+ sessionId={sessionId}
543
+ onStageSuggestions={(s:any[])=>setPendingSuggestions(s)}
544
+ />
411
545
  </div>
412
546
 
413
547
  {/* CENTER - Visualization Area (expandable) */}
@@ -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/upload`,
89
+ `/sessions/${sessionId}/experiments/preview`,
52
90
  formData,
53
91
  {
54
92
  headers: {