syntaxmatrix 2.5.5.5__py3-none-any.whl → 2.6.2__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 (44) hide show
  1. syntaxmatrix/__init__.py +3 -2
  2. syntaxmatrix/agentic/agents.py +1220 -169
  3. syntaxmatrix/agentic/agents_orchestrer.py +326 -0
  4. syntaxmatrix/agentic/code_tools_registry.py +27 -32
  5. syntaxmatrix/auth.py +142 -5
  6. syntaxmatrix/commentary.py +16 -16
  7. syntaxmatrix/core.py +192 -84
  8. syntaxmatrix/db.py +460 -4
  9. syntaxmatrix/{display.py → display_html.py} +2 -6
  10. syntaxmatrix/gpt_models_latest.py +1 -1
  11. syntaxmatrix/media/__init__.py +0 -0
  12. syntaxmatrix/media/media_pixabay.py +277 -0
  13. syntaxmatrix/models.py +1 -1
  14. syntaxmatrix/page_builder_defaults.py +183 -0
  15. syntaxmatrix/page_builder_generation.py +1122 -0
  16. syntaxmatrix/page_layout_contract.py +644 -0
  17. syntaxmatrix/page_patch_publish.py +1471 -0
  18. syntaxmatrix/preface.py +670 -0
  19. syntaxmatrix/profiles.py +28 -10
  20. syntaxmatrix/routes.py +1941 -593
  21. syntaxmatrix/selftest_page_templates.py +360 -0
  22. syntaxmatrix/settings/client_items.py +28 -0
  23. syntaxmatrix/settings/model_map.py +1022 -207
  24. syntaxmatrix/settings/prompts.py +328 -130
  25. syntaxmatrix/static/assets/hero-default.svg +22 -0
  26. syntaxmatrix/static/icons/bot-icon.png +0 -0
  27. syntaxmatrix/static/icons/favicon.png +0 -0
  28. syntaxmatrix/static/icons/logo.png +0 -0
  29. syntaxmatrix/static/icons/logo3.png +0 -0
  30. syntaxmatrix/templates/admin_branding.html +104 -0
  31. syntaxmatrix/templates/admin_features.html +63 -0
  32. syntaxmatrix/templates/admin_secretes.html +108 -0
  33. syntaxmatrix/templates/change_password.html +124 -0
  34. syntaxmatrix/templates/dashboard.html +296 -131
  35. syntaxmatrix/templates/dataset_resize.html +535 -0
  36. syntaxmatrix/templates/edit_page.html +2535 -0
  37. syntaxmatrix/utils.py +2728 -2835
  38. {syntaxmatrix-2.5.5.5.dist-info → syntaxmatrix-2.6.2.dist-info}/METADATA +6 -2
  39. {syntaxmatrix-2.5.5.5.dist-info → syntaxmatrix-2.6.2.dist-info}/RECORD +42 -25
  40. syntaxmatrix/generate_page.py +0 -634
  41. syntaxmatrix/static/icons/hero_bg.jpg +0 -0
  42. {syntaxmatrix-2.5.5.5.dist-info → syntaxmatrix-2.6.2.dist-info}/WHEEL +0 -0
  43. {syntaxmatrix-2.5.5.5.dist-info → syntaxmatrix-2.6.2.dist-info}/licenses/LICENSE.txt +0 -0
  44. {syntaxmatrix-2.5.5.5.dist-info → syntaxmatrix-2.6.2.dist-info}/top_level.txt +0 -0
syntaxmatrix/db.py CHANGED
@@ -14,9 +14,10 @@ TEMPLATES_DIR = os.path.join(_CLIENT_DIR, "templates")
14
14
  os.makedirs(TEMPLATES_DIR, exist_ok=True)
15
15
 
16
16
 
17
- # ***************************************
18
- # Pages Table Functions
19
- # ***************************************
17
+
18
+ # ─────────────────────────────────────────────────────────
19
+ # Page
20
+ # ─────────────────────────────────────────────────────────
20
21
  def init_db():
21
22
  conn = sqlite3.connect(DB_PATH)
22
23
 
@@ -38,10 +39,50 @@ def init_db():
38
39
  )
