workpeg 0.76.0__tar.gz → 0.78.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {workpeg-0.76.0/src/workpeg.egg-info → workpeg-0.78.0}/PKG-INFO +1 -1
- {workpeg-0.76.0 → workpeg-0.78.0}/pyproject.toml +1 -1
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/runtime/ai.js +52 -29
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/scaffold.py +10 -2
- {workpeg-0.76.0 → workpeg-0.78.0/src/workpeg.egg-info}/PKG-INFO +1 -1
- {workpeg-0.76.0 → workpeg-0.78.0}/LICENSE +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/MANIFEST.in +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/README.md +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/setup.cfg +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/__init__.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/build.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/cdn.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/cli.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/config.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/context.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/create_new.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/run.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/runtime.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/submit.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/templates/__init__.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/templates/functions/Dockerfile +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/templates/functions/LICENSE +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/templates/functions/README.md +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/templates/functions/app/__init__.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/templates/functions/app/main.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/templates/functions/requirements.txt +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/__init__.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/actions.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/app.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/build.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/compiler/__init__.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/compiler/ast_parser.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/compiler/ir.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/compiler/js_generator.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/create.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/fake_cloud.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/minify.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/runtime/pegment.css +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/runtime/workpeg-runtime.js +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/serve.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/state.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/validators.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/watcher.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/widgets/__init__.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/widgets/base.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/widgets/canvas.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/widgets/display.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/widgets/forms.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/widgets/futures.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/widgets/gestures.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/widgets/interactive.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/ui/widgets/layout.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg/utils.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg.egg-info/SOURCES.txt +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg.egg-info/dependency_links.txt +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg.egg-info/entry_points.txt +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg.egg-info/requires.txt +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/src/workpeg.egg-info/top_level.txt +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/tests/test_build.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/tests/test_cdn.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/tests/test_changelog.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/tests/test_changelog_guard.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/tests/test_cli.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/tests/test_config.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/tests/test_context.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/tests/test_create_new.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/tests/test_release_script.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/tests/test_run.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/tests/test_runtime.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/tests/test_submit.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/tests/test_utils.py +0 -0
- {workpeg-0.76.0 → workpeg-0.78.0}/tests/test_watch_pipeline.py +0 -0
|
@@ -607,6 +607,7 @@
|
|
|
607
607
|
|
|
608
608
|
// ── conversation view (/chat/<uuid>) ───────────────────────────────────
|
|
609
609
|
var messagesEl = null;
|
|
610
|
+
var scrollDownEl = null; // the "scroll to latest" button (when scrolled up)
|
|
610
611
|
function renderConversation() {
|
|
611
612
|
S.host.innerHTML = "";
|
|
612
613
|
closePlusMenu();
|
|
@@ -614,7 +615,17 @@
|
|
|
614
615
|
var conversation = el("div", "chat-conversation", { children: [messagesEl] });
|
|
615
616
|
|
|
616
617
|
var composer = buildComposer("Message Workpeg AI…");
|
|
618
|
+
// A "scroll to latest" affordance — floats just above the (sticky) composer,
|
|
619
|
+
// shown only when the thread isn't scrolled to the bottom.
|
|
620
|
+
var scrollDown = el("button", "wpai-scroll-down", {
|
|
621
|
+
attrs: { type: "button", "aria-label": "Scroll to latest",
|
|
622
|
+
title: "Scroll to latest" },
|
|
623
|
+
children: [icon("arrow-down", 20)]
|
|
624
|
+
});
|
|
625
|
+
scrollDown.addEventListener("click", function () { scrollToBottom(true); });
|
|
626
|
+
scrollDownEl = scrollDown;
|
|
617
627
|
var bar = el("div", "chat-input-bar", { children: [
|
|
628
|
+
scrollDown,
|
|
618
629
|
el("div", "chat-input-wrap-holder", { children: [composer] })
|
|
619
630
|
] });
|
|
620
631
|
|
|
@@ -623,7 +634,13 @@
|
|
|
623
634
|
S.host.appendChild(center);
|
|
624
635
|
|
|
625
636
|
S.messages.forEach(function (m) { messagesEl.appendChild(renderMessage(m)); });
|
|
626
|
-
|
|
637
|
+
// The surface (#workpeg-ai-root) scrolls — toggle the scroll-to-latest
|
|
638
|
+
// button from there. Rebind (remove+add) so re-renders/re-attaches keep
|
|
639
|
+
// exactly one listener on the current host.
|
|
640
|
+
if (S.host) {
|
|
641
|
+
S.host.removeEventListener("scroll", updateScrollDown);
|
|
642
|
+
S.host.addEventListener("scroll", updateScrollDown);
|
|
643
|
+
}
|
|
627
644
|
// Delegated copy for code blocks (their markup is set via innerHTML, so we
|
|
628
645
|
// can't bind per-button). Copies the block's source text.
|
|
629
646
|
messagesEl.addEventListener("click", function (e) {
|
|
@@ -637,7 +654,7 @@
|
|
|
637
654
|
setTimeout(function () { btn.textContent = "Copy"; }, 1200);
|
|
638
655
|
});
|
|
639
656
|
scrollToBottom();
|
|
640
|
-
|
|
657
|
+
updateScrollDown();
|
|
641
658
|
}
|
|
642
659
|
|
|
643
660
|
function renderMessage(m) {
|
|
@@ -737,9 +754,7 @@
|
|
|
737
754
|
el("div", "chat-message__response-header", { children: [
|
|
738
755
|
thinkingVideoNode(),
|
|
739
756
|
el("div", "chat-message__thinking", { children: [
|
|
740
|
-
el("span", "thinking-text", { text: "Thinking" })
|
|
741
|
-
el("span", "thinking-dot"), el("span", "thinking-dot"),
|
|
742
|
-
el("span", "thinking-dot")
|
|
757
|
+
el("span", "thinking-text", { text: "Thinking" })
|
|
743
758
|
] })
|
|
744
759
|
] })
|
|
745
760
|
]
|
|
@@ -781,7 +796,7 @@
|
|
|
781
796
|
if (messagesEl) { messagesEl.appendChild(renderMessage(userMsg)); scrollToBottom(); }
|
|
782
797
|
|
|
783
798
|
var thinking = thinkingNode();
|
|
784
|
-
if (messagesEl) { messagesEl.appendChild(thinking); scrollToBottom();
|
|
799
|
+
if (messagesEl) { messagesEl.appendChild(thinking); scrollToBottom(); updateScrollDown(); }
|
|
785
800
|
|
|
786
801
|
return json("POST", chatsPath(S.chatId + "/messages/"), { content: text })
|
|
787
802
|
.then(function (created) {
|
|
@@ -985,16 +1000,21 @@
|
|
|
985
1000
|
}
|
|
986
1001
|
|
|
987
1002
|
// ── scroll / fade ──────────────────────────────────────────────────────
|
|
988
|
-
function scrollToBottom() {
|
|
989
|
-
|
|
1003
|
+
function scrollToBottom(smooth) {
|
|
1004
|
+
// The whole surface scrolls (see mount): pin it to the newest message.
|
|
1005
|
+
if (!S.host) return;
|
|
1006
|
+
if (smooth && S.host.scrollTo) {
|
|
1007
|
+
S.host.scrollTo({ top: S.host.scrollHeight, behavior: "smooth" });
|
|
1008
|
+
} else {
|
|
1009
|
+
S.host.scrollTop = S.host.scrollHeight;
|
|
1010
|
+
}
|
|
990
1011
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
var
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
messagesEl.classList.toggle("is-fade-bottom", bottom);
|
|
1012
|
+
// Show the scroll-to-latest button only when the thread isn't at the bottom.
|
|
1013
|
+
function updateScrollDown() {
|
|
1014
|
+
if (!scrollDownEl || !S.host) return;
|
|
1015
|
+
var atBottom = S.host.scrollTop + S.host.clientHeight >=
|
|
1016
|
+
S.host.scrollHeight - 32;
|
|
1017
|
+
scrollDownEl.classList.toggle("is-visible", !atBottom);
|
|
998
1018
|
}
|
|
999
1019
|
|
|
1000
1020
|
// ── misc helpers ───────────────────────────────────────────────────────
|
|
@@ -1079,9 +1099,12 @@
|
|
|
1079
1099
|
host.parentNode.style.position = "relative";
|
|
1080
1100
|
}
|
|
1081
1101
|
if (host.setAttribute) {
|
|
1102
|
+
// We are the SCROLL container: a conversation scrolls on #workpeg-ai-root
|
|
1103
|
+
// itself (so the scrollbar rides the surface's far-right edge, not an inner
|
|
1104
|
+
// column), and the composer sticks to the bottom inside us.
|
|
1082
1105
|
host.setAttribute("style",
|
|
1083
1106
|
"position:absolute;top:0;right:0;bottom:0;left:0;display:block;" +
|
|
1084
|
-
"margin:0;padding:0;overflow:hidden;box-sizing:border-box");
|
|
1107
|
+
"margin:0;padding:0;overflow-y:auto;overflow-x:hidden;box-sizing:border-box");
|
|
1085
1108
|
}
|
|
1086
1109
|
if (reattach) { redraw(chatId); return; }
|
|
1087
1110
|
S.rendered = null; // force a fresh render on (re)mount
|
|
@@ -1126,7 +1149,8 @@
|
|
|
1126
1149
|
// The landing centres in the area BELOW the top nav, so it reads a touch low;
|
|
1127
1150
|
// a bottom pad (~the nav height) lifts it to look centred in the whole panel.
|
|
1128
1151
|
R + " .chat-center{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:32px;min-height:0;height:100%;overflow:hidden;position:relative;padding-bottom:var(--wpai-nav-h,64px)}",
|
|
1129
|
-
|
|
1152
|
+
// conversation centre: grow with the thread (don't clip) so the host scrolls
|
|
1153
|
+
R + " .wpai-center--convo{justify-content:flex-start;padding-bottom:0;gap:0;height:auto;min-height:100%;overflow:visible}",
|
|
1130
1154
|
R + " .chat-landing{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:32px;width:100%;padding:40px 24px}",
|
|
1131
1155
|
R + " .chat-greeting{font-size:var(--text-headline-lg-size,2rem);line-height:40px;font-weight:var(--font-weight-medium,500);color:var(--color-text-primary);text-align:center;margin:0}",
|
|
1132
1156
|
// the landing card: one subtle border around the composer + suggestions
|
|
@@ -1181,13 +1205,9 @@
|
|
|
1181
1205
|
R + " .chat-plus-item--chevron{justify-content:space-between}",
|
|
1182
1206
|
R + " .chat-plus-item--chevron span{display:flex;align-items:center;gap:10px}",
|
|
1183
1207
|
R + " .chat-plus-divider{height:1px;background:var(--color-border-subtle);margin:4px}",
|
|
1184
|
-
// conversation
|
|
1185
|
-
R + " .chat-conversation{flex:1
|
|
1186
|
-
R + " .chat-messages{
|
|
1187
|
-
R + " .chat-messages::-webkit-scrollbar{display:none}",
|
|
1188
|
-
R + " .chat-messages.is-fade-top{-webkit-mask-image:linear-gradient(to bottom,transparent 0,black var(--fade-size));mask-image:linear-gradient(to bottom,transparent 0,black var(--fade-size))}",
|
|
1189
|
-
R + " .chat-messages.is-fade-bottom{-webkit-mask-image:linear-gradient(to bottom,black calc(100% - var(--fade-size)),transparent 100%);mask-image:linear-gradient(to bottom,black calc(100% - var(--fade-size)),transparent 100%)}",
|
|
1190
|
-
R + " .chat-messages.is-fade-top.is-fade-bottom{-webkit-mask-image:linear-gradient(to bottom,transparent 0,black var(--fade-size),black calc(100% - var(--fade-size)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 0,black var(--fade-size),black calc(100% - var(--fade-size)),transparent 100%)}",
|
|
1208
|
+
// conversation — the THREAD flows and grows; #workpeg-ai-root scrolls it.
|
|
1209
|
+
R + " .chat-conversation{flex:1 0 auto;width:100%;max-width:800px;margin:0 auto;display:flex;flex-direction:column;padding:24px 24px 32px}",
|
|
1210
|
+
R + " .chat-messages{display:flex;flex-direction:column;gap:32px;width:min(720px,100%);margin:0 auto}",
|
|
1191
1211
|
R + " .chat-message{display:flex;gap:12px;animation:wpaiMessageIn .3s ease}",
|
|
1192
1212
|
"@keyframes wpaiMessageIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}",
|
|
1193
1213
|
R + " .chat-message--user{justify-content:flex-end}",
|
|
@@ -1237,12 +1257,15 @@
|
|
|
1237
1257
|
// thinking
|
|
1238
1258
|
R + " .chat-message--thinking .chat-message__thinking{display:flex;align-items:center;gap:8px}",
|
|
1239
1259
|
R + " .thinking-text{font-size:var(--text-body-sm-size,13px);color:var(--color-text-secondary)}",
|
|
1240
|
-
R + " .thinking-dot{width:6px;height:6px;background:var(--color-brand-secondary,var(--color-text-secondary));border-radius:50%;animation:wpaiThinking 1.4s ease-in-out infinite}",
|
|
1241
|
-
R + " .thinking-dot:nth-child(2){animation-delay:.2s}",
|
|
1242
|
-
R + " .thinking-dot:nth-child(3){animation-delay:.4s}",
|
|
1243
|
-
"@keyframes wpaiThinking{0%,60%,100%{transform:translateY(0);opacity:.4}30%{transform:translateY(-6px);opacity:1}}",
|
|
1244
1260
|
// bottom input bar
|
|
1245
|
-
|
|
1261
|
+
// the composer sticks to the bottom of the scrolling surface; its gradient
|
|
1262
|
+
// lets the thread fade into it as messages scroll behind.
|
|
1263
|
+
R + " .chat-input-bar{position:sticky;bottom:0;align-self:center;width:min(720px,calc(100% - 48px));z-index:50;padding:12px 0 24px;background:linear-gradient(to bottom,transparent,var(--color-bg-default) 20px)}",
|
|
1264
|
+
// scroll-to-latest button — floats above the sticky composer, hidden until
|
|
1265
|
+
// the thread is scrolled up.
|
|
1266
|
+
R + " .wpai-scroll-down{position:absolute;left:50%;top:-46px;transform:translateX(-50%) translateY(6px);width:36px;height:36px;border-radius:9999px;display:flex;align-items:center;justify-content:center;background:var(--color-surface-raised,var(--color-surface-default));border:1px solid var(--color-border-subtle);color:var(--color-icon-primary,var(--color-text-primary));cursor:pointer;box-shadow:0 4px 12px rgba(0,0,0,.28);opacity:0;visibility:hidden;pointer-events:none;transition:opacity .15s ease,transform .15s ease,visibility .15s}",
|
|
1267
|
+
R + " .wpai-scroll-down.is-visible{opacity:1;visibility:visible;pointer-events:auto;transform:translateX(-50%) translateY(0)}",
|
|
1268
|
+
R + " .wpai-scroll-down:hover{background:var(--color-surface-sunken2,var(--color-surface-default))}",
|
|
1246
1269
|
// banner
|
|
1247
1270
|
// The announcement floats OVER the top nav (fixed to the viewport, so the
|
|
1248
1271
|
// host's overflow can't clip it), centred in the content area to the right of
|
|
@@ -1624,13 +1624,21 @@ class Scaffold(Component):
|
|
|
1624
1624
|
"flexShrink": "0"},
|
|
1625
1625
|
),
|
|
1626
1626
|
self.main,
|
|
1627
|
-
|
|
1627
|
+
# The SCROLL lives on the .pg-main container (below), like the
|
|
1628
|
+
# static design — so the scrollbar rides the whole main area's
|
|
1629
|
+
# right edge, not an inner column inset by the padding. This
|
|
1630
|
+
# column stays `scroll="visible"` only to keep its height:100%
|
|
1631
|
+
# (so the Outlet's flex chain — and the AI page's absolute
|
|
1632
|
+
# fill — still bounds), without a scrollbar of its own.
|
|
1633
|
+
gap=0, align="stretch", scroll="visible",
|
|
1628
1634
|
),
|
|
1629
1635
|
style={
|
|
1630
1636
|
"flex": "1", "minWidth": "0",
|
|
1631
1637
|
"background": "var(--color-bg-default)",
|
|
1632
1638
|
"boxSizing": "border-box",
|
|
1633
|
-
|
|
1639
|
+
# scroll on the container itself (the design's .pg-main);
|
|
1640
|
+
# padding 0 24px 8px 24px (was 0 16px).
|
|
1641
|
+
"overflow": "auto", "padding": "0 24px 8px 24px",
|
|
1634
1642
|
"display": "flex", "flexDirection": "column",
|
|
1635
1643
|
},
|
|
1636
1644
|
),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|