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
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- import os, io, re, json, base64
3
+ import re, json
4
+ import html as _html
5
+ import re as _re
4
6
  from typing import Any, Dict, List, Optional
5
7
 
6
8
  from syntaxmatrix import profiles as _prof
@@ -114,11 +116,49 @@ def sniff_tables_from_html(html: str) -> List[Dict[str, Any]]:
114
116
  return tables
115
117
 
116
118
 
119
+ def sniff_pre_text_from_html(html: str, *, max_lines: int = 18, max_chars: int = 900) -> List[str]:
120
+ """Extract short, useful plain-text snippets from <pre> blocks (metrics, tests, notes)."""
121
+ if not html:
122
+ return []
123
+ pres = _re.findall(r"<pre[^>]*>(.*?)</pre>", html, flags=_re.DOTALL | _re.IGNORECASE)
124
+ out: List[str] = []
125
+ for p in pres:
126
+ t = _strip_tags(p)
127
+ t = _html.unescape(t)
128
+ t = t.replace("\r\n", "\n").replace("\r", "\n")
129
+
130
+ for ln in t.split("\n"):
131
+ ln = (ln or "").strip()
132
+ if not ln:
133
+ continue
134
+ # keep only “result-ish” lines; drop giant dumps if any sneak in
135
+ if len(ln) > 260:
136
+ ln = ln[:260].rstrip() + "…"
137
+ out.append(ln)
138
+
139
+ # de-dup, preserve order
140
+ seen = set()
141
+ cleaned = []
142
+ for ln in out:
143
+ k = ln.lower()
144
+ if k in seen:
145
+ continue
146
+ seen.add(k)
147
+ cleaned.append(ln)
148
+
149
+ # cap total length
150
+ joined = "\n".join(cleaned)
151
+ joined = joined[:max_chars]
152
+ cleaned = joined.split("\n")[:max_lines]
153
+ return [c for c in (x.strip() for x in cleaned) if c]
154
+
155
+
117
156
  def build_display_summary(question: str,
118
157
  mpl_axes: List[Dict[str, Any]],
119
158
  html_blocks: List[str]) -> Dict[str, Any]:
120
159
  html_joined = "\n".join(str(b) for b in html_blocks)
121
160
  tables = sniff_tables_from_html(html_joined)
161
+ pre_snips = sniff_pre_text_from_html(html_joined)
122
162
 
123
163
  axes_clean=[]
124
164
  for ax in mpl_axes:
@@ -132,11 +172,14 @@ def build_display_summary(question: str,
132
172
  return {
133
173
  "question": (question or "").strip(),
134
174
  "axes": axes_clean,
135
- "tables": tables
175
+ "tables": tables,
176
+ "text_snippets": pre_snips,
136
177
  }
137
178
 
138
179
  def _context_strings(context: Dict[str, Any]) -> List[str]:
139
180
  s = [context.get("question","")]
181
+ s += (context.get("text_snippets", []) or [])
182
+
140
183
  for ax in context.get("axes", []) or []:
141
184
  s += [ax.get("title",""), ax.get("x_label",""), ax.get("y_label","")]
142
185
  s += (ax.get("legend", []) or [])
@@ -159,35 +202,63 @@ def phrase_commentary_vision(context: Dict[str, Any], images_b64: List[str]) ->
159
202
  send figures + text; otherwise fall back to a text-only prompt grounded by labels.
160
203
  """
161
204
 
162
- _SYSTEM_VISION = ("""
163
- You are a plots, graphs, and tables data analyst. You analyse and interprete in details and give your responses in plain english what the already-rendered plots and visuals mean as a response to the question. If the relevant information is made available, then, you must first answer the question explicitly and then proceed to explain the plots and tables.
164
- Use the information visible in the attached figures and the provided context strings (texts, tables, plot field names, labels).
165
- You should provide interpretations without prelude or preamble.
166
- """)
205
+ _SYSTEM_VISION = """
206
+ You are an applied data analyst writing an answer to the user's question.
207
+
208
+ Your priority:
209
+ 1) Answer the question directly (clear verdict first).
210
+ 2) Justify the verdict using evidence from the figures and the provided context.
211
+ 3) Keep it readable for a non-technical stakeholder.
212
+
213
+ Rules:
214
+ - Do NOT write a preamble.
215
+ - Do NOT narrate what a chart “looks like”; interpret it in relation to the question.
216
+ - Only use numbers if they are visible in the figures or included in the text snippets/context.
217
+ - Output must be safe HTML only: <b>, <p>, <ul>, <li>, <br>. No <style>, no <script>, no images.
218
+ """.strip()
167
219
 
168
220
  _USER_TMPL_VISION = """
