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.
Files changed (45) hide show
  1. syntaxmatrix/__init__.py +13 -8
  2. syntaxmatrix/agentic/__init__.py +0 -0
  3. syntaxmatrix/agentic/agent_tools.py +24 -0
  4. syntaxmatrix/agentic/agents.py +810 -0
  5. syntaxmatrix/agentic/code_tools_registry.py +37 -0
  6. syntaxmatrix/agentic/model_templates.py +1790 -0
  7. syntaxmatrix/auth.py +308 -14
  8. syntaxmatrix/commentary.py +328 -0
  9. syntaxmatrix/core.py +993 -375
  10. syntaxmatrix/dataset_preprocessing.py +218 -0
  11. syntaxmatrix/db.py +92 -95
  12. syntaxmatrix/display.py +95 -121
  13. syntaxmatrix/generate_page.py +634 -0
  14. syntaxmatrix/gpt_models_latest.py +46 -0
  15. syntaxmatrix/history_store.py +26 -29
  16. syntaxmatrix/kernel_manager.py +96 -17
  17. syntaxmatrix/llm_store.py +1 -1
  18. syntaxmatrix/plottings.py +6 -0
  19. syntaxmatrix/profiles.py +64 -8
  20. syntaxmatrix/project_root.py +55 -43
  21. syntaxmatrix/routes.py +5072 -1398
  22. syntaxmatrix/session.py +19 -0
  23. syntaxmatrix/settings/logging.py +40 -0
  24. syntaxmatrix/settings/model_map.py +300 -33
  25. syntaxmatrix/settings/prompts.py +273 -62
  26. syntaxmatrix/settings/string_navbar.py +3 -3
  27. syntaxmatrix/static/docs.md +272 -0
  28. syntaxmatrix/static/icons/favicon.png +0 -0
  29. syntaxmatrix/static/icons/hero_bg.jpg +0 -0
  30. syntaxmatrix/templates/dashboard.html +608 -147
  31. syntaxmatrix/templates/docs.html +71 -0
  32. syntaxmatrix/templates/error.html +2 -3
  33. syntaxmatrix/templates/login.html +1 -0
  34. syntaxmatrix/templates/register.html +1 -0
  35. syntaxmatrix/ui_modes.py +14 -0
  36. syntaxmatrix/utils.py +2482 -159
  37. syntaxmatrix/vectorizer.py +16 -12
  38. {syntaxmatrix-1.4.6.dist-info → syntaxmatrix-2.5.5.4.dist-info}/METADATA +20 -17
  39. syntaxmatrix-2.5.5.4.dist-info/RECORD +68 -0
  40. syntaxmatrix/model_templates.py +0 -30
  41. syntaxmatrix/static/icons/favicon.ico +0 -0
  42. syntaxmatrix-1.4.6.dist-info/RECORD +0 -54
  43. {syntaxmatrix-1.4.6.dist-info → syntaxmatrix-2.5.5.4.dist-info}/WHEEL +0 -0
  44. {syntaxmatrix-1.4.6.dist-info → syntaxmatrix-2.5.5.4.dist-info}/licenses/LICENSE.txt +0 -0
  45. {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
- <title>Data Dashboard</title>
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; /* Enable scrolling inside the sidebar if needed */
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 30px 30px 30px;
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: 36px 32px 32px 32px;
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
- .dashboard-content h2, .dashboard-content h4 {
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
- </style>
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
- <h2>Data&nbsp;Lab</h2>
602
+ <button class="sidebar-close" aria-label="Close menu">✕</button>
603
+ <h2>ML&nbsp;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
- <!-- FLASH MESSAGES (optional but useful) -->
234
- {% with msgs = get_flashed_messages() %}
235
- {% if msgs %}
236
- <ul class="flashes">
237
- {% for m in msgs %}
238
- <li>{{ m }}</li>
239
- {% endfor %}
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
- </select>
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
- <div>
262
- <form method="post" action="{{ url_for('delete_dataset', dataset_name=selected_dataset) }}"
263
- style="display:inline-block;">
264
- <div>Current dataset:
265
- <strong style="margin:10px;color:green; background:yellow;">{{ selected_dataset }}</strong>
266
- <button class="del-btn" type="submit" title="Delete {{ selected_dataset }}"
267
- onclick="return confirm('Delete {{ selected_dataset }}?');">
268
- 🗑️
269
- </button>
270
- </div>
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
- {% else %}
273
- <p style="color:red; padding:5px;">No datasets uploaded yet. Upload a CSV to begin.</p>
274
- {% endif %}
275
- </div>
276
- {% if section == 'explore' %}
277
- <br>
278
- <h2>Explore Data</h2>
279
- <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;">
280
- <input type="hidden" name="dataset" value="{{ selected_dataset }}">
281
- <label for="askai"><strong>Ask smxAI:</strong></label>
282
- <textarea id="askai" name="askai_question" type="text" rows="4"
283
- style="position:relative; width:90%; padding:8px; font-size:0.8em; border-radius:8px;"
284
- placeholder="Ask me about {{ (selected_dataset or 'your dataset. Upload it first.').replace('_', ' ').replace('.csv', '') }}" required></textarea>
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
- <span class="refined-q-label"><b>Refined Q:</b></span>
296
- <span class="refined-q">{{ refined_question }}</span>
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
- {% endif %}
299
- -->
300
- {% if ai_outputs %}
301
- <h3 style="margin:20px 0 8px;">Result</h3>
302
- {% for html_block in ai_outputs %}
303
- <div class="ai-output" style="margin-bottom:18px;overflow-x:auto; max-width:100%;">
304
- {{ html_block | safe }}
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
- {% endfor %}
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
- </script>
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>