syntaxmatrix 2.6.0__tar.gz → 2.6.1__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.0 → syntaxmatrix-2.6.1}/PKG-INFO +1 -1
  2. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/SyntaxMatrix.egg-info/PKG-INFO +1 -1
  3. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/SyntaxMatrix.egg-info/SOURCES.txt +1 -1
  4. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/setup.py +1 -1
  5. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/core.py +39 -6
  6. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/routes.py +132 -6
  7. syntaxmatrix-2.6.1/syntaxmatrix/templates/admin_branding.html +104 -0
  8. syntaxmatrix-2.6.0/syntaxmatrix/static/icons/logo2.png +0 -0
  9. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/LICENSE.txt +0 -0
  10. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/README.md +0 -0
  11. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/SyntaxMatrix.egg-info/dependency_links.txt +0 -0
  12. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/SyntaxMatrix.egg-info/requires.txt +0 -0
  13. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/SyntaxMatrix.egg-info/top_level.txt +0 -0
  14. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/pyproject.toml +0 -0
  15. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/setup.cfg +0 -0
  16. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/__init__.py +0 -0
  17. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/agentic/__init__.py +0 -0
  18. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/agentic/agent_tools.py +0 -0
  19. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/agentic/agents.py +0 -0
  20. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/agentic/agents_orchestrer.py +0 -0
  21. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/agentic/code_tools_registry.py +0 -0
  22. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/agentic/model_templates.py +0 -0
  23. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/auth.py +0 -0
  24. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/bootstrap.py +0 -0
  25. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/commentary.py +0 -0
  26. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/dataset_preprocessing.py +0 -0
  27. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/db.py +0 -0
  28. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/display_html.py +0 -0
  29. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/emailer.py +0 -0
  30. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/file_processor.py +0 -0
  31. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/gpt_models_latest.py +0 -0
  32. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/history_store.py +0 -0
  33. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/kernel_manager.py +0 -0
  34. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/llm_store.py +0 -0
  35. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/media/__init__.py +0 -0
  36. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/media/media_pixabay.py +0 -0
  37. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/models.py +0 -0
  38. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/page_builder_defaults.py +0 -0
  39. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/page_builder_generation.py +0 -0
  40. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/page_layout_contract.py +0 -0
  41. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/page_patch_publish.py +0 -0
  42. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/plottings.py +0 -0
  43. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/preface.py +0 -0
  44. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/profiles.py +0 -0
  45. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/project_root.py +0 -0
  46. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/selftest_page_templates.py +0 -0
  47. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/session.py +0 -0
  48. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/settings/__init__.py +0 -0
  49. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/settings/client_items.py +0 -0
  50. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/settings/default.yaml +0 -0
  51. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/settings/logging.py +0 -0
  52. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/settings/model_map.py +0 -0
  53. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/settings/prompts.py +0 -0
  54. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/settings/string_navbar.py +0 -0
  55. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/smiv.py +0 -0
  56. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/smpv.py +0 -0
  57. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/static/assets/hero-default.svg +0 -0
  58. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/static/css/style.css +0 -0
  59. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/static/docs.md +0 -0
  60. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/static/icons/bot-icon.png +0 -0
  61. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/static/icons/favicon.png +0 -0
  62. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/static/icons/logo.png +0 -0
  63. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/static/icons/logo3.png +0 -0
  64. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/static/icons/svg_497526.svg +0 -0
  65. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/static/icons/svg_497528.svg +0 -0
  66. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/static/js/chat.js +0 -0
  67. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/static/js/sidebar.js +0 -0
  68. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/static/js/widgets.js +0 -0
  69. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/templates/admin_secretes.html +0 -0
  70. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/templates/change_password.html +0 -0
  71. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/templates/code_cell.html +0 -0
  72. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/templates/dashboard.html +0 -0
  73. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/templates/dataset_resize.html +0 -0
  74. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/templates/docs.html +0 -0
  75. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/templates/edit_page.html +0 -0
  76. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/templates/error.html +0 -0
  77. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/templates/login.html +0 -0
  78. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/templates/register.html +0 -0
  79. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/themes.py +0 -0
  80. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/ui_modes.py +0 -0
  81. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/utils.py +0 -0
  82. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/vector_db.py +0 -0
  83. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/vectordb/__init__.py +0 -0
  84. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/vectordb/adapters/__init__.py +0 -0
  85. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/vectordb/adapters/milvus_adapter.py +0 -0
  86. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/vectordb/adapters/pgvector_adapter.py +0 -0
  87. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/vectordb/adapters/sqlite_adapter.py +0 -0
  88. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/vectordb/base.py +0 -0
  89. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/vectordb/registry.py +0 -0
  90. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/vectorizer.py +0 -0
  91. {syntaxmatrix-2.6.0 → syntaxmatrix-2.6.1}/syntaxmatrix/workspace_db.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syntaxmatrix
