syntaxmatrix 2.5.5.5__py3-none-any.whl → 2.5.6.1__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 +3 -2
- syntaxmatrix/agentic/agents.py +14 -23
- syntaxmatrix/auth.py +142 -5
- syntaxmatrix/core.py +34 -15
- syntaxmatrix/generate_page.py +17 -7
- syntaxmatrix/preface.py +550 -0
- syntaxmatrix/routes.py +238 -177
- syntaxmatrix/templates/change_password.html +124 -0
- syntaxmatrix/templates/dashboard.html +12 -10
- syntaxmatrix/utils.py +363 -481
- {syntaxmatrix-2.5.5.5.dist-info → syntaxmatrix-2.5.6.1.dist-info}/METADATA +1 -1
- {syntaxmatrix-2.5.5.5.dist-info → syntaxmatrix-2.5.6.1.dist-info}/RECORD +15 -13
- {syntaxmatrix-2.5.5.5.dist-info → syntaxmatrix-2.5.6.1.dist-info}/WHEEL +0 -0
- {syntaxmatrix-2.5.5.5.dist-info → syntaxmatrix-2.5.6.1.dist-info}/licenses/LICENSE.txt +0 -0
- {syntaxmatrix-2.5.5.5.dist-info → syntaxmatrix-2.5.6.1.dist-info}/top_level.txt +0 -0
syntaxmatrix/routes.py
CHANGED
|
@@ -35,7 +35,9 @@ from syntaxmatrix.settings.string_navbar import string_navbar_items
|
|
|
35
35
|
from syntaxmatrix.settings.model_map import GPT_MODELS_LATEST, PROVIDERS_MODELS, MODEL_DESCRIPTIONS, PURPOSE_TAGS, EMBEDDING_MODELS
|
|
36
36
|
from syntaxmatrix.project_root import detect_project_root
|
|
37
37
|
from syntaxmatrix import generate_page as _genpage
|
|
38
|
-
from syntaxmatrix import auth as _auth
|
|
38
|
+
from syntaxmatrix import auth as _auth
|
|
39
|
+
from .auth import register_user, authenticate, login_required, admin_required, superadmin_required, update_password
|
|
40
|
+
|
|
39
41
|
from syntaxmatrix import profiles as _prof
|
|
40
42
|
from syntaxmatrix.gpt_models_latest import set_args, extract_output_text as _out
|
|
41
43
|
from syntaxmatrix.agentic.agents import classify_ml_job_agent, refine_question_agent, text_formatter_agent
|
|
@@ -219,24 +221,30 @@ def setup_routes(smx):
|
|
|
219
221
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
220
222
|
<title>{smx.page}</title>
|
|
221
223
|
<style>
|
|
224
|
+
/* ----- HTML/BODY ----------------------------------- */
|
|
225
|
+
html {{
|
|
226
|
+
font-size: clamp(12px, 1.7vw, 18px);
|
|
227
|
+
/* scrollbar-gutter: stable both-edges; */
|
|
228
|
+
}}
|
|
222
229
|
body {{
|
|
223
|
-
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
224
|
-
margin: 0 20px;
|
|
225
230
|
padding: 0;
|
|
231
|
+
margin: 0;
|
|
226
232
|
background: {smx.theme["background"]};
|
|
227
233
|
color: {smx.theme["text_color"]};
|
|
228
234
|
}}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
235
|
+
html, body {{ scroll-behavior: auto; }}
|
|
236
|
+
.admin-grid, .admin-shell .card {{ min-width: 0; }}
|
|
237
|
+
html, body, .admin-shell {{ overflow-x: visible !important; }}
|
|
238
|
+
</style>
|
|
239
|
+
<style>
|
|
240
|
+
/* ----- NAVBAR -------------------------------- */
|
|
233
241
|
/* Desktop Navbar */
|
|
234
242
|
nav {{
|
|
235
243
|
display: flex;
|
|
236
244
|
justify-content: space-between;
|
|
237
245
|
align-items: center;
|
|
238
246
|
background: {smx.theme["nav_background"]};
|
|
239
|
-
padding: 10px
|
|
247
|
+
padding: 10px 24px;
|
|
240
248
|
position: fixed;
|
|
241
249
|
top: 0;
|
|
242
250
|
left: 0;
|
|
@@ -246,19 +254,36 @@ def setup_routes(smx):
|
|
|
246
254
|
.nav-left {{
|
|
247
255
|
display: flex;
|
|
248
256
|
align-items: center;
|
|
249
|
-
}}
|
|
250
|
-
.nav-left .logo {{
|
|
251
|
-
font-size: clamp(1.3rem, 2vw, 1.5rem);
|
|
252
|
-
font-weight: bold;
|
|
253
257
|
color: {smx.theme["nav_text"]};
|
|
254
|
-
|
|
258
|
+
gap: 8px;
|
|
255
259
|
}}
|
|
256
|
-
.nav-left .
|
|
257
|
-
|
|
260
|
+
.nav-left .logo {{
|
|
261
|
+
align-items: center;
|
|
262
|
+
font-weight: bold;
|
|
263
|
+
font-size: clamp(1.4rem, 1.8vw, 1.8rem);
|
|
264
|
+
margin-right: 0;
|
|
265
|
+
}}
|
|
266
|
+
.logo img {{
|
|
267
|
+
display: block;
|
|
268
|
+
width: clamp(1.4rem, 1.8vw, 1.8rem);
|
|
269
|
+
}}
|
|
270
|
+
.nav-left a {{
|
|
258
271
|
color: {smx.theme["nav_text"]};
|
|
259
272
|
text-decoration: none;
|
|
260
273
|
margin-right: 15px;
|
|
261
274
|
}}
|
|
275
|
+
|
|
276
|
+
.nav-left .nav-links a.active,
|
|
277
|
+
.nav-left .nav-links a.active:hover,
|
|
278
|
+
#mobile-nav a.active,
|
|
279
|
+
#mobile-nav a.active:hover {{
|
|
280
|
+
background-color: var(--nav-bg) !important; /* keep the same base */
|
|
281
|
+
box-shadow: inset 0 0 0 9999px rgba(0,0,0,.52); /* darken ~52% */
|
|
282
|
+
border-radius: 6px;
|
|
283
|
+
padding: 2px 8px;
|
|
284
|
+
color:cyan;
|
|
285
|
+
}}
|
|
286
|
+
|
|
262
287
|
.nav-right a {{
|
|
263
288
|
font-size: clamp(1rem, 1.2vw, 1.2rem);
|
|
264
289
|
color: {smx.theme["nav_text"]};
|
|
@@ -289,7 +314,7 @@ def setup_routes(smx):
|
|
|
289
314
|
display: flex;
|
|
290
315
|
flex-direction: column;
|
|
291
316
|
gap: 10px;
|
|
292
|
-
z-index:
|
|
317
|
+
z-index: 1000;
|
|
293
318
|
color: {mobile_text_color};
|
|
294
319
|
}}
|
|
295
320
|
#mobile-nav a {{
|
|
@@ -308,24 +333,29 @@ def setup_routes(smx):
|
|
|
308
333
|
/* Responsive adjustments for mobile */
|
|
309
334
|
@media (max-width: 768px) {{
|
|
310
335
|
.nav-left .nav-links, .nav-right {{
|
|
311
|
-
|
|
336
|
+
display: none;
|
|
312
337
|
}}
|
|
313
338
|
#hamburger-btn {{
|
|
314
339
|
display: block;
|
|
315
340
|
}}
|
|
316
|
-
|
|
341
|
+
body {{
|
|
342
|
+
padding: 0 10px;
|
|
343
|
+
}}
|
|
317
344
|
}}
|
|
318
|
-
|
|
345
|
+
</style>
|
|
346
|
+
|
|
347
|
+
<style>
|
|
348
|
+
/* ----- SIDEBAR ---------------------------------------------------------- */
|
|
319
349
|
#sidebar {{
|
|
320
350
|
position: fixed;
|
|
321
351
|
top: 40px;
|
|
322
|
-
left: -
|
|
352
|
+
left: -260px;
|
|
323
353
|
width: var(--sidebar-w);
|
|
324
|
-
height: calc(100% -
|
|
354
|
+
height: calc(100% - 2px);
|
|
325
355
|
background: {smx.theme["sidebar_background"]};
|
|
326
356
|
overflow-y: auto;
|
|
327
357
|
padding: 10px; 5px;
|
|
328
|
-
font-size: 1.2rem;
|
|
358
|
+
font-size: clamp(1.2rem, 1.4vw, 1.6rem);
|
|
329
359
|
gap: 10px;
|
|
330
360
|
box-shadow: 2px 0 5px rgba(0,0,0,0.3);
|
|
331
361
|
transition: left 0.3s ease;
|
|
@@ -334,7 +364,7 @@ def setup_routes(smx):
|
|
|
334
364
|
}}
|
|
335
365
|
#sidebar a {{
|
|
336
366
|
color: {get_contrast_color(smx.theme["sidebar_background"])};
|
|
337
|
-
|
|
367
|
+
padding:3px;
|
|
338
368
|
text-decoration: none;
|
|
339
369
|
}}
|
|
340
370
|
#sidebar.open {{
|
|
@@ -359,18 +389,28 @@ def setup_routes(smx):
|
|
|
359
389
|
background-color: rgba(0, 0, 0, 0.05);
|
|
360
390
|
transform: scale(1.2);
|
|
361
391
|
}}
|
|
392
|
+
</style>
|
|
393
|
+
<style>
|
|
394
|
+
/* ----- CHAT HISTORY ---------------------------------------------------- */
|
|
362
395
|
#chat-history {{
|
|
363
396
|
width: 100%;
|
|
364
397
|
max-width: 980px;
|
|
365
|
-
margin: 50px auto 10px auto;
|
|
366
|
-
padding: 10px 5px;
|
|
367
398
|
background: {smx.theme["chat_background"]};
|
|
368
399
|
border-radius: 20px;
|
|
369
400
|
overflow-y: auto;
|
|
370
401
|
min-height: 360px;
|
|
402
|
+
margin: 50px auto 10px auto;
|
|
403
|
+
padding: 10px 5px 0 5px;
|
|
404
|
+
padding-bottom: calc(var(--composer-h, 104px) + 78);
|
|
371
405
|
}}
|
|
406
|
+
#chat-history .chat-message {{
|
|
407
|
+
scroll-margin-bottom: calc(var(--composer-h, 104px) + 78);
|
|
408
|
+
}}
|
|
409
|
+
#chat-history, #widget-container {{ overflow-anchor: none; }}
|
|
410
|
+
|
|
372
411
|
#chat-history-default {{
|
|
373
412
|
width: 100%;
|
|
413
|
+
max-width: 100%;
|
|
374
414
|
margin: 45px auto 10px auto;
|
|
375
415
|
padding: 10px 5px;
|
|
376
416
|
background: {smx.theme["chat_background"]};
|
|
@@ -384,13 +424,13 @@ def setup_routes(smx):
|
|
|
384
424
|
transform:scale(1.2);
|
|
385
425
|
transition: all 0.3s ease;
|
|
386
426
|
}}
|
|
427
|
+
|
|
428
|
+
{ _chat_css() }
|
|
429
|
+
|
|
387
430
|
#widget-container {{
|
|
388
|
-
max-width:
|
|
431
|
+
max-width: 100%;
|
|
389
432
|
margin: 0 auto 40px auto;
|
|
390
433
|
}}
|
|
391
|
-
|
|
392
|
-
{ _chat_css() }
|
|
393
|
-
|
|
394
434
|
.closeable-div {{
|
|
395
435
|
position: relative;
|
|
396
436
|
padding: 20px;
|
|
@@ -410,27 +450,20 @@ def setup_routes(smx):
|
|
|
410
450
|
.close-btn:hover {{
|
|
411
451
|
color: #ff0000;
|
|
412
452
|
}}
|
|
413
|
-
|
|
414
|
-
<style>
|
|
453
|
+
|
|
415
454
|
@keyframes spin {{
|
|
416
455
|
0% {{ transform: rotate(0deg); }}
|
|
417
456
|
100% {{ transform: rotate(360deg); }}
|
|
418
457
|
}}
|
|
419
|
-
|
|
420
|
-
</style>
|
|
421
|
-
<style>
|
|
422
458
|
.dropdown:hover .dropdown-content {{
|
|
423
459
|
display: block;
|
|
424
460
|
}}
|
|
425
|
-
</style>
|
|
426
|
-
<style>
|
|
427
461
|
/* Keep the shift amount equal to the actual sidebar width */
|
|
428
462
|
:root {{ --sidebar-w: 16vw; --nav-bg: {{smx.theme["nav_background"]}}; }}
|
|
429
463
|
|
|
430
464
|
/* Messages slide; composer doesn't stay shifted */
|
|
431
|
-
#chat-history,
|
|
432
|
-
|
|
433
|
-
|
|
465
|
+
#chat-history, #widget-container {{ transition: transform .45s ease; }}
|
|
466
|
+
|
|
434
467
|
/* Messages move fully clear of the sidebar */
|
|
435
468
|
body.sidebar-open #chat-history {{ transform: translateX(calc(var(--sidebar-w) * 0.30)); }}
|
|
436
469
|
|
|
@@ -447,56 +480,23 @@ def setup_routes(smx):
|
|
|
447
480
|
#widget-container, #smx-widgets {{
|
|
448
481
|
position: sticky;
|
|
449
482
|
bottom: 0;
|
|
450
|
-
z-index: 1100;
|
|
483
|
+
z-index: 1100;
|
|
451
484
|
background: inherit;
|
|
452
485
|
}}
|
|
453
|
-
|
|
454
|
-
padding-bottom: calc(var(--composer-h, 104px) + 78);
|
|
455
|
-
}}
|
|
456
|
-
#chat-history .chat-message {{
|
|
457
|
-
scroll-margin-bottom: calc(var(--composer-h, 104px) + 78);
|
|
458
|
-
}}
|
|
459
|
-
/* Stop browser scroll-anchoring from fighting our autoscroll */
|
|
460
|
-
#chat-history,
|
|
461
|
-
#widget-container {{
|
|
462
|
-
overflow-anchor: none;
|
|
463
|
-
}}
|
|
464
|
-
|
|
465
|
-
/* Reduce visual “jump” when the scrollbar appears/disappears */
|
|
466
|
-
html {{
|
|
467
|
-
scrollbar-gutter: stable both-edges;
|
|
468
|
-
}}
|
|
469
|
-
|
|
470
|
-
/* Avoid unexpected smooth scrolling that can look like a jerk */
|
|
471
|
-
html, body {{
|
|
472
|
-
scroll-behavior: auto;
|
|
473
|
-
}}
|
|
474
|
-
|
|
486
|
+
|
|
475
487
|
/* Textarea bounds */
|
|
476
488
|
.chat-composer {{ min-width:0; max-height:12vh; }}
|
|
477
|
-
@media (max-width:
|
|
489
|
+
@media (max-width:1200px){{
|
|
478
490
|
.chat-composer {{
|
|
479
491
|
min-height:56px;
|
|
480
492
|
line-height:1.4;
|
|
481
493
|
white-space: pre-wrap;
|
|
482
|
-
padding: 10px 10px 16px
|
|
483
|
-
font-size: 16px;
|
|
484
|
-
overflow-y: auto;
|
|
494
|
+
padding: 10px 10px 16px 12px;
|
|
495
|
+
font-size: 16px;
|
|
496
|
+
overflow-y: auto;
|
|
485
497
|
box-sizing: border-box;
|
|
486
498
|
}}
|
|
487
499
|
}}
|
|
488
|
-
|
|
489
|
-
.nav-left .nav-links a.active,
|
|
490
|
-
.nav-left .nav-links a.active:hover,
|
|
491
|
-
#mobile-nav a.active,
|
|
492
|
-
#mobile-nav a.active:hover {{
|
|
493
|
-
background-color: var(--nav-bg) !important; /* keep the same base */
|
|
494
|
-
box-shadow: inset 0 0 0 9999px rgba(0,0,0,.52); /* darken ~52% */
|
|
495
|
-
border-radius: 6px;
|
|
496
|
-
padding: 2px 8px;
|
|
497
|
-
color:cyan;
|
|
498
|
-
}}
|
|
499
|
-
|
|
500
500
|
</style>
|
|
501
501
|
|
|
502
502
|
<!-- Add MathJax -->
|
|
@@ -513,7 +513,7 @@ def setup_routes(smx):
|
|
|
513
513
|
}});
|
|
514
514
|
</script>
|
|
515
515
|
<script>
|
|
516
|
-
|
|
516
|
+
// Turn the latest bot <p> into fade-in “lines” and reveal them sequentially
|
|
517
517
|
function splitToLines(node){{
|
|
518
518
|
// If there are list items, animate them item-by-item.
|
|
519
519
|
const lis = node.querySelectorAll('li');
|
|
@@ -629,10 +629,13 @@ def setup_routes(smx):
|
|
|
629
629
|
'</form>'
|
|
630
630
|
)
|
|
631
631
|
else:
|
|
632
|
+
# Only show Register link if the consumer app explicitly enabled it.
|
|
633
|
+
reg_link = ""
|
|
634
|
+
if getattr(smx, "registration_enabled", False):
|
|
635
|
+
reg_link = f'|<a href="{url_for("register")}" class="nav-link">Register</a>'
|
|
632
636
|
auth_links = (
|
|
633
637
|
f'<a href="{url_for("login")}" class="nav-link">Login</a>'
|
|
634
|
-
'
|
|
635
|
-
f'<a href="{url_for("register")}" class="nav-link">Register</a>'
|
|
638
|
+
f'{reg_link}'
|
|
636
639
|
)
|
|
637
640
|
|
|
638
641
|
desktop_nav = f"""
|
|
@@ -708,7 +711,7 @@ def setup_routes(smx):
|
|
|
708
711
|
.chat-message.user {{
|
|
709
712
|
background: #e4e8ed;
|
|
710
713
|
float: right;
|
|
711
|
-
margin-right:
|
|
714
|
+
margin-right: 20px;
|
|
712
715
|
border-top-right-radius: 2px;
|
|
713
716
|
}}
|
|
714
717
|
.chat-message.user::after {{
|
|
@@ -1501,33 +1504,7 @@ def setup_routes(smx):
|
|
|
1501
1504
|
@smx.app.route("/", methods=["GET", "POST"])
|
|
1502
1505
|
def home():
|
|
1503
1506
|
smx.page = ""
|
|
1504
|
-
|
|
1505
|
-
# session["current_session"] = {"id": str(uuid.uuid4()), "title": "Current", "history": []}
|
|
1506
|
-
# session.setdefault("past_sessions", [])
|
|
1507
|
-
# session.setdefault("chat_history", [])
|
|
1508
|
-
# session["active_chat_id"] = session["current_session"]["id"]
|
|
1509
|
-
|
|
1510
|
-
# if session.pop("needs_end_chat", False):
|
|
1511
|
-
# current_history = session.get("chat_history", [])
|
|
1512
|
-
# current_session = session.get("current_session", {"id": str(uuid.uuid4()), "title": "Current", "history": []})
|
|
1513
|
-
# past_sessions = session.get("past_sessions", [])
|
|
1514
|
-
|
|
1515
|
-
# generated_title = smx.generate_contextual_title(current_history)
|
|
1516
|
-
# current_session["title"] = generated_title
|
|
1517
|
-
# past_sessions.insert(0, {"id": current_session["id"], "title": current_session["title"]})
|
|
1518
|
-
|
|
1519
|
-
# session["past_sessions"] = past_sessions
|
|
1520
|
-
# session["current_session"] = {"id": str(uuid.uuid4()), "title": "Current", "history": []}
|
|
1521
|
-
# session["active_chat_id"] = session["current_session"]["id"]
|
|
1522
|
-
# session["chat_history"] = []
|
|
1523
|
-
|
|
1524
|
-
# session["user_query"] = ""
|
|
1525
|
-
# session["app_token"] = smx.app_token
|
|
1526
|
-
|
|
1527
|
-
# cur = session.get("current_session")
|
|
1528
|
-
# if cur and cur.get("id") and session.get("active_chat_id") != cur["id"]:
|
|
1529
|
-
# session["active_chat_id"] = cur["id"]
|
|
1530
|
-
|
|
1507
|
+
|
|
1531
1508
|
if not session.get("current_session"):
|
|
1532
1509
|
# metadata only: id + title
|
|
1533
1510
|
session["current_session"] = {"id": str(uuid.uuid4()), "title": "Current"}
|
|
@@ -2397,31 +2374,18 @@ def setup_routes(smx):
|
|
|
2397
2374
|
home_page_html = f"""
|
|
2398
2375
|
{head_html()}
|
|
2399
2376
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
2377
|
+
|
|
2400
2378
|
<style>
|
|
2401
|
-
/* Match /dashboard font scale */
|
|
2402
|
-
:root{{
|
|
2403
|
-
--smx-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
|
|
2404
|
-
Arial, "Noto Sans", "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
|
|
2405
|
-
--smx-font-size: 16px; /* >=16px prevents iOS zoom */
|
|
2406
|
-
--smx-line: 1.55;
|
|
2407
|
-
}}
|
|
2408
|
-
html{{ -webkit-text-size-adjust: 100%; }}
|
|
2409
|
-
body{{ font-family: var(--smx-font); font-size: var(--smx-font-size); line-height: var(--smx-line); }}
|
|
2410
|
-
</style>
|
|
2411
|
-
<style>
|
|
2412
|
-
/* Container sizing & equal gutters */
|
|
2413
2379
|
.chat-container{{
|
|
2414
2380
|
max-width: 820px;
|
|
2415
2381
|
margin-inline: auto;
|
|
2416
2382
|
padding-inline: 12px;
|
|
2417
2383
|
box-sizing: border-box;
|
|
2418
2384
|
}}
|
|
2419
|
-
|
|
2420
|
-
/* Bubbles never overflow the viewport width */
|
|
2421
2385
|
.chat-messages{{
|
|
2422
2386
|
overflow-wrap: anywhere;
|
|
2423
2387
|
word-break: break-word;
|
|
2424
|
-
padding-bottom: 84px;
|
|
2388
|
+
padding-bottom: 84px;
|
|
2425
2389
|
}}
|
|
2426
2390
|
|
|
2427
2391
|
/* Sticky footer input area (safe on iOS address-bar) */
|
|
@@ -2466,8 +2430,7 @@ def setup_routes(smx):
|
|
|
2466
2430
|
@supports (-webkit-touch-callout: none){{
|
|
2467
2431
|
.chat-footer textarea{{ resize: none; }}
|
|
2468
2432
|
}}
|
|
2469
|
-
|
|
2470
|
-
<style>
|
|
2433
|
+
|
|
2471
2434
|
/* Desktop: push chat-history a little more than the base shift */
|
|
2472
2435
|
body.sidebar-open #chat-history{{
|
|
2473
2436
|
transform: translateX(calc(var(--sidebar-shift, var(--sidebar-w)) - 90px));
|
|
@@ -2499,10 +2462,9 @@ def setup_routes(smx):
|
|
|
2499
2462
|
}}
|
|
2500
2463
|
/* Typewriter look during streaming only */
|
|
2501
2464
|
.chat-message.bot.streaming .stream-target{{
|
|
2502
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
|
2503
2465
|
font-variant-ligatures: none;
|
|
2504
2466
|
white-space: pre-wrap; /* keep line breaks as they stream */
|
|
2505
|
-
letter-spacing: 0.
|
|
2467
|
+
letter-spacing: 0.02em; /* subtle spacing for “typed” feel */
|
|
2506
2468
|
}}
|
|
2507
2469
|
|
|
2508
2470
|
/* Blinking caret visible only while streaming */
|
|
@@ -2525,8 +2487,7 @@ def setup_routes(smx):
|
|
|
2525
2487
|
0%, 100% {{ opacity: 0; }}
|
|
2526
2488
|
50% {{ opacity: 1; }}
|
|
2527
2489
|
}}
|
|
2528
|
-
|
|
2529
|
-
<style id="smx-structured-style">
|
|
2490
|
+
|
|
2530
2491
|
/* Container for structured bot content */
|
|
2531
2492
|
.chat-message.bot .smx-structured {{
|
|
2532
2493
|
margin-top: 4px;
|
|
@@ -2540,10 +2501,11 @@ def setup_routes(smx):
|
|
|
2540
2501
|
margin: 8px 0 4px;
|
|
2541
2502
|
font-weight: 700;
|
|
2542
2503
|
}}
|
|
2543
|
-
|
|
2544
|
-
.chat-message.bot .smx-structured
|
|
2545
|
-
.chat-message.bot .smx-structured
|
|
2546
|
-
|
|
2504
|
+
|
|
2505
|
+
.chat-message.bot .smx-structured h1 {{ font-size: 1.3rem; }}
|
|
2506
|
+
.chat-message.bot .smx-structured h2 {{ font-size: 1.2rem; }}
|
|
2507
|
+
.chat-message.bot .smx-structured h3 {{ font-size: 1.1rem; }}
|
|
2508
|
+
|
|
2547
2509
|
/* Paragraphs */
|
|
2548
2510
|
.chat-message.bot .smx-structured p {{
|
|
2549
2511
|
margin: 6px 0;
|
|
@@ -2563,15 +2525,13 @@ def setup_routes(smx):
|
|
|
2563
2525
|
padding: 8px 10px;
|
|
2564
2526
|
border-radius: 8px;
|
|
2565
2527
|
overflow: auto;
|
|
2566
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
|
2567
2528
|
}}
|
|
2568
2529
|
|
|
2569
|
-
/* While streaming
|
|
2530
|
+
/* While streaming, still use a live typing box */
|
|
2570
2531
|
.chat-message.bot.streaming .stream-target {{
|
|
2571
2532
|
white-space: pre-wrap; /* so newlines render during typing */
|
|
2572
2533
|
}}
|
|
2573
|
-
|
|
2574
|
-
<style>
|
|
2534
|
+
|
|
2575
2535
|
/* --- Stop-in-a-ring spinner --- */
|
|
2576
2536
|
#submit-button.stop {{
|
|
2577
2537
|
display: inline-flex;
|
|
@@ -2611,8 +2571,7 @@ def setup_routes(smx):
|
|
|
2611
2571
|
@keyframes smxSpin {{
|
|
2612
2572
|
to {{ transform: rotate(360deg); }}
|
|
2613
2573
|
}}
|
|
2614
|
-
|
|
2615
|
-
<style>
|
|
2574
|
+
|
|
2616
2575
|
/* Force strict top→bottom stacking and align sides without floats */
|
|
2617
2576
|
#chat-history{{
|
|
2618
2577
|
display: flex;
|
|
@@ -2661,6 +2620,14 @@ def setup_routes(smx):
|
|
|
2661
2620
|
body {{
|
|
2662
2621
|
padding-bottom:0;
|
|
2663
2622
|
}}
|
|
2623
|
+
|
|
2624
|
+
html, body {{
|
|
2625
|
+
margin: 0;
|
|
2626
|
+
padding: 0;
|
|
2627
|
+
width: 100%;
|
|
2628
|
+
height: 100%; /* If you want it to be full height too */
|
|
2629
|
+
overflow-x: hidden; /* Optional: Prevents horizontal scrollbar appearing if a slight overflow */
|
|
2630
|
+
}}
|
|
2664
2631
|
</style>
|
|
2665
2632
|
|
|
2666
2633
|
<body>
|
|
@@ -3396,17 +3363,11 @@ def setup_routes(smx):
|
|
|
3396
3363
|
box-sizing: border-box;
|
|
3397
3364
|
max-width: 100%;
|
|
3398
3365
|
}
|
|
3399
|
-
@media (max-width:
|
|
3366
|
+
@media (max-width: 1200px) {
|
|
3400
3367
|
body {
|
|
3401
3368
|
padding-top: 0;
|
|
3402
3369
|
}
|
|
3403
3370
|
}
|
|
3404
|
-
/* undo the mobile overflow clamp on large screens */
|
|
3405
|
-
html, body, .admin-shell{ overflow-x: visible !important; }
|
|
3406
|
-
/* guard against grid items forcing overflow */
|
|
3407
|
-
.admin-grid, .admin-shell .card { min-width: 0; }
|
|
3408
|
-
}
|
|
3409
|
-
|
|
3410
3371
|
/* Section demarcation */
|
|
3411
3372
|
.section{
|
|
3412
3373
|
background: var(--section-bg);
|
|
@@ -3522,10 +3483,6 @@ def setup_routes(smx):
|
|
|
3522
3483
|
/* force all grid items to stack */
|
|
3523
3484
|
.span-3, .span-4, .span-6, .span-8, .span-12 { grid-column: span 12; }
|
|
3524
3485
|
}
|
|
3525
|
-
|
|
3526
|
-
/* Global overflow guards (safe, won’t hide useful content inside lists) */
|
|
3527
|
-
html, body, .admin-shell { overflow-x: hidden; }
|
|
3528
|
-
|
|
3529
3486
|
/* Prevent any inner block from insisting on a width that causes overflow */
|
|
3530
3487
|
.admin-shell .card, .admin-grid { min-width: 0; }
|
|
3531
3488
|
|
|
@@ -3820,6 +3777,34 @@ def setup_routes(smx):
|
|
|
3820
3777
|
)
|
|
3821
3778
|
flash(f"Role '{name}' created.", "info") if ok else flash("Could not create role (reserved/exists/invalid).", "error")
|
|
3822
3779
|
|
|
3780
|
+
elif action == "create_user":
|
|
3781
|
+
viewer_role = (session.get("role") or "").lower()
|
|
3782
|
+
if viewer_role not in ("admin", "superadmin"):
|
|
3783
|
+
flash("You don't have permission to create users.", "error")
|
|
3784
|
+
else:
|
|
3785
|
+
email = (request.form.get("email") or "").strip()
|
|
3786
|
+
username = (request.form.get("username") or "").strip()
|
|
3787
|
+
temp_password = request.form.get("password") or ""
|
|
3788
|
+
role = (request.form.get("role") or "user").strip().lower()
|
|
3789
|
+
|
|
3790
|
+
if not email or not temp_password:
|
|
3791
|
+
flash("Email and password are required to create a user.", "error")
|
|
3792
|
+
elif role not in ("user", "employee"):
|
|
3793
|
+
flash("Invalid role for new user.", "error")
|
|
3794
|
+
else:
|
|
3795
|
+
ok = register_user(email, username, temp_password, role)
|
|
3796
|
+
if ok:
|
|
3797
|
+
# Force this new account to change password on first login
|
|
3798
|
+
_auth.set_must_reset_by_email(email, must_reset=True)
|
|
3799
|
+
flash(
|
|
3800
|
+
"User created. They must change the temporary password on first login.",
|
|
3801
|
+
"success",
|
|
3802
|
+
)
|
|
3803
|
+
else:
|
|
3804
|
+
flash("Could not create user (email or username already in use).", "error")
|
|
3805
|
+
|
|
3806
|
+
return redirect(url_for("admin_panel"))
|
|
3807
|
+
|
|
3823
3808
|
elif action == "set_user_role":
|
|
3824
3809
|
actor_role = (session.get("role") or "").lower()
|
|
3825
3810
|
actor_id = session.get("user_id")
|
|
@@ -4382,14 +4367,45 @@ def setup_routes(smx):
|
|
|
4382
4367
|
"""
|
|
4383
4368
|
|
|
4384
4369
|
employees_card = f"""
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4370
|
+
<div class="card span-12">
|
|
4371
|
+
<h4>Employees</h4>
|
|
4372
|
+
<ul class="catalog-list">
|
|
4373
|
+
{''.join(emp_items) or "<li>No employees yet.</li>"}
|
|
4374
|
+
</ul>
|
|
4375
|
+
{add_form}
|
|
4376
|
+
</div>
|
|
4392
4377
|
"""
|
|
4378
|
+
# Admin-only: create users directly (useful when public registration is disabled)
|
|
4379
|
+
create_user_card = ""
|
|
4380
|
+
if viewer_role in ("admin", "superadmin"):
|
|
4381
|
+
create_user_card = """
|
|
4382
|
+
<div class="card span-4">
|
|
4383
|
+
<h4>Create User</h4>
|
|
4384
|
+
<form method="post" class="form-vertical">
|
|
4385
|
+
<input type="hidden" name="action" value="create_user">
|
|
4386
|
+
<label>Email</label>
|
|
4387
|
+
<input type="email" name="email" required>
|
|
4388
|
+
|
|
4389
|
+
<label>Username (optional)</label>
|
|
4390
|
+
<input type="text" name="username" placeholder="e.g. jsmith">
|
|
4391
|
+
|
|
4392
|
+
<label>Temporary password</label>
|
|
4393
|
+
<input type="password" name="password" required>
|
|
4394
|
+
|
|
4395
|
+
<label>Role</label>
|
|
4396
|
+
<select name="role">
|
|
4397
|
+
<option value="user">User</option>
|
|
4398
|
+
<option value="employee">Employee</option>
|
|
4399
|
+
</select>
|
|
4400
|
+
|
|
4401
|
+
<button type="submit" style="margin-top:.5rem;">Create User</button>
|
|
4402
|
+
</form>
|
|
4403
|
+
<p style="font-size:.75rem;opacity:.7;margin-top:.5rem;">
|
|
4404
|
+
Share the temporary password securely and ask the user to change it after first login.
|
|
4405
|
+
</p>
|
|
4406
|
+
</div>
|
|
4407
|
+
"""
|
|
4408
|
+
|
|
4393
4409
|
from datetime import datetime, timedelta
|
|
4394
4410
|
# Audit (always its own row)
|
|
4395
4411
|
audit_card = ""
|
|
@@ -4528,6 +4544,7 @@ def setup_routes(smx):
|
|
|
4528
4544
|
<div class="admin-grid">
|
|
4529
4545
|
{roles_card}
|
|
4530
4546
|
{employees_card}
|
|
4547
|
+
{create_user_card}
|
|
4531
4548
|
</div>
|
|
4532
4549
|
</section>
|
|
4533
4550
|
"""
|
|
@@ -5214,7 +5231,6 @@ def setup_routes(smx):
|
|
|
5214
5231
|
<title>Edit Page - {{ page_name }}</title>
|
|
5215
5232
|
<style>
|
|
5216
5233
|
body {
|
|
5217
|
-
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
5218
5234
|
background: #f4f7f9;
|
|
5219
5235
|
padding: 20px;
|
|
5220
5236
|
}
|
|
@@ -5279,20 +5295,25 @@ def setup_routes(smx):
|
|
|
5279
5295
|
# ----Register ---------------------------------------
|
|
5280
5296
|
@smx.app.route("/register", methods=["GET", "POST"])
|
|
5281
5297
|
def register():
|
|
5298
|
+
|
|
5299
|
+
# If the consumer app has not enabled registration, redirect to login.
|
|
5300
|
+
if not getattr(smx, "registration_enabled", False):
|
|
5301
|
+
return redirect(url_for("login"))
|
|
5302
|
+
|
|
5282
5303
|
if request.method == "POST":
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5304
|
+
email = request.form["email"].strip()
|
|
5305
|
+
username = request.form["username"].strip()
|
|
5306
|
+
password = request.form["password"]
|
|
5307
|
+
role = request.form.get("role", "user")
|
|
5308
|
+
if not email or not password:
|
|
5309
|
+
flash("email and password required.")
|
|
5310
|
+
else:
|
|
5311
|
+
success = register_user(email, username, password, role)
|
|
5312
|
+
if success:
|
|
5313
|
+
flash("Registration successful—please log in.")
|
|
5314
|
+
return redirect(url_for("login"))
|
|
5315
|
+
else:
|
|
5316
|
+
flash("Email already taken.")
|
|
5296
5317
|
return render_template("register.html")
|
|
5297
5318
|
|
|
5298
5319
|
# ----- Login --------------------------------------------
|
|
@@ -5309,8 +5330,15 @@ def setup_routes(smx):
|
|
|
5309
5330
|
session["username"] = user["username"]
|
|
5310
5331
|
session["role"] = user["role"]
|
|
5311
5332
|
|
|
5312
|
-
#
|
|
5313
|
-
#
|
|
5333
|
+
# If this account was created with a temporary password,
|
|
5334
|
+
# force them through the change-password flow first.
|
|
5335
|
+
if _auth.user_must_reset_password(user["id"]):
|
|
5336
|
+
session["must_reset_password"] = True
|
|
5337
|
+
flash("Please set a new password before continuing.", "warning")
|
|
5338
|
+
return redirect(url_for("change_password"))
|
|
5339
|
+
|
|
5340
|
+
# Clear any stale flag for accounts that no longer need a reset
|
|
5341
|
+
session.pop("must_reset_password", None)
|
|
5314
5342
|
|
|
5315
5343
|
# — Load past chats from chats.db for this user —
|
|
5316
5344
|
chat_ids = SQLHistoryStore.list_chats(user["id"])
|
|
@@ -5341,7 +5369,40 @@ def setup_routes(smx):
|
|
|
5341
5369
|
flash("Invalid username or password.")
|
|
5342
5370
|
return render_template("login.html")
|
|
5343
5371
|
|
|
5344
|
-
|
|
5372
|
+
|
|
5373
|
+
@smx.app.route("/change_password", methods=["GET", "POST"])
|
|
5374
|
+
@login_required
|
|
5375
|
+
def change_password():
|
|
5376
|
+
user_id = session.get("user_id")
|
|
5377
|
+
if not user_id:
|
|
5378
|
+
flash("Please log in again.", "error")
|
|
5379
|
+
return redirect(url_for("login"))
|
|
5380
|
+
|
|
5381
|
+
if request.method == "POST":
|
|
5382
|
+
current = (request.form.get("current_password") or "").strip()
|
|
5383
|
+
new1 = (request.form.get("new_password") or "").strip()
|
|
5384
|
+
new2 = (request.form.get("confirm_password") or "").strip()
|
|
5385
|
+
|
|
5386
|
+
if not new1:
|
|
5387
|
+
flash("New password is required.", "error")
|
|
5388
|
+
elif new1 != new2:
|
|
5389
|
+
flash("New passwords do not match.", "error")
|
|
5390
|
+
elif not _auth.verify_password(user_id, current):
|
|
5391
|
+
flash("Current password is incorrect.", "error")
|
|
5392
|
+
else:
|
|
5393
|
+
# Update password + clear the mandatory-reset flag
|
|
5394
|
+
_auth.update_password(user_id, new1)
|
|
5395
|
+
_auth.clear_must_reset(user_id)
|
|
5396
|
+
session.pop("must_reset_password", None)
|
|
5397
|
+
flash("Password updated successfully.", "success")
|
|
5398
|
+
|
|
5399
|
+
next_url = request.args.get("next") or url_for("dashboard")
|
|
5400
|
+
return redirect(next_url)
|
|
5401
|
+
|
|
5402
|
+
return render_template("change_password.html")
|
|
5403
|
+
|
|
5404
|
+
|
|
5405
|
+
# ----- Logout -------------------------------------------
|
|
5345
5406
|
@smx.app.route("/logout", methods=["POST"])
|
|
5346
5407
|
def logout():
|
|
5347
5408
|
"""Clear session and return to login."""
|
|
@@ -5649,7 +5710,6 @@ def setup_routes(smx):
|
|
|
5649
5710
|
"<meta charset='utf-8'>"
|
|
5650
5711
|
"<title>Result</title>"
|
|
5651
5712
|
"<style>"
|
|
5652
|
-
" body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 42px; padding:24 32; }"
|
|
5653
5713
|
" img { max-width: 100%; height: auto; }"
|
|
5654
5714
|
" table { border-collapse: collapse; margin: 16px 0; }"
|
|
5655
5715
|
" th, td { border: 1px solid #ddd; padding: 6px 10px; }"
|
|
@@ -6453,6 +6513,7 @@ def setup_routes(smx):
|
|
|
6453
6513
|
cell["highlighted_code"] = Markup(_pygmentize(cell["code"]))
|
|
6454
6514
|
|
|
6455
6515
|
highlighted_ai_code = _pygmentize(ai_code)
|
|
6516
|
+
tasks = [tag.replace("_", " ").replace('"', '').capitalize() for tag in tags]
|
|
6456
6517
|
|
|
6457
6518
|
return render_template(
|
|
6458
6519
|
"dashboard.html",
|
|
@@ -6464,7 +6525,7 @@ def setup_routes(smx):
|
|
|
6464
6525
|
highlighted_ai_code=highlighted_ai_code if ai_code else None,
|
|
6465
6526
|
askai_question=smx.sanitize_rough_to_markdown_task(askai_question),
|
|
6466
6527
|
refined_question=refined_question,
|
|
6467
|
-
tasks=
|
|
6528
|
+
tasks=tasks,
|
|
6468
6529
|
data_cells=data_cells,
|
|
6469
6530
|
session_id=session_id,
|
|
6470
6531
|
llm_usage=llm_usage
|