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