169
- question:
170
- {q}
171
-
172
- Visible context strings (tables, plots: titles, axes, legends, headers):
173
- {ctx}
174
-
175
- Write a comprehensive conclusion (~250-350 words) as follows:
176
- - <b>Headline</b>
177
- 2-3 sentence answering the question from an overview of all the output.
178
- - <b>Evidence</b>
179
- 8-10 bullets referencing the (output-texts/tables/panels/axes/legend groups) seen in the output.
180
- As you reference the visuals, you should interprete them in a way to show how they answer the question.
181
- - <b>Limitations</b>
182
- 1 bullet; avoid quoting numbers unless present in context.
183
- - <b>Recommendations</b>
184
- 1 bullet.
185
- """
221
+ <b>Question</b>
222
+ <p>{q}</p>
223
+
224
+ <b>Available evidence</b>
225
+ <p><b>Text snippets:</b><br>{snips}</p>
226
+ <p><b>Plot/table context strings (titles, axes, legends, headers):</b><br>{ctx}</p>
227
+
228
+ Write the response in this exact structure:
229
+
230
+ <b>Answer</b>
231
+ <p>
232
+ Give a direct answer to the question in 2-4 sentences.
233
+ If the correct output is a decision (e.g., association vs none, higher vs lower, best model, significant vs not),
234
+ state it explicitly.
235
+ </p>
236
+
237
+ <b>Key evidence</b>
238
+ <ul>
239
+ <li>5–8 bullets. Each bullet must link evidence → conclusion.</li>
240
+ <li>Reference plots/tables by their titles/axes/headers when possible.</li>
241
+ <li>Use numbers only if present in snippets or clearly visible.</li>
242
+ </ul>
243
+
244
+ <b>What this means</b>
245
+ <ul>
246
+ <li>2-4 bullets translating the finding into a practical takeaway.</li>
247
+ </ul>
248
+
249
+ <b>Limitations</b>
250
+ <ul><li>1-2 bullets (short).</li></ul>
251
+
252
+ <b>Next steps</b>
253
+ <ul><li>2 bullets (actionable).</li></ul>
254
+ """.strip()
186
255
 
187
256
  visible = _context_strings(context)
257
+ snips = "\n".join(context.get("text_snippets", []) or [])
188
258
  user = _USER_TMPL_VISION.format(
189
259
  q=context.get("question",""),
190
- ctx=json.dumps(visible, ensure_ascii=False, indent=2)
260
+ snips=_html.escape(snips).replace("\n", "<br>"),
261
+ ctx=_html.escape(json.dumps(visible, ensure_ascii=False, indent=2)).replace("\n", "<br>")
191
262
  )
192
263
 
193
264
  commentary_profile = _prof.get_profile("imagetexter") or _prof.get_profile("admin")
syntaxmatrix/core.py CHANGED
@@ -16,7 +16,7 @@ from .file_processor import process_admin_pdf_files
16
16
  from google.genai import types
17
17
  from .vector_db import query_embeddings
18
18
  from .vectorizer import embed_text
19
- from typing import List, Generator
19
+ from typing import List, Generator, Optional
20
20
  from .auth import init_auth_db
21
21
  from . import profiles as _prof
22
22
  from syntaxmatrix.smiv import SMIV
