syntaxmatrix 2.6.3__tar.gz → 2.6.4__tar.gz

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 (91) hide show
  1. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/PKG-INFO +1 -1
  2. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/SyntaxMatrix.egg-info/PKG-INFO +1 -1
  3. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/SyntaxMatrix.egg-info/SOURCES.txt +1 -1
  4. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/setup.py +1 -1
  5. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/agentic/agents.py +9 -21
  6. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/core.py +39 -14
  7. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/db.py +16 -0
  8. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/page_builder_generation.py +7 -17
  9. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/page_patch_publish.py +11 -0
  10. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/routes.py +34 -1
  11. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/templates/admin_branding.html +49 -1
  12. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/LICENSE.txt +0 -0
  13. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/README.md +0 -0
  14. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/SyntaxMatrix.egg-info/dependency_links.txt +0 -0
  15. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/SyntaxMatrix.egg-info/requires.txt +0 -0
  16. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/SyntaxMatrix.egg-info/top_level.txt +0 -0
  17. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/pyproject.toml +0 -0
  18. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/setup.cfg +0 -0
  19. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/__init__.py +0 -0
  20. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/agentic/__init__.py +0 -0
  21. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/agentic/agent_tools.py +0 -0
  22. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/agentic/agents_orchestrer.py +0 -0
  23. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/agentic/code_tools_registry.py +0 -0
  24. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/agentic/model_templates.py +0 -0
  25. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/auth.py +0 -0
  26. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/bootstrap.py +0 -0
  27. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/commentary.py +0 -0
  28. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/dataset_preprocessing.py +0 -0
  29. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/display_html.py +0 -0
  30. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/emailer.py +0 -0
  31. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/file_processor.py +0 -0
  32. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/gpt_models_latest.py +0 -0
  33. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/history_store.py +0 -0
  34. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/kernel_manager.py +0 -0
  35. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/llm_store.py +0 -0
  36. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/media/__init__.py +0 -0
  37. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/media/media_pixabay.py +0 -0
  38. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/models.py +0 -0
  39. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/page_builder_defaults.py +0 -0
  40. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/page_layout_contract.py +0 -0
  41. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/plottings.py +0 -0
  42. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/preface.py +0 -0
  43. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/profiles.py +0 -0
  44. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/project_root.py +0 -0
  45. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/selftest_page_templates.py +0 -0
  46. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/session.py +0 -0
  47. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/settings/__init__.py +0 -0
  48. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/settings/client_items.py +0 -0
  49. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/settings/default.yaml +0 -0
  50. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/settings/logging.py +0 -0
  51. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/settings/model_map.py +0 -0
  52. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/settings/prompts.py +0 -0
  53. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/settings/string_navbar.py +0 -0
  54. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/smiv.py +0 -0
  55. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/smpv.py +0 -0
  56. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/static/assets/hero-default.svg +0 -0
  57. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/static/css/style.css +0 -0
  58. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/static/docs.md +0 -0
  59. /syntaxmatrix-2.6.3/syntaxmatrix/static/icons/bot-icon.png → /syntaxmatrix-2.6.4/syntaxmatrix/static/icons/bot_icon.png +0 -0
  60. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/static/icons/favicon.png +0 -0
  61. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/static/icons/logo.png +0 -0
  62. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/static/icons/logo3.png +0 -0
  63. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/static/icons/svg_497526.svg +0 -0
  64. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/static/icons/svg_497528.svg +0 -0
  65. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/static/js/chat.js +0 -0
  66. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/static/js/sidebar.js +0 -0
  67. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/static/js/widgets.js +0 -0
  68. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/templates/admin_features.html +0 -0
  69. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/templates/admin_secretes.html +0 -0
  70. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/templates/change_password.html +0 -0
  71. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/templates/code_cell.html +0 -0
  72. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/templates/dashboard.html +0 -0
  73. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/templates/dataset_resize.html +0 -0
  74. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/templates/docs.html +0 -0
  75. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/templates/edit_page.html +0 -0
  76. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/templates/error.html +0 -0
  77. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/templates/login.html +0 -0
  78. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/templates/register.html +0 -0
  79. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/themes.py +0 -0
  80. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/ui_modes.py +0 -0
  81. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/utils.py +0 -0
  82. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/vector_db.py +0 -0
  83. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/vectordb/__init__.py +0 -0
  84. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/vectordb/adapters/__init__.py +0 -0
  85. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/vectordb/adapters/milvus_adapter.py +0 -0
  86. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/vectordb/adapters/pgvector_adapter.py +0 -0
  87. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/vectordb/adapters/sqlite_adapter.py +0 -0
  88. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/vectordb/base.py +0 -0
  89. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/vectordb/registry.py +0 -0
  90. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/vectorizer.py +0 -0
  91. {syntaxmatrix-2.6.3 → syntaxmatrix-2.6.4}/syntaxmatrix/workspace_db.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syntaxmatrix