3
- Version: 2.6.0
3
+ Version: 2.6.1
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.0
3
+ Version: 2.6.1
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
@@ -67,13 +67,13 @@ syntaxmatrix/static/css/style.css
67
67
  syntaxmatrix/static/icons/bot-icon.png
68
68
  syntaxmatrix/static/icons/favicon.png
69
69
  syntaxmatrix/static/icons/logo.png
70
- syntaxmatrix/static/icons/logo2.png
71
70
  syntaxmatrix/static/icons/logo3.png
72
71
  syntaxmatrix/static/icons/svg_497526.svg
73
72
  syntaxmatrix/static/icons/svg_497528.svg
74
73
  syntaxmatrix/static/js/chat.js
75
74
  syntaxmatrix/static/js/sidebar.js
76
75
  syntaxmatrix/static/js/widgets.js
76
+ syntaxmatrix/templates/admin_branding.html
77
77
  syntaxmatrix/templates/admin_secretes.html
78
78
  syntaxmatrix/templates/change_password.html
79
79
  syntaxmatrix/templates/code_cell.html
@@ -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.0",
11
+ version="2.6.1",
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.",
@@ -40,6 +40,9 @@ _CLIENT_DIR = detect_project_root()
40
40
  _HISTORY_DIR = os.path.join(_CLIENT_DIR, "smx_history")
41
41
  os.makedirs(_HISTORY_DIR, exist_ok=True)
42
42
 
43
+ _BRANDING_DIR = os.path.join(_CLIENT_DIR, "branding")
44
+ os.makedirs(_BRANDING_DIR, exist_ok=True)
45
+
43
46
  _SECRET_PATH = os.path.join(_CLIENT_DIR, ".smx_secret_key")
44
47
 
45
48
  # OPENAI_API_KEY = getenv_api_key(_CLIENT_DIR, "OPENAI_API_KEY"))
@@ -72,6 +75,8 @@ class SyntaxMUI:
72
75
  self.bot_icon = bot_icon
73
76
  self.site_logo = site_logo
74
77
  self.favicon = favicon
78
+ self._default_site_logo = site_logo
79
+ self._default_favicon = favicon
75
80
  self.site_title = site_title
76
81
  self.project_name = project_name
77
82
  self.ui_mode = ui_mode
@@ -99,6 +104,9 @@ class SyntaxMUI:
99
104
  self._last_llm_usage = None
100
105
  routes.setup_routes(self)
101
106
 
107
+ # Apply client branding overrides if present on disk
108
+ self._apply_branding_from_disk()
109
+
102
110
  # LLM Profiles
103
111
  self.admin_profile = {}
104
112
  self.chat_profile = {}
@@ -330,24 +338,49 @@ class SyntaxMUI:
330
338
  col_html += "</div>"
331
339
  return col_html
332
340
 
341
+ # Site Branding
333
342
  def set_site_title(self, title):
334
343
  self.site_title = title
335
-
336
344
  def set_project_name(self, project_name):
337
345
  self.project_name = project_name
338
-
339
346
  def set_favicon(self, icon):
340
347
  self.favicon = icon
341
-
342
-
343
348
  def set_site_logo(self, logo):
344
349
  self.site_logo = logo