@@ -27,17 +27,16 @@ from html import unescape
27
27
  from .plottings import render_plotly, pyplot, describe_plotly, describe_matplotlib
28
28
  from threading import RLock
29
29
  from syntaxmatrix.settings.model_map import GPT_MODELS_LATEST
30
+ from syntaxmatrix.settings.client_items import read_client_file, getenv_api_key
31
+ from syntaxmatrix.plugins.plugin_manager import PluginManager
32
+ from .premium import FeatureGate, ensure_premium_state
33
+ from pathlib import Path
30
34
  from syntaxmatrix.settings.prompts import(
31
35
  SMXAI_CHAT_IDENTITY,
32
36
  SMXAI_CHAT_INSTRUCTIONS,
33
37
  SMXAI_WEBSITE_DESCRIPTION,
34
38
  )
35
- from syntaxmatrix.settings.client_items import read_client_file, getenv_api_key
36
-
37
- from .premium import FeatureGate
38
- from .plugin_manager import PluginManager
39
-
40
-
39
+
41
40
  # ──────── framework‐local storage paths ────────
42
41
  # this ensures the key & data always live under the package dir,
43
42
  _CLIENT_DIR = detect_project_root()
@@ -59,7 +58,7 @@ EDA_OUTPUT = {} # global buffer for EDA output by session
59
58
 
60
59
  class SyntaxMUI:
61
60
  def __init__(self,
62
- host="127.0.0.1",
61
+ host="127.0.0.1",
63
62
  port="5080",
64
63
  user_icon="👩🏿‍🦲",
65
64
  bot_icon="<img src='/static/icons/bot_icon.png' width=20' alt='bot'/>",
@@ -67,10 +66,12 @@ class SyntaxMUI:
67
66
  site_logo="<img src='/static/icons/logo.png' width='45' alt='logo'/>",
68
67
  site_title="SyntaxMatrix",
69
68
  project_name="smxAI",
70
- theme_name="light",
69
+ theme_name="chark",
71
70
  ui_mode = "default"
72
71
  ):
73
- self.app = Flask(__name__)
72
+ self.app = Flask(__name__)
73
+ self._client_dir = Path(_CLIENT_DIR).resolve()
74
+ self._client_root = self._client_dir.parent
74
75
  self.host = host
75
76
  self.port = port
76
77
 
@@ -82,7 +83,6 @@ class SyntaxMUI:
82
83
  self.bot_icon = bot_icon
83
84
  self.site_title = site_title
84
85
  self.project_name = project_name
85
-
86
86
  self._default_site_logo = self.site_logo
87
87
  self._default_favicon = self.favicon
88
88
  self._default_bot_icon = self.bot_icon
@@ -93,25 +93,45 @@ class SyntaxMUI:
93
93
  self.theme_toggle_enabled = False
94
94
  self.user_files_enabled = False
95
95
  self.registration_enabled = False
96
+ self.site_documentation_enabled = False
97
+ self.ml_lab_enabled = False
98
+
96
99
  self.smxai_identity = SMXAI_CHAT_IDENTITY
97
100
  self.smxai_instructions = SMXAI_CHAT_INSTRUCTIONS
98
101
  self.website_description = SMXAI_WEBSITE_DESCRIPTION
102
+ # Preserve framework defaults so client branding can safely override and reset.
103
+ self._default_smxai_identity = self.smxai_identity
104
+ self._default_smxai_instructions = self.smxai_instructions
105
+ self._default_website_description = self.website_description
106
+
99
107
  self._eda_output = {} # {chat_id: html}
100
108
  self._eda_lock = RLock()
101
109
 
102
110
  db.init_db()
111
+ self.db = db
103
112
  self.page = ""
104
113
  self.pages = db.get_pages()
114
+
105
115
  init_auth_db()
116
+ try:
117
+ ensure_premium_state(db=db, client_dir=str(_CLIENT_DIR), trial_days=7)
118
+ except Exception:
119
+ pass
106
120
 
