syntaxmatrix 2.5.7__py3-none-any.whl → 2.5.8__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/agentic/agents.py +16 -25
- syntaxmatrix/routes.py +189 -2
- syntaxmatrix/settings/model_map.py +1 -2
- syntaxmatrix/templates/dashboard.html +148 -61
- syntaxmatrix/templates/dataset_resize.html +535 -0
- syntaxmatrix/utils.py +9 -0
- {syntaxmatrix-2.5.7.dist-info → syntaxmatrix-2.5.8.dist-info}/METADATA +1 -1
- {syntaxmatrix-2.5.7.dist-info → syntaxmatrix-2.5.8.dist-info}/RECORD +11 -10
- {syntaxmatrix-2.5.7.dist-info → syntaxmatrix-2.5.8.dist-info}/WHEEL +0 -0
- {syntaxmatrix-2.5.7.dist-info → syntaxmatrix-2.5.8.dist-info}/licenses/LICENSE.txt +0 -0
- {syntaxmatrix-2.5.7.dist-info → syntaxmatrix-2.5.8.dist-info}/top_level.txt +0 -0
syntaxmatrix/agentic/agents.py
CHANGED
|
@@ -139,7 +139,7 @@ def mlearning_agent(user_prompt, system_prompt, coding_profile):
|
|
|
139
139
|
reasoning_effort, verbosity = "medium", "medium"
|
|
140
140
|
if _model == "gpt-5-nano":
|
|
141
141
|
reasoning_effort, verbosity = "low", "low"
|
|
142
|
-
elif _model in ["gpt-5-mini", "gpt-5-codex
|
|
142
|
+
elif _model in ["gpt-5-mini", "gpt-5-mini-codex"]:
|
|
143
143
|
reasoning_effort, verbosity = "medium", "medium"
|
|
144
144
|
elif _model in ["gpt-5", "gpt-5-codex", "gpt-5-pro"]:
|
|
145
145
|
reasoning_effort, verbosity = "high", "high"
|
|
@@ -163,19 +163,7 @@ def mlearning_agent(user_prompt, system_prompt, coding_profile):
|
|
|
163
163
|
|
|
164
164
|
code = _out(resp).strip()
|
|
165
165
|
if code:
|
|
166
|
-
return code
|
|
167
|
-
|
|
168
|
-
# Try to surface any block reason (safety / policy / etc.)
|
|
169
|
-
block_reason = None
|
|
170
|
-
output = resp.get("output")
|
|
171
|
-
for item in output:
|
|
172
|
-
fr = getattr(item, "finish_reason", None)
|
|
173
|
-
if fr and fr != "stop":
|
|
174
|
-
block_reason = fr
|
|
175
|
-
break
|
|
176
|
-
if block_reason:
|
|
177
|
-
raise RuntimeError(f"{_model} stopped with reason: {block_reason}")
|
|
178
|
-
raise RuntimeError(f"{_model} returned an empty response in this section due to insufficient data.")
|
|
166
|
+
return code
|
|
179
167
|
|
|
180
168
|
except APIError as e:
|
|
181
169
|
# IMPORTANT: return VALID PYTHON so the dashboard can show the error
|
|
@@ -263,7 +251,6 @@ def mlearning_agent(user_prompt, system_prompt, coding_profile):
|
|
|
263
251
|
{"role": "system", "content": system_prompt},
|
|
264
252
|
{"role": "user", "content": user_prompt},
|
|
265
253
|
],
|
|
266
|
-
extra_body={"thinking": {"type": "enabled"}},
|
|
267
254
|
temperature=0,
|
|
268
255
|
stream=False
|
|
269
256
|
)
|
|
@@ -397,9 +384,11 @@ def refine_question_agent(raw_question: str, dataset_context: str | None = None)
|
|
|
397
384
|
|
|
398
385
|
system_prompt = ("""
|
|
399
386
|
- You are a Machine Learning (ML) and Data Science (DS) expert.
|
|
400
|
-
-
|
|
401
|
-
-
|
|
402
|
-
-
|
|
387
|
+
- Your goal is to use the provided dataset summary to convert given question into clear ML job specifications.
|
|
388
|
+
- Use the provided dataset summary to respect columns and aid you in properly refining the user question.
|
|
389
|
+
- Include chronological outline in order to guide a code generator to avoid falling off tracks.
|
|
390
|
+
- DO NOT include any prelude or preamble. Just the refined tasks.
|
|
391
|
+
- If and only if the dataset summary columns are not relevant to your desired columns that you deduced by analysing the question, and you suspect that the wrong dataset was used in the dataset summary, stop and just say: 'incompatible'.
|
|
403
392
|
""")
|
|
404
393
|
|
|
405
394
|
user_prompt = f"User question:\n{raw_question}\n\n"
|
|
@@ -532,9 +521,7 @@ def classify_ml_job_agent(refined_question, dataset_profile):
|
|
|
532
521
|
system_prompt = ("""
|
|
533
522
|
You are a strict machine learning task classifier for an ML workbench.
|
|
534
523
|
Your goal is to correctly label the user's task specifications with the most relevant tags from a fixed list.
|
|
535
|
-
You Must always have 'data_preprocessing' as the 1st tag. Then add
|
|
536
|
-
Your list should be 2-5 tags long. If no relevant tag, default to ["data_preprocessing"]
|
|
537
|
-
If tasks specs and `df` don't match (of different industries, return ['context mismatch']
|
|
524
|
+
You Must always have 'data_preprocessing' as the 1st tag. Then add all other relevant tags.
|
|
538
525
|
You should return only your list of tags, no prelude or preamble.
|
|
539
526
|
""")
|
|
540
527
|
|
|
@@ -554,8 +541,7 @@ def classify_ml_job_agent(refined_question, dataset_profile):
|
|
|
554
541
|
"generative_modeling", "causal_inference", "risk_modeling", "graph_analysis",
|
|
555
542
|
|
|
556
543
|
# Foundational/Pipeline Steps
|
|
557
|
-
"data_preprocessing", "feature_engineering", "statistical_inference",
|
|
558
|
-
"model_validation", "hyperparameter_tuning"
|
|
544
|
+
"data_preprocessing", "feature_engineering", "statistical_inference", "clustering", "hyperparameter_tuning"
|
|
559
545
|
]
|
|
560
546
|
|
|
561
547
|
# --- 2. Construct the Generalized Prompt for the LLM ---
|
|
@@ -575,7 +561,7 @@ def classify_ml_job_agent(refined_question, dataset_profile):
|
|
|
575
561
|
ML Jobs List: {', '.join(ml_task_list)}
|
|
576
562
|
|
|
577
563
|
Respond ONLY with a valid JSON array of strings containing the selected ML job names.
|
|
578
|
-
Example Response: ["
|
|
564
|
+
Example Response: ["data_preprocessing", "regression", "classification", "feature_engineering"]
|
|
579
565
|
"""
|
|
580
566
|
|
|
581
567
|
if dataset_profile:
|
|
@@ -583,7 +569,12 @@ def classify_ml_job_agent(refined_question, dataset_profile):
|
|
|
583
569
|
|
|
584
570
|
llm_profile = _prof.get_profile("classification") or _prof.get_profile("admin")
|
|
585
571
|
if not llm_profile:
|
|
586
|
-
return
|
|
572
|
+
return (
|
|
573
|
+
"<div class='smx-alert smx-alert-warn'>"
|
|
574
|
+
"No LLM profile is configured for Classification. Please, do that in the Admin panel or contact your Administrator."
|
|
575
|
+
"</div>"
|
|
576
|
+
)
|
|
577
|
+
|
|
587
578
|
|
|
588
579
|
llm_profile['client'] = _prof.get_client(llm_profile)
|
|
589
580
|
|
syntaxmatrix/routes.py
CHANGED
|
@@ -65,6 +65,7 @@ _CLIENT_DIR = detect_project_root()
|
|
|
65
65
|
_stream_q = queue.Queue()
|
|
66
66
|
_stream_cancelled = {}
|
|
67
67
|
_last_result_html = {} # { session_id: html_doc }
|
|
68
|
+
_last_resized_csv = {} # { resize_id: bytes for last resized CSV per browser session }
|
|
68
69
|
|
|
69
70
|
# single, reused formatter: inline styles, padding, rounded corners, scroll
|
|
70
71
|
_FMT = _HtmlFmt(
|
|
@@ -5610,8 +5611,19 @@ def setup_routes(smx):
|
|
|
5610
5611
|
dataset_profile = f"modality: tabular; columns: {columns_summary}"
|
|
5611
5612
|
|
|
5612
5613
|
refined_question = refine_question_agent(askai_question, dataset_context)
|
|
5613
|
-
tags =
|
|
5614
|
-
|
|
5614
|
+
tags = []
|
|
5615
|
+
if refined_question.lower() == "incompatible" or refined_question.lower() == "mismatch":
|
|
5616
|
+
return ("""
|
|
5617
|
+
<div style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center;">
|
|
5618
|
+
<h1 style="margin: 0 0 10px 0;">Oops: Context mismatch</h1>
|
|
5619
|
+
<p style="margin: 0;">Please, upload the proper dataset for solution to your query.</p>
|
|
5620
|
+
<br>
|
|
5621
|
+
<a class='button' href='/dashboard' style='text-decoration:none;'>Return</a>
|
|
5622
|
+
</div>
|
|
5623
|
+
""")
|
|
5624
|
+
else:
|
|
5625
|
+
tags = classify_ml_job_agent(refined_question, dataset_profile)
|
|
5626
|
+
|
|
5615
5627
|
ai_code = smx.ai_generate_code(refined_question, tags, df)
|
|
5616
5628
|
llm_usage = smx.get_last_llm_usage()
|
|
5617
5629
|
ai_code = auto_inject_template(ai_code, tags, df)
|
|
@@ -6513,6 +6525,7 @@ def setup_routes(smx):
|
|
|
6513
6525
|
cell["highlighted_code"] = Markup(_pygmentize(cell["code"]))
|
|
6514
6526
|
|
|
6515
6527
|
highlighted_ai_code = _pygmentize(ai_code)
|
|
6528
|
+
smxAI = "Orion"
|
|
6516
6529
|
|
|
6517
6530
|
return render_template(
|
|
6518
6531
|
"dashboard.html",
|
|
@@ -6525,6 +6538,7 @@ def setup_routes(smx):
|
|
|
6525
6538
|
askai_question=smx.sanitize_rough_to_markdown_task(askai_question),
|
|
6526
6539
|
refined_question=refined_question,
|
|
6527
6540
|
tasks=tags,
|
|
6541
|
+
smxAI=smxAI,
|
|
6528
6542
|
data_cells=data_cells,
|
|
6529
6543
|
session_id=session_id,
|
|
6530
6544
|
llm_usage=llm_usage
|
|
@@ -6588,6 +6602,179 @@ def setup_routes(smx):
|
|
|
6588
6602
|
# go back to the dashboard; dashboard() will auto-select the next file
|
|
6589
6603
|
return redirect(url_for("dashboard"))
|
|
6590
6604
|
|
|
6605
|
+
# ── DATASET RESIZE (independent helper page) -------------------------
|
|
6606
|
+
|
|
6607
|
+
|
|
6608
|
+
@smx.app.route("/dataset/resize", methods=["GET", "POST"])
|
|
6609
|
+
def dataset_resize():
|
|
6610
|
+
"""
|
|
6611
|
+
User uploads any CSV and picks a target size (percentage of rows).
|
|
6612
|
+
We keep the last resized CSV in memory and expose a download link.
|
|
6613
|
+
"""
|
|
6614
|
+
# One id per browser session to index _last_resized_csv
|
|
6615
|
+
resize_id = session.get("dataset_resize_id")
|
|
6616
|
+
if not resize_id:
|
|
6617
|
+
resize_id = str(uuid.uuid4())
|
|
6618
|
+
session["dataset_resize_id"] = resize_id
|
|
6619
|
+
|
|
6620
|
+
resize_info = None # stats we pass down to the template
|
|
6621
|
+
|
|
6622
|
+
if request.method == "POST":
|
|
6623
|
+
file = request.files.get("dataset_file")
|
|
6624
|
+
target_pct_raw = (request.form.get("target_pct") or "").strip()
|
|
6625
|
+
strat_col = (request.form.get("strat_col") or "").strip()
|
|
6626
|
+
|
|
6627
|
+
error_msg = None
|
|
6628
|
+
df = None
|
|
6629
|
+
|
|
6630
|
+
# --- Basic validation ---
|
|
6631
|
+
if not file or file.filename == "":
|
|
6632
|
+
error_msg = "Please choose a CSV file."
|
|
6633
|
+
elif not file.filename.lower().endswith(".csv"):
|
|
6634
|
+
error_msg = "Only CSV files are supported."
|
|
6635
|
+
|
|
6636
|
+
# --- Read CSV into a DataFrame ---
|
|
6637
|
+
if not error_msg:
|
|
6638
|
+
try:
|
|
6639
|
+
df = pd.read_csv(file)
|
|
6640
|
+
except Exception as e:
|
|
6641
|
+
error_msg = f"Could not read CSV: {e}"
|
|
6642
|
+
|
|
6643
|
+
# --- Parse target percentage ---
|
|
6644
|
+
pct = None
|
|
6645
|
+
if not error_msg:
|
|
6646
|
+
try:
|
|
6647
|
+
pct = float(target_pct_raw)
|
|
6648
|
+
except Exception:
|
|
6649
|
+
error_msg = "Target size must be a number between 1 and 100."
|
|
6650
|
+
|
|
6651
|
+
if not error_msg and (pct <= 0 or pct > 100):
|
|
6652
|
+
error_msg = "Target size must be between 1 and 100."
|
|
6653
|
+
|
|
6654
|
+
if error_msg:
|
|
6655
|
+
flash(error_msg, "error")
|
|
6656
|
+
else:
|
|
6657
|
+
frac = pct / 100.0
|
|
6658
|
+
n_orig = len(df)
|
|
6659
|
+
n_target = max(1, int(round(n_orig * frac)))
|
|
6660
|
+
|
|
6661
|
+
df_resized = None
|
|
6662
|
+
used_strat = False
|
|
6663
|
+
|
|
6664
|
+
# --- Advanced: stratified sampling by a column (behind 'Show advanced options') ---
|
|
6665
|
+
if strat_col and strat_col in df.columns and n_orig > 0:
|
|
6666
|
+
used_strat = True
|
|
6667
|
+
groups = df.groupby(strat_col, sort=False)
|
|
6668
|
+
|
|
6669
|
+
# First pass: proportional allocation with rounding and minimum 1 per non-empty group
|
|
6670
|
+
allocations = {}
|
|
6671
|
+
total_alloc = 0
|
|
6672
|
+
for key, group in groups:
|
|
6673
|
+
size = len(group)
|
|
6674
|
+
if size <= 0:
|
|
6675
|
+
allocations[key] = 0
|
|
6676
|
+
continue
|
|
6677
|
+
alloc = int(round(size * frac))
|
|
6678
|
+
if alloc == 0 and size > 0:
|
|
6679
|
+
alloc = 1
|
|
6680
|
+
if alloc > size:
|
|
6681
|
+
alloc = size
|
|
6682
|
+
allocations[key] = alloc
|
|
6683
|
+
total_alloc += alloc
|
|
6684
|
+
|
|
6685
|
+
keys = list(allocations.keys())
|
|
6686
|
+
|
|
6687
|
+
# Adjust downwards if we overshot
|
|
6688
|
+
if total_alloc > n_target:
|
|
6689
|
+
idx = 0
|
|
6690
|
+
while total_alloc > n_target and any(v > 1 for v in allocations.values()):
|
|
6691
|
+
k = keys[idx % len(keys)]
|
|
6692
|
+
if allocations[k] > 1:
|
|
6693
|
+
allocations[k] -= 1
|
|
6694
|
+
total_alloc -= 1
|
|
6695
|
+
idx += 1
|
|
6696
|
+
|
|
6697
|
+
# Adjust upwards if we undershot and we still have room in groups
|
|
6698
|
+
if total_alloc < n_target and keys:
|
|
6699
|
+
idx = 0
|
|
6700
|
+
while total_alloc < n_target:
|
|
6701
|
+
k = keys[idx % len(keys)]
|
|
6702
|
+
group_size = len(groups.get_group(k))
|
|
6703
|
+
if allocations[k] < group_size:
|
|
6704
|
+
allocations[k] += 1
|
|
6705
|
+
total_alloc += 1
|
|
6706
|
+
idx += 1
|
|
6707
|
+
if idx > len(keys) * 3:
|
|
6708
|
+
break
|
|
6709
|
+
|
|
6710
|
+
sampled_parts = []
|
|
6711
|
+
for key, group in groups:
|
|
6712
|
+
n_g = allocations.get(key, 0)
|
|
6713
|
+
if n_g > 0:
|
|
6714
|
+
sampled_parts.append(group.sample(n=n_g, random_state=0))
|
|
6715
|
+
|
|
6716
|
+
if sampled_parts:
|
|
6717
|
+
df_resized = (
|
|
6718
|
+
pd.concat(sampled_parts, axis=0)
|
|
6719
|
+
.sample(frac=1.0, random_state=0)
|
|
6720
|
+
.reset_index(drop=True)
|
|
6721
|
+
)
|
|
6722
|
+
|
|
6723
|
+
# --- Default: simple random sample over all rows ---
|
|
6724
|
+
if df_resized is None:
|
|
6725
|
+
if n_target >= n_orig:
|
|
6726
|
+
df_resized = df.copy()
|
|
6727
|
+
else:
|
|
6728
|
+
df_resized = df.sample(n=n_target, random_state=0).reset_index(drop=True)
|
|
6729
|
+
if strat_col and strat_col not in df.columns:
|
|
6730
|
+
flash(
|
|
6731
|
+
f"Column '{strat_col}' not found. Used simple random sampling instead.",
|
|
6732
|
+
"warning",
|
|
6733
|
+
)
|
|
6734
|
+
|
|
6735
|
+
# --- Serialise to CSV in memory and stash in _last_resized_csv ---
|
|
6736
|
+
buf = _std_io.BytesIO()
|
|
6737
|
+
df_resized.to_csv(buf, index=False)
|
|
6738
|
+
buf.seek(0)
|
|
6739
|
+
_last_resized_csv[resize_id] = buf.getvalue()
|
|
6740
|
+
|
|
6741
|
+
resize_info = {
|
|
6742
|
+
"rows_in": n_orig,
|
|
6743
|
+
"rows_out": len(df_resized),
|
|
6744
|
+
"pct": pct,
|
|
6745
|
+
"used_strat": used_strat,
|
|
6746
|
+
"strat_col": strat_col if used_strat else "",
|
|
6747
|
+
}
|
|
6748
|
+
flash("Dataset resized successfully. Use the download link below.", "success")
|
|
6749
|
+
|
|
6750
|
+
return render_template("dataset_resize.html", resize_info=resize_info)
|
|
6751
|
+
|
|
6752
|
+
@smx.app.route("/dataset/resize/download", methods=["GET"])
|
|
6753
|
+
def download_resized_dataset():
|
|
6754
|
+
"""Download the last resized dataset for this browser session as a CSV."""
|
|
6755
|
+
resize_id = session.get("dataset_resize_id")
|
|
6756
|
+
if not resize_id:
|
|
6757
|
+
return ("No resized dataset available.", 404)
|
|
6758
|
+
|
|
6759
|
+
data = _last_resized_csv.get(resize_id)
|
|
6760
|
+
if not data:
|
|
6761
|
+
return ("No resized dataset available.", 404)
|
|
6762
|
+
|
|
6763
|
+
buf = _std_io.BytesIO(data)
|
|
6764
|
+
buf.seek(0)
|
|
6765
|
+
stamp = datetime.now().strftime("%Y%m%d-%H%M%S-%f")
|
|
6766
|
+
filename = f"resized_dataset_{stamp}.csv"
|
|
6767
|
+
|
|
6768
|
+
# Drop it from memory once downloaded
|
|
6769
|
+
_last_resized_csv.pop(resize_id, None)
|
|
6770
|
+
|
|
6771
|
+
return send_file(
|
|
6772
|
+
buf,
|
|
6773
|
+
mimetype="text/csv; charset=utf-8",
|
|
6774
|
+
as_attachment=True,
|
|
6775
|
+
download_name=filename,
|
|
6776
|
+
)
|
|
6777
|
+
|
|
6591
6778
|
|
|
6592
6779
|
def _pdf_fallback_reportlab(full_html: str):
|
|
6593
6780
|
"""ReportLab fallback: extract text + base64 <img> and lay them out."""
|
|
@@ -43,7 +43,7 @@ PROVIDERS_MODELS = {
|
|
|
43
43
|
#5
|
|
44
44
|
"moonshot": [
|
|
45
45
|
"kimi-k2-thinking",
|
|
46
|
-
"kimi-k2",
|
|
46
|
+
"kimi-k2-instruct",
|
|
47
47
|
],
|
|
48
48
|
#6
|
|
49
49
|
"alibaba": [
|
|
@@ -59,7 +59,6 @@ PROVIDERS_MODELS = {
|
|
|
59
59
|
"claude-sonnet-4-5",
|
|
60
60
|
"claude-sonnet-4-0",
|
|
61
61
|
"claude-3-5-haiku-latest",
|
|
62
|
-
"claude-3-haiku-20240307",
|
|
63
62
|
]
|
|
64
63
|
}
|
|
65
64
|
|
|
@@ -160,31 +160,18 @@
|
|
|
160
160
|
padding:0.2rem;
|
|
161
161
|
}
|
|
162
162
|
.del-btn:hover { opacity:0.8; background:red; }
|
|
163
|
-
|
|
164
|
-
/* full-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
width:
|
|
169
|
-
|
|
170
|
-
display:
|
|
163
|
+
|
|
164
|
+
/* Make the Explore Data submit button compact instead of full-width */
|
|
165
|
+
.eda-submit-btn {
|
|
166
|
+
align-self: flex-start; /* stop flex from stretching it to 100% */
|
|
167
|
+
width: auto; /* shrink to content */
|
|
168
|
+
min-width: 7.5rem; /* tweak this if you want it smaller/larger */
|
|
169
|
+
padding: 6px 16px; /* a bit tighter than the global button */
|
|
170
|
+
display: inline-flex;
|
|
171
171
|
align-items: center;
|
|
172
172
|
justify-content: center;
|
|
173
|
-
z-index: 9999;
|
|
174
|
-
}
|
|
175
|
-
/* simple spinner */
|
|
176
|
-
.loader {
|
|
177
|
-
border: 8px solid #eee;
|
|
178
|
-
border-top: 8px solid #333;
|
|
179
|
-
border-radius: 50%;
|
|
180
|
-
width: 60px; height: 60px;
|
|
181
|
-
animation: spin 1s linear infinite;
|
|
182
173
|
}
|
|
183
|
-
|
|
184
|
-
0% { transform: rotate(0deg); }
|
|
185
|
-
100% { transform: rotate(360deg); }
|
|
186
|
-
}
|
|
187
|
-
|
|
174
|
+
|
|
188
175
|
/* --- Mobile fixes --- */
|
|
189
176
|
.dashboard-content img,
|
|
190
177
|
.dashboard-content canvas,
|
|
@@ -618,24 +605,92 @@
|
|
|
618
605
|
details > summary::-webkit-details-marker {
|
|
619
606
|
display: none;
|
|
620
607
|
}
|
|
608
|
+
|
|
609
|
+
/* Spinner that sits inside the Explore Data submit button */
|
|
610
|
+
.eda-btn-spinner {
|
|
611
|
+
display: none;
|
|
612
|
+
width: 1.1rem;
|
|
613
|
+
height: 1.1rem;
|
|
614
|
+
border-radius: 999px;
|
|
615
|
+
border: 2px solid currentColor;
|
|
616
|
+
border-top-color: transparent;
|
|
617
|
+
border-right-color: transparent;
|
|
618
|
+
animation: edaBtnSpin 0.7s linear infinite;
|
|
619
|
+
margin-left: 0; /* was 0.5rem */
|
|
620
|
+
vertical-align: middle;
|
|
621
|
+
box-sizing: border-box;
|
|
622
|
+
}
|
|
623
|
+
.eda-btn-label {
|
|
624
|
+
display: inline-block;
|
|
625
|
+
margin-right: 0.5rem; /* space between text and spinner when both are visible */
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/* While loading: hide the label, show the spinner */
|
|
629
|
+
.eda-btn-loading .eda-btn-label {
|
|
630
|
+
display: none; /* keeps button width stable */
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.eda-btn-loading .eda-btn-spinner {
|
|
634
|
+
display: inline-block;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
@keyframes edaBtnSpin {
|
|
638
|
+
to {
|
|
639
|
+
transform: rotate(360deg);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
.sidebar-links {
|
|
643
|
+
margin-top: 22px;
|
|
644
|
+
}
|
|
645
|
+
.sidebar-links a {
|
|
646
|
+
display: block;
|
|
647
|
+
padding: 12px 10px 12px 0;
|
|
648
|
+
color: #333;
|
|
649
|
+
text-decoration: none;
|
|
650
|
+
font-size: clamp(0.93rem, 2vw, 1.08rem);
|
|
651
|
+
border-radius: 6px;
|
|
652
|
+
margin-bottom: 6px;
|
|
653
|
+
transition: background 0.2s, color 0.2s;
|
|
654
|
+
}
|
|
655
|
+
.sidebar-links a.active,
|
|
656
|
+
.sidebar-links a:hover {
|
|
657
|
+
background: #e0e5ee;
|
|
658
|
+
color: #007acc;
|
|
659
|
+
font-weight: bold;
|
|
660
|
+
}
|
|
661
|
+
/* Sub-links under a main section (e.g. Explore → Resize dataset) */
|
|
662
|
+
.sidebar-links a.sidebar-sub-link {
|
|
663
|
+
font-size: clamp(0.80rem, 1.4vw, 0.95rem); /* smaller than main items */
|
|
664
|
+
padding: 6px 10px 6px 14px; /* slight indent to show hierarchy */
|
|
665
|
+
margin-bottom: 4px;
|
|
666
|
+
opacity: 0.95;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/* Optional: visual cue arrow for sub-items */
|
|
670
|
+
.sidebar-links a.sidebar-sub-link::before {
|
|
671
|
+
content: "↳ ";
|
|
672
|
+
font-size: 0.8em;
|
|
673
|
+
opacity: 0.7;
|
|
674
|
+
}
|
|
621
675
|
</style>
|
|
622
676
|
|
|
623
677
|
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
|
|
624
678
|
</head>
|
|
625
679
|
<body>
|
|
626
680
|
<div id="sidebarScrim" class="sidebar-scrim" aria-hidden="true"></div>
|
|
627
|
-
<div id="loader-overlay">
|
|
628
|
-
<div class="loader"></div>
|
|
629
|
-
</div>
|
|
630
681
|
<div class="dashboard-sidebar">
|
|
631
682
|
<button class="sidebar-close" aria-label="Close menu">✕</button>
|
|
632
683
|
<h2>ML Lab</h2>
|
|
633
684
|
<a href="/">return to home</a>
|
|
634
685
|
<div class="sidebar-links">
|
|
635
686
|
<a href="/dashboard?section=explore"{% if section == 'explore' %} class="active"{% endif %}>Explore</a>
|
|
636
|
-
|
|
687
|
+
|
|
688
|
+
<!-- Explore subsets -->
|
|
689
|
+
<a href="{{ url_for('dataset_resize') }}" class="sidebar-sub-link">Resize dataset</a>
|
|
690
|
+
|
|
637
691
|
<!-- Future: more links here -->
|
|
638
692
|
</div>
|
|
693
|
+
|
|
639
694
|
</div>
|
|
640
695
|
<div class="dashboard-main">
|
|
641
696
|
<button id="sidebarToggle" class="sidebar-toggle" aria-label="Open menu"></button>
|
|
@@ -691,11 +746,18 @@
|
|
|
691
746
|
<h2>Explore Data</h2>
|
|
692
747
|
<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;">
|
|
693
748
|
<input type="hidden" name="dataset" value="{{ selected_dataset }}">
|
|
694
|
-
<label for="askai"><strong>Ask smxAI:</strong></label>
|
|
695
|
-
<textarea id="askai" name="askai_question" type="text" rows="
|
|
696
|
-
style="position:relative; width:90%; padding:
|
|
697
|
-
placeholder="Ask me about {{ (selected_dataset or 'your dataset.
|
|
698
|
-
<button type="submit" style="font-size:1.2rem; width:8rem; padding:4px;">Submit</button>
|
|
749
|
+
<label for="askai"><strong>Ask {{ smxAI }}:</strong></label>
|
|
750
|
+
<textarea id="askai" name="askai_question" type="text" rows="5"
|
|
751
|
+
style="position:relative; width:90%; padding:16px; font-size:0.8em; border-radius:8px;"
|
|
752
|
+
placeholder="Ask me about {{ (selected_dataset or 'your dataset\n. But upload it first.').replace('_', ' ').replace('.csv', '') }}" required></textarea>
|
|
753
|
+
<!-- <button type="submit" style="font-size:1.2rem; width:8rem; padding:4px;">Submit</button> -->
|
|
754
|
+
<button
|
|
755
|
+
type="submit"
|
|
756
|
+
class="btn btn-primary eda-submit-btn"
|
|
757
|
+
>
|
|
758
|
+
<span class="eda-btn-label">Submit</span>
|
|
759
|
+
<span class="eda-btn-spinner" aria-hidden="true"></span>
|
|
760
|
+
</button>
|
|
699
761
|
</form>
|
|
700
762
|
<div style="margin-bottom: 36px;">
|
|
701
763
|
{% if askai_question %}
|
|
@@ -735,6 +797,7 @@
|
|
|
735
797
|
{% endif %}
|
|
736
798
|
{% if ai_outputs %}
|
|
737
799
|
<div class="d-flex align-items-center justify-content-between" style="margin: 12px;">
|
|
800
|
+
<br>
|
|
738
801
|
<h3 class="m-0">Result</h3>
|
|
739
802
|
{% for html_block in ai_outputs %}
|
|
740
803
|
<div class="ai-output" style="margin-bottom:18px;overflow-x:auto; max-width:100%;">
|
|
@@ -787,31 +850,31 @@
|
|
|
787
850
|
</div>
|
|
788
851
|
</div>
|
|
789
852
|
<script>
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
853
|
+
async function copyCode(btn){
|
|
854
|
+
const pre = btn.parentElement.querySelector('pre');
|
|
855
|
+
if(!pre) return;
|
|
856
|
+
const text = pre.innerText;
|
|
857
|
+
|
|
858
|
+
try {
|
|
859
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
860
|
+
await navigator.clipboard.writeText(text);
|
|
861
|
+
} else {
|
|
862
|
+
// fallback
|
|
863
|
+
const range = document.createRange();
|
|
864
|
+
range.selectNodeContents(pre);
|
|
865
|
+
const sel = window.getSelection();
|
|
866
|
+
sel.removeAllRanges(); sel.addRange(range);
|
|
867
|
+
document.execCommand('copy');
|
|
868
|
+
sel.removeAllRanges();
|
|
869
|
+
}
|
|
870
|
+
btn.textContent = 'Copied!';
|
|
871
|
+
setTimeout(()=>btn.textContent='Copy', 1200);
|
|
872
|
+
} catch(e){
|
|
873
|
+
btn.textContent = 'Failed';
|
|
874
|
+
setTimeout(()=>btn.textContent='Copy', 1200);
|
|
875
|
+
}
|
|
812
876
|
}
|
|
813
|
-
|
|
814
|
-
</script>
|
|
877
|
+
</script>
|
|
815
878
|
<script>
|
|
816
879
|
function toggleCodeCell(link) {
|
|
817
880
|
var cell = link.nextElementSibling;
|
|
@@ -824,6 +887,7 @@
|
|
|
824
887
|
link.querySelector("span").innerText = "Show Code";
|
|
825
888
|
}
|
|
826
889
|
}
|
|
890
|
+
|
|
827
891
|
function copyCodeToClipboard(btn) {
|
|
828
892
|
var pre = btn.parentElement.querySelector("pre");
|
|
829
893
|
if (!pre) return;
|
|
@@ -842,13 +906,36 @@
|
|
|
842
906
|
}
|
|
843
907
|
sel.removeAllRanges();
|
|
844
908
|
}
|
|
845
|
-
|
|
846
|
-
document.addEventListener("DOMContentLoaded", ()
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
909
|
+
|
|
910
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
911
|
+
var form = document.getElementById("form-askai");
|
|
912
|
+
if (!form) return;
|
|
913
|
+
|
|
914
|
+
var submitBtn = form.querySelector(".eda-submit-btn");
|
|
915
|
+
if (!submitBtn) return;
|
|
916
|
+
|
|
917
|
+
// When the form actually submits, show the spinner and disable the button
|
|
918
|
+
form.addEventListener("submit", function () {
|
|
919
|
+
submitBtn.classList.add("eda-btn-loading");
|
|
920
|
+
submitBtn.disabled = true;
|
|
921
|
+
// IMPORTANT: no preventDefault here – the browser/htmx still submits normally
|
|
851
922
|
});
|
|
923
|
+
|
|
924
|
+
// If htmx is used on this form, reset the spinner once the request completes or errors
|
|
925
|
+
if (window.htmx) {
|
|
926
|
+
form.addEventListener("htmx:afterRequest", function () {
|
|
927
|
+
submitBtn.classList.remove("eda-btn-loading");
|
|
928
|
+
submitBtn.disabled = false;
|
|
929
|
+
});
|
|
930
|
+
form.addEventListener("htmx:responseError", function () {
|
|
931
|
+
submitBtn.classList.remove("eda-btn-loading");
|
|
932
|
+
submitBtn.disabled = false;
|
|
933
|
+
});
|
|
934
|
+
form.addEventListener("htmx:sendError", function () {
|
|
935
|
+
submitBtn.classList.remove("eda-btn-loading");
|
|
936
|
+
submitBtn.disabled = false;
|
|
937
|
+
});
|
|
938
|
+
}
|
|
852
939
|
});
|
|
853
940
|
</script>
|
|
854
941
|
<script>
|
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Dataset Resizer · SyntaxMatrix ML Lab</title>
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
+
|
|
8
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
--smx-bg: #0f172a;
|
|
11
|
+
--smx-bg-soft: #020617;
|
|
12
|
+
--smx-card: #020617;
|
|
13
|
+
--smx-card-soft: #020617;
|
|
14
|
+
--smx-border: #1f2937;
|
|
15
|
+
--smx-border-soft: #111827;
|
|
16
|
+
--smx-accent: #38bdf8;
|
|
17
|
+
--smx-accent-soft: rgba(56, 189, 248, 0.16);
|
|
18
|
+
--smx-accent-strong: #0ea5e9;
|
|
19
|
+
--smx-text-main: #e5e7eb;
|
|
20
|
+
--smx-text-soft: #9ca3af;
|
|
21
|
+
--smx-danger: #f97373;
|
|
22
|
+
--smx-success: #22c55e;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
* {
|
|
26
|
+
box-sizing: border-box;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
body {
|
|
30
|
+
margin: 0;
|
|
31
|
+
padding: 0;
|
|
32
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
33
|
+
background: radial-gradient(circle at 0 0, #1e293b 0, #020617 55%, #000000 100%);
|
|
34
|
+
color: var(--smx-text-main);
|
|
35
|
+
min-height: 100vh;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.smx-shell {
|
|
39
|
+
max-width: 1120px;
|
|
40
|
+
margin: 0 auto;
|
|
41
|
+
padding: 24px 16px 32px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.smx-topbar {
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
justify-content: space-between;
|
|
48
|
+
gap: 12px;
|
|
49
|
+
margin-bottom: 20px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.smx-topbar-left {
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: 10px;
|
|
56
|
+
min-width: 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.smx-logo-pill {
|
|
60
|
+
width: 32px;
|
|
61
|
+
height: 32px;
|
|
62
|
+
border-radius: 999px;
|
|
63
|
+
background: radial-gradient(circle at 30% 20%, #38bdf8 0, #0ea5e9 35%, #22c55e 80%);
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
justify-content: center;
|
|
67
|
+
font-size: 0.85rem;
|
|
68
|
+
font-weight: 700;
|
|
69
|
+
colour: #0b1120;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.smx-topbar-title {
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-direction: column;
|
|
75
|
+
gap: 2px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.smx-topbar-title-main {
|
|
79
|
+
font-size: 1.1rem;
|
|
80
|
+
font-weight: 600;
|
|
81
|
+
letter-spacing: 0.02em;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.smx-topbar-title-sub {
|
|
85
|
+
font-size: 0.8rem;
|
|
86
|
+
colour: var(--smx-text-soft);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.smx-back-link {
|
|
90
|
+
display: inline-flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
gap: 6px;
|
|
93
|
+
padding: 6px 12px;
|
|
94
|
+
border-radius: 999px;
|
|
95
|
+
border: 1px solid rgba(148, 163, 184, 0.5);
|
|
96
|
+
text-decoration: none;
|
|
97
|
+
font-size: 0.8rem;
|
|
98
|
+
colour: var(--smx-text-main);
|
|
99
|
+
background: rgba(15, 23, 42, 0.7);
|
|
100
|
+
backdrop-filter: blur(10px);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.smx-back-link:hover {
|
|
104
|
+
border-colour: var(--smx-accent);
|
|
105
|
+
colour: var(--smx-accent-strong);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.smx-layout {
|
|
109
|
+
display: grid;
|
|
110
|
+
grid-template-columns: minmax(0, 1.7fr) minmax(0, 1.1fr);
|
|
111
|
+
gap: 18px;
|
|
112
|
+
align-items: flex-start;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.smx-card {
|
|
116
|
+
background: radial-gradient(circle at 0 0, rgba(56, 189, 248, 0.08), rgba(15, 23, 42, 0.98));
|
|
117
|
+
border-radius: 16px;
|
|
118
|
+
border: 1px solid rgba(15, 23, 42, 0.9);
|
|
119
|
+
padding: 18px 18px 18px;
|
|
120
|
+
box-shadow:
|
|
121
|
+
0 18px 40px rgba(15, 23, 42, 0.80),
|
|
122
|
+
0 0 0 0.5px rgba(148, 163, 184, 0.35);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.smx-card-secondary {
|
|
126
|
+
background: radial-gradient(circle at 100% 0, rgba(56, 189, 248, 0.15), rgba(15, 23, 42, 0.98));
|
|
127
|
+
border-radius: 16px;
|
|
128
|
+
border: 1px solid rgba(30, 64, 175, 0.7);
|
|
129
|
+
padding: 18px 18px;
|
|
130
|
+
box-shadow:
|
|
131
|
+
0 16px 32px rgba(15, 23, 42, 0.85),
|
|
132
|
+
0 0 0 0.5px rgba(30, 64, 175, 0.55);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.smx-card-header {
|
|
136
|
+
display: flex;
|
|
137
|
+
align-items: center;
|
|
138
|
+
justify-content: space-between;
|
|
139
|
+
gap: 10px;
|
|
140
|
+
margin-bottom: 12px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.smx-card-title {
|
|
144
|
+
font-size: 1rem;
|
|
145
|
+
font-weight: 600;
|
|
146
|
+
display: inline-flex;
|
|
147
|
+
align-items: center;
|
|
148
|
+
gap: 8px;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.smx-pill {
|
|
152
|
+
padding: 2px 8px;
|
|
153
|
+
border-radius: 999px;
|
|
154
|
+
background: rgba(56, 189, 248, 0.12);
|
|
155
|
+
border: 1px solid rgba(56, 189, 248, 0.5);
|
|
156
|
+
font-size: 0.7rem;
|
|
157
|
+
font-weight: 500;
|
|
158
|
+
letter-spacing: 0.04em;
|
|
159
|
+
text-transform: uppercase;
|
|
160
|
+
colour: var(--smx-accent-strong);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.smx-card-body {
|
|
164
|
+
font-size: 0.85rem;
|
|
165
|
+
colour: var(--smx-text-soft);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.smx-description {
|
|
169
|
+
margin: 0 0 10px;
|
|
170
|
+
line-height: 1.5;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
label {
|
|
174
|
+
display: block;
|
|
175
|
+
font-weight: 500;
|
|
176
|
+
font-size: 0.82rem;
|
|
177
|
+
margin-bottom: 4px;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.smx-field {
|
|
181
|
+
margin-bottom: 10px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
input[type="file"],
|
|
185
|
+
input[type="number"],
|
|
186
|
+
input[type="text"] {
|
|
187
|
+
width: 100%;
|
|
188
|
+
padding: 7px 10px;
|
|
189
|
+
border-radius: 10px;
|
|
190
|
+
border: 1px solid rgba(148, 163, 184, 0.55);
|
|
191
|
+
background: rgba(15, 23, 42, 0.9);
|
|
192
|
+
colour: var(--smx-text-main);
|
|
193
|
+
font-size: 0.85rem;
|
|
194
|
+
color: #fff5;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
input[type="file"] {
|
|
198
|
+
padding: 6px 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
input[type="number"]:focus,
|
|
202
|
+
input[type="text"]:focus {
|
|
203
|
+
outline: none;
|
|
204
|
+
border-colour: var(--smx-accent);
|
|
205
|
+
box-shadow: 0 0 0 1px rgba(56, 189, 248, 0.45);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.smx-hint {
|
|
209
|
+
font-size: 0.75rem;
|
|
210
|
+
colour: var(--smx-text-soft);
|
|
211
|
+
margin: 0 0 6px;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.smx-actions {
|
|
215
|
+
margin-top: 10px;
|
|
216
|
+
display: flex;
|
|
217
|
+
align-items: center;
|
|
218
|
+
justify-content: flex-start;
|
|
219
|
+
gap: 10px;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.btn-primary {
|
|
223
|
+
display: inline-flex;
|
|
224
|
+
align-items: center;
|
|
225
|
+
justify-content: center;
|
|
226
|
+
padding: 7px 16px;
|
|
227
|
+
border-radius: 999px;
|
|
228
|
+
border: none;
|
|
229
|
+
background: linear-gradient(135deg, #0ea5e9, #22c55e);
|
|
230
|
+
colour: #0b1120;
|
|
231
|
+
font-size: 0.85rem;
|
|
232
|
+
font-weight: 600;
|
|
233
|
+
cursor: pointer;
|
|
234
|
+
box-shadow: 0 10px 22px rgba(8, 47, 73, 0.7);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.btn-primary:hover {
|
|
238
|
+
filter: brightness(1.05);
|
|
239
|
+
box-shadow: 0 14px 28px rgba(8, 47, 73, 0.85);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.smx-adv-toggle {
|
|
243
|
+
font-size: 0.8rem;
|
|
244
|
+
colour: var(--smx-accent);
|
|
245
|
+
cursor: pointer;
|
|
246
|
+
text-decoration: none;
|
|
247
|
+
display: inline-flex;
|
|
248
|
+
align-items: centre;
|
|
249
|
+
gap: 4px;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.smx-adv-toggle:hover {
|
|
253
|
+
text-decoration: underline;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#advanced-box {
|
|
257
|
+
display: none;
|
|
258
|
+
margin-top: 8px;
|
|
259
|
+
padding: 10px 10px 8px;
|
|
260
|
+
border-radius: 10px;
|
|
261
|
+
border: 1px dashed rgba(148, 163, 184, 0.6);
|
|
262
|
+
background: rgba(15, 23, 42, 0.8);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.flashes {
|
|
266
|
+
list-style: none;
|
|
267
|
+
padding-left: 0;
|
|
268
|
+
margin: 0 0 10px;
|
|
269
|
+
font-size: 0.78rem;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.flashes li {
|
|
273
|
+
margin-bottom: 4px;
|
|
274
|
+
padding: 6px 8px;
|
|
275
|
+
border-radius: 8px;
|
|
276
|
+
border: 1px solid rgba(250, 204, 21, 0.8);
|
|
277
|
+
background: rgba(251, 191, 36, 0.06);
|
|
278
|
+
colour: #facc15;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.flashes li.error {
|
|
282
|
+
border-colour: rgba(248, 113, 113, 0.9);
|
|
283
|
+
background: rgba(248, 113, 113, 0.08);
|
|
284
|
+
colour: #fecaca;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.flashes li.success {
|
|
288
|
+
border-colour: rgba(34, 197, 94, 0.9);
|
|
289
|
+
background: rgba(22, 163, 74, 0.08);
|
|
290
|
+
colour: #bbf7d0;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.flashes li.warning {
|
|
294
|
+
border-colour: rgba(234, 179, 8, 0.9);
|
|
295
|
+
background: rgba(234, 179, 8, 0.08);
|
|
296
|
+
colour: #fef3c7;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.meta-list {
|
|
300
|
+
list-style: none;
|
|
301
|
+
padding-left: 0;
|
|
302
|
+
margin: 0 0 10px;
|
|
303
|
+
font-size: 0.8rem;
|
|
304
|
+
colour: var(--smx-text-soft);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.meta-list li {
|
|
308
|
+
margin-bottom: 3px;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.download-link {
|
|
312
|
+
display: inline-flex;
|
|
313
|
+
align-items: center;
|
|
314
|
+
justify-content: center;
|
|
315
|
+
padding: 7px 16px;
|
|
316
|
+
border-radius: 999px;
|
|
317
|
+
border: 1px solid rgba(34, 197, 94, 0.9);
|
|
318
|
+
colour: #bbf7d0;
|
|
319
|
+
font-weight: 600;
|
|
320
|
+
font-size: 0.85rem;
|
|
321
|
+
text-decoration: none;
|
|
322
|
+
background: rgba(22, 163, 74, 0.23);
|
|
323
|
+
box-shadow: 0 10px 22px rgba(4, 52, 35, 0.8);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.download-link:hover {
|
|
327
|
+
background: rgba(21, 128, 61, 0.9);
|
|
328
|
+
colour: #ecfdf5;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.smx-side-note-title {
|
|
332
|
+
font-size: 0.9rem;
|
|
333
|
+
font-weight: 600;
|
|
334
|
+
margin-bottom: 6px;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.smx-side-note-text {
|
|
338
|
+
font-size: 0.8rem;
|
|
339
|
+
colour: var(--smx-text-soft);
|
|
340
|
+
margin: 0 0 10px;
|
|
341
|
+
line-height: 1.5;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.smx-badge-dot {
|
|
345
|
+
display: inline-block;
|
|
346
|
+
width: 7px;
|
|
347
|
+
height: 7px;
|
|
348
|
+
border-radius: 999px;
|
|
349
|
+
background: var(--smx-accent);
|
|
350
|
+
margin-right: 6px;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
@media (max-width: 880px) {
|
|
354
|
+
.smx-layout {
|
|
355
|
+
grid-template-columns: minmax(0, 1fr);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
@media (max-width: 640px) {
|
|
360
|
+
.smx-shell {
|
|
361
|
+
padding-top: 16px;
|
|
362
|
+
}
|
|
363
|
+
.smx-card,
|
|
364
|
+
.smx-card-secondary {
|
|
365
|
+
border-radius: 12px;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
</style>
|
|
369
|
+
</head>
|
|
370
|
+
<body>
|
|
371
|
+
<div class="smx-shell">
|
|
372
|
+
<div class="smx-topbar">
|
|
373
|
+
<div class="smx-topbar-left">
|
|
374
|
+
<div class="smx-logo-pill">SM</div>
|
|
375
|
+
<div class="smx-topbar-title">
|
|
376
|
+
<div class="smx-topbar-title-main">Dataset Resizer</div>
|
|
377
|
+
<div class="smx-topbar-title-sub">Subset of Explore · ML Lab</div>
|
|
378
|
+
</div>
|
|
379
|
+
</div>
|
|
380
|
+
<a href="{{ url_for('dashboard', section='explore') }}" class="smx-back-link">
|
|
381
|
+
<span aria-hidden="true">←</span>
|
|
382
|
+
<span>Back to ML Lab</span>
|
|
383
|
+
</a>
|
|
384
|
+
</div>
|
|
385
|
+
|
|
386
|
+
<div class="smx-layout">
|
|
387
|
+
<!-- Main card: form -->
|
|
388
|
+
<div class="smx-card">
|
|
389
|
+
{% with msgs = get_flashed_messages(with_categories=true) %}
|
|
390
|
+
{% if msgs %}
|
|
391
|
+
<ul class="flashes">
|
|
392
|
+
{% for category, msg in msgs %}
|
|
393
|
+
<li class="{{ category }}">{{ msg }}</li>
|
|
394
|
+
{% endfor %}
|
|
395
|
+
</ul>
|
|
396
|
+
{% endif %}
|
|
397
|
+
{% endwith %}
|
|
398
|
+
|
|
399
|
+
<div class="smx-card-header">
|
|
400
|
+
<div class="smx-card-title">
|
|
401
|
+
<span>Resize a dataset</span>
|
|
402
|
+
<span class="smx-pill">Explore subset</span>
|
|
403
|
+
</div>
|
|
404
|
+
</div>
|
|
405
|
+
<div class="smx-card-body">
|
|
406
|
+
<p class="smx-description">
|
|
407
|
+
Upload any CSV that feels too large to work with. Choose the percentage you want to keep,
|
|
408
|
+
and the resizer will return a smaller sample that preserves the overall feel of the data.
|
|
409
|
+
</p>
|
|
410
|
+
|
|
411
|
+
<form method="post" enctype="multipart/form-data">
|
|
412
|
+
<div class="smx-field">
|
|
413
|
+
<label for="dataset_file">CSV file</label>
|
|
414
|
+
<input id="dataset_file" type="file" name="dataset_file" accept=".csv" required>
|
|
415
|
+
<p class="smx-hint">
|
|
416
|
+
This is independent of the datasets listed in the ML Lab sidebar. You can bring in any CSV here.
|
|
417
|
+
</p>
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
<div class="smx-field">
|
|
421
|
+
<label for="target_pct">Target size (%)</label>
|
|
422
|
+
<input
|
|
423
|
+
id="target_pct"
|
|
424
|
+
name="target_pct"
|
|
425
|
+
type="number"
|
|
426
|
+
min="1"
|
|
427
|
+
max="100"
|
|
428
|
+
step="1"
|
|
429
|
+
placeholder="For example: 20 for 20% of the rows"
|
|
430
|
+
required
|
|
431
|
+
>
|
|
432
|
+
<p class="smx-hint">
|
|
433
|
+
We will sample roughly this share of rows. For very large files, even 5–10 % can be enough
|
|
434
|
+
to explore patterns.
|
|
435
|
+
</p>
|
|
436
|
+
</div>
|
|
437
|
+
|
|
438
|
+
<a href="#" id="advToggle" class="smx-adv-toggle">
|
|
439
|
+
<span aria-hidden="true">▸</span>
|
|
440
|
+
<span>Show advanced options</span>
|
|
441
|
+
</a>
|
|
442
|
+
|
|
443
|
+
<div id="advanced-box">
|
|
444
|
+
<div class="smx-field" style="margin-top:4px;">
|
|
445
|
+
<label for="strat_col">Stratify by column (optional)</label>
|
|
446
|
+
<input
|
|
447
|
+
id="strat_col"
|
|
448
|
+
name="strat_col"
|
|
449
|
+
type="text"
|
|
450
|
+
placeholder="Type a label/segment column to keep class balance"
|
|
451
|
+
>
|
|
452
|
+
<p class="smx-hint">
|
|
453
|
+
If you supply a valid column name (for example a target label), the resizer will allocate rows per
|
|
454
|
+
class so proportions remain close to the original.
|
|
455
|
+
</p>
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
|
|
459
|
+
<div class="smx-actions">
|
|
460
|
+
<button type="submit" class="btn-primary">
|
|
461
|
+
Create resized CSV
|
|
462
|
+
</button>
|
|
463
|
+
</div>
|
|
464
|
+
</form>
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
|
|
468
|
+
<!-- Side card: summary + download -->
|
|
469
|
+
<div class="smx-card-secondary">
|
|
470
|
+
<div class="smx-card-header">
|
|
471
|
+
<div class="smx-card-title">
|
|
472
|
+
<span class="smx-badge-dot"></span>
|
|
473
|
+
<span>Resize summary</span>
|
|
474
|
+
</div>
|
|
475
|
+
</div>
|
|
476
|
+
<div class="smx-card-body">
|
|
477
|
+
{% if resize_info %}
|
|
478
|
+
<p class="smx-side-note-text">
|
|
479
|
+
Here is a quick view of what was produced from your last run. You can download the resized CSV
|
|
480
|
+
and pass it straight into your modelling workflow.
|
|
481
|
+
</p>
|
|
482
|
+
<ul class="meta-list">
|
|
483
|
+
<li><strong>Original rows:</strong> {{ resize_info.rows_in }}</li>
|
|
484
|
+
<li><strong>Resized rows:</strong> {{ resize_info.rows_out }}</li>
|
|
485
|
+
<li><strong>Target size:</strong> {{ "%.1f"|format(resize_info.pct) }} %</li>
|
|
486
|
+
{% if resize_info.used_strat and resize_info.strat_col %}
|
|
487
|
+
<li><strong>Stratified by:</strong> {{ resize_info.strat_col }}</li>
|
|
488
|
+
{% endif %}
|
|
489
|
+
</ul>
|
|
490
|
+
<a href="{{ url_for('download_resized_dataset') }}" class="download-link">
|
|
491
|
+
Download resized CSV
|
|
492
|
+
</a>
|
|
493
|
+
{% else %}
|
|
494
|
+
<p class="smx-side-note-title">No resized dataset yet</p>
|
|
495
|
+
<p class="smx-side-note-text">
|
|
496
|
+
Once you upload a file and choose a percentage, this panel will display the row counts and a download
|
|
497
|
+
button for the resized sample.
|
|
498
|
+
</p>
|
|
499
|
+
{% endif %}
|
|
500
|
+
</div>
|
|
501
|
+
</div>
|
|
502
|
+
</div>
|
|
503
|
+
</div>
|
|
504
|
+
|
|
505
|
+
<script>
|
|
506
|
+
(function () {
|
|
507
|
+
const toggle = document.getElementById("advToggle");
|
|
508
|
+
const box = document.getElementById("advanced-box");
|
|
509
|
+
if (!toggle || !box) return;
|
|
510
|
+
|
|
511
|
+
let open = false;
|
|
512
|
+
const labelSpan = toggle.querySelector("span:nth-child(2)");
|
|
513
|
+
const arrowSpan = toggle.querySelector("span:nth-child(1)");
|
|
514
|
+
|
|
515
|
+
function refresh() {
|
|
516
|
+
box.style.display = open ? "block" : "none";
|
|
517
|
+
if (labelSpan) {
|
|
518
|
+
labelSpan.textContent = open ? "Hide advanced options" : "Show advanced options";
|
|
519
|
+
}
|
|
520
|
+
if (arrowSpan) {
|
|
521
|
+
arrowSpan.textContent = open ? "▾" : "▸";
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
toggle.addEventListener("click", function (ev) {
|
|
526
|
+
ev.preventDefault();
|
|
527
|
+
open = !open;
|
|
528
|
+
refresh();
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
refresh();
|
|
532
|
+
})();
|
|
533
|
+
</script>
|
|
534
|
+
</body>
|
|
535
|
+
</html>
|
syntaxmatrix/utils.py
CHANGED
|
@@ -629,6 +629,15 @@ def harden_ai_code(code: str) -> str:
|
|
|
629
629
|
# 6) Final safety wrapper
|
|
630
630
|
fixed = fixed.replace("\t", " ")
|
|
631
631
|
fixed = textwrap.dedent(fixed).strip("\n")
|
|
632
|
+
|
|
633
|
+
# Normalise any mistaken template imports the LLM may have invented.
|
|
634
|
+
# If the model writes "from syntaxmatrix.templates import viz_count_bar",
|
|
635
|
+
# redirect that import to the real template module.
|
|
636
|
+
fixed = re.sub(
|
|
637
|
+
r"from\s+syntaxmatrix\.templates\s+import\s+([^\n]+)",
|
|
638
|
+
r"from syntaxmatrix.agentic.model_templates import \1",
|
|
639
|
+
fixed,
|
|
640
|
+
)
|
|
632
641
|
fixed = _ensure_metrics_imports(fixed)
|
|
633
642
|
fixed = _strip_stray_backrefs(fixed)
|
|
634
643
|
fixed = _wrap_metric_calls(fixed)
|
|
@@ -18,25 +18,25 @@ syntaxmatrix/plottings.py,sha256=MjHQ9T1_oC5oyr4_wkM2GJDrpjp0sbvudbs2lGaMyzk,610
|
|
|
18
18
|
syntaxmatrix/preface.py,sha256=EOK3lflMJ-0B6SRJtVXhzZjhvu-bfXzw-sy1TbTYOVs,17009
|
|
19
19
|
syntaxmatrix/profiles.py,sha256=0-lky7Wj-WQlP5CbvTyw1tI2M0FiqhhTkLZYLRhD5AU,2251
|
|
20
20
|
syntaxmatrix/project_root.py,sha256=1ckvbFVV1szHtHsfSCoGcImHkRwbfszmPG1kGh9ZZlE,2227
|
|
21
|
-
syntaxmatrix/routes.py,sha256=
|
|
21
|
+
syntaxmatrix/routes.py,sha256=z8ANOl2MQIzcLGbFxiUNviE4E4xQwOWH_gb8zZgU2dA,310998
|
|
22
22
|
syntaxmatrix/session.py,sha256=v0qgxnVM_LEaNvZQJSa-13Q2eiwc3RDnjd2SahNnHQk,599
|
|
23
23
|
syntaxmatrix/smiv.py,sha256=1lSN3UYpXvYoVNd6VrkY5iZuF_nDxD6xxvLnTn9wcbQ,1405
|
|
24
24
|
syntaxmatrix/smpv.py,sha256=rrCgYqfjBaK2n5qzfQyXK3bHFMvgNcCIqPaXquOLtDM,3600
|
|
25
25
|
syntaxmatrix/themes.py,sha256=qa90vPZTuNNKB37loZhChQfu5QqkaJG4JxgI_4QgCxw,3576
|
|
26
26
|
syntaxmatrix/ui_modes.py,sha256=5lfKK3AKAB-JQCWfi1GRYp4sQqg4Z0fC3RJ8G3VGCMw,152
|
|
27
|
-
syntaxmatrix/utils.py,sha256=
|
|
27
|
+
syntaxmatrix/utils.py,sha256=NrClmFCAoPO7Rgd93SAaWTrMjLNkY1zPf7b8aoK8Ohw,123940
|
|
28
28
|
syntaxmatrix/vector_db.py,sha256=ozvOcMHt52xFAvcp-vAqT69kECPq9BwL8Rzgq3AJaMs,5824
|
|
29
29
|
syntaxmatrix/vectorizer.py,sha256=5w_UQiUIirm_W-Q9TcaEI8LTcTYIuDBdKfz79T1aZ8g,1366
|
|
30
30
|
syntaxmatrix/workspace_db.py,sha256=Xu9OlW8wo3iaH5Y88ZMdLOf-fiZxF1NBb5rAw3KcbfY,4715
|
|
31
31
|
syntaxmatrix/agentic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
32
|
syntaxmatrix/agentic/agent_tools.py,sha256=yQwavONP23ziMxNQf3j2Y4TVo_LxEsiAWecKuBK8WDg,866
|
|
33
|
-
syntaxmatrix/agentic/agents.py,sha256=
|
|
33
|
+
syntaxmatrix/agentic/agents.py,sha256=sK_lgmZTBah-8B86yuRowUGjpLjUsNPmlWKhjSuP75A,29380
|
|
34
34
|
syntaxmatrix/agentic/code_tools_registry.py,sha256=Wp4-KHtp0BUVciqSbmionBsQMVFOnvJPruBJeNiuwkk,1564
|
|
35
35
|
syntaxmatrix/agentic/model_templates.py,sha256=A3ROE3BHkvnU9cxqSGjlCBIw9U15zRaTKgK-WxcZtUI,76033
|
|
36
36
|
syntaxmatrix/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
37
|
syntaxmatrix/settings/default.yaml,sha256=BznvF1D06VMPbT6UX3MQ4zUkXxTXLnAA53aUu8G4O38,569
|
|
38
38
|
syntaxmatrix/settings/logging.py,sha256=U8iTDFv0H1ECdIzH9He2CtOVlK1x5KHCk126Zn5Vi7M,1362
|
|
39
|
-
syntaxmatrix/settings/model_map.py,sha256=
|
|
39
|
+
syntaxmatrix/settings/model_map.py,sha256=wuVfjAs6R35ILdi384fJtOfdOGwV7LNkMjcIUkNFIdc,12001
|
|
40
40
|
syntaxmatrix/settings/prompts.py,sha256=dLNijnw9UHlAg5qxcSaLPhTmR7SdDDyOFcMKhlCA4eQ,21695
|
|
41
41
|
syntaxmatrix/settings/string_navbar.py,sha256=NqgTzo3J9rRI4c278VG6kpoViFfmi2FKmL6sO0R-bus,83
|
|
42
42
|
syntaxmatrix/static/docs.md,sha256=rWlKjNcpS2cs5DElGNYuaA-XXdGZnRGMXx62nACvDwE,11105
|
|
@@ -51,7 +51,8 @@ syntaxmatrix/static/js/sidebar.js,sha256=zHp4skKLY2Dlqx7aLPQ8_cR0iTRT17W0SC2TR38
|
|
|
51
51
|
syntaxmatrix/static/js/widgets.js,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
52
52
|
syntaxmatrix/templates/change_password.html,sha256=YWEcnwJLccLyKGzQxIrc0xuP-p00BtEIwcYq4oFvJ-0,3332
|
|
53
53
|
syntaxmatrix/templates/code_cell.html,sha256=LOr9VjvNQcOGKKJ1ecpcZh3C3qsUxBHueg2iQtpdxl8,638
|
|
54
|
-
syntaxmatrix/templates/dashboard.html,sha256=
|
|
54
|
+
syntaxmatrix/templates/dashboard.html,sha256=_uYNtfUzO71ZMRduQ07vB9CkYa2BYB7Ed3k7Kf_Dsb0,35599
|
|
55
|
+
syntaxmatrix/templates/dataset_resize.html,sha256=MRDbEGTjuMASdMn1yhnwKRyyM70-bL7u05i5EtrNVQg,14909
|
|
55
56
|
syntaxmatrix/templates/docs.html,sha256=KVi5JrZD3gwOduiZhAz7hQrKY9SrQ_bsHOODj0Nj09s,3552
|
|
56
57
|
syntaxmatrix/templates/error.html,sha256=Iu5ykHnhw8jrxVBNn6B95e90W5u9I2hySCiLtaoOJMs,3290
|
|
57
58
|
syntaxmatrix/templates/login.html,sha256=V_bWHozS1xCeHPsvAAfaGG-_2lAE7K8d05IarQN1PS8,2677
|
|
@@ -63,8 +64,8 @@ syntaxmatrix/vectordb/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
|
|
|
63
64
|
syntaxmatrix/vectordb/adapters/milvus_adapter.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
64
65
|
syntaxmatrix/vectordb/adapters/pgvector_adapter.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
65
66
|
syntaxmatrix/vectordb/adapters/sqlite_adapter.py,sha256=L8M2qHfwZRAFVxWeurUVdHaJXz6F5xTUSWh3uy6TSUs,6035
|
|
66
|
-
syntaxmatrix-2.5.
|
|
67
|
-
syntaxmatrix-2.5.
|
|
68
|
-
syntaxmatrix-2.5.
|
|
69
|
-
syntaxmatrix-2.5.
|
|
70
|
-
syntaxmatrix-2.5.
|
|
67
|
+
syntaxmatrix-2.5.8.dist-info/licenses/LICENSE.txt,sha256=j1P8naTdy1JMxTC80XYQjbyAQnuOlpDusCUhncrvpy8,1083
|
|
68
|
+
syntaxmatrix-2.5.8.dist-info/METADATA,sha256=0n7w_0wpDvswlDt7bSklNnXuA2CrL3pZns-9ZSRbkVQ,18090
|
|
69
|
+
syntaxmatrix-2.5.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
70
|
+
syntaxmatrix-2.5.8.dist-info/top_level.txt,sha256=HKP_zkl4V_nt7osC15DlacoBZktHrbZYOqf_pPkF3T8,13
|
|
71
|
+
syntaxmatrix-2.5.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|