syntaxmatrix 2.6.4.4__py3-none-any.whl → 3.0.1__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 +206 -26
  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 +656 -63
  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 +9847 -8004
  21. syntaxmatrix/settings/model_map.py +50 -65
  22. syntaxmatrix/settings/prompts.py +1186 -414
  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.1.dist-info/METADATA +219 -0
  34. {syntaxmatrix-2.6.4.4.dist-info → syntaxmatrix-3.0.1.dist-info}/RECORD +38 -33
  35. {syntaxmatrix-2.6.4.4.dist-info → syntaxmatrix-3.0.1.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.1.dist-info}/top_level.txt +0 -0
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