107
121
  self.widgets = OrderedDict()
108
- self.theme = DEFAULT_THEMES.get(theme_name, DEFAULT_THEMES["light"])
109
- self.system_output_buffer = "" # Ephemeral buffer initialized
122
+ self.theme = DEFAULT_THEMES.get(theme_name, DEFAULT_THEMES["chark"])
123
+ self.system_output_buffer = "" # Ephemeral buffer initialised
110
124
  self.app_token = str(uuid.uuid4()) # NEW: Unique token for each app launch.
111
125
  self.admin_pdf_chunks = {} # In-memory store for admin PDF chunks
112
- self.user_file_chunks = {} # In-memory store of user‑uploaded chunks, scoped per chat session
113
-
126
+ self.user_file_chunks = {} # In-memory store of user‑uploaded chunks, scoped per chat
114
127
  self._last_llm_usage = None
128
+
129
+ # Apply persisted feature flags + theme (fail-soft)
130
+ try:
131
+ self._apply_feature_flags_from_db()
132
+ except Exception:
133
+ pass
134
+
115
135
  routes.setup_routes(self)
116
136
 
117
137
  # Apply client branding overrides if present on disk
@@ -182,7 +202,7 @@ class SyntaxMUI:
182
202
  if not hasattr(self, "_recent_visual_summaries"):
183
203
  self._recent_visual_summaries = []
184
204
  # keep last 6
185
- self._recent_visual_summaries = (self._recent_visual_summaries + [summary])[-6:]
205
+ self._recent_visual_summaries = (self._recent_visual_summaries + [summary]) # [-6:]
186
206
 
187
207
  def set_plottings(self, fig_or_html, note=None):
188
208
  # prefer current chat id; fall back to per-browser sid; finally "default"
@@ -340,18 +360,29 @@ class SyntaxMUI:
340
360
  DEFAULT_THEMES[theme_name] = theme
341
361
  self.theme = DEFAULT_THEMES[theme_name]
342
362
  else:
343
- self.theme = DEFAULT_THEMES["light"]
344
- self.error("Theme must be 'light', 'dark', or a custom dict.")
363
+ self.theme = DEFAULT_THEMES["chark"]
364
+ self.error("Theme must be 'chark', 'light', 'dark', or a custom dict.")
345
365
 
346
-
347
- def enable_theme_toggle(self):
348
- self.theme_toggle_enabled = True
349
-
350
- def enable_user_files(self):
351
- self.user_files_enabled = True
366
+ def enable_theme_toggle(self, bul: bool = True):
367
+ self.theme_toggle_enabled = bool(bul)
368
+ return self.theme_toggle_enabled
369
+
370
+ def enable_user_files(self, bul: bool = True):
371
+ self.user_files_enabled = bool(bul)
372
+ return self.user_files_enabled
373
+
374
+ def enable_registration(self, bul: bool = True):
375
+ self.registration_enabled = bool(bul)
376
+ return self.registration_enabled
377
+
378
+ def enable_site_documentation(self, bul: bool = True):
379
+ self.site_documentation_enabled = bool(bul)
380
+ return self.site_documentation_enabled
381
+
382
+ def enable_ml_lab(self, bul: bool = True):
383
+ self.ml_lab_enabled = bool(bul)
384
+ return self.ml_lab_enabled
352
385
 
353
- def enable_registration(self):
354
- self.registration_enabled = True
355
386
 
356
387
  def _apply_feature_flags_from_db(self):