345
-
346
350
  def set_user_icon(self, icon):
347
351
  self.user_icon = icon
348
-
349
352
  def set_bot_icon(self, icon):
350
353
  self.bot_icon = icon
354
+
355
+ def _apply_branding_from_disk(self):
356
+ """
357
+ If a client logo/favicon exists in syntaxmatrixdir/branding/,
358
+ use it; otherwise keep the framework defaults.
359
+ """
360
+ branding_dir = os.path.join(_CLIENT_DIR, "branding")
361
+
362
+ def _pick(basename: str):
363
+ for ext in (".png", ".jpg", ".jpeg"):
364
+ fn = f"{basename}{ext}"
365
+ p = os.path.join(branding_dir, fn)
366
+ if os.path.exists(p):
367
+ return fn
368
+ return None
369
+
370
+ logo_fn = _pick("logo")
371
+ fav_fn = _pick("favicon")
372
+
373
+ if logo_fn:
374
+ # Use client-served endpoint (added in routes.py below)
375
+ self.site_logo = f"<img src='/branding/{logo_fn}' width='45' alt='logo'/>"
376
+ else:
377
+ self.site_logo = getattr(self, "_default_site_logo", self.site_logo)
378
+
379
+ if fav_fn:
380
+ self.favicon = f"/branding/{fav_fn}"
381
+ else:
382
+ self.favicon = getattr(self, "_default_favicon", self.favicon)
383
+
351
384
 
352
385
  def text_input(self, key, id, label, placeholder=""):
353
386
  if not placeholder:
@@ -4508,8 +4508,8 @@ def setup_routes(smx):
4508
4508
  # SYSTEM FILES
4509
4509
  # ────────────────────────────────────────────────────────────────────────────────
4510
4510
  sys_files_card = f"""
4511
- <div class="card span-4">
4512
- <h4>Upload System Files (PDFs only)</h4>
4511
+ <div class="card span-3">
4512
+ <h4>Upload System Files<br>(PDFs only)</h4>
4513
4513
  <form id="form-upload" method="post" enctype="multipart/form-data" style="display:inline-block;">
4514
4514
  <input type="file" name="upload_files" accept=".pdf" multiple>
4515
4515
  <button type="submit" name="action" value="upload_files">Upload</button>
@@ -4540,7 +4540,7 @@ def setup_routes(smx):
4540
4540
  """
4541
4541
 
4542
4542
  manage_sys_files_card = f"""
4543
- <div class='card span-4'>
4543
+ <div class='card span-3'>
4544
4544
  <h4>Manage Company Files</h4>
4545
4545
  <ul class="catalog-list" style="list-style:none; padding-left:0; margin:0;">
4546
4546
  {sys_files_html or "<li>No company file has been uploaded yet.</li>"}
@@ -4964,7 +4964,7 @@ def setup_routes(smx):
4964
4964
  pixabay_saved = bool(os.environ.get("PIXABAY_API_KEY"))
4965
4965
 
4966
4966
  secretes_link_card = f"""
4967
- <div class="card span-4">
4967
+ <div class="card span-3">
4968
4968
  <h4>Integrations (Secrets)</h4>
4969
4969
  <div style="font-size:.72rem;color:#555;margin-top:-6px;margin-bottom:10px;line-height:1.35;">
4970
4970
  Store secrete credentials.
@@ -4973,17 +4973,28 @@ def setup_routes(smx):
4973
4973
  </div>
4974
4974
  """
4975
4975
 
4976
+ branding_link_card = f"""
4977
+ <div class="card span-3">
4978
+ <h4>Branding</h4>
4979
+ <div style="font-size:.72rem;color:#555;margin-top:-6px;margin-bottom:10px;line-height:1.35;">
4980
+ Upload your company logo and favicon (PNG/JPG). Defaults are used if nothing is uploaded.
4981
+ </div>
4982
+ <a href="{url_for('admin_branding')}" class="btn">Manage branding</a>
4983
+ </div>
4984
+ """
4985
+
4976
4986
  system_section = f"""
4977
4987
  <section id="system" class="section">
