syntaxmatrix 2.6.4.4__py3-none-any.whl → 3.0.0__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 +6 -4
- syntaxmatrix/agentic/agents.py +195 -15
- syntaxmatrix/agentic/agents_orchestrer.py +16 -10
- syntaxmatrix/client_docs.py +237 -0
- syntaxmatrix/commentary.py +96 -25
- syntaxmatrix/core.py +142 -56
- syntaxmatrix/dataset_preprocessing.py +2 -2
- syntaxmatrix/db.py +0 -17
- syntaxmatrix/kernel_manager.py +174 -150
- syntaxmatrix/page_builder_generation.py +654 -50
- syntaxmatrix/page_layout_contract.py +25 -3
- syntaxmatrix/page_patch_publish.py +368 -15
- syntaxmatrix/plugins/__init__.py +0 -0
- syntaxmatrix/premium/__init__.py +10 -2
- syntaxmatrix/premium/catalogue/__init__.py +121 -0
- syntaxmatrix/premium/gate.py +15 -3
- syntaxmatrix/premium/state.py +507 -0
- syntaxmatrix/premium/verify.py +222 -0
- syntaxmatrix/profiles.py +1 -1
- syntaxmatrix/routes.py +9782 -8004
- syntaxmatrix/settings/model_map.py +50 -65
- syntaxmatrix/settings/prompts.py +1435 -380
- syntaxmatrix/settings/string_navbar.py +4 -4
- syntaxmatrix/static/icons/bot_icon.png +0 -0
- syntaxmatrix/static/icons/bot_icon2.png +0 -0
- syntaxmatrix/templates/admin_billing.html +408 -0
- syntaxmatrix/templates/admin_branding.html +65 -2
- syntaxmatrix/templates/admin_features.html +54 -0
- syntaxmatrix/templates/dashboard.html +285 -8
- syntaxmatrix/templates/edit_page.html +199 -18
- syntaxmatrix/themes.py +17 -17
- syntaxmatrix/workspace_db.py +0 -23
- syntaxmatrix-3.0.0.dist-info/METADATA +219 -0
- {syntaxmatrix-2.6.4.4.dist-info → syntaxmatrix-3.0.0.dist-info}/RECORD +38 -33
- {syntaxmatrix-2.6.4.4.dist-info → syntaxmatrix-3.0.0.dist-info}/WHEEL +1 -1
- syntaxmatrix/settings/default.yaml +0 -13
- syntaxmatrix-2.6.4.4.dist-info/METADATA +0 -539
- syntaxmatrix-2.6.4.4.dist-info/licenses/LICENSE.txt +0 -21
- /syntaxmatrix/{plugin_manager.py → plugins/plugin_manager.py} +0 -0
- /syntaxmatrix/static/icons/{logo3.png → logo2.png} +0 -0
- {syntaxmatrix-2.6.4.4.dist-info → syntaxmatrix-3.0.0.dist-info}/top_level.txt +0 -0
syntaxmatrix/kernel_manager.py
CHANGED
|
@@ -44,135 +44,46 @@ class SyntaxMatrixKernelManager:
|
|
|
44
44
|
def cleanup_all(cls):
|
|
45
45
|
for sid in list(cls._kernels):
|
|
46
46
|
cls.shutdown_kernel(sid)
|
|
47
|
+
|
|
48
|
+
def _prefer_display_over_to_html(code: str) -> str:
|
|
49
|
+
"""
|
|
50
|
+
Convert common 'print(df.to_html())' patterns into IPython display(df).
|
|
51
|
+
Best-effort only; avoids breaking code.
|
|
52
|
+
"""
|
|
53
|
+
if not code or "to_html" not in code:
|
|
54
|
+
return code
|
|
55
|
+
|
|
56
|
+
# Replace print(X.to_html()) -> display(X)
|
|
57
|
+
code2 = _re.sub(
|
|
58
|
+
r"print\(\s*([A-Za-z_][A-Za-z0-9_\.]*)\s*\.to_html\(\s*\)\s*\)",
|
|
59
|
+
r"display(\1)",
|
|
60
|
+
code,
|
|
61
|
+
flags=_re.IGNORECASE,
|
|
62
|
+
)
|
|
47
63
|
|
|
64
|
+
# Replace print(pd.DataFrame(...).to_html()) -> display(pd.DataFrame(...))
|
|
65
|
+
code2 = _re.sub(
|
|
66
|
+
r"print\(\s*(pd\.DataFrame\([^\)]*\))\s*\.to_html\(\s*\)\s*\)",
|
|
67
|
+
r"display(\1)",
|
|
68
|
+
code2,
|
|
69
|
+
flags=_re.IGNORECASE,
|
|
70
|
+
)
|
|
48
71
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
_local_stdout = ""
|
|
54
|
-
_local_stderr = ""
|
|
72
|
+
# If we introduced display(), ensure import exists
|
|
73
|
+
if "display(" in code2 and "from IPython.display import display" not in code2:
|
|
74
|
+
code2 = "from IPython.display import display\n" + code2
|
|
55
75
|
|
|
56
|
-
|
|
57
|
-
exec_namespace = {}
|
|
76
|
+
return code2
|
|
58
77
|
|
|
59
|
-
if not hasattr(_pd.core.generic.NDFrame, "_patch_safe_reduce"):
|
|
60
|
-
_AGG_FUNCS = (
|
|
61
|
-
"sum", "mean", "median", "std", "var",
|
|
62
|
-
"min", "max", "prod",
|
|
63
|
-
)
|
|
64
78
|
|
|
65
|
-
|
|
66
|
-
orig = getattr(_pd.core.generic.NDFrame, name)
|
|
67
|
-
|
|
68
|
-
@wraps(orig)
|
|
69
|
-
def _wrapper(self, *args, **kwargs):
|
|
70
|
-
try:
|
|
71
|
-
return orig(self, *args, **kwargs)
|
|
72
|
-
|
|
73
|
-
except TypeError as exc:
|
|
74
|
-
msg = str(exc)
|
|
75
|
-
if (
|
|
76
|
-
"can only concatenate str" not in msg
|
|
77
|
-
and "could not convert" not in msg
|
|
78
|
-
):
|
|
79
|
-
raise # - not the error we’re guarding against
|
|
80
|
-
|
|
81
|
-
# Caller already supplied numeric_only (positional or kw) → re-raise
|
|
82
|
-
if len(args) >= 3 or "numeric_only" in kwargs:
|
|
83
|
-
raise
|
|
84
|
-
|
|
85
|
-
kwargs = dict(kwargs)
|
|
86
|
-
kwargs["numeric_only"] = True
|
|
87
|
-
return orig(self, *args, **kwargs)
|
|
88
|
-
|
|
89
|
-
return _wrapper
|
|
90
|
-
|
|
91
|
-
for _fn in _AGG_FUNCS:
|
|
92
|
-
setattr(
|
|
93
|
-
_pd.core.generic.NDFrame,
|
|
94
|
-
f"_orig_{_fn}",
|
|
95
|
-
getattr(_pd.core.generic.NDFrame, _fn),
|
|
96
|
-
)
|
|
97
|
-
setattr(
|
|
98
|
-
_pd.core.generic.NDFrame,
|
|
99
|
-
_fn,
|
|
100
|
-
_make_wrapper(_fn),
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
# marker so we don’t patch twice in the same kernel
|
|
104
|
-
_pd.core.generic.NDFrame._patch_safe_reduce = True
|
|
105
|
-
|
|
106
|
-
if "_pandas_sum_patched" not in exec_namespace:
|
|
107
|
-
|
|
108
|
-
if not hasattr(_pd.core.generic.NDFrame, "_orig_sum"):
|
|
109
|
-
_pd.core.generic.NDFrame._orig_sum = _pd.core.generic.NDFrame.sum
|
|
110
|
-
_pd.core.generic.NDFrame._orig_mean = _pd.core.generic.NDFrame.mean # ← NEW
|
|
111
|
-
|
|
112
|
-
def _safe_agg(orig_func):
|
|
113
|
-
def wrapper(self, *args, **kwargs):
|
|
114
|
-
try:
|
|
115
|
-
return orig_func(self, *args, **kwargs)
|
|
116
|
-
|
|
117
|
-
except TypeError as exc:
|
|
118
|
-
# Only rescue the classic mixed-dtype failure
|
|
119
|
-
if ("can only concatenate" not in str(exc) and
|
|
120
|
-
"could not convert" not in str(exc)):
|
|
121
|
-
raise
|
|
122
|
-
|
|
123
|
-
# Caller already gave numeric_only → we must not override
|
|
124
|
-
if "numeric_only" in kwargs or len(args) >= 3:
|
|
125
|
-
raise
|
|
126
|
-
|
|
127
|
-
kwargs = dict(kwargs)
|
|
128
|
-
kwargs["numeric_only"] = True
|
|
129
|
-
return orig_func(self, *args, **kwargs)
|
|
130
|
-
return wrapper
|
|
131
|
-
|
|
132
|
-
_pd.core.generic.NDFrame.sum = _safe_agg(_pd.core.generic.NDFrame._orig_sum)
|
|
133
|
-
_pd.core.generic.NDFrame.mean = _safe_agg(_pd.core.generic.NDFrame._orig_mean)
|
|
134
|
-
|
|
135
|
-
exec_namespace["_pandas_sum_patched"] = True
|
|
136
|
-
|
|
137
|
-
# inject cached df if we have one
|
|
138
|
-
if _df_cache is not None:
|
|
139
|
-
exec_namespace["df"] = _df_cache
|
|
140
|
-
|
|
141
|
-
try:
|
|
142
|
-
# Prevent any print()/stdout/stderr from hitting your server console
|
|
143
|
-
_buf_out, _buf_err = io.StringIO(), io.StringIO()
|
|
144
|
-
with contextlib.redirect_stdout(_buf_out), contextlib.redirect_stderr(_buf_err):
|
|
145
|
-
exec(code, exec_namespace, exec_namespace)
|
|
146
|
-
|
|
147
|
-
_local_stdout = _buf_out.getvalue()
|
|
148
|
-
_local_stderr = _buf_err.getvalue()
|
|
149
|
-
|
|
150
|
-
# ── show a friendly “missing package” hint ────
|
|
151
|
-
except (ModuleNotFoundError, ImportError) as e:
|
|
152
|
-
missing = getattr(e, "name", None) or str(e).split("'")[1]
|
|
153
|
-
hint = (
|
|
154
|
-
|
|
155
|
-
f"<div style='color:red; font-weight:bold;'>"
|
|
156
|
-
f"Missing package: <code>{missing}</code><br>"
|
|
157
|
-
f"Activate this virtual-env and run:<br>"
|
|
158
|
-
f"<code>pip install {missing}</code><br>"
|
|
159
|
-
f"then re-run your query.</div>"
|
|
160
|
-
)
|
|
161
|
-
return [hint], []
|
|
162
|
-
|
|
163
|
-
except Exception:
|
|
164
|
-
pass
|
|
79
|
+
_df_cache = None
|
|
165
80
|
|
|
166
|
-
|
|
167
|
-
if "df" in exec_namespace:
|
|
168
|
-
_df_cache = exec_namespace["df"]
|
|
81
|
+
def execute_code_in_kernel(kc, code, timeout=120):
|
|
169
82
|
|
|
170
|
-
|
|
83
|
+
code = SyntaxMatrixKernelManager._prefer_display_over_to_html(code)
|
|
171
84
|
if "display(" in code and "from IPython.display import display" not in code:
|
|
172
85
|
code = "from IPython.display import display\n" + code
|
|
173
86
|
|
|
174
|
-
# ------------------------------------------------------------------
|
|
175
|
-
# everything below is the original while-loop message collector
|
|
176
87
|
msg_id = kc.execute(
|
|
177
88
|
code,
|
|
178
89
|
user_expressions={"_last": "(_)",},
|
|
@@ -211,18 +122,6 @@ def execute_code_in_kernel(kc, code, timeout=120):
|
|
|
211
122
|
if txt:
|
|
212
123
|
output_blocks.append(f"<pre>{_html.escape(txt)}</pre>")
|
|
213
124
|
|
|
214
|
-
# elif mtype in ("execute_result", "display_data"):
|
|
215
|
-
# data = content["data"]
|
|
216
|
-
# if "text/html" in data:
|
|
217
|
-
# output_blocks.append(data["text/html"])
|
|
218
|
-
# elif "image/png" in data:
|
|
219
|
-
# output_blocks.append(
|
|
220
|
-
# f"<img src='data:image/png;base64,{data['image/png']}' "
|
|
221
|
-
# f"style='max-width:100%;'/>"
|
|
222
|
-
# )
|
|
223
|
-
# else:
|
|
224
|
-
# output_blocks.append(f"<pre>{data.get('text/plain','')}</pre>")
|
|
225
|
-
|
|
226
125
|
elif mtype in ("execute_result", "display_data"):
|
|
227
126
|
data = content.get("data", {})
|
|
228
127
|
if "text/html" in data:
|
|
@@ -246,38 +145,163 @@ def execute_code_in_kernel(kc, code, timeout=120):
|
|
|
246
145
|
# keep the traceback html-friendly
|
|
247
146
|
traceback_html = "<br>".join(content["traceback"])
|
|
248
147
|
errors.append(f"<pre style='color:red;'>{traceback_html}</pre>")
|
|
249
|
-
# --- surface the locally captured commentary (stdout/stderr) back to the UI ---
|
|
250
|
-
if _local_stdout.strip():
|
|
251
|
-
# Put commentary first so the user sees it above plots/tables
|
|
252
|
-
output_blocks.insert(0, f"<pre>{_html.escape(_local_stdout)}</pre>")
|
|
253
|
-
if _local_stderr.strip():
|
|
254
|
-
errors.insert(0, f"<pre style='color:#b00;'>{_html.escape(_local_stderr)}</pre>")
|
|
255
148
|
|
|
256
149
|
def _smx_strip_display_reprs(text: str) -> str:
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
text =
|
|
263
|
-
|
|
264
|
-
|
|
150
|
+
"""Remove noisy Jupyter display reprs while keeping line breaks readable."""
|
|
151
|
+
if text is None:
|
|
152
|
+
return ""
|
|
153
|
+
# Strip common display wrappers that sometimes leak into stdout
|
|
154
|
+
text = text.replace("<IPython.core.display.HTML object>", "")
|
|
155
|
+
text = text.replace("<IPython.core.display.Markdown object>", "")
|
|
156
|
+
text = text.replace("<IPython.core.display.Image object>", "")
|
|
157
|
+
|
|
158
|
+
# Normalise line endings but KEEP newlines
|
|
159
|
+
text = text.replace("\r\n", "\n").replace("\r", "\n")
|
|
160
|
+
|
|
161
|
+
# Collapse runs of spaces/tabs per line (do not flatten newlines)
|
|
162
|
+
lines = []
|
|
163
|
+
for ln in text.split("\n"):
|
|
164
|
+
ln = _re.sub(r"[\t ]{2,}", " ", ln).rstrip()
|
|
165
|
+
lines.append(ln)
|
|
166
|
+
|
|
167
|
+
text = "\n".join(lines).strip()
|
|
168
|
+
|
|
169
|
+
# Avoid huge blank sections
|
|
170
|
+
text = _re.sub(r"\n{3,}", "\n\n", text)
|
|
265
171
|
return text
|
|
266
172
|
|
|
173
|
+
def _sanitize_table_html(table_html: str) -> str:
|
|
174
|
+
"""Best-effort sanitiser for DataFrame-style HTML tables (blocks scripts/events)."""
|
|
175
|
+
if not table_html:
|
|
176
|
+
return ""
|
|
177
|
+
# Remove scripts and styles outright
|
|
178
|
+
table_html = _re.sub(
|
|
179
|
+
r"<\s*script[^>]*>.*?<\s*/\s*script\s*>",
|
|
180
|
+
"",
|
|
181
|
+
table_html,
|
|
182
|
+
flags=_re.IGNORECASE | _re.DOTALL,
|
|
183
|
+
)
|
|
184
|
+
table_html = _re.sub(
|
|
185
|
+
r"<\s*style[^>]*>.*?<\s*/\s*style\s*>",
|
|
186
|
+
"",
|
|
187
|
+
table_html,
|
|
188
|
+
flags=_re.IGNORECASE | _re.DOTALL,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Drop inline event handlers (onload=, onclick=, etc.)
|
|
192
|
+
table_html = _re.sub(r"\son\w+\s*=\s*\"[^\"]*\"", "", table_html, flags=_re.IGNORECASE)
|
|
193
|
+
table_html = _re.sub(r"\son\w+\s*=\s*\'[^\']*\'", "", table_html, flags=_re.IGNORECASE)
|
|
194
|
+
|
|
195
|
+
# Block javascript: URLs
|
|
196
|
+
table_html = _re.sub(r"javascript\s*:", "", table_html, flags=_re.IGNORECASE)
|
|
197
|
+
return table_html.strip()
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _smx_ensure_table_class(table_html: str) -> str:
|
|
201
|
+
"""Ensure DataFrame tables pick up the dashboard's .smx-table styling."""
|
|
202
|
+
if not table_html:
|
|
203
|
+
return ""
|
|
204
|
+
|
|
205
|
+
def _inject_class(m):
|
|
206
|
+
tag = m.group(0)
|
|
207
|
+
|
|
208
|
+
# If there's already a class="", append smx-table
|
|
209
|
+
if _re.search(r"\bclass\s*=", tag, flags=_re.IGNORECASE):
|
|
210
|
+
tag = _re.sub(
|
|
211
|
+
r'(class\s*=\s*["\'])([^"\']*)(["\'])',
|
|
212
|
+
lambda mm: f"{mm.group(1)}{mm.group(2)} smx-table{mm.group(3)}",
|
|
213
|
+
tag,
|
|
214
|
+
count=1,
|
|
215
|
+
flags=_re.IGNORECASE,
|
|
216
|
+
)
|
|
217
|
+
else:
|
|
218
|
+
# add class attr
|
|
219
|
+
tag = tag[:-1] + ' class="smx-table">'
|
|
220
|
+
|
|
221
|
+
# Remove noisy border attr if present (optional)
|
|
222
|
+
tag = _re.sub(r'\sborder\s*=\s*["\']?\d+["\']?', "", tag, flags=_re.IGNORECASE)
|
|
223
|
+
return tag
|
|
224
|
+
|
|
225
|
+
return _re.sub(r"<table\b[^>]*>", _inject_class, table_html, count=1, flags=_re.IGNORECASE)
|
|
226
|
+
|
|
227
|
+
def _smx_wrap_df_table(table_html: str, max_h: int = 460) -> str:
|
|
228
|
+
"""
|
|
229
|
+
Wrap table in a scroll box and apply the same table class the dashboard already styles.
|
|
230
|
+
"""
|
|
231
|
+
table_html = _smx_ensure_table_class(table_html)
|
|
232
|
+
if not table_html:
|
|
233
|
+
return ""
|
|
234
|
+
return (
|
|
235
|
+
f"<div class='smx-scroll' style='overflow:auto; max-height:{int(max_h)}px;'>"
|
|
236
|
+
f"{table_html}"
|
|
237
|
+
f"</div>"
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
def _split_text_and_tables(text: str):
|
|
241
|
+
"""Yield (kind, chunk) where kind is 'text' or 'table'."""
|
|
242
|
+
if not text:
|
|
243
|
+
return [("text", "")]
|
|
244
|
+
out = []
|
|
245
|
+
last = 0
|
|
246
|
+
for m in _TABLE_RE.finditer(text):
|
|
247
|
+
if m.start() > last:
|
|
248
|
+
out.append(("text", text[last:m.start()]))
|
|
249
|
+
out.append(("table", m.group(1)))
|
|
250
|
+
last = m.end()
|
|
251
|
+
if last < len(text):
|
|
252
|
+
out.append(("text", text[last:]))
|
|
253
|
+
return out
|
|
254
|
+
|
|
255
|
+
_TABLE_RE = _re.compile(r"(<table\b.*?</table>)", flags=_re.IGNORECASE | _re.DOTALL)
|
|
256
|
+
|
|
267
257
|
_cleaned_blocks = []
|
|
268
258
|
for blk in output_blocks:
|
|
269
259
|
# pre-wrapped plaintext
|
|
270
260
|
if blk.startswith("<pre>") and blk.endswith("</pre>"):
|
|
271
261
|
inner = blk[5:-6]
|
|
272
|
-
inner =
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
#
|
|
262
|
+
inner = _html.unescape(inner)
|
|
263
|
+
|
|
264
|
+
# If stdout contains HTML tables (e.g., DataFrame.to_html()), render them as tables
|
|
265
|
+
# rather than showing the raw <table> markup inside a <pre> block.
|
|
266
|
+
low = inner.lower()
|
|
267
|
+
if "<table" in low and "</table>" in low:
|
|
268
|
+
parts = _split_text_and_tables(inner)
|
|
269
|
+
else:
|
|
270
|
+
parts = [("text", inner)]
|
|
271
|
+
|
|
272
|
+
for kind, chunk in parts:
|
|
273
|
+
if not chunk:
|
|
274
|
+
continue
|
|
275
|
+
if kind == "table":
|
|
276
|
+
safe_tbl = _sanitize_table_html(chunk)
|
|
277
|
+
if safe_tbl:
|
|
278
|
+
_cleaned_blocks.append(_smx_wrap_df_table(safe_tbl))
|
|
279
|
+
else:
|
|
280
|
+
txt = _smx_strip_display_reprs(chunk)
|
|
281
|
+
if txt:
|
|
282
|
+
_cleaned_blocks.append(f"<pre>{_html.escape(txt)}</pre>")
|
|
283
|
+
|
|
276
284
|
continue
|
|
277
285
|
|
|
278
286
|
# html/img payloads: just remove stray repr tokens if they slipped in
|
|
279
287
|
cleaned = _re.sub(r"<IPython\.core\.display\.[A-Za-z]+\s+object>", "", blk)
|
|
280
|
-
|
|
288
|
+
low = cleaned.lower()
|
|
289
|
+
if "<table" in low and "</table>" in low:
|
|
290
|
+
parts = _split_text_and_tables(cleaned)
|
|
291
|
+
for kind, chunk in parts:
|
|
292
|
+
if not chunk:
|
|
293
|
+
continue
|
|
294
|
+
if kind == "table":
|
|
295
|
+
safe_tbl = _sanitize_table_html(chunk)
|
|
296
|
+
wrapped = _smx_wrap_df_table(safe_tbl)
|
|
297
|
+
if wrapped:
|
|
298
|
+
_cleaned_blocks.append(wrapped)
|
|
299
|
+
else:
|
|
300
|
+
# keep non-table html as-is
|
|
301
|
+
if chunk.strip():
|
|
302
|
+
_cleaned_blocks.append(chunk)
|
|
303
|
+
else:
|
|
304
|
+
_cleaned_blocks.append(cleaned)
|
|
281
305
|
output_blocks = _cleaned_blocks
|
|
282
306
|
|
|
283
307
|
return output_blocks, errors
|