357
388
  """
@@ -364,8 +395,25 @@ class SyntaxMUI:
364
395
  stream_v = db.get_setting("feature.stream_mode", "1")
365
396
  user_files_v = db.get_setting("feature.user_files", "1")
366
397
 
398
+ # NEW defaults are all False (0)
399
+ docs_v = db.get_setting("feature.site_documentation", "0")
400
+ ml_v = db.get_setting("feature.ml_lab", "0")
401
+ reg_v = db.get_setting("feature.registration", "0")
402
+ theme_toggle_v = db.get_setting("feature.theme_toggle", "0")
403
+
404
+ # Theme is a choice (default light)
405
+ theme_name = db.get_setting("branding.theme_name", "light")
406
+
367
407
  self.is_streaming = _truthy(stream_v)
368
408
  self.user_files_enabled = _truthy(user_files_v)
409
+
410
+ self.site_documentation_enabled = _truthy(docs_v)
411
+ self.ml_lab_enabled = _truthy(ml_v)
412
+ self.registration_enabled = _truthy(reg_v)
413
+ self.theme_toggle_enabled = _truthy(theme_toggle_v)
414
+
415
+ # Apply the chosen theme to the instance
416
+ self.set_theme(str(theme_name or "light").strip().lower() or "light")
369
417
  except Exception:
370
418
  # Keep defaults if DB isn't ready for any reason
371
419
  pass
@@ -399,6 +447,31 @@ class SyntaxMUI:
399
447
  use it; otherwise keep the framework defaults.
400
448
  Also pulls site_title and project_name from app_settings.
401
449
  """
450
+
451
+ # Premium gating: ignore custom branding unless entitled.
452
+ try:
453
+ if hasattr(self, "feature_gate") and self.feature_gate and (not self.feature_gate.enabled("branding_controls")):
454
+ self.site_logo = getattr(self, "_default_site_logo", self.site_logo)
455
+ self.favicon = getattr(self, "_default_favicon", self.favicon)
456
+ self.bot_icon = getattr(self, "_default_bot_icon", self.bot_icon)
457
+ try:
458
+ self.set_smxai_identity("")
459
+ self.set_smxai_instructions("")
460
+ self.set_website_description(getattr(self, "_default_website_description", self.website_description))
461
+ except Exception:
462
+ pass
463
+ self.site_title = getattr(self, "_default_site_title", self.site_title)
464
+ self.project_name = getattr(self, "_default_project_name", self.project_name)
465
+ return
466
+ except Exception:
467
+ # Fail closed: if we cannot resolve entitlements, do not apply custom branding.
468
+ self.site_logo = getattr(self, "_default_site_logo", self.site_logo)
469
+ self.favicon = getattr(self, "_default_favicon", self.favicon)
470
+ self.bot_icon = getattr(self, "_default_bot_icon", self.bot_icon)
471
+ self.site_title = getattr(self, "_default_site_title", self.site_title)
472
+ self.project_name = getattr(self, "_default_project_name", self.project_name)
473
+ return
474
+
402
475
  branding_dir = os.path.join(_CLIENT_DIR, "branding")
403
476
 
404
477
  def _pick_any(*basenames: str):
@@ -432,6 +505,18 @@ class SyntaxMUI:
432
505
  self.bot_icon = f"<img src='/branding/{bot_fn}' width='20' alt='bot'/>"
433
506
  else:
434
507
  self.bot_icon = getattr(self, "_default_bot_icon", self.bot_icon)
508
+ # AI branding (stored in DB; blank value means "use framework default")
509
+ ident = (self.db.get_setting("branding.smxai_identity", "") or "").strip()
510
+ instr = (self.db.get_setting("branding.smxai_instructions", "") or "").strip()
511
+ wdesc = (self.db.get_setting("branding.website_description", "") or "").strip()
512
+
513
+ # Use the setters so behaviour is consistent across the framework.
514
+ self.set_smxai_identity(ident)
515
+ self.set_smxai_instructions(instr)
516
+ if wdesc:
517
+ self.set_website_description(wdesc)
518
+ else:
519
+ self.set_website_description(getattr(self, "_default_website_description", self.website_description))
435
520
 
436
521
  # Site title + project name (DB settings; fall back to defaults)
437
522
  try:
@@ -619,17 +704,20 @@ class SyntaxMUI:
619
704
  # *********** LLM CLIENT HELPERS **********************
620
705
  # ──────────────────────────────────────────────────────────────