4978
4988
  <h2>System</h2>
4979
4989
  <div class="admin-grid">
4980
4990
  {secretes_link_card}
4991
+ {branding_link_card}
4981
4992
  {sys_files_card}
4982
4993
  {manage_sys_files_card}
4983
4994
  </div>
4995
+
4984
4996
  </section>
4985
4997
  """
4986
-
4987
4998
  users_section = f"""
4988
4999
  <section id="users" class="section">
4989
5000
  <h2>Users</h2>
@@ -5518,6 +5529,114 @@ def setup_routes(smx):
5518
5529
  return render_template("admin_secretes.html", secret_names=names)
5519
5530
 
5520
5531
 
5532
+ @smx.app.route("/admin/branding", methods=["GET", "POST"])
5533
+ @admin_required
5534
+ def admin_branding():
5535
+ branding_dir = os.path.join(_CLIENT_DIR, "branding")
5536
+ os.makedirs(branding_dir, exist_ok=True)
5537
+
5538
+ allowed_ext = {".png", ".jpg", ".jpeg"}
5539
+ max_logo_bytes = 5 * 1024 * 1024 # 5 MB
5540
+ max_favicon_bytes = 1 * 1024 * 1024 # 1 MB
5541
+
5542
+ def _find(base: str):
5543
+ for ext in (".png", ".jpg", ".jpeg"):
5544
+ p = os.path.join(branding_dir, f"{base}{ext}")
5545
+ if os.path.exists(p):
5546
+ return f"{base}{ext}"
5547
+ return None
5548
+
5549
+ def _delete_all(base: str):
5550
+ for ext in (".png", ".jpg", ".jpeg"):
5551
+ p = os.path.join(branding_dir, f"{base}{ext}")
5552
+ if os.path.exists(p):
5553
+ try:
5554
+ os.remove(p)
5555
+ except Exception:
5556
+ pass
5557
+
5558
+ def _save_upload(field_name: str, base: str, max_bytes: int):
5559
+ f = request.files.get(field_name)
5560
+ if not f or not f.filename:
5561
+ return False, None
5562
+
5563
+ ext = os.path.splitext(f.filename.lower())[1].strip()
5564
+ if ext not in allowed_ext:
5565
+ return False, f"Invalid file type for {base}. Use PNG or JPG."
5566
+
5567
+ # size check
5568
+ try:
5569
+ f.stream.seek(0, os.SEEK_END)
5570
+ size = f.stream.tell()
5571
+ f.stream.seek(0)
5572
+ except Exception:
5573
+ size = None
5574
+
5575
+ if size is not None and size > max_bytes:
5576
+ return False, f"{base.capitalize()} is too large. Max {max_bytes // (1024*1024)} MB."
5577
+
5578
+ # Replace existing logo.* / favicon.*
5579
+ _delete_all(base)
5580
+
5581
+ out_path = os.path.join(branding_dir, f"{base}{ext}")
5582
+ try:
5583
+ f.save(out_path)
5584
+ except Exception as e:
5585
+ return False, f"Failed to save {base}: {e}"
5586
+
5587
+ return True, None
5588
+
5589
+ # POST actions
5590
+ if request.method == "POST":
5591
+ action = (request.form.get("action") or "upload").strip().lower()
5592
+
5593
+ if action == "reset":
5594
+ _delete_all("logo")
5595
+ _delete_all("favicon")
5596
+ try:
5597
+ smx._apply_branding_from_disk()
5598
+ except Exception:
5599
+ pass
5600
+ flash("Branding reset to defaults ✓")
5601
+ return redirect(url_for("admin_branding"))
5602
+
5603
+ ok1, err1 = _save_upload("logo_file", "logo", max_logo_bytes)
5604
+ ok2, err2 = _save_upload("favicon_file", "favicon", max_favicon_bytes)
5605
+
5606
+ if err1:
5607
+ flash(err1, "error")
5608
+ if err2:
5609
+ flash(err2, "error")
5610
+
5611
+ if ok1 or ok2:
5612
+ try:
5613
+ smx._apply_branding_from_disk()
5614
+ except Exception:
5615
+ pass
5616
+ flash("Branding updated ✓")
5617
+
5618
+ return redirect(url_for("admin_branding"))
5619
+
5620
+ # GET: show current status
5621
+ logo_fn = _find("logo")
5622
+ fav_fn = _find("favicon")
5623
+
5624
+ cache_bust = int(time.time())
5625
+
5626
+ logo_url = f"/branding/{logo_fn}?v={cache_bust}" if logo_fn else None
5627
+ favicon_url = f"/branding/{fav_fn}?v={cache_bust}" if fav_fn else None
5628
+
5629
+ default_logo_html = getattr(smx, "_default_site_logo", smx.site_logo)
5630
+ default_favicon_url = getattr(smx, "_default_favicon", smx.favicon)
5631
+
5632
+ return render_template(
5633
+ "admin_branding.html",
5634
+ logo_url=logo_url,
5635
+ favicon_url=favicon_url,
5636
+ default_logo_html=Markup(default_logo_html),
5637
+ default_favicon_url=default_favicon_url,
5638
+ )
5639
+
5521
5640
  @smx.app.route("/admin/delete.json", methods=["POST"])
