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.
Files changed (41) hide show
  1. syntaxmatrix/__init__.py +6 -4
  2. syntaxmatrix/agentic/agents.py +195 -15
  3. syntaxmatrix/agentic/agents_orchestrer.py +16 -10
  4. syntaxmatrix/client_docs.py +237 -0
  5. syntaxmatrix/commentary.py +96 -25
  6. syntaxmatrix/core.py +142 -56
  7. syntaxmatrix/dataset_preprocessing.py +2 -2
  8. syntaxmatrix/db.py +0 -17
  9. syntaxmatrix/kernel_manager.py +174 -150
  10. syntaxmatrix/page_builder_generation.py +654 -50
  11. syntaxmatrix/page_layout_contract.py +25 -3
  12. syntaxmatrix/page_patch_publish.py +368 -15
  13. syntaxmatrix/plugins/__init__.py +0 -0
  14. syntaxmatrix/premium/__init__.py +10 -2
  15. syntaxmatrix/premium/catalogue/__init__.py +121 -0
  16. syntaxmatrix/premium/gate.py +15 -3
  17. syntaxmatrix/premium/state.py +507 -0
  18. syntaxmatrix/premium/verify.py +222 -0
  19. syntaxmatrix/profiles.py +1 -1
  20. syntaxmatrix/routes.py +9782 -8004
  21. syntaxmatrix/settings/model_map.py +50 -65
  22. syntaxmatrix/settings/prompts.py +1435 -380
  23. syntaxmatrix/settings/string_navbar.py +4 -4
  24. syntaxmatrix/static/icons/bot_icon.png +0 -0
  25. syntaxmatrix/static/icons/bot_icon2.png +0 -0
  26. syntaxmatrix/templates/admin_billing.html +408 -0
  27. syntaxmatrix/templates/admin_branding.html +65 -2
  28. syntaxmatrix/templates/admin_features.html +54 -0
  29. syntaxmatrix/templates/dashboard.html +285 -8
  30. syntaxmatrix/templates/edit_page.html +199 -18
  31. syntaxmatrix/themes.py +17 -17
  32. syntaxmatrix/workspace_db.py +0 -23
  33. syntaxmatrix-3.0.0.dist-info/METADATA +219 -0
  34. {syntaxmatrix-2.6.4.4.dist-info → syntaxmatrix-3.0.0.dist-info}/RECORD +38 -33
  35. {syntaxmatrix-2.6.4.4.dist-info → syntaxmatrix-3.0.0.dist-info}/WHEEL +1 -1
  36. syntaxmatrix/settings/default.yaml +0 -13
  37. syntaxmatrix-2.6.4.4.dist-info/METADATA +0 -539
  38. syntaxmatrix-2.6.4.4.dist-info/licenses/LICENSE.txt +0 -21
  39. /syntaxmatrix/{plugin_manager.py → plugins/plugin_manager.py} +0 -0
  40. /syntaxmatrix/static/icons/{logo3.png → logo2.png} +0 -0
  41. {syntaxmatrix-2.6.4.4.dist-info → syntaxmatrix-3.0.0.dist-info}/top_level.txt +0 -0
@@ -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
- _df_cache = None
50
-
51
- def execute_code_in_kernel(kc, code, timeout=120):
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
- global _df_cache
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
- def _make_wrapper(name):
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
- # cache df for next call
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
- # Auto-import display if needed (original code) :contentReference[oaicite:0]{index=0}
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
- if not text:
258
- return text
259
- # remove tokens like "<IPython.core.display.HTML object>"
260
- text = _re.sub(r"<IPython\.core\.display\.[A-Za-z]+\s+object>", "", text)
261
- # if these were printed as lists, remove leftover brackets/commas
262
- text = _re.sub(r"[\[\],]", " ", text)
263
- # collapse whitespace
264
- text = _re.sub(r"\s+", " ", text).strip()
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 = _smx_strip_display_reprs(_html.unescape(inner))
273
- if inner:
274
- _cleaned_blocks.append(f"<pre>{_html.escape(inner)}</pre>")
275
- # if empty after cleaning, drop it
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
- _cleaned_blocks.append(cleaned)
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