3
- Version: 2.6.3
3
+ Version: 2.6.4
4
4
  Summary: SyntaxMUI: A customizable framework for Python AI Assistant Projects.
5
5
  Author: Bob Nti
6
6
  Author-email: bob.nti@syntaxmatrix.net
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syntaxmatrix
3
- Version: 2.6.3
3
+ Version: 2.6.4
4
4
  Summary: SyntaxMUI: A customizable framework for Python AI Assistant Projects.
5
5
  Author: Bob Nti
6
6
  Author-email: bob.nti@syntaxmatrix.net
@@ -64,7 +64,7 @@ syntaxmatrix/settings/string_navbar.py
64
64
  syntaxmatrix/static/docs.md
65
65
  syntaxmatrix/static/assets/hero-default.svg
66
66
  syntaxmatrix/static/css/style.css
67
- syntaxmatrix/static/icons/bot-icon.png
67
+ syntaxmatrix/static/icons/bot_icon.png
68
68
  syntaxmatrix/static/icons/favicon.png
69
69
  syntaxmatrix/static/icons/logo.png
70
70
  syntaxmatrix/static/icons/logo3.png
@@ -8,7 +8,7 @@ with open(os.path.join(this_directory, "README.md"), encoding="utf-8") as f:
8
8
 
9
9
  setup(
10
10
  name="syntaxmatrix",
11
- version="2.6.3",
11
+ version="2.6.4",
12
12
  author="Bob Nti",
13
13
  author_email="bob.nti@syntaxmatrix.net",
14
14
  description="SyntaxMUI: A customizable framework for Python AI Assistant Projects.",
@@ -1333,7 +1333,7 @@ def agentic_generate_page(*,
1333
1333
  #{page_id} p{{ margin:0; color:var(--mut); line-height:1.65; }}
1334
1334
  #{page_id} .hero{{ padding:0; }}
1335
1335
  #{page_id} .card{{ border:1px solid var(--bd); border-radius:var(--r); background:var(--card); padding:14px; }}
1336
- #{page_id} .btnrow{{ display:flex; gap:10px; flex-wrap:wrap; margin-top:18px; }}
1336
+ #{page_id} .btnRow, #{page_id} .btnrow{{ display:flex; gap:10px; flex-wrap:wrap; margin-top:18px; }}
1337
1337
  #{page_id} .btn{{ display:inline-flex; gap:8px; align-items:center; border-radius:999px; padding:10px 14px;
1338
1338
  border:1px solid var(--bd); text-decoration:none; background: rgba(99,102,241,.12); color:inherit; }}
1339
1339
  #{page_id} .btn-primary{{ background: rgba(99,102,241,.22); border-color: rgba(99,102,241,.35); }}