5522
5641
  def admin_delete_universal():
5523
5642
 
@@ -6407,13 +6526,20 @@ def setup_routes(smx):
6407
6526
 
6408
6527
  return jsonify({"file_paths": file_paths})
6409
6528
 
6410
-
6529
+
6411
6530
  # Serve the raw media files
6412
6531
  @smx.app.route('/uploads/media/<path:filename>')
6413
6532
  def serve_media(filename):
6414
6533
  media_dir = os.path.join(_CLIENT_DIR, 'uploads', 'media')
6415
6534
  return send_from_directory(media_dir, filename)
6416
6535
 
6536
+
6537
+ @smx.app.route("/branding/<path:filename>")
6538
+ def serve_branding(filename):
6539
+ branding_dir = os.path.join(_CLIENT_DIR, "branding")
6540
+ return send_from_directory(branding_dir, filename)
6541
+
6542
+
6417
6543
  # ────────────────────────────────────────────────────────────────────────────────────────
6418
6544
  # DASHBOARD
6419
6545
  # ────────────────────────────────────────────────────────────────────────────────────────
@@ -0,0 +1,104 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Branding</title>
7
+ <style>
8
+ body{font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;margin:0;background:#0b1224;color:#e5e7eb;}
9
+ .wrap{max-width:980px;margin:0 auto;padding:18px;}
10
+ .top{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:14px;}
11
+ .top h1{margin:0;font-size:1.1rem;}
12
+ .btn{display:inline-block;padding:8px 12px;border-radius:999px;border:1px solid rgba(148,163,184,.35);color:#e5e7eb;text-decoration:none;background:transparent;cursor:pointer;font-weight:650;font-size:.85rem;}
13
+ .btn:hover{border-color:rgba(56,189,248,.6);color:#38bdf8;}
14
+ .card{background:rgba(2,6,23,.65);border:1px solid rgba(148,163,184,.25);border-radius:14px;padding:14px;box-shadow:0 18px 36px rgba(15,23,42,.6);margin-bottom:12px;}
15
+ label{display:block;font-size:.78rem;font-weight:750;margin-bottom:6px;color:#d1d5db;}
16
+ input[type="file"]{width:100%;padding:10px 11px;border-radius:12px;border:1px solid rgba(148,163,184,.25);background:rgba(2,6,23,.8);color:#e5e7eb;outline:none;}
17
+ .row{display:grid;grid-template-columns:1fr 1fr;gap:10px;}
18
+ @media (max-width:760px){.row{grid-template-columns:1fr;}}
19
+ .hint{color:#9ca3af;font-size:.78rem;line-height:1.35;margin-top:8px;}
20
+ .flash{background:rgba(34,197,94,.12);border:1px solid rgba(34,197,94,.35);padding:10px;border-radius:12px;margin-bottom:10px;color:#bbf7d0;font-size:.85rem;}
21
+ .flash.warn{background:rgba(239,68,68,.10);border-color:rgba(239,68,68,.30);color:#fecaca;}
22
+ .preview{display:flex;gap:14px;align-items:center;flex-wrap:wrap;margin-top:10px;}
23
+ .chip{padding:6px 10px;border-radius:999px;border:1px solid rgba(148,163,184,.25);font-size:.8rem;color:#cbd5e1;}
24
+ .danger{border-color:rgba(239,68,68,.55);color:#fecaca;}
25
+ .danger:hover{border-color:rgba(239,68,68,.9);color:#fff;}
26
+ </style>
27
+ </head>
28
+ <body>
29
+ <div class="wrap">
30
+ <div class="top">
31
+ <h1>Branding</h1>
32
+ <a class="btn" href="{{ url_for('admin_panel') }}#system">Back to Admin</a>
33
+ </div>
34
+
35
+ {% with messages = get_flashed_messages(with_categories=True) %}
36
+ {% if messages %}
37
+ {% for cat, m in messages %}
38
+ <div class="flash {{ 'warn' if cat == 'error' else '' }}">{{ m }}</div>
39
+ {% endfor %}
40
+ {% endif %}
41
+ {% endwith %}
42
+
43
+ <div class="card">
44
+ <h2 style="margin:0 0 10px;font-size:.95rem;">Upload logo and favicon</h2>
45
+
46
+ <form method="post" enctype="multipart/form-data">
47
+ <input type="hidden" name="action" value="upload">
48
+
49
+ <div class="row">
50
+ <div>
51
+ <label>Logo (PNG/JPG)</label>
52
+ <input type="file" name="logo_file" accept=".png,.jpg,.jpeg,image/png,image/jpeg">
53
+ <div class="hint">Tip: a wide logo works best. Up to 5 MB.</div>
54
+ </div>
55
+
56
+ <div>
57
+ <label>Favicon (PNG/JPG)</label>
58
+ <input type="file" name="favicon_file" accept=".png,.jpg,.jpeg,image/png,image/jpeg">
59
+ <div class="hint">Tip: square image (32×32 or 48×48). Up to 1 MB.</div>
60
+ </div>
61
+ </div>
62
+
63
+ <div style="margin-top:12px;display:flex;justify-content:flex-end;gap:10px;">
64
+ <button class="btn" type="submit">Save</button>
65
+ </div>
66
+ </form>
67
+
68
+ <div class="preview">
69
+ <div>
70
+ <div class="chip">Current logo</div>
71
+ <div style="margin-top:8px;">
72
+ {% if logo_url %}
73
+ <img src="{{ logo_url }}" alt="logo" style="max-height:44px;max-width:240px;">
74
+ {% else %}
75
+ {{ default_logo_html|safe }}
76
+ {% endif %}
77
+ </div>
78
+ </div>
79
+
80
+ <div>
81
+ <div class="chip">Current favicon</div>
82
+ <div style="margin-top:8px;">
83
+ {% if favicon_url %}
84
+ <img src="{{ favicon_url }}" alt="favicon" style="width:32px;height:32px;border-radius:6px;">
85
+ {% else %}
86
+ <img src="{{ default_favicon_url }}" alt="favicon" style="width:32px;height:32px;border-radius:6px;">
87
+ {% endif %}
88
+ </div>
89
+ </div>
90
+ </div>
91
+ </div>
92
+
93
+ <div class="card">
94
+ <h2 style="margin:0 0 10px;font-size:.95rem;">Reset</h2>
95
+ <div class="hint">Removes the uploaded logo/favicon so the app falls back to the SyntaxMatrix defaults.</div>
96
+
97
+ <form method="post" onsubmit="return confirm('Reset branding to defaults?');" style="margin-top:10px;">
98
+ <input type="hidden" name="action" value="reset">
99
+ <button class="btn danger" type="submit">Reset to defaults</button>
100
+ </form>
101
+ </div>
102
+ </div>
103
+ </body>
104
+ </html>
File without changes
File without changes
File without changes