ivoryos 1.0.9__py3-none-any.whl → 1.4.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. docs/source/conf.py +84 -0
  2. ivoryos/__init__.py +17 -207
  3. ivoryos/app.py +154 -0
  4. ivoryos/config.py +1 -0
  5. ivoryos/optimizer/ax_optimizer.py +191 -0
  6. ivoryos/optimizer/base_optimizer.py +84 -0
  7. ivoryos/optimizer/baybe_optimizer.py +193 -0
  8. ivoryos/optimizer/nimo_optimizer.py +173 -0
  9. ivoryos/optimizer/registry.py +11 -0
  10. ivoryos/routes/auth/auth.py +43 -14
  11. ivoryos/routes/auth/templates/change_password.html +32 -0
  12. ivoryos/routes/control/control.py +101 -366
  13. ivoryos/routes/control/control_file.py +33 -0
  14. ivoryos/routes/control/control_new_device.py +152 -0
  15. ivoryos/routes/control/templates/controllers.html +193 -0
  16. ivoryos/routes/control/templates/controllers_new.html +112 -0
  17. ivoryos/routes/control/utils.py +40 -0
  18. ivoryos/routes/data/data.py +197 -0
  19. ivoryos/routes/data/templates/components/step_card.html +78 -0
  20. ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +14 -8
  21. ivoryos/routes/data/templates/workflow_view.html +360 -0
  22. ivoryos/routes/design/__init__.py +4 -0
  23. ivoryos/routes/design/design.py +348 -657
  24. ivoryos/routes/design/design_file.py +68 -0
  25. ivoryos/routes/design/design_step.py +171 -0
  26. ivoryos/routes/design/templates/components/action_form.html +53 -0
  27. ivoryos/routes/design/templates/components/actions_panel.html +25 -0
  28. ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
  29. ivoryos/routes/design/templates/components/canvas.html +5 -0
  30. ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
  31. ivoryos/routes/design/templates/components/canvas_header.html +75 -0
  32. ivoryos/routes/design/templates/components/canvas_main.html +39 -0
  33. ivoryos/routes/design/templates/components/deck_selector.html +10 -0
  34. ivoryos/routes/design/templates/components/edit_action_form.html +53 -0
  35. ivoryos/routes/design/templates/components/info_modal.html +318 -0
  36. ivoryos/routes/design/templates/components/instruments_panel.html +88 -0
  37. ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
  38. ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
  39. ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -0
  40. ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
  41. ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
  42. ivoryos/routes/design/templates/components/modals.html +6 -0
  43. ivoryos/routes/design/templates/components/python_code_overlay.html +56 -0
  44. ivoryos/routes/design/templates/components/sidebar.html +15 -0
  45. ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
  46. ivoryos/routes/design/templates/experiment_builder.html +44 -0
  47. ivoryos/routes/execute/__init__.py +0 -0
  48. ivoryos/routes/execute/execute.py +377 -0
  49. ivoryos/routes/execute/execute_file.py +78 -0
  50. ivoryos/routes/execute/templates/components/error_modal.html +20 -0
  51. ivoryos/routes/execute/templates/components/logging_panel.html +56 -0
  52. ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
  53. ivoryos/routes/execute/templates/components/run_panel.html +9 -0
  54. ivoryos/routes/execute/templates/components/run_tabs.html +60 -0
  55. ivoryos/routes/execute/templates/components/tab_bayesian.html +520 -0
  56. ivoryos/routes/execute/templates/components/tab_configuration.html +383 -0
  57. ivoryos/routes/execute/templates/components/tab_repeat.html +18 -0
  58. ivoryos/routes/execute/templates/experiment_run.html +30 -0
  59. ivoryos/routes/library/__init__.py +0 -0
  60. ivoryos/routes/library/library.py +157 -0
  61. ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +32 -23
  62. ivoryos/routes/main/main.py +31 -3
  63. ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
  64. ivoryos/server.py +180 -0
  65. ivoryos/socket_handlers.py +52 -0
  66. ivoryos/static/ivoryos_logo.png +0 -0
  67. ivoryos/static/js/action_handlers.js +384 -0
  68. ivoryos/static/js/db_delete.js +23 -0
  69. ivoryos/static/js/script_metadata.js +39 -0
  70. ivoryos/static/js/socket_handler.js +40 -5
  71. ivoryos/static/js/sortable_design.js +107 -56
  72. ivoryos/static/js/ui_state.js +114 -0
  73. ivoryos/templates/base.html +67 -8
  74. ivoryos/utils/bo_campaign.py +180 -3
  75. ivoryos/utils/client_proxy.py +267 -36
  76. ivoryos/utils/db_models.py +300 -65
  77. ivoryos/utils/decorators.py +34 -0
  78. ivoryos/utils/form.py +63 -29
  79. ivoryos/utils/global_config.py +34 -1
  80. ivoryos/utils/nest_script.py +314 -0
  81. ivoryos/utils/py_to_json.py +295 -0
  82. ivoryos/utils/script_runner.py +599 -165
  83. ivoryos/utils/serilize.py +201 -0
  84. ivoryos/utils/task_runner.py +71 -21
  85. ivoryos/utils/utils.py +50 -6
  86. ivoryos/version.py +1 -1
  87. ivoryos-1.4.4.dist-info/METADATA +263 -0
  88. ivoryos-1.4.4.dist-info/RECORD +119 -0
  89. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/WHEEL +1 -1
  90. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/top_level.txt +1 -0
  91. tests/unit/test_type_conversion.py +42 -0
  92. tests/unit/test_util.py +3 -0
  93. ivoryos/routes/control/templates/control/controllers.html +0 -78
  94. ivoryos/routes/control/templates/control/controllers_home.html +0 -55
  95. ivoryos/routes/control/templates/control/controllers_new.html +0 -89
  96. ivoryos/routes/database/database.py +0 -306
  97. ivoryos/routes/database/templates/database/step_card.html +0 -7
  98. ivoryos/routes/database/templates/database/workflow_view.html +0 -130
  99. ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
  100. ivoryos/routes/design/templates/design/experiment_run.html +0 -558
  101. ivoryos-1.0.9.dist-info/METADATA +0 -218
  102. ivoryos-1.0.9.dist-info/RECORD +0 -61
  103. /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
  104. /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
  105. /ivoryos/routes/{database → data}/__init__.py +0 -0
  106. /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
  107. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,384 @@
