syntaxmatrix 2.6.4.4__py3-none-any.whl → 3.0.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.
- syntaxmatrix/__init__.py +6 -4
- syntaxmatrix/agentic/agents.py +195 -15
- syntaxmatrix/agentic/agents_orchestrer.py +16 -10
- syntaxmatrix/client_docs.py +237 -0
- syntaxmatrix/commentary.py +96 -25
- syntaxmatrix/core.py +142 -56
- syntaxmatrix/dataset_preprocessing.py +2 -2
- syntaxmatrix/db.py +0 -17
- syntaxmatrix/kernel_manager.py +174 -150
- syntaxmatrix/page_builder_generation.py +654 -50
- syntaxmatrix/page_layout_contract.py +25 -3
- syntaxmatrix/page_patch_publish.py +368 -15
- syntaxmatrix/plugins/__init__.py +0 -0
- syntaxmatrix/premium/__init__.py +10 -2
- syntaxmatrix/premium/catalogue/__init__.py +121 -0
- syntaxmatrix/premium/gate.py +15 -3
- syntaxmatrix/premium/state.py +507 -0
- syntaxmatrix/premium/verify.py +222 -0
- syntaxmatrix/profiles.py +1 -1
- syntaxmatrix/routes.py +9782 -8004
- syntaxmatrix/settings/model_map.py +50 -65
- syntaxmatrix/settings/prompts.py +1435 -380
- syntaxmatrix/settings/string_navbar.py +4 -4
- syntaxmatrix/static/icons/bot_icon.png +0 -0
- syntaxmatrix/static/icons/bot_icon2.png +0 -0
- syntaxmatrix/templates/admin_billing.html +408 -0
- syntaxmatrix/templates/admin_branding.html +65 -2
- syntaxmatrix/templates/admin_features.html +54 -0
- syntaxmatrix/templates/dashboard.html +285 -8
- syntaxmatrix/templates/edit_page.html +199 -18
- syntaxmatrix/themes.py +17 -17
- syntaxmatrix/workspace_db.py +0 -23
- syntaxmatrix-3.0.0.dist-info/METADATA +219 -0
- {syntaxmatrix-2.6.4.4.dist-info → syntaxmatrix-3.0.0.dist-info}/RECORD +38 -33
- {syntaxmatrix-2.6.4.4.dist-info → syntaxmatrix-3.0.0.dist-info}/WHEEL +1 -1
- syntaxmatrix/settings/default.yaml +0 -13
- syntaxmatrix-2.6.4.4.dist-info/METADATA +0 -539
- syntaxmatrix-2.6.4.4.dist-info/licenses/LICENSE.txt +0 -21
- /syntaxmatrix/{plugin_manager.py → plugins/plugin_manager.py} +0 -0
- /syntaxmatrix/static/icons/{logo3.png → logo2.png} +0 -0
- {syntaxmatrix-2.6.4.4.dist-info → syntaxmatrix-3.0.0.dist-info}/top_level.txt +0 -0
syntaxmatrix/__init__.py
CHANGED
|
@@ -20,8 +20,8 @@ error = _app_instance.error
|
|
|
20
20
|
success = _app_instance.success
|
|
21
21
|
info = _app_instance.info
|
|
22
22
|
warning = _app_instance.warning
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
plt_plot = _app_instance.plt_plot
|
|
24
|
+
plotly_plot = _app_instance.plotly_plot
|
|
25
25
|
|
|
26
26
|
set_user_icon = _app_instance.set_user_icon
|
|
27
27
|
set_bot_icon = _app_instance.set_bot_icon
|
|
@@ -53,12 +53,14 @@ stream_process_query= _app_instance.stream_process_query
|
|
|
53
53
|
process_query_stream = _app_instance.process_query_stream
|
|
54
54
|
process_query = _app_instance.process_query
|
|
55
55
|
embed_query = _app_instance.embed_query
|
|
56
|
-
enable_user_files = _app_instance.enable_user_files
|
|
57
|
-
enable_registration = _app_instance.enable_registration
|
|
58
56
|
stream_write = _app_instance.stream_write
|
|
59
57
|
enable_stream = _app_instance.enable_stream
|
|
60
58
|
stream = _app_instance.stream
|
|
61
59
|
get_stream_args = _app_instance.get_stream_args
|
|
60
|
+
enable_user_files = _app_instance.enable_user_files
|
|
61
|
+
enable_registration = _app_instance.enable_registration
|
|
62
|
+
enable_site_documentation = _app_instance.enable_site_documentation
|
|
63
|
+
enable_ml_lab = _app_instance.enable_ml_lab
|
|
62
64
|
|
|
63
65
|
|
|
64
66
|
app = _app_instance.app
|
syntaxmatrix/agentic/agents.py
CHANGED
|
@@ -21,6 +21,7 @@ from dataclasses import dataclass
|
|
|
21
21
|
import hashlib
|
|
22
22
|
from PIL import Image
|
|
23
23
|
from syntaxmatrix.page_layout_contract import normalise_layout, validate_layout
|
|
24
|
+
from syntaxmatrix.page_builder_generation import compile_layout_to_html
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
def token_calculator(total_input_content, llm_profile):
|
|
@@ -596,6 +597,7 @@ def classify_ml_job_agent(refined_question, dataset_profile):
|
|
|
596
597
|
def agentic_generate_page(*,
|
|
597
598
|
page_slug: str,
|
|
598
599
|
website_description: str,
|
|
600
|
+
page_instructions: str = "",
|
|
599
601
|
client_dir: str,
|
|
600
602
|
pixabay_api_key: str = "",
|
|
601
603
|
llm_profile: dict | None = None,
|
|
@@ -707,6 +709,22 @@ def agentic_generate_page(*,
|
|
|
707
709
|
|
|
708
710
|
llm_profile = _prof.get_profile('coder')
|
|
709
711
|
llm_profile['client'] = _prof.get_client(llm_profile)
|
|
712
|
+
|
|
713
|
+
# OpenAI JSON mode requirement:
|
|
714
|
+
# If we request text.format={type:"json_object"} (or response_format json_object),
|
|
715
|
+
# OpenAI will error unless the word "JSON" appears somewhere in the prompt context.
|
|
716
|
+
def _ensure_json_mode_prompts(sp: str, up: str):
|
|
717
|
+
sp = sp or ""
|
|
718
|
+
up = up or ""
|
|
719
|
+
hint = "\n\nReturn ONLY valid JSON. Do not include markdown. JSON only."
|
|
720
|
+
if "json" not in sp.lower():
|
|
721
|
+
sp = sp + hint
|
|
722
|
+
if "json" not in up.lower():
|
|
723
|
+
up = up + "\n\nRespond in JSON."
|
|
724
|
+
return sp, up
|
|
725
|
+
|
|
726
|
+
system_prompt, user_prompt = _ensure_json_mode_prompts(system_prompt, user_prompt)
|
|
727
|
+
|
|
710
728
|
client = llm_profile["client"]
|
|
711
729
|
model = llm_profile["model"]
|
|
712
730
|
provider = llm_profile["provider"].lower()
|
|
@@ -806,11 +824,13 @@ def agentic_generate_page(*,
|
|
|
806
824
|
if not m:
|
|
807
825
|
raise RuntimeError(f"Model did not return JSON. Output was:\n{txt[:800]}")
|
|
808
826
|
return json.loads(m.group(0))
|
|
809
|
-
|
|
827
|
+
|
|
828
|
+
html = compile_layout_to_html(plan, page_slug=page_slug)
|
|
829
|
+
|
|
810
830
|
return openai_sdk_response()
|
|
811
831
|
|
|
812
832
|
|
|
813
|
-
def _page_plan_system_prompt(spec: dict) -> str:
|
|
833
|
+
def _page_plan_system_prompt(spec: dict, page_instructions: str = "") -> str:
|
|
814
834
|
allowed_sections = spec.get("allowed_section_types") or ["hero", "features", "gallery", "testimonials", "faq", "cta", "richtext"]
|
|
815
835
|
req = spec.get("required_sections") or []
|
|
816
836
|
|
|
@@ -820,6 +840,11 @@ def agentic_generate_page(*,
|
|
|
820
840
|
[f"- {r['id']} (type: {r['type']})" for r in req]
|
|
821
841
|
)
|
|
822
842
|
|
|
843
|
+
extra = ""
|
|
844
|
+
pi = (page_instructions or "").strip()
|
|
845
|
+
if pi:
|
|
846
|
+
extra = "\n\nPAGE-SPECIFIC INSTRUCTIONS (highest priority):\n" + pi + "\n"
|
|
847
|
+
|
|
823
848
|
return f"""
|
|
824
849
|
You are a senior UX designer + product copywriter for modern software websites.
|
|
825
850
|
|
|
@@ -834,6 +859,7 @@ def agentic_generate_page(*,
|
|
|
834
859
|
- Choose icon names only from the allowed icon list.
|
|
835
860
|
- Provide image search queries for items that need images. Keep queries on-topic (software/AI/tech).
|
|
836
861
|
|
|
862
|
+
{extra}
|
|
837
863
|
{req_lines}
|
|
838
864
|
|
|
839
865
|
OUTPUT:
|
|
@@ -887,7 +913,7 @@ def agentic_generate_page(*,
|
|
|
887
913
|
""".strip()
|
|
888
914
|
|
|
889
915
|
|
|
890
|
-
def _make_page_plan(*, page_slug: str, website_description: str, template_spec: dict) -> dict:
|
|
916
|
+
def _make_page_plan(*, page_slug: str, website_description: str, template_spec: dict, page_instructions: str = "") -> dict:
|
|
891
917
|
slug = _slugify(page_slug)
|
|
892
918
|
wd = _strip(website_description)
|
|
893
919
|
if not wd:
|
|
@@ -910,7 +936,7 @@ def agentic_generate_page(*,
|
|
|
910
936
|
}, indent=2)
|
|
911
937
|
|
|
912
938
|
plan = _get_json_call(
|
|
913
|
-
system_prompt=_page_plan_system_prompt(template_spec),
|
|
939
|
+
system_prompt=_page_plan_system_prompt(template_spec, page_instructions=page_instructions),
|
|
914
940
|
user_prompt=user_prompt
|
|
915
941
|
)
|
|
916
942
|
|
|
@@ -959,9 +985,10 @@ def agentic_generate_page(*,
|
|
|
959
985
|
raise ValueError("Plan is too light on imagery; needs at least 4 items marked needsImage=true.")
|
|
960
986
|
|
|
961
987
|
|
|
962
|
-
def _repair_plan(*, plan: dict, error_msg: str, website_description: str) -> dict:
|
|
963
|
-
# Ask
|
|
964
|
-
system_prompt = _page_plan_system_prompt() + "\n\nYou are repairing an existing plan. Keep it consistent and improve only what is needed."
|
|
988
|
+
def _repair_plan(*, plan: dict, error_msg: str, website_description: str, template_spec: dict, page_instructions: str = "") -> dict:
|
|
989
|
+
# Ask the model to repair the plan, not recreate randomly.
|
|
990
|
+
system_prompt = _page_plan_system_prompt(template_spec, page_instructions=page_instructions) + "\n\nYou are repairing an existing plan. Keep it consistent and improve only what is needed."
|
|
991
|
+
|
|
965
992
|
user_prompt = json.dumps({
|
|
966
993
|
"ERROR": error_msg,
|
|
967
994
|
"WEBSITE_DESCRIPTION": website_description,
|
|
@@ -1023,6 +1050,88 @@ def agentic_generate_page(*,
|
|
|
1023
1050
|
else:
|
|
1024
1051
|
hero["items"] = [{"id": "hero_media", "type": "card", "title": "Hero image", "text": "", "imageUrl": img}]
|
|
1025
1052
|
|
|
1053
|
+
|
|
1054
|
+
def _ensure_gallery_section(plan: dict, template_spec: dict) -> None:
|
|
1055
|
+
"""
|
|
1056
|
+
If we're generating a gallery page, guarantee at least one section has type="gallery"
|
|
1057
|
+
so compile_layout_to_html activates the horizontal rail + arrows + lightbox.
|
|
1058
|
+
"""
|
|
1059
|
+
try:
|
|
1060
|
+
if (template_spec or {}).get("category") != "gallery":
|
|
1061
|
+
return
|
|
1062
|
+
except Exception:
|
|
1063
|
+
return
|
|
1064
|
+
|
|
1065
|
+
secs = plan.get("sections")
|
|
1066
|
+
if not isinstance(secs, list) or not secs:
|
|
1067
|
+
return
|
|
1068
|
+
|
|
1069
|
+
def _stype(s):
|
|
1070
|
+
return str((s or {}).get("type") or "").strip().lower()
|
|
1071
|
+
|
|
1072
|
+
# If already has a gallery section, we're done.
|
|
1073
|
+
for s in secs:
|
|
1074
|
+
if isinstance(s, dict) and _stype(s) == "gallery":
|
|
1075
|
+
return
|
|
1076
|
+
|
|
1077
|
+
# Otherwise, convert the most image-heavy non-hero section into a gallery.
|
|
1078
|
+
best = None
|
|
1079
|
+
best_score = -1
|
|
1080
|
+
for s in secs:
|
|
1081
|
+
if not isinstance(s, dict):
|
|
1082
|
+
continue
|
|
1083
|
+
if _stype(s) == "hero":
|
|
1084
|
+
continue
|
|
1085
|
+
items = s.get("items") if isinstance(s.get("items"), list) else []
|
|
1086
|
+
score = 0
|
|
1087
|
+
for it in items:
|
|
1088
|
+
if not isinstance(it, dict):
|
|
1089
|
+
continue
|
|
1090
|
+
if it.get("needsImage") or it.get("imgQuery") or it.get("imageUrl"):
|
|
1091
|
+
score += 1
|
|
1092
|
+
if score > best_score:
|
|
1093
|
+
best_score = score
|
|
1094
|
+
best = s
|
|
1095
|
+
|
|
1096
|
+
if best and best_score > 0:
|
|
1097
|
+
best["type"] = "gallery"
|
|
1098
|
+
best.setdefault("id", "sec_gallery")
|
|
1099
|
+
if not str(best.get("title") or "").strip():
|
|
1100
|
+
best["title"] = "Gallery"
|
|
1101
|
+
if "cols" not in best:
|
|
1102
|
+
best["cols"] = 3
|
|
1103
|
+
return
|
|
1104
|
+
|
|
1105
|
+
# If nothing suitable exists, insert a fresh gallery section after the hero.
|
|
1106
|
+
new_sec = {
|
|
1107
|
+
"id": "sec_gallery",
|
|
1108
|
+
"type": "gallery",
|
|
1109
|
+
"title": "Gallery",
|
|
1110
|
+
"text": "Browse highlights from the platform modules and UI surfaces.",
|
|
1111
|
+
"cols": 3,
|
|
1112
|
+
"items": [
|
|
1113
|
+
{"id": "g1", "type": "card", "title": "Admin panel", "text": "Page Studio controls and content management.", "icon": "", "imgQuery": "admin panel dashboard ui", "needsImage": True},
|
|
1114
|
+
{"id": "g2", "type": "card", "title": "Docs hub", "text": "Two-column documentation view with navigation.", "icon": "", "imgQuery": "documentation website sidebar layout", "needsImage": True},
|
|
1115
|
+
{"id": "g3", "type": "card", "title": "ML lab", "text": "EDA and model results exported as reports.", "icon": "", "imgQuery": "machine learning dashboard analytics", "needsImage": True},
|
|
1116
|
+
{"id": "g4", "type": "card", "title": "Chat assistant", "text": "Grounded answers with retrieval context.", "icon": "", "imgQuery": "chatbot interface web app", "needsImage": True},
|
|
1117
|
+
{"id": "g5", "type": "card", "title": "Uploads", "text": "PDF and CSV ingestion workflows.", "icon": "", "imgQuery": "file upload web interface", "needsImage": True},
|
|
1118
|
+
{"id": "g6", "type": "card", "title": "Deployment", "text": "Client instances, roles, and configuration.", "icon": "", "imgQuery": "cloud deployment dashboard", "needsImage": True},
|
|
1119
|
+
],
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
# Insert after hero if present; else prepend.
|
|
1123
|
+
hero_idx = None
|
|
1124
|
+
for i, s in enumerate(secs):
|
|
1125
|
+
if isinstance(s, dict) and _stype(s) == "hero":
|
|
1126
|
+
hero_idx = i
|
|
1127
|
+
break
|
|
1128
|
+
|
|
1129
|
+
if hero_idx is None:
|
|
1130
|
+
secs.insert(0, new_sec)
|
|
1131
|
+
else:
|
|
1132
|
+
secs.insert(hero_idx + 1, new_sec)
|
|
1133
|
+
|
|
1134
|
+
|
|
1026
1135
|
TEMPLATE_SPECS = {
|
|
1027
1136
|
"generic_v1": {
|
|
1028
1137
|
"category": "landing",
|
|
@@ -1084,16 +1193,36 @@ def agentic_generate_page(*,
|
|
|
1084
1193
|
"min_images": 6,
|
|
1085
1194
|
"max_images": 9,
|
|
1086
1195
|
},
|
|
1196
|
+
"gallery_rail_v1": {
|
|
1197
|
+
"category": "gallery",
|
|
1198
|
+
"template": {"id": "gallery_rail_v1", "version": "1.0.0"},
|
|
1199
|
+
"allowed_section_types": ["hero", "gallery", "testimonials", "faq", "cta", "richtext"],
|
|
1200
|
+
"required_sections": [
|
|
1201
|
+
{"id": "sec_hero", "type": "hero"},
|
|
1202
|
+
{"id": "sec_gallery", "type": "gallery"},
|
|
1203
|
+
{"id": "sec_cta", "type": "cta"},
|
|
1204
|
+
],
|
|
1205
|
+
"min_sections": 3,
|
|
1206
|
+
"max_sections": 6,
|
|
1207
|
+
"min_images": 6,
|
|
1208
|
+
"max_images": 9,
|
|
1209
|
+
},
|
|
1087
1210
|
}
|
|
1088
1211
|
|
|
1089
1212
|
def _select_template_spec(slug: str) -> dict:
|
|
1090
1213
|
s = _slugify(slug)
|
|
1214
|
+
|
|
1215
|
+
if any(k in s for k in ("gallery", "portfolio", "showcase", "screenshots", "photos")):
|
|
1216
|
+
return TEMPLATE_SPECS["gallery_rail_v1"]
|
|
1217
|
+
|
|
1091
1218
|
if "service" in s:
|
|
1092
1219
|
if any(k in s for k in ("pricing", "plan", "plans", "package", "packages", "tier", "tiers")):
|
|
1093
1220
|
return TEMPLATE_SPECS["services_detail_v1"]
|
|
1094
1221
|
return TEMPLATE_SPECS["services_grid_v1"]
|
|
1222
|
+
|
|
1095
1223
|
if "about" in s:
|
|
1096
1224
|
return TEMPLATE_SPECS["about_glass_hero_v1"]
|
|
1225
|
+
|
|
1097
1226
|
return TEMPLATE_SPECS["generic_v1"]
|
|
1098
1227
|
|
|
1099
1228
|
|
|
@@ -1134,7 +1263,16 @@ def agentic_generate_page(*,
|
|
|
1134
1263
|
return r.content
|
|
1135
1264
|
|
|
1136
1265
|
|
|
1137
|
-
def _save_image(
|
|
1266
|
+
def _save_image(
|
|
1267
|
+
img_bytes: bytes,
|
|
1268
|
+
out_path_no_ext: str,
|
|
1269
|
+
*,
|
|
1270
|
+
max_width: int = 1920,
|
|
1271
|
+
thumb_out_path_no_ext: str | None = None,
|
|
1272
|
+
thumb_max_w: int = 640,
|
|
1273
|
+
thumb_max_h: int = 420,
|
|
1274
|
+
) -> tuple[str, str | None]:
|
|
1275
|
+
|
|
1138
1276
|
img = Image.open(io.BytesIO(img_bytes))
|
|
1139
1277
|
img.load()
|
|
1140
1278
|
|
|
@@ -1154,7 +1292,26 @@ def agentic_generate_page(*,
|
|
|
1154
1292
|
else:
|
|
1155
1293
|
img.save(out_path, "PNG", optimize=True)
|
|
1156
1294
|
|
|
1157
|
-
|
|
1295
|
+
thumb_path = None
|
|
1296
|
+
if thumb_out_path_no_ext:
|
|
1297
|
+
try:
|
|
1298
|
+
# Create a thumbnail derived from the already-decoded image
|
|
1299
|
+
t = img.copy()
|
|
1300
|
+
t.thumbnail((int(thumb_max_w), int(thumb_max_h)))
|
|
1301
|
+
t_has_alpha = ("A" in t.getbands())
|
|
1302
|
+
t_ext = ".png" if t_has_alpha else ".jpg"
|
|
1303
|
+
thumb_path = thumb_out_path_no_ext + t_ext
|
|
1304
|
+
os.makedirs(os.path.dirname(thumb_path), exist_ok=True)
|
|
1305
|
+
|
|
1306
|
+
if t_ext == ".jpg":
|
|
1307
|
+
rgbt = t.convert("RGB") if t.mode != "RGB" else t
|
|
1308
|
+
rgbt.save(thumb_path, "JPEG", quality=80, optimize=True, progressive=True)
|
|
1309
|
+
else:
|
|
1310
|
+
t.save(thumb_path, "PNG", optimize=True)
|
|
1311
|
+
except Exception:
|
|
1312
|
+
thumb_path = None
|
|
1313
|
+
|
|
1314
|
+
return out_path, thumb_path
|
|
1158
1315
|
|
|
1159
1316
|
|
|
1160
1317
|
def _pick_pixabay_hit(hits: list[dict], *, min_width: int) -> dict | None:
|
|
@@ -1255,9 +1412,24 @@ def agentic_generate_page(*,
|
|
|
1255
1412
|
except Exception:
|
|
1256
1413
|
pass
|
|
1257
1414
|
|
|
1258
|
-
|
|
1415
|
+
thumb_base = os.path.join(media_dir, "images", "thumbs", f"pixabay-{pid}")
|
|
1416
|
+
|
|
1417
|
+
saved, thumb_saved = _save_image(
|
|
1418
|
+
chosen_bytes,
|
|
1419
|
+
base,
|
|
1420
|
+
max_width=max_width,
|
|
1421
|
+
thumb_out_path_no_ext=thumb_base,
|
|
1422
|
+
thumb_max_w=640,
|
|
1423
|
+
thumb_max_h=420,
|
|
1424
|
+
)
|
|
1425
|
+
|
|
1259
1426
|
rel = os.path.relpath(saved, media_dir).replace("\\", "/")
|
|
1260
1427
|
it["imageUrl"] = f"/uploads/media/{rel}"
|
|
1428
|
+
|
|
1429
|
+
if thumb_saved:
|
|
1430
|
+
trel = os.path.relpath(thumb_saved, media_dir).replace("\\", "/")
|
|
1431
|
+
it["thumbUrl"] = f"/uploads/media/{trel}"
|
|
1432
|
+
|
|
1261
1433
|
downloads += 1
|
|
1262
1434
|
except Exception:
|
|
1263
1435
|
continue
|
|
@@ -1383,13 +1555,15 @@ def agentic_generate_page(*,
|
|
|
1383
1555
|
#{page_id} .hero-panel{{
|
|
1384
1556
|
max-width:700px;
|
|
1385
1557
|
border:1px solid rgba(148,163,184,.30);
|
|
1386
|
-
background:rgba(2,6,23,.24);
|
|
1387
1558
|
border-radius:var(--r);
|
|
1388
1559
|
padding:18px;
|
|
1389
|
-
backdrop-filter: blur(4px);
|
|
1390
1560
|
-webkit-backdrop-filter: blur(4px);
|
|
1391
1561
|
box-shadow: 0 18px 40px rgba(2,6,23,.18);
|
|
1392
1562
|
color:#e2e8f0;
|
|
1563
|
+
background:rgba(2,6,23,.13); /* more transparent */
|
|
1564
|
+
backdrop-filter: blur(18px) saturate(155%);
|
|
1565
|
+
-webkit-backdrop-filter: blur(18px) saturate(155%);
|
|
1566
|
+
|
|
1393
1567
|
}}
|
|
1394
1568
|
#{page_id} .hero-panel p{{ color:rgba(226,232,240,.84); }}
|
|
1395
1569
|
#{page_id} .hero-panel h1{{ text-shadow:0 10px 30px rgba(2,6,23,.45); }}
|
|
@@ -1610,8 +1784,13 @@ def agentic_generate_page(*,
|
|
|
1610
1784
|
notes = []
|
|
1611
1785
|
|
|
1612
1786
|
tpl_spec = _select_template_spec(page_slug)
|
|
1613
|
-
plan = _make_page_plan(
|
|
1787
|
+
plan = _make_page_plan(
|
|
1788
|
+
page_slug=page_slug,
|
|
1789
|
+
website_description=website_description,
|
|
1790
|
+
template_spec=tpl_spec, page_instructions=page_instructions
|
|
1791
|
+
)
|
|
1614
1792
|
|
|
1793
|
+
_ensure_gallery_section(plan, tpl_spec)
|
|
1615
1794
|
|
|
1616
1795
|
for attempt in range(max_retries + 1):
|
|
1617
1796
|
try:
|
|
@@ -1621,7 +1800,8 @@ def agentic_generate_page(*,
|
|
|
1621
1800
|
notes.append(f"plan_validation_failed: {e}")
|
|
1622
1801
|
if attempt >= max_retries:
|
|
1623
1802
|
raise
|
|
1624
|
-
plan = _repair_plan(plan=plan, error_msg=str(e), website_description=website_description)
|
|
1803
|
+
plan = _repair_plan(plan=plan, error_msg=str(e), website_description=website_description, template_spec=tpl_spec, page_instructions=page_instructions)
|
|
1804
|
+
|
|
1625
1805
|
|
|
1626
1806
|
# Fill images locally (Pixabay) to avoid broken links
|
|
1627
1807
|
if pixabay_api_key:
|
|
@@ -1659,7 +1839,7 @@ def agentic_generate_page(*,
|
|
|
1659
1839
|
if _contains_placeholders(blob):
|
|
1660
1840
|
raise RuntimeError("Refusing to publish: plan still contains placeholder-style text.")
|
|
1661
1841
|
|
|
1662
|
-
html =
|
|
1842
|
+
html = compile_layout_to_html(plan, page_slug=page_slug)
|
|
1663
1843
|
return {
|
|
1664
1844
|
"slug": _slugify(plan.get("page") or page_slug),
|
|
1665
1845
|
"plan": plan,
|
|
@@ -299,25 +299,31 @@ class OrchestrateMLSystem:
|
|
|
299
299
|
REFINED ML TASKS & SPECS:
|
|
300
300
|
{refined_tasks}
|
|
301
301
|
|
|
302
|
-
CONSTRAINTS:
|
|
302
|
+
### CONSTRAINTS:
|
|
303
303
|
- Assume 'df' is already loaded in the namespace. Do not use pd.read_csv.
|
|
304
304
|
- Include all up-to-dateimports (pandas, numpy, sklearn, matplotlib, seaborn, etc.).
|
|
305
305
|
- Avoid any deprecated mehods, imports, statements.
|
|
306
|
-
- FIRST: Implement the 'Cold Start' cleaning and preprocessing logic (Check for nulls
|
|
306
|
+
- FIRST: Implement the 'Cold Start' cleaning (if not already done) and preprocessing logic (Check for nulls and impute based on column types in context).
|
|
307
307
|
- SECOND: (Modeling) Implement the ML pipeline (Split, Scale, Train, Predict).
|
|
308
308
|
- THIRD: Implement the visualizations using the specific templates requested.
|
|
309
|
-
- Return ONLY the executable Python code. NO MARKDOWN (```), NO EXPLANATION.
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
309
|
+
- Return ONLY the executable Python code. NO MARKDOWN (```), NO EXPLANATION, NO PRELUDE, NO PREAMBLE.
|
|
310
|
+
|
|
311
|
+
### Output rendering rules (STRICT)
|
|
312
|
+
- NEVER use: print(df.to_html()), DataFrame.to_html(), Styler.to_html(), or printing raw "<table>...</table>" strings.
|
|
313
|
+
- For any DataFrame (including classification reports, contingency tables, summary tables, head/tail/middle samples):
|
|
314
|
+
- Always render with:
|
|
315
|
+
from IPython.display import display
|
|
316
|
+
display(df)
|
|
317
|
+
- For styled tables you may use:
|
|
318
|
+
display(df.style)
|
|
319
|
+
but only if it improves readability (keep it simple).
|
|
320
|
+
- If you need text commentary, use print("...") ONLY for plain text. Do not print HTML.
|
|
321
|
+
Always import display when you intend to show DataFrames: from IPython.display import display
|
|
316
322
|
"""
|
|
317
323
|
coder_profile = _prof.get_profile("coder")
|
|
318
324
|
if not coder_profile:
|
|
319
325
|
return "Error!: Set an appropriate coder profile"
|
|
320
|
-
|
|
326
|
+
|
|
321
327
|
thought, raw_code, usage = self._generate_ml_response(coder_profile ,system_prompt, user_prompt)
|
|
322
328
|
|
|
323
329
|
# Robustly strip any potential markdown formatting
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# syntaxmatrix/client_docs.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import html
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import List, Tuple
|
|
9
|
+
|
|
10
|
+
from flask import abort, render_template
|
|
11
|
+
from markupsafe import Markup
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class TocItem:
|
|
16
|
+
level: int
|
|
17
|
+
id: str
|
|
18
|
+
text: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_slug_rx = re.compile(r"[^a-z0-9\- ]+")
|
|
22
|
+
_ws_rx = re.compile(r"\s+")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _slugify(text: str) -> str:
|
|
26
|
+
t = text.strip().lower()
|
|
27
|
+
t = _slug_rx.sub("", t)
|
|
28
|
+
t = _ws_rx.sub("-", t)
|
|
29
|
+
t = t.strip("-")
|
|
30
|
+
return t or "section"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _extract_headings(md: str) -> List[Tuple[int, str]]:
|
|
34
|
+
"""
|
|
35
|
+
Extract ATX-style markdown headings (#, ##, ###, ...), ignoring fenced code blocks.
|
|
36
|
+
"""
|
|
37
|
+
headings: List[Tuple[int, str]] = []
|
|
38
|
+
in_code = False
|
|
39
|
+
for line in md.splitlines():
|
|
40
|
+
if line.strip().startswith("```"):
|
|
41
|
+
in_code = not in_code
|
|
42
|
+
continue
|
|
43
|
+
if in_code:
|
|
44
|
+
continue
|
|
45
|
+
|
|
46
|
+
m = re.match(r"^(#{1,6})\s+(.+?)\s*$", line)
|
|
47
|
+
if not m:
|
|
48
|
+
continue
|
|
49
|
+
level = len(m.group(1))
|
|
50
|
+
title = m.group(2).strip()
|
|
51
|
+
# Avoid weird headings like "### ----"
|
|
52
|
+
if title and not all(ch in "-_=*" for ch in title):
|
|
53
|
+
headings.append((level, title))
|
|
54
|
+
return headings
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _render_markdown_minimal(md: str, heading_ids: dict[str, str]) -> str:
|
|
58
|
+
"""
|
|
59
|
+
Minimal markdown renderer (safe-by-default):
|
|
60
|
+
- headings, paragraphs, bullet lists, code fences, inline code, links, images
|
|
61
|
+
- everything else is HTML-escaped
|
|
62
|
+
"""
|
|
63
|
+
lines = md.splitlines()
|
|
64
|
+
out: List[str] = []
|
|
65
|
+
|
|
66
|
+
in_code = False
|
|
67
|
+
code_lang = ""
|
|
68
|
+
code_buf: List[str] = []
|
|
69
|
+
|
|
70
|
+
in_ul = False
|
|
71
|
+
|
|
72
|
+
def flush_ul():
|
|
73
|
+
nonlocal in_ul
|
|
74
|
+
if in_ul:
|
|
75
|
+
out.append("</ul>")
|
|
76
|
+
in_ul = False
|
|
77
|
+
|
|
78
|
+
def flush_code():
|
|
79
|
+
nonlocal in_code, code_lang, code_buf
|
|
80
|
+
if not in_code:
|
|
81
|
+
return
|
|
82
|
+
code_text = "\n".join(code_buf)
|
|
83
|
+
out.append(
|
|
84
|
+
f'<pre class="smx-code"><code class="language-{html.escape(code_lang)}">'
|
|
85
|
+
f"{html.escape(code_text)}</code></pre>"
|
|
86
|
+
)
|
|
87
|
+
in_code = False
|
|
88
|
+
code_lang = ""
|
|
89
|
+
code_buf = []
|
|
90
|
+
|
|
91
|
+
def inline_fmt(s: str) -> str:
|
|
92
|
+
s = html.escape(s)
|
|
93
|
+
|
|
94
|
+
# inline code: `code`
|
|
95
|
+
s = re.sub(r"`([^`]+)`", lambda m: f"<code>{html.escape(m.group(1))}</code>", s)
|
|
96
|
+
|
|
97
|
+
# images: 
|
|
98
|
+
s = re.sub(
|
|
99
|
+
r"!\[([^\]]*)\]\(([^)]+)\)",
|
|
100
|
+
lambda m: f'<img alt="{html.escape(m.group(1))}" src="{html.escape(m.group(2))}" />',
|
|
101
|
+
s,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# links: [text](url)
|
|
105
|
+
s = re.sub(
|
|
106
|
+
r"\[([^\]]+)\]\(([^)]+)\)",
|
|
107
|
+
lambda m: f'<a href="{html.escape(m.group(2))}" target="_blank" rel="noopener noreferrer">{html.escape(m.group(1))}</a>',
|
|
108
|
+
s,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# bold **text**
|
|
112
|
+
s = re.sub(r"\*\*([^*]+)\*\*", r"<strong>\1</strong>", s)
|
|
113
|
+
|
|
114
|
+
# italics *text* (simple)
|
|
115
|
+
s = re.sub(r"(?<!\*)\*([^*]+)\*(?!\*)", r"<em>\1</em>", s)
|
|
116
|
+
|
|
117
|
+
return s
|
|
118
|
+
|
|
119
|
+
# Pre-map heading text to stable IDs (supports duplicates)
|
|
120
|
+
# heading_ids is already built with de-duplication.
|
|
121
|
+
for raw in lines:
|
|
122
|
+
line = raw.rstrip("\n")
|
|
123
|
+
|
|
124
|
+
# fenced code blocks
|
|
125
|
+
m_code = re.match(r"^\s*```(\w+)?\s*$", line)
|
|
126
|
+
if m_code:
|
|
127
|
+
flush_ul()
|
|
128
|
+
if in_code:
|
|
129
|
+
flush_code()
|
|
130
|
+
else:
|
|
131
|
+
in_code = True
|
|
132
|
+
code_lang = (m_code.group(1) or "").strip()
|
|
133
|
+
code_buf = []
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
if in_code:
|
|
137
|
+
code_buf.append(line)
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
# headings
|
|
141
|
+
m_h = re.match(r"^(#{1,6})\s+(.+?)\s*$", line)
|
|
142
|
+
if m_h:
|
|
143
|
+
flush_ul()
|
|
144
|
+
level = len(m_h.group(1))
|
|
145
|
+
text = m_h.group(2).strip()
|
|
146
|
+
hid = heading_ids.get(text) or _slugify(text)
|
|
147
|
+
out.append(
|
|
148
|
+
f'<h{level} id="{html.escape(hid)}" class="smx-h">{inline_fmt(text)}</h{level}>'
|
|
149
|
+
)
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
# bullet list items (supports "-" or "*")
|
|
153
|
+
m_li = re.match(r"^\s*[-*]\s+(.+)\s*$", line)
|
|
154
|
+
if m_li:
|
|
155
|
+
if not in_ul:
|
|
156
|
+
out.append("<ul>")
|
|
157
|
+
in_ul = True
|
|
158
|
+
out.append(f"<li>{inline_fmt(m_li.group(1).strip())}</li>")
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
# blank line ends lists/paragraph chunks
|
|
162
|
+
if line.strip() == "":
|
|
163
|
+
flush_ul()
|
|
164
|
+
out.append("")
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
# blockquote (single-line)
|
|
168
|
+
m_bq = re.match(r"^\s*>\s+(.+)\s*$", line)
|
|
169
|
+
if m_bq:
|
|
170
|
+
flush_ul()
|
|
171
|
+
out.append(f"<blockquote>{inline_fmt(m_bq.group(1).strip())}</blockquote>")
|
|
172
|
+
continue
|
|
173
|
+
|
|
174
|
+
# horizontal rule
|
|
175
|
+
if re.match(r"^\s*---\s*$", line):
|
|
176
|
+
flush_ul()
|
|
177
|
+
out.append("<hr/>")
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
# normal paragraph line
|
|
181
|
+
flush_ul()
|
|
182
|
+
out.append(f"<p>{inline_fmt(line.strip())}</p>")
|
|
183
|
+
|
|
184
|
+
flush_ul()
|
|
185
|
+
flush_code()
|
|
186
|
+
|
|
187
|
+
return "\n".join(out)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def build_docs_html_and_toc(md: str) -> Tuple[str, List[TocItem]]:
|
|
191
|
+
headings = _extract_headings(md)
|
|
192
|
+
|
|
193
|
+
# Build stable unique IDs
|
|
194
|
+
used: dict[str, int] = {}
|
|
195
|
+
heading_ids: dict[str, str] = {}
|
|
196
|
+
|
|
197
|
+
toc: List[TocItem] = []
|
|
198
|
+
for level, title in headings:
|
|
199
|
+
base = _slugify(title)
|
|
200
|
+
n = used.get(base, 0)
|
|
201
|
+
used[base] = n + 1
|
|
202
|
+
hid = base if n == 0 else f"{base}-{n+1}"
|
|
203
|
+
|
|
204
|
+
# map by raw title (good enough for this README)
|
|
205
|
+
# if duplicates exist with same title, only first maps here; duplicates are still in TOC correctly
|
|
206
|
+
if title not in heading_ids:
|
|
207
|
+
heading_ids[title] = hid
|
|
208
|
+
|
|
209
|
+
toc.append(TocItem(level=level, id=hid, text=title))
|
|
210
|
+
|
|
211
|
+
html_content = _render_markdown_minimal(md, heading_ids)
|
|
212
|
+
return html_content, toc
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def register_client_docs_routes(app, client_dir: str) -> None:
|
|
216
|
+
"""
|
|
217
|
+
Call this once during app initialisation.
|
|
218
|
+
Exposes:
|
|
219
|
+
GET /docs
|
|
220
|
+
"""
|
|
221
|
+
@app.get("/docs")
|
|
222
|
+
def smx_client_docs():
|
|
223
|
+
readme_path = os.path.join(client_dir, "README.md")
|
|
224
|
+
if not os.path.exists(readme_path):
|
|
225
|
+
abort(404, description="README.md not found in client root")
|
|
226
|
+
|
|
227
|
+
with open(readme_path, "r", encoding="utf-8") as f:
|
|
228
|
+
md = f.read()
|
|
229
|
+
|
|
230
|
+
docs_html, toc = build_docs_html_and_toc(md)
|
|
231
|
+
|
|
232
|
+
return render_template(
|
|
233
|
+
"client_docs.html",
|
|
234
|
+
page_title="System Documentation",
|
|
235
|
+
toc=toc,
|
|
236
|
+
docs_html=Markup(docs_html),
|
|
237
|
+
)
|