621
706
  def set_smxai_identity(self, profile):
622
- self.set_smxai_identity = profile
623
-
707
+ """Set the system identity/profile used by the chat model."""
708
+ profile = (profile or "").strip()
709
+ self.smxai_identity = profile if profile else getattr(self, "_default_smxai_identity", self.smxai_identity)
624
710
 
625
711
  def set_smxai_instructions(self, instructions):
626
- self.set_smxai_instructions = instructions
712
+ """Set additional system instructions used by the chat model."""
713
+ instructions = (instructions or "").strip()
714
+ self.smxai_instructions = instructions if instructions else getattr(self, "_default_smxai_instructions", self.smxai_instructions)
627
715
 
628
716
 
629
717
  def set_website_description(self, desc):
630
718
  self.website_description = desc
631
719
 
632
-
720
+
633
721
  def embed_query(self, q):
634
722
  return embed_text(q)
635
723
 
@@ -705,7 +793,7 @@ class SyntaxMUI:
705
793
  self.classifier_profile['client'] = _prof.get_client(classifier_profile)
706
794
 
707
795
  _client = self.classifier_profile['client']
708
- _provider = self.classifier_profile['provider']
796
+ _provider = self.classifier_profile['provider'].lower()
709
797
  _model = self.classifier_profile['model']
710
798
 
711
799
  # New instruction format with hybrid option
@@ -818,7 +906,7 @@ class SyntaxMUI:
818
906
  """
819
907
 
820
908
  _client = self.summarizer_profile['client']
821
- _provider = self.summarizer_profile['provider']
909
+ _provider = self.summarizer_profile['provider'].lower()
822
910
  _model = self.summarizer_profile['model']
823
911
 
824
912
  def google_generated_title():
@@ -829,7 +917,7 @@ class SyntaxMUI:
829
917
  )
830
918
  return response.text.strip()
831
919
  except Exception as e:
832
- return f"Google Summary agent error!"
920
+ return f"Google Summary agent error: {e}"
833
921
 
834
922
  def gpt_models_latest_generated_title():
835
923
  try:
@@ -905,16 +993,15 @@ class SyntaxMUI:
905
993
  if not chat_profile:
906
994
  yield """
907
995
  <p style='color:red;'>
908
- Error!<br>
909
- Chat profile is not configured. Add a chat profile inside the admin panel.
910
- To do that, you must login first or contact your administrator.
996
+ Error, Chat profile is not configured!
997
+ Login to the admin panel and add the LLM profile for chatting or contact your administrator.
911
998
  </p>
912
999
  """
913
1000
  return None
914
1001
  self.chat_profile = chat_profile
915
1002
  self.chat_profile['client'] = _prof.get_client(chat_profile)
916
1003
 
917
- _provider = self.chat_profile['provider']
1004
+ _provider = self.chat_profile['provider'].lower()
918
1005
  _client = self.chat_profile['client']
919
1006
  _model = self.chat_profile['model']
920
1007
 
@@ -927,19 +1014,14 @@ class SyntaxMUI:
927
1014
  """
928
1015
 
929
1016
  try:
930
- if _provider == "google": # Google, non openai skd series
931
-
932
- for chunk in _client.models.generate_content_stream(
1017
+ if _provider == "google":
1018
+ chuncks = _client.models.generate_content_stream(
933
1019
  model=_model,
934
1020
  contents=_contents,
935
- config=types.GenerateContentConfig(
936
- system_instruction=self.smxai_identity,
937
- temperature=0.3,
938
- max_output_tokens=1024,
939
- ),
940
- ):
941
-
942
- yield chunk.text
1021
+ )
1022
+ for chunk in chuncks:
1023
+ if chunk:
1024
+ yield chunk.text
943
1025
 
944
1026
  elif _provider == "openai" and _model in self.get_gpt_models_latest(): # GPt 5 series