1
+ // ============================================================================
2
+ // STATE MANAGEMENT
3
+ // ============================================================================
4
+
5
+ let previousHtmlState = null; // Store previous instrument panel state
6
+ let lastFocusedElement = null; // Track focus for modal management
7
+
8
+ // ============================================================================
9
+ // MODE & BATCH MANAGEMENT
10
+ // ============================================================================
11
+
12
+ function getMode() {
13
+ return sessionStorage.getItem("mode") || "single";
14
+ }
15
+
16
+ function setMode(mode, triggerUpdate = true) {
17
+ sessionStorage.setItem("mode", mode);
18
+
19
+ const modeButtons = document.querySelectorAll(".mode-toggle");
20
+ const batchOptions = document.getElementById("batch-options");
21
+
22
+ modeButtons.forEach(b => b.classList.toggle("active", b.dataset.mode === mode));
23
+
24
+ if (batchOptions) {
25
+ batchOptions.style.display = (mode === "batch") ? "inline-flex" : "none";
26
+ }
27
+
28
+ if (triggerUpdate) updateCode();
29
+ }
30
+
31
+ function getBatch() {
32
+ return sessionStorage.getItem("batch") || "sample";
33
+ }
34
+
35
+ function setBatch(batch, triggerUpdate = true) {
36
+ sessionStorage.setItem("batch", batch);
37
+
38
+ const batchButtons = document.querySelectorAll(".batch-toggle");
39
+ batchButtons.forEach(b => b.classList.toggle("active", b.dataset.batch === batch));
40
+
41
+ if (triggerUpdate) updateCode();
42
+ }
43
+
44
+ // ============================================================================
45
+ // CODE OVERLAY MANAGEMENT
46
+ // ============================================================================
47
+
48
+ async function updateCode() {
49
+ try {
50
+ const params = new URLSearchParams({ mode: getMode(), batch: getBatch() });
51
+ const res = await fetch(scriptCompileUrl + "?" + params.toString());
52
+ if (!res.ok) return;
53
+
54
+ const data = await res.json();
55
+ const codeElem = document.getElementById("python-code");
56
+
57
+ codeElem.removeAttribute("data-highlighted"); // Reset highlight.js flag
58
+ codeElem.textContent = data.code['script'] || "# No code found";
59
+
60
+ if (window.hljs) {
61
+ hljs.highlightElement(codeElem);
62
+ }
63
+ } catch (err) {
64
+ console.error("Error updating code:", err);
65
+ }
66
+ }
67
+
68
+ function initializeCodeOverlay() {
69
+ const codeElem = document.getElementById("python-code");
70
+ const copyBtn = document.getElementById("copy-code");
71
+ const downloadBtn = document.getElementById("download-code");
72
+
73
+ if (!copyBtn || !downloadBtn) return; // Elements don't exist
74
+
75
+ // Remove old listeners by cloning (prevents duplicate bindings)
76
+ const newCopyBtn = copyBtn.cloneNode(true);
77
+ const newDownloadBtn = downloadBtn.cloneNode(true);
78
+ copyBtn.parentNode.replaceChild(newCopyBtn, copyBtn);
79
+ downloadBtn.parentNode.replaceChild(newDownloadBtn, downloadBtn);
80
+
81
+ // Copy to clipboard
82
+ newCopyBtn.addEventListener("click", () => {
83
+ navigator.clipboard.writeText(codeElem.textContent)
84
+ .then(() => alert("Code copied!"))
85
+ .catch(err => console.error("Failed to copy", err));
86
+ });
87
+
88
+ // Download current code
89
+ newDownloadBtn.addEventListener("click", () => {
90
+ const blob = new Blob([codeElem.textContent], { type: "text/plain" });
91
+ const url = URL.createObjectURL(blob);
92
+ const a = document.createElement("a");
93
+ a.href = url;
94
+ a.download = "script.py";
95
+ a.click();
96
+ URL.revokeObjectURL(url);
97
+ });
98
+ updateCode();
99
+ }
100
+
101
+ // ============================================================================
102
+ // UI UPDATE FUNCTIONS
103
+ // ============================================================================
104
+
105
+ function updateActionCanvas(html) {
106
+ document.getElementById("canvas-action-wrapper").innerHTML = html;
107
+ initializeCanvas();
108
+
109
+ const mode = getMode();
110
+ const batch = getBatch();
111
+
112
+ // Rebind event handlers for mode/batch toggles
113
+ document.querySelectorAll(".mode-toggle").forEach(btn => {
114
+ btn.addEventListener("click", () => setMode(btn.dataset.mode));
115
+ });
116
+ document.querySelectorAll(".batch-toggle").forEach(btn => {
117
+ btn.addEventListener("click", () => setBatch(btn.dataset.batch));
118
+ });
119
+
120
+ // Restore toggle UI state (without triggering updates)
121
+ setMode(mode, false);
122
+ setBatch(batch, false);
123
+
124
+ // Reinitialize code overlay buttons
125
+ initializeCodeOverlay();
126
+
127
+ }
128
+
129
+ function updateInstrumentPanel(link) {
130
+ const url = link.dataset.getUrl;
131
+
132
+ fetch(url)
133
+ .then(res => res.json())
134
+ .then(data => {
135
+ if (data.html) {
136
+ document.getElementById("sidebar-wrapper").innerHTML = data.html;
137
+ initializeDragHandlers();
138
+ }
139
+ })
140
+ .catch(err => console.error("Error updating instrument panel:", err));
141
+ }
142
+
143
+ // ============================================================================
144
+ // WORKFLOW MANAGEMENT
145
+ // ============================================================================
146
+
147
+ function saveWorkflow(link) {
148
+ const url = link.dataset.postUrl;
149
+
150
+ fetch(url, {
151
+ method: 'POST',
152
+ headers: {
153
+ 'Content-Type': 'application/json'
154
+ }
155
+ })
156
+ .then(res => res.json())
157
+ .then(data => {
158
+ if (data.success) {
159
+ window.location.reload();
160
+ } else {
161
+ alert("Failed to save workflow: " + data.error);
162
+ }
163
+ })
164
+ .catch(err => {
165
+ console.error("Save error:", err);
166
+ alert("Something went wrong.");
167
+ });
168
+ }
169
+
170
+ function clearDraft() {
171
+ fetch(scriptDeleteUrl, {
172
+ method: "DELETE",
173
+ headers: {
174
+ "Content-Type": "application/json",
175
+ },
176
+ })
177
+ .then(res => res.json())
178
+ .then(data => {
179
+ if (data.success) {
180
+ window.location.reload();
181
+ } else {
182
+ alert("Failed to clear draft");
183
+ }
184
+ })
185
+ .catch(error => console.error("Failed to clear draft", error));
186
+ }
187
+
188
+ // ============================================================================
189
+ // ACTION MANAGEMENT (CRUD Operations)
190
+ // ============================================================================
191
+
192
+ function addMethodToDesign(event, form) {
193
+ event.preventDefault();
194
+
195
+ const formData = new FormData(form);
196
+
197
+ fetch(form.action, {
198
+ method: 'POST',
199
+ body: formData
200
+ })
201
+ .then(response => response.json())
202
+ .then(data => {
203
+ if (data.success) {
204
+ updateActionCanvas(data.html);
205
+ hideModal();
206
+ } else {
207
+ alert("Failed to add method: " + data.error);
208
+ }
209
+ })
210
+ .catch(error => console.error('Error:', error));
211
+ }
212
+
213
+ function editAction(uuid) {
214
+ if (!uuid) {
215
+ console.error('Invalid UUID');
216
+ return;
217
+ }
218
+
219
+ // Store current state for rollback
220
+ previousHtmlState = document.getElementById('instrument-panel').innerHTML;
221
+
222
+ fetch(scriptStepUrl.replace('0', uuid), {
223
+ method: 'GET',
224
+ headers: {
225
+ 'Content-Type': 'application/json'
226
+ }
227
+ })
228
+ .then(response => {
229
+ if (!response.ok) {
230
+ return response.json().then(err => {
231
+ if (err.warning) {
232
+ alert(err.warning);
233
+ }
234
+ // Restore panel so user isn't stuck
235
+ if (previousHtmlState) {
236
+ document.getElementById('instrument-panel').innerHTML = previousHtmlState;
237
+ previousHtmlState = null;
238
+ }
239
+ throw new Error("Step fetch failed: " + response.status);
240
+ });
241
+ }
242
+ return response.text();
243
+ })
244
+ .then(html => {
245
+ document.getElementById('instrument-panel').innerHTML = html;
246
+
247
+ // Set up back button
248
+ const backButton = document.getElementById('back');
249
+ if (backButton) {
250
+ backButton.addEventListener('click', function(e) {
251
+ e.preventDefault();
252
+ if (previousHtmlState) {
253
+ document.getElementById('instrument-panel').innerHTML = previousHtmlState;
254
+ previousHtmlState = null;
255
+ }
256
+ });
257
+ }
258
+ })
259
+ .catch(error => console.error('Error:', error));
260
+ }
261
+
262
+ function submitEditForm(event) {
263
+ event.preventDefault();
264
+
265
+ const form = event.target;
266
+ const formData = new FormData(form);
267
+
268
+ fetch(form.action, {
269
+ method: 'POST',
270
+ body: formData
271
+ })
272
+ .then(response => response.text())
273
+ .then(html => {
274
+ if (html) {
275
+ updateActionCanvas(html);
276
+
277
+ // Restore previous instrument panel state
278
+ if (previousHtmlState) {
279
+ document.getElementById('instrument-panel').innerHTML = previousHtmlState;
280
+ previousHtmlState = null;
281
+ }
282
+
283
+ // Check for warnings
284
+ showWarningIfExists(html);
285
+ }
286
+ })
287
+ .catch(error => console.error('Error:', error));
288
+ }
289
+
290
+ function duplicateAction(uuid) {
291
+ if (!uuid) {
292
+ console.error('Invalid UUID');
293
+ return;
294
+ }
295
+
296
+ fetch(scriptStepDupUrl.replace('0', uuid), {
297
+ method: 'POST',
298
+ headers: {
299
+ 'Content-Type': 'application/json'
300
+ }
301
+ })
302
+ .then(response => response.text())
303
+ .then(html => {
304
+ updateActionCanvas(html);
305
+ showWarningIfExists(html);
306
+ })
307
+ .catch(error => console.error('Error:', error));
308
+ }
309
+
310
+ function deleteAction(uuid) {
311
+ if (!uuid) {
312
+ console.error('Invalid UUID');
313
+ return;
314
+ }
315
+
316
+ fetch(scriptStepUrl.replace('0', uuid), {
317
+ method: 'DELETE',
318
+ headers: {
319
+ 'Content-Type': 'application/json'
320
+ }
321
+ })
322
+ .then(response => response.text())
323
+ .then(html => {
324
+ updateActionCanvas(html);
325
+ showWarningIfExists(html);
326
+ })
327
+ .catch(error => console.error('Error:', error));
328
+ }
329
+
330
+ // ============================================================================
331
+ // MODAL MANAGEMENT
332
+ // ============================================================================
333
+
334
+ function hideModal() {
335
+ if (document.activeElement) {
336
+ document.activeElement.blur();
337
+ }
338
+
339
+ $('#dropModal').modal('hide');
340
+
341
+ if (lastFocusedElement) {
342
+ lastFocusedElement.focus();
343
+ }
344
+ }
345
+
346
+ // ============================================================================
347
+ // UTILITY FUNCTIONS
348
+ // ============================================================================
349
+
350
+ function showWarningIfExists(html) {
351
+ const parser = new DOMParser();
352
+ const doc = parser.parseFromString(html, 'text/html');
353
+ const warningDiv = doc.querySelector('#warning');
354
+
355
+ if (warningDiv && warningDiv.textContent.trim()) {
356
+ alert(warningDiv.textContent.trim());
357
+ }
358
+ }
359
+
360
+ // ============================================================================
361
+ // INITIALIZATION
362
+ // ============================================================================
363
+
364
+ document.addEventListener("DOMContentLoaded", function() {
365
+ const mode = getMode();
366
+ const batch = getBatch();
367
+
368
+ // Set up mode/batch toggle listeners
369
+ document.querySelectorAll(".mode-toggle").forEach(btn => {
370
+ btn.addEventListener("click", () => setMode(btn.dataset.mode));
371
+ });
372
+
373
+ document.querySelectorAll(".batch-toggle").forEach(btn => {
374
+ btn.addEventListener("click", () => setBatch(btn.dataset.batch));
375
+ });
376
+
377
+ // Restore UI state (without triggering updates)
378
+ setMode(mode, false);
379
+ setBatch(batch, false);
380
+
381
+ // Initialize code overlay
382
+ initializeCodeOverlay();
383
+
384
+ });
@@ -0,0 +1,23 @@
1
+
2
+ function deleteWorkflow(link) {
3
+ const url = link.dataset.deleteUrl;
4
+
5
+ fetch(url, {
6
+ method: 'DELETE',
7
+ headers: {
8
+ 'Content-Type': 'application/json'
9
+ }
10
+ })
11
+ .then(res => res.json())
12
+ .then(data => {
13
+ if (data.success) {
14
+ window.location.reload(); // or remove the row dynamically
15
+ } else {
16
+ alert("Failed to delete workflow: " + data.error);
17
+ }
18
+ })
19
+ .catch(err => {
20
+ console.error("Delete error:", err);
21
+ alert("Something went wrong.");
22
+ });
23
+ }
@@ -0,0 +1,39 @@
1
+ function editScriptName(event) {
2
+ event.preventDefault(); // Prevent full form submission
3
+ const newName = document.getElementById("new-name").value;
4
+ fetch(scriptMetaUrl, {
5
+ method: "PATCH",
6
+ headers: {
7
+ "Content-Type": "application/json",
8
+ },
9
+ body: JSON.stringify({name: newName})
10
+ })
11
+ .then(res => res.json())
12
+ .then(data => {
13
+ if (data.success) {
14
+ window.location.reload(); // or update the title on page directly
15
+ } else {
16
+ alert("Failed to rename script");
17
+ }
18
+ })
19
+ .catch(error => console.error("Failed to rename script", error));
20
+ }
21
+
22
+ function lockScriptEditing() {
23
+ fetch(scriptMetaUrl, {
24
+ method: "PATCH",
25
+ headers: {
26
+ "Content-Type": "application/json",
27
+ },
28
+ body: JSON.stringify({ status: "finalized" })
29
+ })
30
+ .then(res => res.json())
31
+ .then(data => {
32
+ if (data.success) {
33
+ window.location.reload();
34
+ } else {
35
+ alert("Failed to update script status");
36
+ }
37
+ })
38
+ .catch(error => console.error("Failed to update script status", error));
39
+ }
@@ -1,5 +1,5 @@
1
1
  document.addEventListener("DOMContentLoaded", function() {
2
- var socket = io.connect('http://' + document.domain + ':' + location.port);
2
+ var socket = io();
3
3
  socket.on('connect', function() {
4
4
  console.log('Connected');
5
5
  });
@@ -37,13 +37,43 @@ document.addEventListener("DOMContentLoaded", function() {
37
37
  console.error("Error received:", errorData);
38
38
  var progressBar = document.getElementById('progress-bar-inner');
39
39
 
40
- progressBar.classList.remove('bg-success');
41
- progressBar.classList.add('bg-danger'); // Red color for error
42
- // Show error modal
40
+ progressBar.classList.remove('bg-success', 'bg-warning');
41
+ progressBar.classList.add('bg-danger');
42
+
43
43
  var errorModal = new bootstrap.Modal(document.getElementById('error-modal'));
44
- document.getElementById('error-message').innerText = "An error occurred: " + errorData.message;
44
+ document.getElementById('errorModalLabel').innerText = "Error Detected";
45
+ document.getElementById('error-message').innerText =
46
+ "An error occurred: " + errorData.message;
47
+
48
+ // Show all buttons again
49
+ document.getElementById('retry-btn').style.display = "inline-block";
50
+ document.getElementById('continue-btn').style.display = "inline-block";
51
+ document.getElementById('stop-btn').style.display = "inline-block";
52
+
45
53
  errorModal.show();
54
+ });
55
+
56
+
57
+ socket.on('human_intervention', function(data) {
58
+ console.warn("Human intervention required:", data);
59
+ var progressBar = document.getElementById('progress-bar-inner');
46
60
 
61
+ // Set progress bar to yellow
62
+ progressBar.classList.remove('bg-success', 'bg-danger');
63
+ progressBar.classList.add('bg-warning');
64
+
65
+ // Reuse error modal but update content
66
+ var errorModal = new bootstrap.Modal(document.getElementById('error-modal'));
67
+ document.getElementById('errorModalLabel').innerText = "Human Intervention Required";
68
+ document.getElementById('error-message').innerText =
69
+ "Workflow paused: " + (data.message || "Please check and manually resume.");
70
+
71
+ // Optionally: hide retry button, since it may not apply
72
+ document.getElementById('retry-btn').style.display = "none";
73
+ document.getElementById('continue-btn').style.display = "inline-block";
74
+ document.getElementById('stop-btn').style.display = "inline-block";
75
+
76
+ errorModal.show();
47
77
  });
48
78
 
49
79
  // Handle Pause/Resume Button
@@ -71,6 +101,11 @@ document.addEventListener("DOMContentLoaded", function() {
71
101
  document.getElementById('continue-btn').addEventListener('click', function() {
72
102
  socket.emit('pause'); // Resume execution
73
103
  console.log("Execution resumed.");
104
+
105
+ // Reset progress bar color to running (blue)
106
+ var progressBar = document.getElementById('progress-bar-inner');
107
+ progressBar.classList.remove('bg-danger', 'bg-warning');
108
+ progressBar.classList.add('bg-primary');
74
109
  });
75
110
 
76
111
  document.getElementById('retry-btn').addEventListener('click', function() {