syntaxmatrix 1.4.6__py3-none-any.whl → 2.5.5.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.
- syntaxmatrix/__init__.py +13 -8
- syntaxmatrix/agentic/__init__.py +0 -0
- syntaxmatrix/agentic/agent_tools.py +24 -0
- syntaxmatrix/agentic/agents.py +810 -0
- syntaxmatrix/agentic/code_tools_registry.py +37 -0
- syntaxmatrix/agentic/model_templates.py +1790 -0
- syntaxmatrix/auth.py +308 -14
- syntaxmatrix/commentary.py +328 -0
- syntaxmatrix/core.py +993 -375
- syntaxmatrix/dataset_preprocessing.py +218 -0
- syntaxmatrix/db.py +92 -95
- syntaxmatrix/display.py +95 -121
- syntaxmatrix/generate_page.py +634 -0
- syntaxmatrix/gpt_models_latest.py +46 -0
- syntaxmatrix/history_store.py +26 -29
- syntaxmatrix/kernel_manager.py +96 -17
- syntaxmatrix/llm_store.py +1 -1
- syntaxmatrix/plottings.py +6 -0
- syntaxmatrix/profiles.py +64 -8
- syntaxmatrix/project_root.py +55 -43
- syntaxmatrix/routes.py +5072 -1398
- syntaxmatrix/session.py +19 -0
- syntaxmatrix/settings/logging.py +40 -0
- syntaxmatrix/settings/model_map.py +300 -33
- syntaxmatrix/settings/prompts.py +273 -62
- syntaxmatrix/settings/string_navbar.py +3 -3
- syntaxmatrix/static/docs.md +272 -0
- syntaxmatrix/static/icons/favicon.png +0 -0
- syntaxmatrix/static/icons/hero_bg.jpg +0 -0
- syntaxmatrix/templates/dashboard.html +608 -147
- syntaxmatrix/templates/docs.html +71 -0
- syntaxmatrix/templates/error.html +2 -3
- syntaxmatrix/templates/login.html +1 -0
- syntaxmatrix/templates/register.html +1 -0
- syntaxmatrix/ui_modes.py +14 -0
- syntaxmatrix/utils.py +2482 -159
- syntaxmatrix/vectorizer.py +16 -12
- {syntaxmatrix-1.4.6.dist-info → syntaxmatrix-2.5.5.4.dist-info}/METADATA +20 -17
- syntaxmatrix-2.5.5.4.dist-info/RECORD +68 -0
- syntaxmatrix/model_templates.py +0 -30
- syntaxmatrix/static/icons/favicon.ico +0 -0
- syntaxmatrix-1.4.6.dist-info/RECORD +0 -54
- {syntaxmatrix-1.4.6.dist-info → syntaxmatrix-2.5.5.4.dist-info}/WHEEL +0 -0
- {syntaxmatrix-1.4.6.dist-info → syntaxmatrix-2.5.5.4.dist-info}/licenses/LICENSE.txt +0 -0
- {syntaxmatrix-1.4.6.dist-info → syntaxmatrix-2.5.5.4.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
|
-
<
|
|
4
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
5
|
+
<title>mlearning lab</title>
|
|
5
6
|
<style>
|
|
6
7
|
body {
|
|
7
8
|
font-family: Helvetica, Arial, sans-serif;
|
|
@@ -18,7 +19,7 @@
|
|
|
18
19
|
border-right: 1px solid #ccc;
|
|
19
20
|
padding: 26px 10px 10px 14px;
|
|
20
21
|
box-sizing: border-box;
|
|
21
|
-
overflow-y: auto;
|
|
22
|
+
overflow-y: auto;
|
|
22
23
|
z-index: 100;
|
|
23
24
|
font-size: clamp(0.95rem, 2vw, 1.09rem);
|
|
24
25
|
}
|
|
@@ -51,7 +52,7 @@
|
|
|
51
52
|
}
|
|
52
53
|
.dashboard-main {
|
|
53
54
|
margin-left: 220px;
|
|
54
|
-
padding: 36px
|
|
55
|
+
padding: 36px 40px 30px 10px;
|
|
55
56
|
min-height: 100vh;
|
|
56
57
|
box-sizing: border-box;
|
|
57
58
|
overflow-x: auto;
|
|
@@ -86,7 +87,7 @@
|
|
|
86
87
|
.dashboard-content {
|
|
87
88
|
background: #fff;
|
|
88
89
|
width: 100%;
|
|
89
|
-
padding:
|
|
90
|
+
padding: 10px;
|
|
90
91
|
border-radius: 0 0 10px 10px;
|
|
91
92
|
box-shadow: 0 3px 12px rgba(80,120,160,0.08);
|
|
92
93
|
min-height: 320px;
|
|
@@ -94,19 +95,16 @@
|
|
|
94
95
|
overflow-x: auto;
|
|
95
96
|
margin-right: 1vw;
|
|
96
97
|
}
|
|
97
|
-
|
|
98
|
-
font-size: clamp(1.06rem, 2.5vw, 1.5rem);
|
|
99
|
-
}
|
|
98
|
+
|
|
100
99
|
.smx-table {
|
|
101
|
-
font-size: clamp(0.86rem, 1.8vw, 1.01rem);
|
|
102
100
|
padding: clamp(3px, 1vw, 9px) clamp(4px, 2vw, 13px);
|
|
103
|
-
border-collapse: collapse;
|
|
104
|
-
width: 100%;
|
|
105
101
|
margin-bottom: 16px;
|
|
106
|
-
font-size: 0.98em;
|
|
107
102
|
background: #fff;
|
|
108
103
|
display: block;
|
|
109
104
|
overflow-x: auto;
|
|
105
|
+
width: max-content;
|
|
106
|
+
border-collapse: collapse;
|
|
107
|
+
font-size: 13px;
|
|
110
108
|
}
|
|
111
109
|
.smx-table th {
|
|
112
110
|
position: sticky;
|
|
@@ -121,36 +119,11 @@
|
|
|
121
119
|
border: 1px solid #ddd;
|
|
122
120
|
padding: 7px 10px;
|
|
123
121
|
}
|
|
124
|
-
@media (max-width: 900px) {
|
|
125
|
-
.dashboard-sidebar {
|
|
126
|
-
width: 110px;
|
|
127
|
-
padding: 10px 2vw 6px 2vw;
|
|
128
|
-
}
|
|
129
|
-
.dashboard-main {
|
|
130
|
-
margin-left: 110px;
|
|
131
|
-
padding: clamp(8px, 2vw, 14px) clamp(2vw, 2vw, 10px);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
.smx-table {
|
|
135
|
-
border-collapse: collapse;
|
|
136
|
-
font-size: 13px;
|
|
137
|
-
}
|
|
138
|
-
.smx-table th,
|
|
139
|
-
.smx-table td {
|
|
140
|
-
padding: 4px 8px;
|
|
141
|
-
white-space: nowrap;
|
|
142
|
-
}
|
|
143
122
|
.smx-scroll {
|
|
144
123
|
overflow-x: auto;
|
|
145
124
|
overflow-y: hidden;
|
|
146
125
|
max-width: 100%;
|
|
147
126
|
}
|
|
148
|
-
.smx-table {
|
|
149
|
-
width: max-content;
|
|
150
|
-
border-collapse: collapse;
|
|
151
|
-
font-size: 13px;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
127
|
.smx-table th,
|
|
155
128
|
.smx-table td {
|
|
156
129
|
padding: 4px 8px;
|
|
@@ -183,8 +156,7 @@
|
|
|
183
156
|
padding:0.2rem;
|
|
184
157
|
}
|
|
185
158
|
.del-btn:hover { opacity:0.8; background:red; }
|
|
186
|
-
|
|
187
|
-
<style>
|
|
159
|
+
|
|
188
160
|
/* full-screen overlay */
|
|
189
161
|
#loader-overlay {
|
|
190
162
|
position: fixed;
|
|
@@ -208,16 +180,427 @@
|
|
|
208
180
|
0% { transform: rotate(0deg); }
|
|
209
181
|
100% { transform: rotate(360deg); }
|
|
210
182
|
}
|
|
183
|
+
|
|
184
|
+
/* --- Mobile fixes --- */
|
|
185
|
+
.dashboard-content img,
|
|
186
|
+
.dashboard-content canvas,
|
|
187
|
+
.dashboard-content svg,
|
|
188
|
+
.dashboard-content iframe,
|
|
189
|
+
.dashboard-content .plotly-graph-div {
|
|
190
|
+
max-width: 100% !important;
|
|
191
|
+
height: auto !important;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* Wrap-wide tables will scroll horizontally on small screens */
|
|
195
|
+
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
|
|
196
|
+
.table-wrap table { min-width: 640px; }
|
|
197
|
+
</style>
|
|
198
|
+
<style>
|
|
199
|
+
/* Off-canvas sidebar behaviour on phones */
|
|
200
|
+
@media (max-width: 768px) {
|
|
201
|
+
.sidebar-toggle{ display: none; }
|
|
202
|
+
.dashboard-main {
|
|
203
|
+
margin-left: 0 !important;
|
|
204
|
+
padding: 16px !important;
|
|
205
|
+
}
|
|
206
|
+
.dashboard-sidebar {
|
|
207
|
+
position: fixed !important;
|
|
208
|
+
inset: 0 auto 0 0;
|
|
209
|
+
width: 80vw !important; /* drawer width */
|
|
210
|
+
max-width: 320px;
|
|
211
|
+
transform: translateX(-100%);
|
|
212
|
+
transition: transform .28s ease;
|
|
213
|
+
z-index: 1000;
|
|
214
|
+
box-shadow: 0 10px 30px rgba(0,0,0,.18);
|
|
215
|
+
}
|
|
216
|
+
.dashboard-sidebar.open { transform: translateX(0); }
|
|
217
|
+
}
|
|
218
|
+
/* Always-on mobile toggle button */
|
|
219
|
+
.sidebar-toggle{
|
|
220
|
+
position: fixed; /* stays on screen */
|
|
221
|
+
top: 10px;
|
|
222
|
+
left: 10px;
|
|
223
|
+
z-index: 1100;
|
|
224
|
+
display: inline-flex;
|
|
225
|
+
align-items: center;
|
|
226
|
+
justify-content: center;
|
|
227
|
+
width: 44px; height: 44px;
|
|
228
|
+
border: 0;
|
|
229
|
+
border-radius: 10px;
|
|
230
|
+
background: #0d6efd; /* solid, visible colour */
|
|
231
|
+
color: #fff;
|
|
232
|
+
box-shadow: 0 4px 14px rgba(0,0,0,.18);
|
|
233
|
+
cursor: pointer;
|
|
234
|
+
}
|
|
235
|
+
.sidebar-toggle::before{ content: "☰"; font-size: 22px; line-height: 1; }
|
|
236
|
+
.sidebar-toggle.is-open::before{ content: "✕"; } /* becomes close */
|
|
237
|
+
.sidebar-toggle:focus{ outline: 3px solid rgba(13,110,253,.5); outline-offset: 2px; }
|
|
238
|
+
|
|
239
|
+
@media (min-width: 769px){
|
|
240
|
+
.sidebar-toggle{ display: none; } /* desktop: hidden */
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Close button inside drawer */
|
|
244
|
+
.dashboard-sidebar .sidebar-close{
|
|
245
|
+
position: absolute;
|
|
246
|
+
top: 8px; right: 8px;
|
|
247
|
+
width: 36px; height: 36px;
|
|
248
|
+
border: 0; border-radius: 8px;
|
|
249
|
+
background: transparent;
|
|
250
|
+
font-size: 22px;
|
|
251
|
+
line-height: 1;
|
|
252
|
+
cursor: pointer;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* Scrim behind the drawer */
|
|
256
|
+
.sidebar-scrim{
|
|
257
|
+
position: fixed; inset: 0;
|
|
258
|
+
background: rgba(0,0,0,.25);
|
|
259
|
+
z-index: 900; /* below the drawer, above content */
|
|
260
|
+
display: none;
|
|
261
|
+
}
|
|
262
|
+
.sidebar-scrim.show{ display: block; }
|
|
263
|
+
body.no-scroll{ overflow: hidden; }
|
|
264
|
+
|
|
265
|
+
/* Make the Ask AI form fluid */
|
|
266
|
+
#form-askai{ width: 100% !important; max-width: 100% !important; }
|
|
267
|
+
#form-askai *{ box-sizing: border-box; }
|
|
268
|
+
#form-askai textarea,
|
|
269
|
+
#form-askai input[type="text"],
|
|
270
|
+
#form-askai select{
|
|
271
|
+
width: 100% !important;
|
|
272
|
+
max-width: 100% !important;
|
|
273
|
+
min-width: 0 !important;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* If the form sits inside a card/panel, ensure the wrapper can shrink */
|
|
277
|
+
.askai-card, .explore-card, .dashboard-content{
|
|
278
|
+
min-width: 0;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@media (max-width: 768px){
|
|
282
|
+
.askai-card, .explore-card{
|
|
283
|
+
margin: 0 !important;
|
|
284
|
+
padding: 12px !important;
|
|
285
|
+
border-radius: 10px;
|
|
286
|
+
}
|
|
287
|
+
#form-askai textarea{ min-height: 120px; }
|
|
288
|
+
}
|
|
289
|
+
/* Fix fieldset overflow on small screens (Chromium/Firefox quirk) */
|
|
290
|
+
.dashboard-content form#form-askai,
|
|
291
|
+
.dashboard-content form#form-askai fieldset {
|
|
292
|
+
width: 100% !important;
|
|
293
|
+
max-width: 100% !important;
|
|
294
|
+
min-width: 0 !important;
|
|
295
|
+
min-inline-size: 0 !important; /* critical: prevents min-content expansion */
|
|
296
|
+
box-sizing: border-box;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* Ensure the dashboard area never shows a 1–2px horizontal creep */
|
|
300
|
+
.dashboard-content { overflow-x: clip; } /* or hidden */
|
|
301
|
+
/* If you have a wrapper (use your actual class if different) */
|
|
302
|
+
.askai-card, .explore-card {
|
|
303
|
+
width: 100% !important;
|
|
304
|
+
max-width: 100% !important;
|
|
305
|
+
min-width: 0 !important;
|
|
306
|
+
box-sizing: border-box;
|
|
307
|
+
}
|
|
308
|
+
#form-askai textarea,
|
|
309
|
+
#form-askai input[type="text"],
|
|
310
|
+
#form-askai select {
|
|
311
|
+
width: 100% !important;
|
|
312
|
+
max-width: 100% !important;
|
|
313
|
+
min-width: 0 !important;
|
|
314
|
+
box-sizing: border-box;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/* Centre the main column and balance the side gutters */
|
|
318
|
+
@media (max-width: 768px){
|
|
319
|
+
.dashboard-main{
|
|
320
|
+
max-width: 100%;
|
|
321
|
+
margin-inline: auto !important;
|
|
322
|
+
padding-inline: 12px !important;
|
|
323
|
+
box-sizing: border-box;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/* Neutralise Bootstrap's negative row margins in this page.
|
|
328
|
+
(Rows assume they're inside .container; here they aren't.)
|
|
329
|
+
*/
|
|
330
|
+
.dashboard-main .row{
|
|
331
|
+
margin-left: 0 !important;
|
|
332
|
+
margin-right: 0 !important;
|
|
333
|
+
}
|
|
334
|
+
.dashboard-main .row > [class*="col"]{
|
|
335
|
+
padding-left: 0.5rem !important;
|
|
336
|
+
padding-right: 0.5rem !important;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/* Make the cards/panels line up perfectly with the column */
|
|
340
|
+
.dashboard-main .card,
|
|
341
|
+
.dashboard-main .panel,
|
|
342
|
+
.dashboard-main .explore-card{
|
|
343
|
+
width: 100%;
|
|
344
|
+
max-width: 100%;
|
|
345
|
+
margin-left: 0 !important;
|
|
346
|
+
margin-right: 0 !important;
|
|
347
|
+
box-sizing: border-box;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* Belt-and-braces: prevent any 1-2px creep from borders */
|
|
351
|
+
.dashboard-main, .dashboard-content{ overflow-x: clip; }
|
|
352
|
+
|
|
353
|
+
@media (max-width: 768px){
|
|
354
|
+
.dashboard-main{
|
|
355
|
+
padding-right: 24px;
|
|
356
|
+
}
|
|
357
|
+
.dashboard-content {
|
|
358
|
+
margin-right: 24px;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/* Make the border count inside the element's width */
|
|
363
|
+
.dashboard-content{
|
|
364
|
+
box-sizing: border-box !important;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/* Centre it with symmetric gutters and avoid the 1px crop */
|
|
368
|
+
@media (max-width: 768px){
|
|
369
|
+
.dashboard-content{
|
|
370
|
+
width: auto !important;
|
|
371
|
+
max-width: none !important;
|
|
372
|
+
margin-inline: 12px !important;
|
|
373
|
+
padding-inline: 12px !important;
|
|
374
|
+
border: 1px solid #dee2e6;
|
|
375
|
+
border-radius: 10px;
|
|
376
|
+
background: #fff;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/* Neutralise Bootstrap row negatives that can push borders off-screen */
|
|
380
|
+
.dashboard-content .row{
|
|
381
|
+
margin-left: 0 !important;
|
|
382
|
+
margin-right: 0 !important;
|
|
383
|
+
}
|
|
384
|
+
.dashboard-content .row > [class^="col"],
|
|
385
|
+
.dashboard-content .row > [class*=" col"]{
|
|
386
|
+
padding-left: 12px !important;
|
|
387
|
+
padding-right: 12px !important;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/* Belt-and-braces: draw the stroke *inside* the box to defeat subpixel rounding */
|
|
392
|
+
@media (max-width: 768px){
|
|
393
|
+
.dashboard-content{
|
|
394
|
+
outline: 1px solid #dee2e6;
|
|
395
|
+
outline-offset: -1px;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
</style>
|
|
399
|
+
<style>
|
|
400
|
+
/* Code block container + copy button */
|
|
401
|
+
.code-wrap{ position:relative; margin:14px 0; }
|
|
402
|
+
.code-wrap pre{ /* Pygments emits <div class="highlight"><pre ...> */
|
|
403
|
+
margin:0; /* ensure padding/scroll come from server-side prestyles too */
|
|
404
|
+
max-width:100%;
|
|
405
|
+
overflow:auto;
|
|
406
|
+
box-sizing:border-box;
|
|
407
|
+
border-radius:8px;
|
|
408
|
+
}
|
|
409
|
+
.code-copy-btn{
|
|
410
|
+
position:absolute; top:8px; right:8px;
|
|
411
|
+
background:#0d6efd; color:#fff;
|
|
412
|
+
border:0; border-radius:8px;
|
|
413
|
+
font-weight:600; font-size:0.9rem;
|
|
414
|
+
padding:6px 12px;
|
|
415
|
+
cursor:pointer;
|
|
416
|
+
box-shadow:0 2px 8px rgba(0,0,0,.12);
|
|
417
|
+
}
|
|
418
|
+
.code-copy-btn:hover{ background:#0b5ed7; }
|
|
419
|
+
</style>
|
|
420
|
+
<style>
|
|
421
|
+
.eda-grid{
|
|
422
|
+
display:grid;
|
|
423
|
+
grid-template-columns:repeat(auto-fit, minmax(360px,1fr));
|
|
424
|
+
gap:12px;
|
|
425
|
+
align-items:start;
|
|
426
|
+
}
|
|
427
|
+
.eda-card{
|
|
428
|
+
background:#fff;
|
|
429
|
+
border:1px solid #e5e7eb;
|
|
430
|
+
border-radius:12px;
|
|
431
|
+
padding:14px 16px;
|
|
432
|
+
box-shadow:0 1px 8px rgba(0,0,0,.04);
|
|
433
|
+
opacity:0; transform:translateY(6px);
|
|
434
|
+
animation:edaFade .35s ease forwards;
|
|
435
|
+
animation-delay: calc(var(--i, 0) * 60ms);
|
|
436
|
+
}
|
|
437
|
+
/* 12-column grid */
|
|
438
|
+
.eda-grid.eda-grid-12{
|
|
439
|
+
display:grid;
|
|
440
|
+
grid-auto-flow:dense;
|
|
441
|
+
grid-template-columns:repeat(12, minmax(0,1fr));
|
|
442
|
+
gap:12px; align-items:start;
|
|
443
|
+
}
|
|
444
|
+
/* Helpers (use these in cell["span"]) */
|
|
445
|
+
.eda-col-3 { grid-column:span 3; } /* third width → 3:3:3:3 */
|
|
446
|
+
.eda-col-4 { grid-column:span 4; } /* third width → 4:4:4 */
|
|
447
|
+
.eda-col-5 { grid-column:span 5; } /* third width → 5:7 */
|
|
448
|
+
.eda-col-6 { grid-column:span 6; } /* half width → 6:6 */
|
|
449
|
+
.eda-col-7 { grid-column:span 7; } /* third width → 7:5 */
|
|
450
|
+
.eda-col-8 { grid-column:span 8; } /* two-thirds → 8:4 */
|
|
451
|
+
.eda-col-9 { grid-column:span 9; } /* two-thirds → 9:3 */
|
|
452
|
+
.eda-col-12 { grid-column:span 12;} /* full width */
|
|
453
|
+
|
|
454
|
+
/* Breakpoints */
|
|
455
|
+
@media (max-width:960px){
|
|
456
|
+
.eda-grid.eda-grid-12{ grid-template-columns:repeat(6, minmax(0,1fr)); }
|
|
457
|
+
.eda-col-9{ grid-column:span 6; }
|
|
458
|
+
.eda-col-8{ grid-column:span 6; }
|
|
459
|
+
.eda-col-7{ grid-column:span 6; }
|
|
460
|
+
.eda-col-6{ grid-column:span 6; }
|
|
461
|
+
.eda-col-5{ grid-column:span 6; }
|
|
462
|
+
.eda-col-4{ grid-column:span 6; }
|
|
463
|
+
.eda-col-3{ grid-column:span 6; }
|
|
464
|
+
}
|
|
465
|
+
@media (max-width:540px){
|
|
466
|
+
.eda-grid.eda-grid-12{ grid-template-columns:repeat(1, 1fr); }
|
|
467
|
+
.eda-card, .eda-col-3, .eda-col-4, .eda-col-5, .eda-col-6, .eda-col-7, .eda-col-8, .eda-col-9, .eda-col-12 {
|
|
468
|
+
grid-column:span 12;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
.eda-card h3{ margin:0 0 .5rem; font-size:1.02rem; }
|
|
472
|
+
.eda-card .eda-body{ overflow:auto; }
|
|
473
|
+
.eda-card details summary{ cursor:pointer; color:#0366d6; margin-top:.5rem; }
|
|
474
|
+
@keyframes edaFade{ to { opacity:1; transform:none; } }
|
|
475
|
+
|
|
476
|
+
</style>
|
|
477
|
+
<style id="smx-theme">
|
|
478
|
+
:root{
|
|
479
|
+
--smx-bg: #f3f6fb;
|
|
480
|
+
--smx-surface: #ffffff;
|
|
481
|
+
--smx-elev1: #fcfdff;
|
|
482
|
+
--smx-border: #dfe5ee;
|
|
483
|
+
--smx-text: #0f172a;
|
|
484
|
+
--smx-muted: #667085;
|
|
485
|
+
--smx-accent: #007acc; /* SyntaxMatrix blue (keeps your current tone) */
|
|
486
|
+
--smx-accent-2: #14b8a6; /* teal for subtle gradients */
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
body.smx-theme{
|
|
490
|
+
background: linear-gradient(180deg, var(--smx-bg) 0%, #eef3f9 60%, #eaf0f7 100%);
|
|
491
|
+
color: var(--smx-text);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/* Sidebar + tabs pick up accent */
|
|
495
|
+
.dashboard-sidebar{
|
|
496
|
+
background: linear-gradient(180deg, #f0f5fb 0%, #eaf1f8 100%);
|
|
497
|
+
border-right: 1px solid var(--smx-border);
|
|
498
|
+
}
|
|
499
|
+
.dashboard-sidebar h2{ color: var(--smx-accent); }
|
|
500
|
+
.dashboard-tabs a{ background:#e9f2fb; border-color: var(--smx-border); color:#1f2a37; }
|
|
501
|
+
.dashboard-tabs .active a, .dashboard-tabs a.active{
|
|
502
|
+
background: var(--smx-surface);
|
|
503
|
+
color: var(--smx-accent);
|
|
504
|
+
border-color: var(--smx-border);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/* Cards: clearer elevation + accent top bar */
|
|
508
|
+
.eda-card{
|
|
509
|
+
background: var(--smx-surface);
|
|
510
|
+
border: 1px solid var(--smx-border);
|
|
511
|
+
border-radius: 14px;
|
|
512
|
+
box-shadow: 0 6px 18px rgba(16,24,40,.06);
|
|
513
|
+
position: relative;
|
|
514
|
+
}
|
|
515
|
+
.eda-card::before{
|
|
516
|
+
content:"";
|
|
517
|
+
position:absolute; inset:0 0 auto 0; height:4px; border-radius:14px 14px 0 0;
|
|
518
|
+
background: linear-gradient(90deg, var(--smx-accent), var(--smx-accent-2));
|
|
519
|
+
}
|
|
520
|
+
.eda-card h3{
|
|
521
|
+
margin: 0; padding: 10px 12px;
|
|
522
|
+
font-size: 1rem; font-weight: 700; color: #1f2937;
|
|
523
|
+
border-bottom: 1px solid var(--smx-border);
|
|
524
|
+
}
|
|
525
|
+
.eda-card .eda-body{ padding: 10px 12px; }
|
|
526
|
+
|
|
527
|
+
/* Stat tiles harmonised */
|
|
528
|
+
.smx-statwrap{ gap: 12px !important; }
|
|
529
|
+
.smx-stat{
|
|
530
|
+
background: var(--smx-elev1) !important;
|
|
531
|
+
border: 1px solid var(--smx-border) !important;
|
|
532
|
+
box-shadow: 0 2px 10px rgba(16,24,40,.05);
|
|
533
|
+
border-radius: 12px !important;
|
|
534
|
+
}
|
|
535
|
+
.smx-stat h4{ color: var(--smx-muted) !important; letter-spacing:.2px; }
|
|
536
|
+
|
|
537
|
+
/* Buttons */
|
|
538
|
+
button, .btn{
|
|
539
|
+
background: var(--smx-accent);
|
|
540
|
+
color: #fff;
|
|
541
|
+
border: 1px solid #0a66c2;
|
|
542
|
+
border-radius: 10px;
|
|
543
|
+
}
|
|
544
|
+
button:hover, .btn:hover{ background:#0566b1; }
|
|
545
|
+
|
|
546
|
+
/* Forms & selects */
|
|
547
|
+
select, textarea, input[type="text"], input[type="file"]{
|
|
548
|
+
border:1px solid var(--smx-border);
|
|
549
|
+
border-radius:10px;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/* Content panel */
|
|
553
|
+
.dashboard-content{
|
|
554
|
+
background: var(--smx-surface);
|
|
555
|
+
border: 1px solid var(--smx-border);
|
|
556
|
+
border-radius: 14px;
|
|
557
|
+
box-shadow: 0 8px 24px rgba(16,24,40,.05);
|
|
558
|
+
}
|
|
559
|
+
</style>
|
|
560
|
+
<style id="smx-contrast">
|
|
561
|
+
/* Dark slate backdrop for the whole page */
|
|
562
|
+
body.smx-theme.smx-contrast{
|
|
563
|
+
background:
|
|
564
|
+
radial-gradient(1100px 520px at 15% -10%, #16253a 0%, #0f2036 40%, #0b1628 70%, #0a1424 100%),
|
|
565
|
+
linear-gradient(180deg, #0a1424 0%, #0c172a 100%);
|
|
566
|
+
color: #e9eef6;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/* Let the dark backdrop show through; keep content readable */
|
|
570
|
+
.dashboard-main{ background: transparent !important; }
|
|
571
|
+
.dashboard-content{
|
|
572
|
+
background: rgba(255,255,255,0.96) !important;
|
|
573
|
+
border: 1px solid #d4deea !important;
|
|
574
|
+
border-radius: 14px;
|
|
575
|
+
box-shadow: 0 10px 28px rgba(8,20,38,0.28);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/* Cards sit on a brighter surface with clear edge + shadow */
|
|
579
|
+
.eda-card{
|
|
580
|
+
background: #ffffff !important;
|
|
581
|
+
border: 1px solid #d8e3f0 !important;
|
|
582
|
+
border-radius: 14px;
|
|
583
|
+
box-shadow: 0 8px 24px rgba(8,20,38,0.22);
|
|
584
|
+
}
|
|
585
|
+
.eda-card::before{
|
|
586
|
+
background: linear-gradient(90deg, #0b8ae5, #14b8a6);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/* Tidy headings/labels inside light surfaces */
|
|
590
|
+
.eda-card h3{ color: #1f2937 !important; }
|
|
591
|
+
.smx-stat h4{ color: #64748b !important; }
|
|
211
592
|
</style>
|
|
212
593
|
|
|
213
594
|
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
|
|
214
595
|
</head>
|
|
215
596
|
<body>
|
|
597
|
+
<div id="sidebarScrim" class="sidebar-scrim" aria-hidden="true"></div>
|
|
216
598
|
<div id="loader-overlay">
|
|
217
599
|
<div class="loader"></div>
|
|
218
600
|
</div>
|
|
219
601
|
<div class="dashboard-sidebar">
|
|
220
|
-
<
|
|
602
|
+
<button class="sidebar-close" aria-label="Close menu">✕</button>
|
|
603
|
+
<h2>ML Lab</h2>
|
|
221
604
|
<a href="/">return to home</a>
|
|
222
605
|
<div class="sidebar-links">
|
|
223
606
|
<a href="/dashboard?section=explore"{% if section == 'explore' %} class="active"{% endif %}>Explore</a>
|
|
@@ -226,126 +609,175 @@
|
|
|
226
609
|
</div>
|
|
227
610
|
</div>
|
|
228
611
|
<div class="dashboard-main">
|
|
612
|
+
<button id="sidebarToggle" class="sidebar-toggle" aria-label="Open menu"></button>
|
|
229
613
|
<ul class="dashboard-tabs">
|
|
230
614
|
<li class="{{ 'active' if section == 'explore' else '' }}"><a href="/dashboard?section=explore">Explore</a></li>
|
|
615
|
+
|
|
231
616
|
</ul>
|
|
232
617
|
<div class="dashboard-content">
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
{%
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
</ul>
|
|
241
|
-
{% endif %}
|
|
242
|
-
{% endwith %}
|
|
243
|
-
<!-- Upload form -->
|
|
244
|
-
<div style="width:70vw; margin-bottom:10px; border:1px solid gray; border-radius:10px; padding:10px;">
|
|
245
|
-
<form action="/dashboard/upload" method="post" enctype="multipart/form-data" style="padding:5px; margin-bottom:5px;">
|
|
246
|
-
<input style="cursor:pointer;" type="file" name="dataset_file" accept=".csv" required>
|
|
247
|
-
<button type="submit">Upload CSV</button>
|
|
248
|
-
</form>
|
|
249
|
-
|
|
250
|
-
<!-- Dataset selection dropdown -->
|
|
251
|
-
{% if datasets %}
|
|
252
|
-
<form method="get" action="/dashboard" style="margin-top:45px; margin-bottom:8px;padding:5px;">
|
|
253
|
-
<input type="hidden" name="section" value="explore"/>
|
|
254
|
-
<label for="dataset_select"><strong>Select dataset:</strong></label>
|
|
255
|
-
<select name="dataset" id="dataset_select" onchange="this.form.submit()" style="margin-top:5px; height:24px;">
|
|
256
|
-
{% for ds in datasets %}
|
|
257
|
-
<option value="{{ ds }}" {% if ds == selected_dataset %}selected{% endif %}>{{ ds }}</option>
|
|
618
|
+
<div class="explore-card">
|
|
619
|
+
<!-- FLASH MESSAGES (optional but useful) -->
|
|
620
|
+
{% with msgs = get_flashed_messages() %}
|
|
621
|
+
{% if msgs %}
|
|
622
|
+
<ul class="flashes">
|
|
623
|
+
{% for m in msgs %}
|
|
624
|
+
<li>{{ m }}</li>
|
|
258
625
|
{% endfor %}
|
|
259
|
-
</
|
|
626
|
+
</ul>
|
|
627
|
+
{% endif %}
|
|
628
|
+
{% endwith %}
|
|
629
|
+
<!-- Upload form -->
|
|
630
|
+
<div style="width:70vw; margin-bottom:10px; border:1px solid gray; border-radius:10px; padding:10px;">
|
|
631
|
+
<form action="/dashboard/upload" method="post" enctype="multipart/form-data" style="padding:5px; margin-bottom:5px;">
|
|
632
|
+
<input style="cursor:pointer;" type="file" name="dataset_file" accept=".csv" required>
|
|
633
|
+
<button type="submit">Upload CSV</button>
|
|
260
634
|
</form>
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
635
|
+
|
|
636
|
+
<!-- Dataset selection dropdown -->
|
|
637
|
+
{% if datasets %}
|
|
638
|
+
<form method="get" action="/dashboard" style="margin-top:45px; margin-bottom:8px;padding:5px;">
|
|
639
|
+
<input type="hidden" name="section" value="explore"/>
|
|
640
|
+
<label for="dataset_select"><strong>Select dataset:</strong></label>
|
|
641
|
+
<select name="dataset" id="dataset_select" onchange="this.form.submit()" style="margin-top:5px; height:24px;">
|
|
642
|
+
{% for ds in datasets %}
|
|
643
|
+
<option value="{{ ds }}" {% if ds == selected_dataset %}selected{% endif %}>{{ ds }}</option>
|
|
644
|
+
{% endfor %}
|
|
645
|
+
</select>
|
|
646
|
+
</form>
|
|
647
|
+
<form method="post" action="{{ url_for('delete_dataset', dataset_name=selected_dataset) }}"
|
|
648
|
+
style="display:inline-block;">
|
|
649
|
+
<div>Current dataset:
|
|
650
|
+
<strong style="margin:10px;color:green; background:yellow;">{{ selected_dataset }}</strong>
|
|
651
|
+
<button class="del-btn" type="submit" title="Delete {{ selected_dataset }}"
|
|
652
|
+
onclick="return confirm('Delete {{ selected_dataset }}?');">
|
|
653
|
+
🗑️
|
|
654
|
+
</button>
|
|
655
|
+
</div>
|
|
656
|
+
</form>
|
|
657
|
+
{% else %}
|
|
658
|
+
<p style="color:red; padding:5px;">No datasets uploaded yet. Upload a CSV to begin.</p>
|
|
659
|
+
{% endif %}
|
|
660
|
+
</div>
|
|
661
|
+
{% if section == 'explore' %}
|
|
662
|
+
<br>
|
|
663
|
+
<h2>Explore Data</h2>
|
|
664
|
+
<form id="form-askai" method="post" action="/dashboard?section=explore" style="margin-top:5px;padding:12px; border:1px solid grey;border-radius:5px;width:70vw;">
|
|
665
|
+
<input type="hidden" name="dataset" value="{{ selected_dataset }}">
|
|
666
|
+
<label for="askai"><strong>Ask smxAI:</strong></label>
|
|
667
|
+
<textarea id="askai" name="askai_question" type="text" rows="4"
|
|
668
|
+
style="position:relative; width:90%; padding:8px; font-size:0.8em; border-radius:8px;"
|
|
669
|
+
placeholder="Ask me about {{ (selected_dataset or 'your dataset. Upload it first.').replace('_', ' ').replace('.csv', '') }}" required></textarea>
|
|
670
|
+
<button type="submit" style="font-size:1.2rem; width:8rem; padding:4px;">Submit</button>
|
|
271
671
|
</form>
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
<button type="submit" style="font-size:1.2rem; width:8rem; padding:4px;">Submit</button>
|
|
286
|
-
</form>
|
|
287
|
-
{% if askai_question %}
|
|
288
|
-
<div class="askai-qblock" style="margin:20px 5px;">
|
|
289
|
-
<span class="askai-q-label"><b>Q:</b></span>
|
|
290
|
-
<span class="askai-q">{{ askai_question }}</span>
|
|
291
|
-
</div>
|
|
292
|
-
<!--
|
|
293
|
-
{% if refined_question and refined_question != askai_question %}
|
|
672
|
+
<div style="margin-bottom: 36px;">
|
|
673
|
+
{% if askai_question %}
|
|
674
|
+
<!--
|
|
675
|
+
<div class="askai-qblock" style="margin:20px 5px;">
|
|
676
|
+
<span class="askai-q-label"><b>Task:</b></span>
|
|
677
|
+
<span class="askai-q">{{ askai_question }}</span>
|
|
678
|
+
</div>
|
|
679
|
+
-->
|
|
680
|
+
<br><br>
|
|
681
|
+
<div class="refined-qblock">
|
|
682
|
+
<span class="refined-q-label"><b>My Thought Process:</b></span><br>
|
|
683
|
+
<span class="refined-q">{{ refined_question|safe }}</span>
|
|
684
|
+
</div><br>
|
|
294
685
|
<div class="refined-qblock">
|
|
295
|
-
|
|
296
|
-
|
|
686
|
+
<b>Tasks Performed: </b><br>
|
|
687
|
+
{% for task in tasks %}
|
|
688
|
+
<span class="refined-q">- {{ task.replace('_', ' ').capitalize() + ', ' }}</span><br>
|
|
689
|
+
{% endfor %}
|
|
690
|
+
</div><br>
|
|
691
|
+
{% if llm_usage %}
|
|
692
|
+
<div class="refined-qblock">
|
|
693
|
+
<b>LLM: </b><span>{{ llm_usage.provider.capitalize() }} | {{ llm_usage.model }}</span><br>
|
|
694
|
+
<b>Token Usage: </b>
|
|
695
|
+
<li>- Input Tokens: {{ llm_usage.input_tokens }}</li>
|
|
696
|
+
<li>- Output Tokens: {{ llm_usage.output_tokens }}</li>
|
|
697
|
+
<li>- Total Tokens: {{ llm_usage.total_tokens }}</li>
|
|
698
|
+
</ul>
|
|
699
|
+
</div>
|
|
700
|
+
{% endif %}
|
|
701
|
+
{% endif %}
|
|
702
|
+
{% if ai_outputs %}
|
|
703
|
+
<div class="d-flex align-items-center justify-content-between" style="margin: 12px;">
|
|
704
|
+
<h3 class="m-0">Result</h3>
|
|
297
705
|
</div>
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
706
|
+
{% for html_block in ai_outputs %}
|
|
707
|
+
<div class="ai-output" style="margin-bottom:18px;overflow-x:auto; max-width:100%;">
|
|
708
|
+
{{ html_block | safe }}
|
|
709
|
+
</div>
|
|
710
|
+
{% endfor %}
|
|
711
|
+
{% endif %}
|
|
712
|
+
{% if ai_code %}
|
|
713
|
+
<div>
|
|
714
|
+
<a href="#" onclick="toggleCode();return false;" id="toggle-link">Show Code</a>
|
|
715
|
+
<div id="ai-code-block" class="code-wrap" style="display:none;">
|
|
716
|
+
{{ highlighted_ai_code|safe }}
|
|
717
|
+
<button type="button" class="code-copy-btn" onclick="copyCode(this)">Copy</button>
|
|
718
|
+
</div>
|
|
719
|
+
</div>
|
|
720
|
+
{% endif %}
|
|
721
|
+
</div>
|
|
722
|
+
<script>
|
|
723
|
+
function toggleCode() {
|
|
724
|
+
var el = document.getElementById('ai-code-block');
|
|
725
|
+
var link = document.getElementById('toggle-link');
|
|
726
|
+
if (el.style.display === 'none') {
|
|
727
|
+
el.style.display = '';
|
|
728
|
+
link.innerText = 'Hide Code';
|
|
729
|
+
} else {
|
|
730
|
+
el.style.display = 'none';
|
|
731
|
+
link.innerText = 'Show Code';
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
</script>
|
|
735
|
+
<div class="eda-grid eda-grid-12">
|
|
736
|
+
{% for cell in data_cells %}
|
|
737
|
+
<section class="eda-card {{ cell.span or '' }}" style="--i: {{ loop.index0 }}">
|
|
738
|
+
<h3>{{ cell.title }}</h3>
|
|
739
|
+
<div class="eda-body">
|
|
740
|
+
{{ cell.output|safe }}
|
|
305
741
|
</div>
|
|
742
|
+
{% if cell.highlighted_code %}
|
|
743
|
+
<details>
|
|
744
|
+
<summary>Show code</summary>
|
|
745
|
+
<div style="margin-top:.5rem">{{ cell.highlighted_code|safe }}</div>
|
|
746
|
+
</details>
|
|
747
|
+
{% endif %}
|
|
748
|
+
</section>
|
|
306
749
|
{% endfor %}
|
|
307
|
-
{% endif %}
|
|
308
|
-
|
|
309
|
-
{% if ai_code %}
|
|
310
|
-
<div>
|
|
311
|
-
<a href="#" onclick="toggleCode();return false;" id="toggle-link">Show Code</a>
|
|
312
|
-
<pre id="ai-code-block" style="display:none; background:#222; color:#eee; padding:10px; border-radius:8px; overflow:auto;">{{ ai_code }}</pre>
|
|
313
|
-
</div>
|
|
314
|
-
<script>
|
|
315
|
-
function toggleCode() {
|
|
316
|
-
var el = document.getElementById('ai-code-block');
|
|
317
|
-
var link = document.getElementById('toggle-link');
|
|
318
|
-
if (el.style.display === 'none') {
|
|
319
|
-
el.style.display = '';
|
|
320
|
-
link.innerText = 'Hide Code';
|
|
321
|
-
} else {
|
|
322
|
-
el.style.display = 'none';
|
|
323
|
-
link.innerText = 'Show Code';
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
</script>
|
|
327
|
-
{% endif %}
|
|
328
|
-
{% endif %}
|
|
329
|
-
{% for cell in data_cells %}
|
|
330
|
-
<h4>{{ cell.title }}</h4>
|
|
331
|
-
<div style="overflow-x:auto; max-width:100%;">
|
|
332
|
-
{{ cell.output|safe }}
|
|
333
|
-
{% set data_code = cell.code %}
|
|
334
|
-
{% include 'code_cell.html' %}
|
|
335
750
|
</div>
|
|
336
|
-
{%
|
|
337
|
-
|
|
338
|
-
{% elif section == 'analyze' %}
|
|
339
|
-
<h2>Analyze Data</h2>
|
|
340
|
-
<p>Statistical tests, group comparison, feature selection, etc. will go here.</p>
|
|
341
|
-
{% elif section == 'model' %}
|
|
342
|
-
<h2>Model</h2>
|
|
343
|
-
<p>Train/test ML models, evaluate metrics, feature importance, etc. will go here.</p>
|
|
344
|
-
{% else %}
|
|
345
|
-
<h2>Section not found</h2>
|
|
346
|
-
{% endif %}
|
|
751
|
+
{% endif %}
|
|
752
|
+
</div>
|
|
347
753
|
</div>
|
|
348
754
|
</div>
|
|
755
|
+
<script>
|
|
756
|
+
async function copyCode(btn){
|
|
757
|
+
const pre = btn.parentElement.querySelector('pre');
|
|
758
|
+
if(!pre) return;
|
|
759
|
+
const text = pre.innerText;
|
|
760
|
+
|
|
761
|
+
try {
|
|
762
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
763
|
+
await navigator.clipboard.writeText(text);
|
|
764
|
+
} else {
|
|
765
|
+
// fallback
|
|
766
|
+
const range = document.createRange();
|
|
767
|
+
range.selectNodeContents(pre);
|
|
768
|
+
const sel = window.getSelection();
|
|
769
|
+
sel.removeAllRanges(); sel.addRange(range);
|
|
770
|
+
document.execCommand('copy');
|
|
771
|
+
sel.removeAllRanges();
|
|
772
|
+
}
|
|
773
|
+
btn.textContent = 'Copied!';
|
|
774
|
+
setTimeout(()=>btn.textContent='Copy', 1200);
|
|
775
|
+
} catch(e){
|
|
776
|
+
btn.textContent = 'Failed';
|
|
777
|
+
setTimeout(()=>btn.textContent='Copy', 1200);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
</script>
|
|
349
781
|
<script>
|
|
350
782
|
function toggleCodeCell(link) {
|
|
351
783
|
var cell = link.nextElementSibling;
|
|
@@ -376,8 +808,7 @@
|
|
|
376
808
|
}
|
|
377
809
|
sel.removeAllRanges();
|
|
378
810
|
}
|
|
379
|
-
|
|
380
|
-
<script>
|
|
811
|
+
|
|
381
812
|
document.addEventListener("DOMContentLoaded", () => {
|
|
382
813
|
const form = document.getElementById("form-askai");
|
|
383
814
|
const overlay = document.getElementById("loader-overlay");
|
|
@@ -386,5 +817,35 @@
|
|
|
386
817
|
});
|
|
387
818
|
});
|
|
388
819
|
</script>
|
|
820
|
+
<script>
|
|
821
|
+
const sidebar = document.querySelector('.dashboard-sidebar');
|
|
822
|
+
const toggle = document.getElementById('sidebarToggle');
|
|
823
|
+
const scrim = document.getElementById('sidebarScrim');
|
|
824
|
+
const closeBtn= document.querySelector('.dashboard-sidebar .sidebar-close');
|
|
825
|
+
|
|
826
|
+
function setOpen(open){
|
|
827
|
+
if(!sidebar || !toggle) return;
|
|
828
|
+
sidebar.classList.toggle('open', open);
|
|
829
|
+
toggle.classList.toggle('is-open', open);
|
|
830
|
+
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
831
|
+
document.body.classList.toggle('no-scroll', open);
|
|
832
|
+
if (scrim) scrim.classList.toggle('show', open);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
toggle?.addEventListener('click', () => setOpen(!sidebar.classList.contains('open')));
|
|
836
|
+
closeBtn?.addEventListener('click', () => setOpen(false));
|
|
837
|
+
scrim?.addEventListener('click', () => setOpen(false));
|
|
838
|
+
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') setOpen(false); });
|
|
839
|
+
</script>
|
|
840
|
+
<script>
|
|
841
|
+
// Resize Plotly charts responsively
|
|
842
|
+
window.addEventListener('resize', () => {
|
|
843
|
+
if (window.Plotly) {
|
|
844
|
+
document.querySelectorAll('.js-plotly-plot').forEach(el => {
|
|
845
|
+
try { window.Plotly.Plots.resize(el); } catch(e) {}
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
</script>
|
|
389
850
|
</body>
|
|
390
851
|
</html>
|