945
1027
  input_prompt = (
@@ -986,7 +1068,7 @@ class SyntaxMUI:
986
1068
  if token:
987
1069
  yield token
988
1070
  except Exception as e:
989
- yield f"Error during streaming: {type(e).__name__}: {e}"
1071
+ yield f"Error during streaming: CLIENT {_client}" # {type(e).__name__}: {e}"
990
1072
 
991
1073
 
992
1074
  def process_query(self, query, context, history, stream=False):
@@ -996,16 +1078,15 @@ class SyntaxMUI:
996
1078
  if not chat_profile:
997
1079
  yield """
998
1080
  <p style='color:red;'>
999
- Error!<br>
1000
- Chat profile is not configured. Add a chat profile inside the admin panel.
1001
- To do that, you must login first or contact your administrator.
1081
+ Error, Chat profile is not configured!
1082
+ Login to the admin panel and add the LLM profile for chatting or contact your administrator.
1002
1083
  </p>
1003
1084
  """
1004
- return None
1085
+ return None
1005
1086
 
1006
1087
  self.chat_profile = chat_profile
1007
1088
  self.chat_profile['client'] = _prof.get_client(chat_profile)
1008
- _provider = self.chat_profile['provider']
1089
+ _provider = self.chat_profile['provider'].lower()
1009
1090
  _client = self.chat_profile['client']
1010
1091
  _model = self.chat_profile['model']
1011
1092
  _contents = f"""
@@ -1557,7 +1638,12 @@ class SyntaxMUI:
1557
1638
  current_profile['client'] = _prof.get_client(current_profile)
1558
1639
  return current_profile
1559
1640
 
1560
- def run(self):
1641
+ def run(self, browser: Optional[str] = None) -> None:
1561
1642
  url = f"http://{self.host}:{self.port}/"
1562
- webbrowser.open(url)
1643
+
1644
+ if browser:
1645
+ webbrowser.get(browser).open(url)
1646
+ else:
1647
+ webbrowser.open(url)
1648
+
1563
1649
  self.app.run(host=self.host, port=self.port, debug=False)
@@ -181,9 +181,9 @@ def _impute_for_analysis(df: pd.DataFrame) -> Tuple[pd.DataFrame, Dict[str, str]
181
181
  def ensure_cleaned_df(DATA_FOLDER: str, cleaned_folder: str, df: pd.DataFrame) -> pd.DataFrame:
182
182
  """
183
183
  Build (or reuse) an analysis-ready cleaned dataset and persist to:
184
- f"{DATA_FOLDER}/{selected_dataset}/cleaned_df.csv"
184
+ f"{DATA_FOLDER}/{selected_dataset_processed}/cleaned_df.csv"
185
185
  Also writes a missingness audit:
186
- f"{DATA_FOLDER}/{selected_dataset}/missingness.csv"
186
+ f"{DATA_FOLDER}/{selected_dataset_processed}/missingness.csv"
187
187
  Returns the cleaned frame. Does NOT mutate the provided df.
188
188
  """
189
189
  target_dir = os.path.join(DATA_FOLDER, cleaned_folder)
syntaxmatrix/db.py CHANGED
@@ -628,23 +628,6 @@ def get_setting(key: str, default: str | None = None) -> str | None:
628
628
  conn.close()
629
629
 
630
630
 
631
- # ============================================================================
632
- # Optional DB backend override (Premium hook)
633
- #
634
- # Default behaviour (no env vars): SQLite stays in use exactly as before.
635
- #
636
- # To enable a premium backend (e.g. Postgres), set:
637
- # SMX_DB_PROVIDER=postgres
638
- #
639
- # Optional:
640
- # SMX_DB_BACKEND_MODULE=syntaxmatrix_premium.db_backends.postgres_backend
641
- #
642
- # The backend module should either expose:
643
- # - install(target_globals: dict) -> None (preferred)
644
- # OR export a compatible surface (functions/constants) that will be copied
645
- # into this module namespace.
646
- # ============================================================================
647
-
648
631
  def _smx_apply_optional_backend_override() -> None:
649
632
  import os
650
633
  import importlib