@@ -1451,24 +1451,16 @@ def agentic_generate_page(*,
1451
1451
  sec_dom_id = _strip(s.get("id"))
1452
1452
  sec_id_attr = f' id="{esc(sec_dom_id)}"' if sec_dom_id else ""
1453
1453
 
1454
- # HERO BANNER (no /admin links)
1454
+ # HERO BANNER (NO automatic CTAs - user controls hero buttons via the editor)
1455
1455
  if st == "hero":
1456
- hero_img = ""
1457
- for it in items:
1458
- u = _strip(it.get("imageUrl"))
1459
- if u:
1460
- hero_img = u
1461
- break
1462
-
1463
- primary = meta.get("primaryCta") or {}
1464
- secondary = meta.get("secondaryCta") or {}
1465
- cta_anchor = "#" + (sec_id_by_type.get("cta") or "sec_cta")
1466
- feats_anchor = "#" + (sec_id_by_type.get("features") or "sec_features")
1456
+ hero_img = _strip(s.get("imageUrl"))
1467
1457
 
1468
- primary_label = primary.get("label") or "Request a demo"
1469
- primary_href = primary.get("href") or cta_anchor
1470
- secondary_label = secondary.get("label") or "See capabilities"
1471
- secondary_href = secondary.get("href") or feats_anchor
1458
+ if not hero_img:
1459
+ for it in items:
1460
+ u = _strip(it.get("imageUrl"))
1461
+ if u:
1462
+ hero_img = u
1463
+ break
1472
1464
 
1473
1465
  bg_style = f"style=\"background-image:url('{esc(hero_img)}')\"" if hero_img else ""
1474
1466
 
@@ -1481,10 +1473,6 @@ def agentic_generate_page(*,
1481
1473
  <p class="kicker">{esc(meta.get("pageTitle") or title)}</p>
1482
1474
  <h1>{title}</h1>
1483
1475
  <p class="lead">{text}</p>
1484
- <div class="btnrow">
1485
- {_btn(primary_label, primary_href, primary=True)}
1486
- {_btn(secondary_label, secondary_href)}
1487
- </div>
1488
1476
  </div>
1489
1477
  </div>
1490
1478
  </section>
@@ -58,7 +58,7 @@ class SyntaxMUI:
58
58
  host="127.0.0.1",
59
59
  port="5080",
60
60
  user_icon="👩🏿‍🦲",
61
- bot_icon="<img src='/static/icons/bot-icon.png' width=20' alt='bot'/>",
61
+ bot_icon="<img src='/static/icons/bot_icon.png' width=20' alt='bot'/>",
62
62
  favicon="/static/icons/favicon.png",
63
63
  site_logo="<img src='/static/icons/logo.png' width='45' alt='logo'/>",
64
64
  site_title="SyntaxMatrix",
@@ -72,13 +72,19 @@ class SyntaxMUI:
72
72
 
73
73
  self.get_app_secrete()
74
74
  self.user_icon = user_icon
75
- self.bot_icon = bot_icon
75
+
76
76
  self.site_logo = site_logo
77
77
  self.favicon = favicon
78
- self._default_site_logo = site_logo
79
- self._default_favicon = favicon
78
+ self.bot_icon = bot_icon
80
79
  self.site_title = site_title
81
80
  self.project_name = project_name
81
+
82
+ self._default_site_logo = self.site_logo
83
+ self._default_favicon = self.favicon
84
+ self._default_bot_icon = self.bot_icon
85
+ self._default_site_title = self.site_title
86
+ self._default_project_name = self.project_name
87
+
82
88
  self.ui_mode = ui_mode
83
89
  self.theme_toggle_enabled = False
84
90
  self.user_files_enabled = False
@@ -373,33 +379,52 @@ class SyntaxMUI:
373
379
 
374
380
  def _apply_branding_from_disk(self):
375
381
  """
376
- If a client logo/favicon exists in syntaxmatrixdir/branding/,
382
+ If a client logo/favicon/boticon exists in syntaxmatrixdir/branding/,
377
383
  use it; otherwise keep the framework defaults.
384
+ Also pulls site_title and project_name from app_settings.
378
385
  """
379
386
  branding_dir = os.path.join(_CLIENT_DIR, "branding")
380
387
 
381
- def _pick(basename: str):
382
- for ext in (".png", ".jpg", ".jpeg"):
383
- fn = f"{basename}{ext}"
384
- p = os.path.join(branding_dir, fn)
385
- if os.path.exists(p):
386
- return fn
388
+ def _pick_any(*basenames: str):
389
+ for base in basenames:
390
+ for ext in (".png", ".jpg", ".jpeg"):
391
+ fn = f"{base}{ext}"
392
+ p = os.path.join(branding_dir, fn)
393
+ if os.path.exists(p):
394
+ return fn
387
395
  return None
388
396
 
389
- logo_fn = _pick("logo")
390
- fav_fn = _pick("favicon")
397
+ # Files live in the same folder. We support both boticon.* and bot_icon.* (cleanup + backwards compatible).
398
+ logo_fn = _pick_any("logo")
399
+ fav_fn = _pick_any("favicon")
400
+ bot_fn = _pick_any("boticon", "bot_icon")
391
401
 
402
+ # Logo (HTML snippet like framework default)
392
403
  if logo_fn:
393
- # Use client-served endpoint (added in routes.py below)
394
404
  self.site_logo = f"<img src='/branding/{logo_fn}' width='45' alt='logo'/>"
395
405
  else:
396
406
  self.site_logo = getattr(self, "_default_site_logo", self.site_logo)
397
407
 
408
+ # Favicon (URL string like framework default)
398
409
  if fav_fn:
399
410
  self.favicon = f"/branding/{fav_fn}"
400
411
  else:
401
412
  self.favicon = getattr(self, "_default_favicon", self.favicon)
402
413
 
414
+ # Bot icon (HTML snippet like framework default)
415
+ if bot_fn:
416
+ self.bot_icon = f"<img src='/branding/{bot_fn}' width='20' alt='bot'/>"
417
+ else:
418
+ self.bot_icon = getattr(self, "_default_bot_icon", self.bot_icon)
419
+
420
+ # Site title + project name (DB settings; fall back to defaults)
421
+ try:
422
+ self.site_title = db.get_setting("branding.site_title", getattr(self, "_default_site_title", self.site_title)) or getattr(self, "_default_site_title", self.site_title)
423
+ self.project_name = db.get_setting("branding.project_name", getattr(self, "_default_project_name", self.project_name)) or getattr(self, "_default_project_name", self.project_name)
424
+ except Exception:
425
+ self.site_title = getattr(self, "_default_site_title", self.site_title)
426
+ self.project_name = getattr(self, "_default_project_name", self.project_name)
427
+
403
428
 
404
429
  def text_input(self, key, id, label, placeholder=""):
405
430
  if not placeholder:
@@ -76,6 +76,22 @@ def init_db():
76
76
  )
77
77
  """)
78
78
 
79
+ # Default settings (if they don't exist yet)
80
+ default_settings = [
81
+ ("branding.site_title", "SyntaxMatrix"),
82
+ ("branding.project_name", "smxAI"),
83
+ ("branding.bot_icon", "default_bot_icon.png"),
84
+ ]
85
+
86
+ for key, value in default_settings:
87
+ existing_value = conn.execute(
88
+ "SELECT value FROM app_settings WHERE key = ?", (key,)
89
+ ).fetchone()
90
+ if not existing_value:
91
+ conn.execute(
92
+ "INSERT INTO app_settings (key, value) VALUES (?, ?)", (key, value)
93
+ )
94
+
79
95
  conn.commit()
80
96
  conn.close()
81
97
 
@@ -893,33 +893,23 @@ def compile_layout_to_html(layout: Dict[str, Any], *, page_slug: str) -> str:
893
893
  # ---------------------------
894
894
  # HERO CTA buttons (NO /admin links)
895
895
  # ---------------------------
896
- cta1_label = (s.get("heroCta1Label") or "").strip() or "Explore features"
897
- cta2_label = (s.get("heroCta2Label") or "").strip() or "Talk to us"
896
+ # --- Hero CTAs: render ONLY if user explicitly set hrefs ---
897
+ cta1_label = (s.get("heroCta1Label") or "").strip()
898
+ cta2_label = (s.get("heroCta2Label") or "").strip()
898
899
 
899
- # If the key exists and is blank => hide button
900
- if "heroCta1Href" in s:
901
- cta1_href_raw = str(s.get("heroCta1Href") or "")
902
- else:
903
- cta1_href_raw = "#" + sec_id_by_type.get("features", "sec_features")
904
-
905
- if "heroCta2Href" in s:
906
- cta2_href_raw = str(s.get("heroCta2Href") or "")
907
- else:
908
- cta2_href_raw = "#" + sec_id_by_type.get("cta", "sec_cta")
909
-
910
- cta1_href = safe_href(cta1_href_raw)
911
- cta2_href = safe_href(cta2_href_raw)
900
+ cta1_href = safe_href(str(s.get("heroCta1Href") or "")) if "heroCta1Href" in s else ""
901
+ cta2_href = safe_href(str(s.get("heroCta2Href") or "")) if "heroCta2Href" in s else ""
912
902
 
913
903
  btns = []
914
904
  if cta1_href:
915
905
  btns.append(
916
906
  f'<a class="btn" data-smx="hero-cta" data-cta="1" href="{esc(cta1_href)}">'
917
- f'<span class="icon">{_ICON_SVGS["arrow"]}</span>{esc(cta1_label)}</a>'
907
+ f'<span class="icon">{_ICON_SVGS["arrow"]}</span>{esc(cta1_label or "Button")}</a>'
918
908
  )
919
909
  if cta2_href:
920
910
  btns.append(
921
911
  f'<a class="btn" data-smx="hero-cta" data-cta="2" href="{esc(cta2_href)}">'
922
- f'<span class="icon">{_ICON_SVGS["arrow"]}</span>{esc(cta2_label)}</a>'
912
+ f'<span class="icon">{_ICON_SVGS["arrow"]}</span>{esc(cta2_label or "Button")}</a>'
923
913
  )
924
914
 
925
915
  btn_row_html = f'<div class="btnRow">{"".join(btns)}</div>' if btns else ""
@@ -897,6 +897,17 @@ def _patch_hero(html: str, hero_section: Dict[str, Any]) -> Tuple[str, bool]:
897
897
  if "btnRow" in (parent.get("class") or []) and not parent.find("a"):
898
898
  parent.decompose()
899
899
  changed = True
900
+
901
+ # If layout has no explicit hero CTA hrefs, remove any existing btnRow from the hero
902
+ cta1 = hero_section.get("heroCta1Href") if isinstance(hero_section, dict) else None
903
+ cta2 = hero_section.get("heroCta2Href") if isinstance(hero_section, dict) else None
904
+ cta1_ok = bool(_safe_href(str(cta1 or ""))) if cta1 is not None else False
905
+ cta2_ok = bool(_safe_href(str(cta2 or ""))) if cta2 is not None else False
906
+
907
+ if not (cta1_ok or cta2_ok):
908
+ for row in list(hero_tag.select(".btnRow")):
909
+ row.decompose()
910
+ changed = True
900
911
 
901
912
  return str(soup), changed
902
913
 
@@ -5559,6 +5559,7 @@ def setup_routes(smx):
5559
5559
  allowed_ext = {".png", ".jpg", ".jpeg"}
5560
5560
  max_logo_bytes = 5 * 1024 * 1024 # 5 MB
5561
5561
  max_favicon_bytes = 1 * 1024 * 1024 # 1 MB
5562
+ max_bot_icon_bytes = 1 * 1024 * 1024 # 1 MB
5562
5563
 
5563
5564
  def _find(base: str):
5564
5565
  for ext in (".png", ".jpg", ".jpeg"):
@@ -5614,48 +5615,80 @@ def setup_routes(smx):
5614
5615
  if action == "reset":
5615
5616
  _delete_all("logo")
5616
5617
  _delete_all("favicon")
5618
+ _delete_all("boticon")
5619
+ _delete_all("bot_icon")
5620
+
5621
+ # Reset default values for site title, project name, and bot icon
5622
+ db.set_setting("branding.site_title", "SyntaxMatrix")
5623
+ db.set_setting("branding.project_name", "smxAI")
5624
+
5625
+ # Apply branding reset from disk (for logo and favicon)
5617
5626
  try:
5618
5627
  smx._apply_branding_from_disk()
5619
5628
  except Exception:
5620
5629
  pass
5630
+
5621
5631
  flash("Branding reset to defaults ✓")
5622
5632
  return redirect(url_for("admin_branding"))
5623
5633
 
5624
5634
  ok1, err1 = _save_upload("logo_file", "logo", max_logo_bytes)
5625
5635
  ok2, err2 = _save_upload("favicon_file", "favicon", max_favicon_bytes)
5636
+ ok3, err3 = _save_upload("bot_icon_file", "boticon", max_bot_icon_bytes)
5626
5637
 
5627
5638
  if err1:
5628
5639
  flash(err1, "error")
5629
5640
  if err2:
5630
5641
  flash(err2, "error")
5642
+ if err3:
5643
+ flash(err3, "error")
5631
5644
 
5632
- if ok1 or ok2:
5645
+ if ok1 or ok2 or ok3:
5633
5646
  try:
5634
5647
  smx._apply_branding_from_disk()
5635
5648
  except Exception:
5636
5649
  pass
5637
5650
  flash("Branding updated ✓")
5638
5651
 
5652
+ # Update site title and project name in DB
5653
+ site_title = request.form.get("site_title", "").strip() or "SyntaxMatrix"
5654
+ project_name = request.form.get("project_name", "").strip() or "smxAI"
5655
+ db.set_setting("branding.site_title", site_title)
5656
+ db.set_setting("branding.project_name", project_name)
5657
+
5658
+ # After saving branding info, apply changes to the smx object
5659
+ smx._apply_branding_from_disk()
5660
+
5661
+ flash("Branding updated ✓")
5662
+
5639
5663
  return redirect(url_for("admin_branding"))
5640
5664
 
5641
5665
  # GET: show current status
5642
5666
  logo_fn = _find("logo")
5643
5667
  fav_fn = _find("favicon")
5668
+ bot_icon_fn = _find("boticon") or _find("bot_icon")
5644
5669
 
5645
5670
  cache_bust = int(time.time())
5646
5671
 
5647
5672
  logo_url = f"/branding/{logo_fn}?v={cache_bust}" if logo_fn else None
5648
5673
  favicon_url = f"/branding/{fav_fn}?v={cache_bust}" if fav_fn else None
5674
+ bot_icon_url = f"/branding/{bot_icon_fn}?v={cache_bust}" if bot_icon_fn else None
5649
5675
 
5676
+ site_title = db.get_setting("branding.site_title", "SyntaxMatrix")
5677
+ project_name = db.get_setting("branding.project_name", "smxAI")
5650
5678
  default_logo_html = getattr(smx, "_default_site_logo", smx.site_logo)
5651
5679
  default_favicon_url = getattr(smx, "_default_favicon", smx.favicon)
5680
+ default_bot_icon_html = getattr(smx, "_default_bot_icon", smx.bot_icon)
5652
5681
 
5653
5682
  return render_template(
5654
5683
  "admin_branding.html",
5655
5684
  logo_url=logo_url,
5656
5685
  favicon_url=favicon_url,
5686
+ bot_icon_url=bot_icon_url,
5687
+ site_title=site_title,
5688
+ project_name=project_name,
5657
5689
  default_logo_html=Markup(default_logo_html),
5658
5690
  default_favicon_url=default_favicon_url,
5691
+ default_bot_icon_html=Markup(default_bot_icon_html),
5659
5692
  )
5660
5693
 
5661
5694
 
@@ -41,8 +41,29 @@
41
41
  {% endwith %}
42
42
 
43
43
  <div class="card">
44
- <h2 style="margin:0 0 10px;font-size:.95rem;">Upload logo and favicon</h2>
44
+ <form method="post" enctype="multipart/form-data">
45
+ <input type="hidden" name="action" value="upload">
46
+
47
+ <div class="row">
48
+ <div>
49
+ <label>Site title</label>
50
+ <input type="text" name="site_title" value="{{ site_title }}" placeholder="e.g. SyntaxMatrix">
51
+ <div class="hint">The title displayed in the header and browser tab.</div>
52
+ </div>
53
+
54
+ <div>
55
+ <label>Project name</label>
56
+ <input type="text" name="project_name" value="{{ project_name }}" placeholder="e.g. smxAI">
57
+ <div class="hint">The project name shown in metadata and internal use.</div>
58
+ </div>
59
+ </div>
45
60
 
61
+ <div style="margin-top:12px;display:flex;justify-content:flex-end;gap:10px;">
62
+ <button class="btn" type="submit">Save</button>
63
+ </div>
64
+ </form>
65
+
66
+ <h2 style="margin:0 0 10px;font-size:.95rem;">Upload logo and favicon</h2>
46
67
  <form method="post" enctype="multipart/form-data">
47
68
  <input type="hidden" name="action" value="upload">
48
69
 
@@ -60,6 +81,22 @@
60
81
  </div>
61
82
  </div>
62
83
 
84
+ <div style="margin-top:12px;display:flex;justify-content:flex-end;gap:10px;">
85
+ <button class="btn" type="submit">Save</button>
86
+ </div>
87
+ </form>
88
+
89
+ <form method="post" enctype="multipart/form-data">
90
+ <input type="hidden" name="action" value="upload">
91
+
92
+ <div class="row">
93
+ <div>
94
+ <label>Bot icon (PNG/JPG)</label>
95
+ <input type="file" name="bot_icon_file" accept=".png,.jpg,.jpeg,image/png,image/jpeg">
96
+ <div class="hint">Tip: square icon (64x64 recommended). Up to 1 MB.</div>
97
+ </div>
98
+ </div>
99
+
63
100
  <div style="margin-top:12px;display:flex;justify-content:flex-end;gap:10px;">
64
101
  <button class="btn" type="submit">Save</button>
65
102
  </div>
@@ -87,6 +124,17 @@
87
124
  {% endif %}
88
125
  </div>
89
126
  </div>
127
+
128
+ <div>
129
+ <div class="chip">Current bot icon</div>
130
+ <div style="margin-top:8px;">
131
+ {% if bot_icon_url %}
132
+ <img src="{{ bot_icon_url }}" alt="bot icon" style="width:64px;height:64px;border-radius:6px;">
133
+ {% else %}
134
+ {{ default_bot_icon_html|safe }}
135
+ {% endif %}
136
+ </div>
137
+ </div>
90
138
  </div>
91
139
  </div>
92
140
 
File without changes
File without changes
File without changes