39
40
  """)
40
41
 
42
+ conn.execute("""
43
+ CREATE TABLE IF NOT EXISTS secretes (
44
+ name TEXT PRIMARY KEY,
45
+ value TEXT NOT NULL,
46
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
47
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
48
+ )
49
+ """)
50
+
51
+ conn.execute("""
52
+ CREATE TABLE IF NOT EXISTS media_assets (
53
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
54
+ kind TEXT NOT NULL DEFAULT 'image',
55
+ rel_path TEXT NOT NULL UNIQUE,
56
+ thumb_path TEXT,
57
+ sha256 TEXT,
58
+ dhash TEXT,
59
+ width INTEGER,
60
+ height INTEGER,
61
+ mime TEXT,
62
+ source TEXT,
63
+ source_url TEXT,
64
+ author TEXT,
65
+ licence TEXT,
66
+ tags TEXT,
67
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
68
+ )
69
+ """)
70
+
71
+ conn.execute("""
72
+ CREATE TABLE IF NOT EXISTS app_settings (
73
+ key TEXT PRIMARY KEY,
74
+ value TEXT NOT NULL,
75
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
76
+ )
77
+ """)
78
+
41
79
  conn.commit()
42
80
  conn.close()
43
81
 
44
82
 
83
+ # ***************************************
84
+ # Pages Helpers
85
+ # ***************************************
45
86
  def get_pages():
46
87
  """Return {page_name: html} resolving relative paths under syntaxmatrixdir/templates."""
47
88
  import sqlite3
@@ -62,6 +103,44 @@ def get_pages():
62
103
  return pages
63
104
 
64
105
 
106
+ def get_page_html(name: str):
107
+ """
108
+ Return the latest HTML for a single page by reading the file path stored in DB.
109
+ This avoids stale in-memory cache (smx.pages) across Gunicorn workers.
110
+ """
111
+ if not name:
112
+ return None
113
+
114
+ conn = sqlite3.connect(DB_PATH)
115
+ try:
116
+ row = conn.execute(
117
+ "SELECT content FROM pages WHERE lower(name) = lower(?)",
118
+ (name.strip(),)
119
+ ).fetchone()
120
+ finally:
121
+ conn.close()
122
+
123
+ if not row:
124
+ return None
125
+
126
+ stored = (row[0] or "").strip()
127
+ if not stored:
128
+ return None
129
+
130
+ # Backwards compatible: if DB ever stored raw HTML instead of a file path
131
+ if stored.lstrip().startswith("<"):
132
+ return stored
133
+
134
+ # Resolve path (relative under syntaxmatrixdir)
135
+ abs_path = stored if os.path.isabs(stored) else os.path.join(_CLIENT_DIR, stored)
136
+
137
+ try:
138
+ with open(abs_path, "r", encoding="utf-8") as f:
139
+ return f.read()
140
+ except Exception:
141
+ return None
142
+
143
+
65
144
  def add_page(name, html):
66
145
  """Create templates/<slug>.html and store a relative path in the DB."""
67
146
  filename = secure_filename(name.lower()) + ".html"
@@ -151,4 +230,381 @@ def delete_page(name):
151
230
 
152
231
  cur.execute("DELETE FROM pages WHERE name = ?", (name,))
153
232
  conn.commit()
154
- conn.close()
233
+ conn.close()
234
+
235
+
236
+ # ***************************************
237
+ # Secrete Helpers
238
+ # ***************************************
239
+ def set_secret(name: str, value: str) -> None:
240
+ """Create/update a secret in the local SyntaxMatrix DB."""
241
+ if not name:
242
+ return
243
+ name = name.strip().upper()
244
+ conn = sqlite3.connect(DB_PATH)
245
+ try:
246
+ with conn:
247
+ conn.execute(
248
+ """
249
+ INSERT INTO secretes (name, value, updated_at)
250
+ VALUES (?, ?, CURRENT_TIMESTAMP)
251
+ ON CONFLICT(name) DO UPDATE SET
252
+ value = excluded.value,
253
+ updated_at = CURRENT_TIMESTAMP
254
+ """,
255
+ (name, value),
256
+ )
257
+ finally:
258
+ conn.close()
259
+
260
+
261
+ def get_secret(name: str) -> str | None:
262
+ """Get a secret value, or None if missing."""
263
+ if not name:
264
+ return None
265
+ name = name.strip().upper()
266
+ conn = sqlite3.connect(DB_PATH)
267
+ try:
268
+ row = conn.execute("SELECT value FROM secretes WHERE name = ?", (name,)).fetchone()
269
+ return row[0] if row else None
270
+ finally:
271
+ conn.close()
272
+
273
+
274
+ def delete_secret(name: str) -> None:
275
+ if not name:
276
+ return
277
+ name = name.strip().upper()
278
+ conn = sqlite3.connect(DB_PATH)
279
+ try:
280
+ with conn:
281
+ conn.execute("DELETE FROM secretes WHERE name = ?", (name,))
282
+ finally:
283
+ conn.close()
284
+
285
+
286
+ def list_secret_names() -> list[str]:
287
+ """Return secret names only (never values)."""
288
+ conn = sqlite3.connect(DB_PATH)
289
+ try:
290
+ rows = conn.execute("SELECT name FROM secretes ORDER BY name").fetchall()
291
+ return [r[0] for r in rows]
292
+ finally:
293
+ conn.close()
294
+
295
+
296
+ # ─────────────────────────────────────────────────────────
297
+ # Page navigation metadata (show_in_nav / nav_label)
298
+ # ─────────────────────────────────────────────────────────
299
+ def _init_page_nav_table():
300
+ """Ensure the page_nav table exists and has all expected columns."""
301
+ import sqlite3 # safe if already imported at top
302
+ conn = sqlite3.connect(DB_PATH)
303
+ try:
304
+ with conn:
305
+ # Create table if missing (with nav_order already defined)
306
+ conn.execute("""
307
+ CREATE TABLE IF NOT EXISTS page_nav (
308
+ page_name TEXT PRIMARY KEY,
309
+ show_in_nav INTEGER NOT NULL DEFAULT 1,
310
+ nav_label TEXT,
311
+ nav_order INTEGER
312
+ )
313
+ """)
314
+
315
+ # If table existed before, make sure nav_order column is present
316
+ cur = conn.execute("PRAGMA table_info(page_nav)")
317
+ cols = [row[1] for row in cur.fetchall()]
318
+ if "nav_order" not in cols:
319
+ conn.execute("ALTER TABLE page_nav ADD COLUMN nav_order INTEGER")
320
+ finally:
321
+ conn.close()
322
+
323
+
324
+ def set_page_nav(
325
+ page_name: str,
326
+ show_in_nav: bool = True,
327
+ nav_label: str | None = None,
328
+ nav_order: int | None = None,
329
+ ) -> None:
330
+ """
331
+ Upsert navigation preferences for a page.
332
+ """
333
+ if not page_name:
334
+ return
335
+ _init_page_nav_table()
336
+ import sqlite3
337
+ conn = sqlite3.connect(DB_PATH)
338
+ try:
339
+ with conn:
340
+ conn.execute("""
341
+ INSERT INTO page_nav (page_name, show_in_nav, nav_label, nav_order)
342
+ VALUES (?, ?, ?, ?)
343
+ ON CONFLICT(page_name) DO UPDATE SET
344
+ show_in_nav = excluded.show_in_nav,
345
+ nav_label = excluded.nav_label,
346
+ nav_order = excluded.nav_order
347
+ """, (page_name.lower(), 1 if show_in_nav else 0, nav_label, nav_order),
348
+ )
349
+ finally:
350
+ conn.close()
351
+
352
+
353
+ def get_page_nav_map() -> dict:
354
+ """
355
+ Return a dict mapping page_name -> {"show_in_nav": bool, "nav_label": str|None, "nav_order": int|None}.
356
+ """
357
+ _init_page_nav_table()
358
+ import sqlite3
359
+ conn = sqlite3.connect(DB_PATH)
360
+ try:
361
+ cur = conn.cursor()
362
+ cur.execute("SELECT page_name, show_in_nav, nav_label, nav_order FROM page_nav")
363
+ rows = cur.fetchall()
364
+ finally:
365
+ conn.close()
366
+
367
+ result = {}
368
+ for name, show, label, order_val in rows:
369
+ if not name:
370
+ continue
371
+ result[name.lower()] = {
372
+ "show_in_nav": bool(show),
373
+ "nav_label": label,
374
+ "nav_order": order_val,
375
+ }
376
+ return result
377
+
378
+
379
+ # ─────────────────────────────────────────────────────────
380
+ # Page layouts (builder JSON) kept separate from final HTML
381
+ # ─────────────────────────────────────────────────────────
382
+ def _init_page_layouts_table():
383
+ import sqlite3
384
+ conn = sqlite3.connect(DB_PATH)
385
+ try:
386
+ with conn:
387
+ conn.execute("""
388
+ CREATE TABLE IF NOT EXISTS page_layouts (
389
+ page_name TEXT PRIMARY KEY,
390
+ layout_json TEXT NOT NULL,
391
+ is_detached INTEGER NOT NULL DEFAULT 0,
392
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
393
+ )
394
+ """)
395
+ finally:
396
+ conn.close()
397
+
398
+
399
+ def upsert_page_layout(page_name: str, layout_json: str, is_detached: bool | None = None) -> None:
400
+ if not page_name:
401
+ return
402
+ _init_page_layouts_table()
403
+ import sqlite3
404
+ conn = sqlite3.connect(DB_PATH)
405
+ try:
406
+ with conn:
407
+ if is_detached is None:
408
+ conn.execute(
409
+ """
410
+ INSERT INTO page_layouts (page_name, layout_json)
411
+ VALUES (?, ?)
412
+ ON CONFLICT(page_name) DO UPDATE SET
413
+ layout_json = excluded.layout_json,
414
+ updated_at = CURRENT_TIMESTAMP
415
+ """,
416
+ (page_name.lower(), layout_json),
417
+ )
418
+ else:
419
+ conn.execute(
420
+ """
421
+ INSERT INTO page_layouts (page_name, layout_json, is_detached)
422
+ VALUES (?, ?, ?)
423
+ ON CONFLICT(page_name) DO UPDATE SET
424
+ layout_json = excluded.layout_json,
425
+ is_detached = excluded.is_detached,
426
+ updated_at = CURRENT_TIMESTAMP
427
+ """,
428
+ (page_name.lower(), layout_json, 1 if is_detached else 0),
429
+ )
430
+ finally:
431
+ conn.close()
432
+
433
+
434
+ def get_page_layout(page_name: str) -> dict | None:
435
+ if not page_name:
436
+ return None
437
+ _init_page_layouts_table()
438
+ import sqlite3
439
+ conn = sqlite3.connect(DB_PATH)
440
+ try:
441
+ row = conn.execute(
442
+ "SELECT page_name, layout_json, is_detached, updated_at FROM page_layouts WHERE page_name = ?",
443
+ (page_name.lower(),),
444
+ ).fetchone()
445
+ if not row:
446
+ return None
447
+ return {
448
+ "page_name": row[0],
449
+ "layout_json": row[1],
450
+ "is_detached": bool(row[2]),
451
+ "updated_at": row[3],
452
+ }
453
+ finally:
454
+ conn.close()
455
+
456
+
457
+ def _init_media_assets_table() -> None:
458
+
459
+ conn = sqlite3.connect(DB_PATH)
460
+ try:
461
+ with conn:
462
+ conn.execute(
463
+ """
464
+ CREATE TABLE IF NOT EXISTS media_assets (
465
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
466
+ kind TEXT NOT NULL DEFAULT 'image',
467
+ rel_path TEXT NOT NULL UNIQUE,
468
+ thumb_path TEXT,
469
+ sha256 TEXT,
470
+ dhash TEXT,
471
+ width INTEGER,
472
+ height INTEGER,
473
+ mime TEXT,
474
+ source TEXT,
475
+ source_url TEXT,
476
+ author TEXT,
477
+ licence TEXT,
478
+ tags TEXT,
479
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
480
+ )
481
+ """
482
+ )
483
+ finally:
484
+ conn.close()
485
+
486
+
487
+ def upsert_media_asset(
488
+ *,
489
+ rel_path: str,
490
+ kind: str = "image",
491
+ thumb_path: str | None = None,
492
+ sha256: str | None = None,
493
+ dhash: str | None = None,
494
+ width: int | None = None,
495
+ height: int | None = None,
496
+ mime: str | None = None,
497
+ source: str | None = None,
498
+ source_url: str | None = None,
499
+ author: str | None = None,
500
+ licence: str | None = None,
501
+ tags: str | None = None,
502
+ ) -> None:
503
+ if not rel_path:
504
+ return
505
+ _init_media_assets_table()
506
+
507
+ conn = sqlite3.connect(DB_PATH)
508
+ try:
509
+ with conn:
510
+ conn.execute(
511
+ """
512
+ INSERT INTO media_assets (
513
+ kind, rel_path, thumb_path, sha256, dhash, width, height, mime,
514
+ source, source_url, author, licence, tags
515
+ )
516
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
517
+ ON CONFLICT(rel_path) DO UPDATE SET
518
+ kind = excluded.kind,
519
+ thumb_path = excluded.thumb_path,
520
+ sha256 = excluded.sha256,
521
+ dhash = excluded.dhash,
522
+ width = excluded.width,
523
+ height = excluded.height,
524
+ mime = excluded.mime,
525
+ source = excluded.source,
526
+ source_url = excluded.source_url,
527
+ author = excluded.author,
528
+ licence = excluded.licence,
529
+ tags = excluded.tags
530
+ """,
531
+ (
532
+ kind, rel_path, thumb_path, sha256, dhash, width, height, mime,
533
+ source, source_url, author, licence, tags,
534
+ ),
535
+ )
536
+ finally:
537
+ conn.close()
538
+
539
+
540
+ def get_media_asset_by_rel_path(rel_path: str) -> dict | None:
541
+ if not rel_path:
542
+ return None
543
+ _init_media_assets_table()
544
+ import sqlite3
545
+ conn = sqlite3.connect(DB_PATH)
546
+ try:
547
+ row = conn.execute(
548
+ """
549
+ SELECT kind, rel_path, thumb_path, sha256, dhash, width, height, mime,
550
+ source, source_url, author, licence, tags, created_at
551
+ FROM media_assets WHERE rel_path = ?
552
+ """,
553
+ (rel_path,),
554
+ ).fetchone()
555
+ if not row:
556
+ return None
557
+ return {
558
+ "kind": row[0],
559
+ "rel_path": row[1],
560
+ "thumb_path": row[2],
561
+ "sha256": row[3],
562
+ "dhash": row[4],
563
+ "width": row[5],
564
+ "height": row[6],
565
+ "mime": row[7],
566
+ "source": row[8],
567
+ "source_url": row[9],
568
+ "author": row[10],
569
+ "licence": row[11],
570
+ "tags": row[12],
571
+ "created_at": row[13],
572
+ }
573
+ finally:
574
+ conn.close()
575
+
576
+ # ***************************************
577
+ # App Settings Helpers (feature toggles)
578
+ # ***************************************
579
+ def set_setting(key: str, value: str) -> None:
580
+ if not key:
581
+ return
582
+ key = key.strip()
583
+ value = "" if value is None else str(value)
584
+ conn = sqlite3.connect(DB_PATH)
585
+ try:
586
+ with conn:
587
+ conn.execute(
588
+ """
589
+ INSERT INTO app_settings (key, value, updated_at)
590
+ VALUES (?, ?, CURRENT_TIMESTAMP)
591
+ ON CONFLICT(key) DO UPDATE SET
592
+ value = excluded.value,
593
+ updated_at = CURRENT_TIMESTAMP
594
+ """,
595
+ (key, value),
596
+ )
597
+ finally:
598
+ conn.close()
599
+
600
+
601
+ def get_setting(key: str, default: str | None = None) -> str | None:
602
+ if not key:
603
+ return default
604
+ key = key.strip()
605
+ conn = sqlite3.connect(DB_PATH)
606
+ try:
607
+ row = conn.execute("SELECT value FROM app_settings WHERE key = ?", (key,)).fetchone()
608
+ return row[0] if row else default
609
+ finally:
610
+ conn.close()
@@ -28,8 +28,6 @@ except Exception: # pragma: no cover
28
28
  __all__ = ["show"]
29
29
 
30
30
 
31
- # ---- internal helpers -------------------------------------------------------
32
-
33
31
 
34
32
  def _wrap_html_table(html: str) -> str:
35
33
  """Apply consistent UI styling and horizontal scrolling."""
@@ -47,10 +45,7 @@ def _wrap_html_table(html: str) -> str:
47
45
  )
48
46
 
49
47
 
50
- # ---- public API -------------------------------------------------------------
51
-
52
-
53
- def show(obj: Any) -> None:
48
+ def show_table(obj: Any) -> None:
54
49
  """
55
50
  Render common objects so the Dashboard (or chat) always shows output.
56
51
 
@@ -104,3 +99,4 @@ def show(obj: Any) -> None:
104
99
  # 6) Fallback: show as preformatted text (safe and predictable)
105
100
  display(HTML(f"<pre>{obj}</pre>"))
106
101
  return None
102
+
@@ -36,7 +36,7 @@ def set_args(
36
36
  "store": store,
37
37
  "truncation": truncation,
38
38
  }
39
- if model == "gpt-5.1-chat-latest":
39
+ if model == "gpt-5.1-chat-latest" or "gpt-5.2-chat-latest":
40
40
  args = base_params
41
41
  else:
42
42
  args = {**base_params,
File without changes