@prettier-ai/dsh-client-ui-chat 0.1.2-alpha.1 → 0.1.2-alpha.3
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.
- package/README.i18n.yaml +2 -2
- package/README.md +11 -2
- package/README.zh.md +11 -2
- package/lib/client.js +663 -234
- package/lib/index.js +1 -2
- package/lib/types/client/chat/ChatView.d.ts +1 -1
- package/lib/types/client/chat/MessageIconActions.d.ts +6 -7
- package/lib/types/client/chat/MessageItem.d.ts +5 -5
- package/lib/types/client/chat/TurnNavigator.d.ts +11 -6
- package/lib/types/client/chat/TurnUsagePanel.d.ts +30 -0
- package/lib/types/client/chat/turn-rail-items.d.ts +36 -0
- package/lib/types/client/contract/slots.d.ts +2 -0
- package/lib/types/client/conversation-nodes/inbox.d.ts +18 -9
- package/lib/types/client/locale.d.ts +14 -6
- package/package.json +34 -55
- package/lib/types/client/chat/TurnUsageDisclosure.d.ts +0 -9
package/lib/client.js
CHANGED
|
@@ -8,6 +8,7 @@ window.__ModuleLoader__.load({
|
|
|
8
8
|
let react = require("react");
|
|
9
9
|
let _prettier_ai_dsh_client_ui_primitives = require("@prettier-ai/dsh-client-ui-primitives");
|
|
10
10
|
let _prettier_ai_dsh_client_store = require("@prettier-ai/dsh-client-store");
|
|
11
|
+
let react_dom = require("react-dom");
|
|
11
12
|
//#region ../../util/workspace-path/src/index.ts
|
|
12
13
|
/**
|
|
13
14
|
* Browser-safe Workspace path and display helpers.
|
|
@@ -946,7 +947,7 @@ window.__ModuleLoader__.load({
|
|
|
946
947
|
}
|
|
947
948
|
//#endregion
|
|
948
949
|
//#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-chat/src/client/chat/MessageIconActions.module.css.mjs
|
|
949
|
-
const css$12 = ".BmHDMW_actions{height:calc(28px + var(--dsh-content-font-delta,0px));align-items:center;gap:
|
|
950
|
+
const css$12 = ".BmHDMW_actions{height:calc(28px + var(--dsh-content-font-delta,0px));align-items:center;gap:8px;display:flex}.BmHDMW_timeStart{font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);white-space:nowrap;padding-right:12px}.BmHDMW_timeEnd{font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);white-space:nowrap}@media (hover:hover){[data-actions-reveal=hover] .BmHDMW_actions{opacity:0;transition:opacity 80ms}[data-actions-reveal=hover]:hover .BmHDMW_actions,[data-actions-reveal=hover]:focus-within .BmHDMW_actions{opacity:1}}.BmHDMW_action{width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}.BmHDMW_action svg{width:calc(15px + var(--dsh-content-font-delta,0px));height:calc(15px + var(--dsh-content-font-delta,0px))}.BmHDMW_action:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.BmHDMW_action[data-unavailable]{cursor:default;opacity:.4}.BmHDMW_action[data-unavailable]:hover{color:var(--dsw-alias-label-tertiary);background:0 0}.BmHDMW_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}";
|
|
950
951
|
const tagId$12 = "@prettier-ai/dsh-client-ui-chat/MessageIconActions.module.css";
|
|
951
952
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$12) + "]") === null) {
|
|
952
953
|
const tag = document.createElement("style");
|
|
@@ -958,7 +959,6 @@ window.__ModuleLoader__.load({
|
|
|
958
959
|
var MessageIconActions_module_css_default = {
|
|
959
960
|
"action": "BmHDMW_action",
|
|
960
961
|
"actions": "BmHDMW_actions",
|
|
961
|
-
"runTimeDot": "BmHDMW_runTimeDot",
|
|
962
962
|
"timeEnd": "BmHDMW_timeEnd",
|
|
963
963
|
"timeStart": "BmHDMW_timeStart",
|
|
964
964
|
"visuallyHidden": "BmHDMW_visuallyHidden"
|
|
@@ -970,7 +970,7 @@ window.__ModuleLoader__.load({
|
|
|
970
970
|
* @param props - Copy text, event time, clock side, branch callback, className.
|
|
971
971
|
* @returns The actions row element.
|
|
972
972
|
*/
|
|
973
|
-
function MessageIconActions({ text, time,
|
|
973
|
+
function MessageIconActions({ text, time, clock, onBranch, branchUnavailable = false, className, extraActions, usageAction, t }) {
|
|
974
974
|
const day = useCalendarDay();
|
|
975
975
|
const reasonId = (0, react.useId)();
|
|
976
976
|
const [copied, setCopied] = (0, react.useState)(false);
|
|
@@ -997,41 +997,9 @@ window.__ModuleLoader__.load({
|
|
|
997
997
|
}, 1e3);
|
|
998
998
|
});
|
|
999
999
|
}, [copied, text]);
|
|
1000
|
-
const clockEl = time === void 0 ? null : (0, react_jsx_runtime.
|
|
1000
|
+
const clockEl = time === void 0 ? null : (0, react_jsx_runtime.jsx)("span", {
|
|
1001
1001
|
className: clock === "start" ? MessageIconActions_module_css_default.timeStart : MessageIconActions_module_css_default.timeEnd,
|
|
1002
|
-
children:
|
|
1003
|
-
formatMessageClock(time, t, day),
|
|
1004
|
-
runMs !== void 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1005
|
-
" ",
|
|
1006
|
-
(0, react_jsx_runtime.jsx)("span", {
|
|
1007
|
-
className: MessageIconActions_module_css_default.runTimeDot,
|
|
1008
|
-
"aria-hidden": true,
|
|
1009
|
-
children: "·"
|
|
1010
|
-
}),
|
|
1011
|
-
" ",
|
|
1012
|
-
t("message.ranFor", { duration: formatRunDuration(runMs, t) })
|
|
1013
|
-
] }),
|
|
1014
|
-
ttftMs !== void 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1015
|
-
" ",
|
|
1016
|
-
(0, react_jsx_runtime.jsx)("span", {
|
|
1017
|
-
className: MessageIconActions_module_css_default.runTimeDot,
|
|
1018
|
-
"aria-hidden": true,
|
|
1019
|
-
children: "·"
|
|
1020
|
-
}),
|
|
1021
|
-
" ",
|
|
1022
|
-
t("message.ttft", { seconds: formatLatencySeconds(ttftMs) })
|
|
1023
|
-
] }),
|
|
1024
|
-
tokensPerSecond !== void 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1025
|
-
" ",
|
|
1026
|
-
(0, react_jsx_runtime.jsx)("span", {
|
|
1027
|
-
className: MessageIconActions_module_css_default.runTimeDot,
|
|
1028
|
-
"aria-hidden": true,
|
|
1029
|
-
children: "·"
|
|
1030
|
-
}),
|
|
1031
|
-
" ",
|
|
1032
|
-
t("message.tokensPerSecond", { tps: formatTokensPerSecond(tokensPerSecond) })
|
|
1033
|
-
] })
|
|
1034
|
-
]
|
|
1002
|
+
children: formatMessageClock(time, t, day)
|
|
1035
1003
|
});
|
|
1036
1004
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1037
1005
|
className: className === void 0 ? MessageIconActions_module_css_default.actions : `${MessageIconActions_module_css_default.actions} ${className}`,
|
|
@@ -1068,6 +1036,7 @@ window.__ModuleLoader__.load({
|
|
|
1068
1036
|
className: MessageIconActions_module_css_default.visuallyHidden,
|
|
1069
1037
|
children: t("message.branchUnavailable")
|
|
1070
1038
|
}),
|
|
1039
|
+
usageAction,
|
|
1071
1040
|
clock === "end" ? clockEl : null
|
|
1072
1041
|
]
|
|
1073
1042
|
});
|
|
@@ -1200,7 +1169,7 @@ window.__ModuleLoader__.load({
|
|
|
1200
1169
|
});
|
|
1201
1170
|
}
|
|
1202
1171
|
/** Right-aligned bubble shared by user and steering rows. */
|
|
1203
|
-
function UserStyleBubble({ content, renderMessageImages, actions, pending = false, echo = false, referenceLabels = [], previewImages, t }) {
|
|
1172
|
+
function UserStyleBubble({ content, renderMessageImages, actions, pending = false, echo = false, referenceLabels = [], previewImages, reveal = "always", t }) {
|
|
1204
1173
|
const { text, images: contentImages, rest } = contentParts(content);
|
|
1205
1174
|
const images = previewImages ?? contentImages;
|
|
1206
1175
|
const truncated = (total) => t("json.truncated", { total });
|
|
@@ -1209,7 +1178,7 @@ window.__ModuleLoader__.load({
|
|
|
1209
1178
|
className: MessageItem_module_css_default.userRow,
|
|
1210
1179
|
"data-pending-steering": pending || void 0,
|
|
1211
1180
|
"data-submission-echo": echo || void 0,
|
|
1212
|
-
"data-
|
|
1181
|
+
"data-actions-reveal": reveal,
|
|
1213
1182
|
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
1214
1183
|
className: MessageItem_module_css_default.userStack,
|
|
1215
1184
|
children: [
|
|
@@ -1254,10 +1223,10 @@ window.__ModuleLoader__.load({
|
|
|
1254
1223
|
});
|
|
1255
1224
|
}
|
|
1256
1225
|
/**
|
|
1257
|
-
* Render one local submission echo with the
|
|
1258
|
-
*
|
|
1259
|
-
*
|
|
1260
|
-
*
|
|
1226
|
+
* Render one local transcript or steering submission echo with the same
|
|
1227
|
+
* visual language and surface marker as the Host occurrence that replaces
|
|
1228
|
+
* it: draft text plus object-URL previews, visible from the submit click
|
|
1229
|
+
* until the durable `user/message` or steering occurrence renders.
|
|
1261
1230
|
* @param props - the session snapshot's pending submission and render seats.
|
|
1262
1231
|
* @returns the echoed user bubble.
|
|
1263
1232
|
*/
|
|
@@ -1274,6 +1243,7 @@ window.__ModuleLoader__.load({
|
|
|
1274
1243
|
...image.height === void 0 ? {} : { height: image.height }
|
|
1275
1244
|
} })), [submission.images]),
|
|
1276
1245
|
renderMessageImages,
|
|
1246
|
+
pending: submission.placement === "steering",
|
|
1277
1247
|
echo: true,
|
|
1278
1248
|
t,
|
|
1279
1249
|
actions: (text) => (0, react_jsx_runtime.jsx)(MessageIconActions, {
|
|
@@ -1286,12 +1256,20 @@ window.__ModuleLoader__.load({
|
|
|
1286
1256
|
});
|
|
1287
1257
|
}
|
|
1288
1258
|
/** User and admitted-steering keyed Chat renderer. */
|
|
1289
|
-
const UserMessageNodeView = (0, react.memo)(function UserMessageNodeView({ node, renderMessageImages, t }) {
|
|
1259
|
+
const UserMessageNodeView = (0, react.memo)(function UserMessageNodeView({ node, renderMessageImages, useChat, t }) {
|
|
1290
1260
|
const data = node.data;
|
|
1261
|
+
const isLatestUserRow = useChat((snapshot) => {
|
|
1262
|
+
for (let index = snapshot.order.length - 1; index >= 0; index -= 1) {
|
|
1263
|
+
const candidate = snapshot.nodes.get(snapshot.order[index] ?? "");
|
|
1264
|
+
if (candidate?.kind === "user" || candidate?.kind === "steering") return candidate.key === node.key;
|
|
1265
|
+
}
|
|
1266
|
+
return true;
|
|
1267
|
+
});
|
|
1291
1268
|
return (0, react_jsx_runtime.jsx)(UserStyleBubble, {
|
|
1292
1269
|
content: data.content,
|
|
1293
1270
|
renderMessageImages,
|
|
1294
1271
|
...data.referenceLabels === void 0 ? {} : { referenceLabels: data.referenceLabels },
|
|
1272
|
+
reveal: isLatestUserRow ? "always" : "hover",
|
|
1295
1273
|
t,
|
|
1296
1274
|
actions: (text) => (0, react_jsx_runtime.jsx)(MessageIconActions, {
|
|
1297
1275
|
text,
|
|
@@ -1650,7 +1628,7 @@ window.__ModuleLoader__.load({
|
|
|
1650
1628
|
});
|
|
1651
1629
|
//#endregion
|
|
1652
1630
|
//#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-chat/src/client/chat/TurnNavigator.module.css.mjs
|
|
1653
|
-
const css$10 = ".alqd3G_slot{z-index:6;pointer-events:none;height:0;position:sticky;top:0}.
|
|
1631
|
+
const css$10 = ".alqd3G_slot{z-index:6;pointer-events:none;height:0;position:sticky;top:0}.alqd3G_frame{--turn-rail-band:calc(var(--dsh-conversation-viewport-height,100dvh) - var(--dsh-composer-height,152px));--turn-preview-height:100px;top:calc(var(--turn-rail-band) / 2);right:calc(12px - (var(--dsh-composer-side-clearance) + 16px));width:28px;height:min(var(--turn-natural-height), max(0px, calc(var(--turn-rail-band) - 64px)), 420px);cursor:pointer;pointer-events:auto;transition:height .22s cubic-bezier(.2,.8,.2,1);position:absolute;transform:translateY(-50%)}.alqd3G_scroller{overscroll-behavior:contain;scrollbar-width:none;position:absolute;inset:0;overflow-y:auto}.alqd3G_scroller::-webkit-scrollbar{display:none}.alqd3G_fadeTop{mask-image:linear-gradient(#0000 0,#000 24px 100%)}.alqd3G_fadeBottom{mask-image:linear-gradient(#000 0 calc(100% - 24px),#0000 100%)}.alqd3G_fadeTop.alqd3G_fadeBottom{mask-image:linear-gradient(#0000 0,#000 24px calc(100% - 24px),#0000 100%)}.alqd3G_marks{height:var(--turn-natural-height);position:relative}.alqd3G_markPosition{top:calc(var(--turn-natural-position) + var(--turn-rail-inset));height:10px;transition:top .22s cubic-bezier(.2,.8,.2,1);animation:.15s ease-out alqd3G_dsh-turn-mark-enter;position:absolute;left:0;right:0;transform:translateY(-50%)}.alqd3G_mark{cursor:pointer;pointer-events:none;background:0 0;border:0;border-radius:8px;width:20px;padding:0;position:absolute;inset:0 0 0 auto}.alqd3G_mark:before{background:var(--dsw-alias-border-l4);content:\"\";border-radius:2px;width:12px;height:2px;transition:width .14s,background-color .14s;position:absolute;top:50%;right:0;transform:translateY(-50%)}.alqd3G_markUnloaded:before{opacity:.6;width:8px}.alqd3G_markPreview:before{background:var(--dsw-alias-label-tertiary);width:18px}.alqd3G_markBusy:before{animation:1s ease-in-out infinite alqd3G_dsh-turn-mark-busy}.alqd3G_markActive:before{background:var(--dsw-alias-label-primary);width:20px}.alqd3G_mark:focus-visible:before{background:var(--dsw-alias-state-business-primary);width:20px}.alqd3G_mark:focus-visible{outline:1px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.alqd3G_preview{top:clamp(0px, calc(var(--turn-natural-position) + var(--turn-rail-inset) - var(--turn-scroll-top,0px) - var(--turn-preview-height) / 2), calc(100% - var(--turn-preview-height)));box-sizing:border-box;width:min(300px,100cqw - 120px);max-height:var(--turn-preview-height);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-shadow-lv2);pointer-events:none;border-radius:10px;padding:10px 12px;transition:top .14s cubic-bezier(.2,.8,.2,1);animation:.12s ease-out alqd3G_dsh-turn-preview-enter;position:absolute;right:calc(100% + 10px);overflow:hidden}.alqd3G_previewPrompt,.alqd3G_previewResponse{-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.alqd3G_previewPrompt{font:var(--dsw-font-xs-strong-13);-webkit-line-clamp:1}.alqd3G_previewResponse{color:var(--dsw-alias-label-caption);font:var(--dsw-font-xxs-12);-webkit-line-clamp:3;margin-top:4px}@keyframes alqd3G_dsh-turn-mark-enter{0%{opacity:0}to{opacity:1}}@keyframes alqd3G_dsh-turn-preview-enter{0%{opacity:0;transform:translate(4px)}to{opacity:1;transform:translate(0)}}@keyframes alqd3G_dsh-turn-mark-busy{0%,to{opacity:1}50%{opacity:.35}}@container (width<=900px){.alqd3G_slot{display:none}}@media (prefers-reduced-motion:reduce){.alqd3G_frame,.alqd3G_scroller,.alqd3G_markPosition,.alqd3G_mark:before,.alqd3G_markBusy:before,.alqd3G_preview{scroll-behavior:auto;transition:none;animation:none}}";
|
|
1654
1632
|
const tagId$10 = "@prettier-ai/dsh-client-ui-chat/TurnNavigator.module.css";
|
|
1655
1633
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$10) + "]") === null) {
|
|
1656
1634
|
const tag = document.createElement("style");
|
|
@@ -1660,96 +1638,173 @@ window.__ModuleLoader__.load({
|
|
|
1660
1638
|
document.head.appendChild(tag);
|
|
1661
1639
|
}
|
|
1662
1640
|
var TurnNavigator_module_css_default = {
|
|
1641
|
+
"dsh-turn-mark-busy": "alqd3G_dsh-turn-mark-busy",
|
|
1663
1642
|
"dsh-turn-mark-enter": "alqd3G_dsh-turn-mark-enter",
|
|
1664
1643
|
"dsh-turn-preview-enter": "alqd3G_dsh-turn-preview-enter",
|
|
1644
|
+
"fadeBottom": "alqd3G_fadeBottom",
|
|
1645
|
+
"fadeTop": "alqd3G_fadeTop",
|
|
1646
|
+
"frame": "alqd3G_frame",
|
|
1665
1647
|
"mark": "alqd3G_mark",
|
|
1666
1648
|
"markActive": "alqd3G_markActive",
|
|
1649
|
+
"markBusy": "alqd3G_markBusy",
|
|
1667
1650
|
"markPosition": "alqd3G_markPosition",
|
|
1668
1651
|
"markPreview": "alqd3G_markPreview",
|
|
1652
|
+
"markUnloaded": "alqd3G_markUnloaded",
|
|
1669
1653
|
"marks": "alqd3G_marks",
|
|
1670
1654
|
"preview": "alqd3G_preview",
|
|
1671
1655
|
"previewPrompt": "alqd3G_previewPrompt",
|
|
1672
1656
|
"previewResponse": "alqd3G_previewResponse",
|
|
1673
|
-
"
|
|
1657
|
+
"scroller": "alqd3G_scroller",
|
|
1674
1658
|
"slot": "alqd3G_slot"
|
|
1675
1659
|
};
|
|
1676
1660
|
//#endregion
|
|
1677
1661
|
//#region lib/types/client/chat/TurnNavigator.js
|
|
1678
|
-
/**
|
|
1662
|
+
/** Fixed pitch between neighbouring marks; overflow scrolls inside the frame. */
|
|
1679
1663
|
const TURN_SPACING_PX = 10;
|
|
1680
1664
|
/** Rail padding above the first mark and below the last one, per end. */
|
|
1681
1665
|
const RAIL_INSET_PX = 6;
|
|
1682
|
-
|
|
1683
|
-
|
|
1666
|
+
/** Fade band the mask reserves at a scrollable end. */
|
|
1667
|
+
const FADE_PX = 24;
|
|
1668
|
+
function itemPosition(index) {
|
|
1669
|
+
return { "--turn-natural-position": `${String(index * TURN_SPACING_PX)}px` };
|
|
1670
|
+
}
|
|
1671
|
+
function frameStyle(count, scrollTop) {
|
|
1684
1672
|
return {
|
|
1685
|
-
"--turn-natural-
|
|
1686
|
-
"--turn-
|
|
1673
|
+
"--turn-natural-height": `${String((count - 1) * TURN_SPACING_PX + 2 * RAIL_INSET_PX)}px`,
|
|
1674
|
+
"--turn-rail-inset": `${String(RAIL_INSET_PX)}px`,
|
|
1675
|
+
"--turn-scroll-top": `${String(scrollTop)}px`
|
|
1687
1676
|
};
|
|
1688
1677
|
}
|
|
1689
|
-
function
|
|
1678
|
+
function itemAtPointer(items, frame, scrollTop, clientY) {
|
|
1679
|
+
const offset = clientY - frame.getBoundingClientRect().top + scrollTop - RAIL_INSET_PX;
|
|
1680
|
+
return items[Math.max(0, Math.min(items.length - 1, Math.round(offset / TURN_SPACING_PX)))];
|
|
1681
|
+
}
|
|
1682
|
+
const RAIL_AT_REST = {
|
|
1683
|
+
top: 0,
|
|
1684
|
+
canScrollUp: false,
|
|
1685
|
+
canScrollDown: false
|
|
1686
|
+
};
|
|
1687
|
+
function railScrollState(scroller) {
|
|
1688
|
+
const top = scroller.scrollTop;
|
|
1690
1689
|
return {
|
|
1691
|
-
|
|
1692
|
-
|
|
1690
|
+
top,
|
|
1691
|
+
canScrollUp: top > 1,
|
|
1692
|
+
canScrollDown: top < scroller.scrollHeight - scroller.clientHeight - 1
|
|
1693
1693
|
};
|
|
1694
1694
|
}
|
|
1695
|
-
function
|
|
1696
|
-
|
|
1697
|
-
const usableHeight = Math.max(1, rect.height - 2 * RAIL_INSET_PX);
|
|
1698
|
-
const ratio = Math.max(0, Math.min(1, (clientY - rect.top - RAIL_INSET_PX) / usableHeight));
|
|
1699
|
-
return items[Math.round(ratio * (items.length - 1))];
|
|
1695
|
+
function sameRailScrollState(left, right) {
|
|
1696
|
+
return left.top === right.top && left.canScrollUp === right.canScrollUp && left.canScrollDown === right.canScrollDown;
|
|
1700
1697
|
}
|
|
1701
|
-
function TurnNavigatorRail({ items, activeTurn, onNavigate, t }) {
|
|
1698
|
+
function TurnNavigatorRail({ items, activeTurn, busyTurn, onNavigate, t }) {
|
|
1702
1699
|
const [previewTurn, setPreviewTurn] = (0, react.useState)(null);
|
|
1700
|
+
const [scrollState, setScrollState] = (0, react.useState)(RAIL_AT_REST);
|
|
1701
|
+
const scrollerRef = (0, react.useRef)(null);
|
|
1702
|
+
/** While the pointer works the rail, follow must not move it under the hand. */
|
|
1703
|
+
const pointerInsideRef = (0, react.useRef)(false);
|
|
1703
1704
|
const previewId = (0, react.useId)();
|
|
1705
|
+
const syncScrollState = () => {
|
|
1706
|
+
const scroller = scrollerRef.current;
|
|
1707
|
+
if (scroller === null) return;
|
|
1708
|
+
const next = railScrollState(scroller);
|
|
1709
|
+
setScrollState((current) => sameRailScrollState(current, next) ? current : next);
|
|
1710
|
+
};
|
|
1711
|
+
(0, react.useEffect)(() => {
|
|
1712
|
+
const scroller = scrollerRef.current;
|
|
1713
|
+
if (scroller === null || typeof ResizeObserver === "undefined") return;
|
|
1714
|
+
const observer = new ResizeObserver(syncScrollState);
|
|
1715
|
+
observer.observe(scroller);
|
|
1716
|
+
return () => {
|
|
1717
|
+
observer.disconnect();
|
|
1718
|
+
};
|
|
1719
|
+
}, []);
|
|
1720
|
+
(0, react.useEffect)(syncScrollState, [items.length]);
|
|
1721
|
+
(0, react.useEffect)(() => {
|
|
1722
|
+
const scroller = scrollerRef.current;
|
|
1723
|
+
const index = items.findIndex((item) => item.turn === activeTurn);
|
|
1724
|
+
if (scroller === null || index < 0 || pointerInsideRef.current) return;
|
|
1725
|
+
const markTop = index * TURN_SPACING_PX + RAIL_INSET_PX;
|
|
1726
|
+
const viewTop = scroller.scrollTop;
|
|
1727
|
+
const viewHeight = scroller.clientHeight;
|
|
1728
|
+
if (viewHeight <= 0 || markTop >= viewTop + FADE_PX && markTop <= viewTop + viewHeight - FADE_PX) return;
|
|
1729
|
+
const target = Math.max(0, markTop - viewHeight / 2);
|
|
1730
|
+
const reduced = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1731
|
+
if (typeof scroller.scrollTo === "function") scroller.scrollTo({
|
|
1732
|
+
top: target,
|
|
1733
|
+
behavior: reduced ? "auto" : "smooth"
|
|
1734
|
+
});
|
|
1735
|
+
else scroller.scrollTop = target;
|
|
1736
|
+
syncScrollState();
|
|
1737
|
+
}, [activeTurn, items]);
|
|
1704
1738
|
if (items.length < 2) return null;
|
|
1705
1739
|
const previewIndex = items.findIndex((item) => item.turn === previewTurn);
|
|
1706
1740
|
const preview = previewIndex < 0 ? void 0 : items[previewIndex];
|
|
1707
|
-
const previewPosition = previewIndex < 0 ? void 0 : itemPosition(previewIndex
|
|
1741
|
+
const previewPosition = previewIndex < 0 ? void 0 : itemPosition(previewIndex);
|
|
1708
1742
|
const previewAtPointer = (event) => {
|
|
1709
|
-
|
|
1743
|
+
const scrollTop = scrollerRef.current?.scrollTop ?? 0;
|
|
1744
|
+
setPreviewTurn(itemAtPointer(items, event.currentTarget, scrollTop, event.clientY)?.turn ?? null);
|
|
1710
1745
|
};
|
|
1711
1746
|
const navigateAtPointer = (event) => {
|
|
1712
|
-
const
|
|
1747
|
+
const scrollTop = scrollerRef.current?.scrollTop ?? 0;
|
|
1748
|
+
const item = itemAtPointer(items, event.currentTarget, scrollTop, event.clientY);
|
|
1713
1749
|
if (item !== void 0) onNavigate(item);
|
|
1714
1750
|
};
|
|
1751
|
+
const fadeClasses = [TurnNavigator_module_css_default.scroller];
|
|
1752
|
+
if (scrollState.canScrollUp) fadeClasses.push(TurnNavigator_module_css_default.fadeTop);
|
|
1753
|
+
if (scrollState.canScrollDown) fadeClasses.push(TurnNavigator_module_css_default.fadeBottom);
|
|
1715
1754
|
return (0, react_jsx_runtime.jsx)("div", {
|
|
1716
1755
|
className: TurnNavigator_module_css_default.slot,
|
|
1717
1756
|
children: (0, react_jsx_runtime.jsxs)("nav", {
|
|
1718
|
-
className: TurnNavigator_module_css_default.
|
|
1719
|
-
style:
|
|
1757
|
+
className: TurnNavigator_module_css_default.frame,
|
|
1758
|
+
style: frameStyle(items.length, scrollState.top),
|
|
1720
1759
|
"aria-label": t("chat.turnNavigation.label"),
|
|
1721
1760
|
onClick: navigateAtPointer,
|
|
1722
1761
|
onPointerMove: previewAtPointer,
|
|
1762
|
+
onPointerEnter: () => {
|
|
1763
|
+
pointerInsideRef.current = true;
|
|
1764
|
+
},
|
|
1723
1765
|
onPointerLeave: () => {
|
|
1766
|
+
pointerInsideRef.current = false;
|
|
1724
1767
|
setPreviewTurn(null);
|
|
1725
1768
|
},
|
|
1726
1769
|
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1770
|
+
ref: scrollerRef,
|
|
1771
|
+
className: fadeClasses.join(" "),
|
|
1772
|
+
onScroll: () => {
|
|
1773
|
+
syncScrollState();
|
|
1774
|
+
},
|
|
1775
|
+
children: (0, react_jsx_runtime.jsx)("div", {
|
|
1776
|
+
className: TurnNavigator_module_css_default.marks,
|
|
1777
|
+
children: items.map((item, index) => {
|
|
1778
|
+
const active = item.turn === activeTurn;
|
|
1779
|
+
const showingPreview = item.turn === previewTurn;
|
|
1780
|
+
const classes = [TurnNavigator_module_css_default.mark];
|
|
1781
|
+
if (item.anchor.kind === "unloaded") classes.push(TurnNavigator_module_css_default.markUnloaded);
|
|
1782
|
+
if (active) classes.push(TurnNavigator_module_css_default.markActive);
|
|
1783
|
+
else if (showingPreview) classes.push(TurnNavigator_module_css_default.markPreview);
|
|
1784
|
+
if (item.turn === busyTurn) classes.push(TurnNavigator_module_css_default.markBusy);
|
|
1785
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
1786
|
+
className: TurnNavigator_module_css_default.markPosition,
|
|
1787
|
+
style: itemPosition(index),
|
|
1788
|
+
children: (0, react_jsx_runtime.jsx)("button", {
|
|
1789
|
+
type: "button",
|
|
1790
|
+
className: classes.join(" "),
|
|
1791
|
+
"aria-label": t(item.anchor.kind === "loaded" ? "chat.turnNavigation.jump" : "chat.turnNavigation.jumpLoad", { turn: item.turn }),
|
|
1792
|
+
"aria-current": active ? "true" : void 0,
|
|
1793
|
+
"aria-busy": item.turn === busyTurn ? "true" : void 0,
|
|
1794
|
+
"aria-describedby": showingPreview ? previewId : void 0,
|
|
1795
|
+
onClick: (event) => {
|
|
1796
|
+
event.stopPropagation();
|
|
1797
|
+
onNavigate(item);
|
|
1798
|
+
},
|
|
1799
|
+
onFocus: () => {
|
|
1800
|
+
setPreviewTurn(item.turn);
|
|
1801
|
+
},
|
|
1802
|
+
onBlur: () => {
|
|
1803
|
+
setPreviewTurn(null);
|
|
1804
|
+
}
|
|
1805
|
+
})
|
|
1806
|
+
}, item.turn);
|
|
1807
|
+
})
|
|
1753
1808
|
})
|
|
1754
1809
|
}), preview !== void 0 && previewPosition !== void 0 && (0, react_jsx_runtime.jsxs)("div", {
|
|
1755
1810
|
id: previewId,
|
|
@@ -1768,9 +1823,12 @@ window.__ModuleLoader__.load({
|
|
|
1768
1823
|
});
|
|
1769
1824
|
}
|
|
1770
1825
|
/**
|
|
1771
|
-
*
|
|
1826
|
+
* Fixed-pitch rail of every known Turn — loaded marks scroll, unloaded marks
|
|
1827
|
+
* page history in first — with hover and focus previews. Overflow scrolls
|
|
1828
|
+
* inside the frame, gradient fades marking each scrollable end, and the
|
|
1829
|
+
* active mark keeps itself in view while the pointer is elsewhere.
|
|
1772
1830
|
*
|
|
1773
|
-
* Memoized because it renders two host elements per
|
|
1831
|
+
* Memoized because it renders two host elements per Turn while the
|
|
1774
1832
|
* enclosing view re-renders on every streaming delta: without the guard a long
|
|
1775
1833
|
* session rebuilds hundreds of marks per commit for a rail that only changes
|
|
1776
1834
|
* when a Turn is added, removed, or becomes active. Its props must therefore
|
|
@@ -1778,6 +1836,79 @@ window.__ModuleLoader__.load({
|
|
|
1778
1836
|
*/
|
|
1779
1837
|
const TurnNavigator = (0, react.memo)(TurnNavigatorRail);
|
|
1780
1838
|
//#endregion
|
|
1839
|
+
//#region lib/types/client/chat/turn-rail-items.js
|
|
1840
|
+
/**
|
|
1841
|
+
* View-layer union of the host turn outline and the loaded rail items. The
|
|
1842
|
+
* conversation snapshot never carries projection values, so this merge is the
|
|
1843
|
+
* one place the rail's two sources meet: the `turnOutline` projection names
|
|
1844
|
+
* every turn of the session, and the loaded window supplies anchors and
|
|
1845
|
+
* richer previews for the turns it holds.
|
|
1846
|
+
*/
|
|
1847
|
+
const EMPTY_ITEMS$1 = [];
|
|
1848
|
+
/**
|
|
1849
|
+
* Structurally narrow one wire outline entry (projection values cross the
|
|
1850
|
+
* wire). `turn` and `seq` are the load-bearing fields — a mark cannot exist
|
|
1851
|
+
* or jump without them — so their damage drops the entry; the previews are
|
|
1852
|
+
* decorative, so a malformed one degrades to `''` and the turn stays
|
|
1853
|
+
* navigable by number.
|
|
1854
|
+
*/
|
|
1855
|
+
function outlineEntry(value) {
|
|
1856
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
1857
|
+
const entry = value;
|
|
1858
|
+
if (typeof entry.turn !== "number" || !Number.isSafeInteger(entry.turn) || entry.turn < 0) return void 0;
|
|
1859
|
+
if (typeof entry.seq !== "number" || !Number.isSafeInteger(entry.seq) || entry.seq < 0) return void 0;
|
|
1860
|
+
return {
|
|
1861
|
+
turn: entry.turn,
|
|
1862
|
+
seq: entry.seq,
|
|
1863
|
+
prompt: typeof entry.prompt === "string" ? entry.prompt : "",
|
|
1864
|
+
response: typeof entry.response === "string" ? entry.response : ""
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
/** Wire outline entries, or none when the projection is absent or malformed. */
|
|
1868
|
+
function outlineEntries(outline) {
|
|
1869
|
+
return Array.isArray(outline) ? outline : EMPTY_ITEMS$1;
|
|
1870
|
+
}
|
|
1871
|
+
/**
|
|
1872
|
+
* Merge the host outline with the loaded rail items into the full ladder.
|
|
1873
|
+
* A turn present in both sides keeps the loaded anchor, taking an outline
|
|
1874
|
+
* preview only where the window's own is empty (a mid-Turn window head, or a
|
|
1875
|
+
* turn whose loaded nodes carry no text); turns on one side only pass
|
|
1876
|
+
* through. Result ascends by turn.
|
|
1877
|
+
* @param loaded - loaded-window rail items (timeline order).
|
|
1878
|
+
* @param outline - `turnOutline` projection value, treated as wire data.
|
|
1879
|
+
* @returns every known turn, ascending; a stable empty array when none.
|
|
1880
|
+
*/
|
|
1881
|
+
function mergeTurnRailItems(loaded, outline) {
|
|
1882
|
+
const byTurn = /* @__PURE__ */ new Map();
|
|
1883
|
+
for (const raw of outlineEntries(outline)) {
|
|
1884
|
+
const entry = outlineEntry(raw);
|
|
1885
|
+
if (entry === void 0) continue;
|
|
1886
|
+
byTurn.set(entry.turn, {
|
|
1887
|
+
turn: entry.turn,
|
|
1888
|
+
prompt: entry.prompt,
|
|
1889
|
+
response: entry.response,
|
|
1890
|
+
anchor: {
|
|
1891
|
+
kind: "unloaded",
|
|
1892
|
+
seq: entry.seq
|
|
1893
|
+
}
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
for (const item of loaded) {
|
|
1897
|
+
const preview = byTurn.get(item.turn);
|
|
1898
|
+
byTurn.set(item.turn, {
|
|
1899
|
+
turn: item.turn,
|
|
1900
|
+
prompt: item.prompt !== "" ? item.prompt : preview?.prompt ?? "",
|
|
1901
|
+
response: item.response !== "" ? item.response : preview?.response ?? "",
|
|
1902
|
+
anchor: {
|
|
1903
|
+
kind: "loaded",
|
|
1904
|
+
key: item.anchorKey
|
|
1905
|
+
}
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
if (byTurn.size === 0) return EMPTY_ITEMS$1;
|
|
1909
|
+
return [...byTurn.values()].sort((left, right) => left.turn - right.turn);
|
|
1910
|
+
}
|
|
1911
|
+
//#endregion
|
|
1781
1912
|
//#region lib/types/client/chat/ChatView.js
|
|
1782
1913
|
/** Active column host when present; otherwise the view-local scroller. */
|
|
1783
1914
|
function scrollerOf(from) {
|
|
@@ -1915,10 +2046,12 @@ window.__ModuleLoader__.load({
|
|
|
1915
2046
|
* The chat view slot entry: pure component over the composed props; each
|
|
1916
2047
|
* ordered business Node crosses the keyed renderer seat.
|
|
1917
2048
|
*/
|
|
1918
|
-
function ChatView({ useSession, useChat, useSessions, useStore, actions, renderSlot, sessionId, openFile, loadOlder, loadImage, openView, chatScroll, forkAt, fileMentions, useTranscriptView, t }) {
|
|
2049
|
+
function ChatView({ useSession, useChat, useSessions, useStore, actions, renderSlot, sessionId, openFile, loadOlder, loadThrough, loadImage, openView, chatScroll, forkAt, fileMentions, useTranscriptView, useProjection, t }) {
|
|
1919
2050
|
const order = useChat((s) => s.order);
|
|
1920
2051
|
const nodeStore = useChat((s) => s.nodes);
|
|
1921
2052
|
const turnNavigationItems = useChat((s) => s.navigation.items());
|
|
2053
|
+
const turnOutline = useProjection("turnOutline");
|
|
2054
|
+
const railItems = (0, react.useMemo)(() => mergeTurnRailItems(turnNavigationItems, turnOutline), [turnNavigationItems, turnOutline]);
|
|
1922
2055
|
const timeline = useChat((s) => s.timeline);
|
|
1923
2056
|
const inbox = useSession((s) => s.queue);
|
|
1924
2057
|
const cwd = useSessions((s) => s.byId[sessionId]?.cwd);
|
|
@@ -1961,7 +2094,7 @@ window.__ModuleLoader__.load({
|
|
|
1961
2094
|
const visibleSubmissions = (0, react.useMemo)(() => {
|
|
1962
2095
|
if (pendingSubmissions.length === 0) return pendingSubmissions;
|
|
1963
2096
|
const observed = observedRpcIds(order, nodeStore, inbox);
|
|
1964
|
-
return pendingSubmissions.filter((submission) => !observed.has(submission.requestId));
|
|
2097
|
+
return pendingSubmissions.filter((submission) => submission.placement !== "queued" && !observed.has(submission.requestId));
|
|
1965
2098
|
}, [
|
|
1966
2099
|
pendingSubmissions,
|
|
1967
2100
|
order,
|
|
@@ -1983,6 +2116,15 @@ window.__ModuleLoader__.load({
|
|
|
1983
2116
|
/** Paging anchor: semantic row/position at click, updated by reader scrolls
|
|
1984
2117
|
* while the request is pending and restored after the prepend lands. */
|
|
1985
2118
|
const anchorRef = (0, react.useRef)(null);
|
|
2119
|
+
/** Unloaded-turn jump in flight: target turn plus its load-through seq. */
|
|
2120
|
+
const pendingJumpRef = (0, react.useRef)(null);
|
|
2121
|
+
/** Whether the in-flight jump already landed mid-paging (settle then only corrects an untouched landing). */
|
|
2122
|
+
const jumpLandedRef = (0, react.useRef)(false);
|
|
2123
|
+
const [busyJumpTurn, setBusyJumpTurn] = (0, react.useState)(null);
|
|
2124
|
+
/** Bumped when a loadThrough completion settles, after its last page's commit. */
|
|
2125
|
+
const [jumpSettleTick, setJumpSettleTick] = (0, react.useState)(0);
|
|
2126
|
+
/** Window head at the last settle-time repage; an unmoved head falls back instead of repaging forever. */
|
|
2127
|
+
const jumpRepageHeadRef = (0, react.useRef)(null);
|
|
1986
2128
|
const firstSeqRef = (0, react.useRef)(null);
|
|
1987
2129
|
const openedRef = (0, react.useRef)(false);
|
|
1988
2130
|
const lastKeyRef = (0, react.useRef)(null);
|
|
@@ -2007,13 +2149,17 @@ window.__ModuleLoader__.load({
|
|
|
2007
2149
|
return;
|
|
2008
2150
|
}
|
|
2009
2151
|
const el = scrollerOf(local);
|
|
2152
|
+
if (el.scrollHeight - el.scrollTop - el.clientHeight <= 25) {
|
|
2153
|
+
const latest = turnNavigationItems.at(-1)?.turn ?? first.turn;
|
|
2154
|
+
setActiveTurn((current) => current === latest ? current : latest);
|
|
2155
|
+
return;
|
|
2156
|
+
}
|
|
2010
2157
|
const reading = turnAtLine(local, el.getBoundingClientRect().top + Math.min(96, el.clientHeight * .2));
|
|
2011
2158
|
let next = first.turn;
|
|
2012
2159
|
if (reading !== null) for (const item of turnNavigationItems) {
|
|
2013
2160
|
if (item.turn > reading) break;
|
|
2014
2161
|
next = item.turn;
|
|
2015
2162
|
}
|
|
2016
|
-
if (el.scrollHeight - el.scrollTop - el.clientHeight <= 25) next = turnNavigationItems.at(-1)?.turn ?? next;
|
|
2017
2163
|
setActiveTurn((current) => current === next ? current : next);
|
|
2018
2164
|
}, [turnNavigationItems]);
|
|
2019
2165
|
const activeTurnRef = (0, react.useRef)(null);
|
|
@@ -2038,6 +2184,8 @@ window.__ModuleLoader__.load({
|
|
|
2038
2184
|
}, [scheduleActiveTurn]);
|
|
2039
2185
|
const toBottom = (el) => {
|
|
2040
2186
|
anchorRef.current = null;
|
|
2187
|
+
pendingJumpRef.current = null;
|
|
2188
|
+
setBusyJumpTurn((current) => current === null ? current : null);
|
|
2041
2189
|
el.scrollTop = el.scrollHeight;
|
|
2042
2190
|
observedTopRef.current = el.scrollTop;
|
|
2043
2191
|
atBottomRef.current = true;
|
|
@@ -2045,6 +2193,50 @@ window.__ModuleLoader__.load({
|
|
|
2045
2193
|
chatScroll.save(null);
|
|
2046
2194
|
setActiveTurn(turnNavigationItems.at(-1)?.turn ?? null);
|
|
2047
2195
|
};
|
|
2196
|
+
const landOnRowRef = (0, react.useRef)(() => {});
|
|
2197
|
+
landOnRowRef.current = (local, el, row, turn) => {
|
|
2198
|
+
el.scrollTop += flowTop(row, el) - 24;
|
|
2199
|
+
observedTopRef.current = el.scrollTop;
|
|
2200
|
+
const isAtBottom = el.scrollHeight - el.scrollTop - el.clientHeight <= 25;
|
|
2201
|
+
atBottomRef.current = isAtBottom;
|
|
2202
|
+
setAtBottom(isAtBottom);
|
|
2203
|
+
setActiveTurn(turn);
|
|
2204
|
+
const position = isAtBottom ? null : scrollPosition(local, el);
|
|
2205
|
+
if (isAtBottom) chatScroll.save(null);
|
|
2206
|
+
else if (position !== null) chatScroll.save(position);
|
|
2207
|
+
};
|
|
2208
|
+
/**
|
|
2209
|
+
* Land the pending jump once its Turn has a rendered anchor row; false
|
|
2210
|
+
* while it must keep waiting. Mid-jump landings (`settle` false) keep the
|
|
2211
|
+
* jump armed with the target row as the paging anchor, so later chunks and
|
|
2212
|
+
* the load-earlier button's unmount re-land on the same row; the settling
|
|
2213
|
+
* call clears the jump.
|
|
2214
|
+
*/
|
|
2215
|
+
const realizePendingJump = (local, el, settle) => {
|
|
2216
|
+
const pending = pendingJumpRef.current;
|
|
2217
|
+
if (pending === null) return true;
|
|
2218
|
+
const item = railItems.find((candidate) => candidate.turn === pending.turn);
|
|
2219
|
+
if (item === void 0 || item.anchor.kind !== "loaded") return false;
|
|
2220
|
+
const row = anchorElement(local, item.anchor.key);
|
|
2221
|
+
if (row === null) return false;
|
|
2222
|
+
if (settle) {
|
|
2223
|
+
pendingJumpRef.current = null;
|
|
2224
|
+
setBusyJumpTurn(null);
|
|
2225
|
+
const held = anchorRef.current;
|
|
2226
|
+
const landedEarlier = jumpLandedRef.current;
|
|
2227
|
+
jumpLandedRef.current = false;
|
|
2228
|
+
anchorRef.current = null;
|
|
2229
|
+
if (!landedEarlier || held?.key === item.anchor.key) landOnRowRef.current(local, el, row, pending.turn);
|
|
2230
|
+
return true;
|
|
2231
|
+
}
|
|
2232
|
+
landOnRowRef.current(local, el, row, pending.turn);
|
|
2233
|
+
jumpLandedRef.current = true;
|
|
2234
|
+
anchorRef.current = {
|
|
2235
|
+
key: item.anchor.key,
|
|
2236
|
+
top: flowTop(row, el)
|
|
2237
|
+
};
|
|
2238
|
+
return true;
|
|
2239
|
+
};
|
|
2048
2240
|
(0, react.useLayoutEffect)(() => {
|
|
2049
2241
|
const local = listRef.current;
|
|
2050
2242
|
/* v8 ignore next -- ref-null guard: React attaches the ref before layout effects run. */
|
|
@@ -2079,6 +2271,10 @@ window.__ModuleLoader__.load({
|
|
|
2079
2271
|
const row = anchorElement(local, anchor.key);
|
|
2080
2272
|
if (row !== null) el.scrollTop += flowTop(row, el) - anchor.top;
|
|
2081
2273
|
observedTopRef.current = el.scrollTop;
|
|
2274
|
+
if (!realizePendingJump(local, el, false) && row !== null) anchorRef.current = {
|
|
2275
|
+
key: anchor.key,
|
|
2276
|
+
top: flowTop(row, el)
|
|
2277
|
+
};
|
|
2082
2278
|
firstSeqRef.current = firstSeq;
|
|
2083
2279
|
/* v8 ignore next -- ?? arm: a prepend adds nodes, so the flow list here is never empty. */
|
|
2084
2280
|
lastKeyRef.current = lastKey;
|
|
@@ -2096,7 +2292,11 @@ window.__ModuleLoader__.load({
|
|
|
2096
2292
|
lastSteeringIdRef.current = lastSteeringId;
|
|
2097
2293
|
lastSubmissionIdRef.current = lastSubmissionId;
|
|
2098
2294
|
followSigRef.current = followSig;
|
|
2099
|
-
if (appendedUser || appendedSteering || appendedSubmission || tipMoved && atBottomRef.current)
|
|
2295
|
+
if (appendedUser || appendedSteering || appendedSubmission || tipMoved && atBottomRef.current) {
|
|
2296
|
+
toBottom(el);
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
if (pendingJumpRef.current !== null) realizePendingJump(local, el, false);
|
|
2100
2300
|
});
|
|
2101
2301
|
const onScrollRef = (0, react.useRef)(() => {});
|
|
2102
2302
|
onScrollRef.current = () => {
|
|
@@ -2165,6 +2365,39 @@ window.__ModuleLoader__.load({
|
|
|
2165
2365
|
(0, react.useEffect)(() => {
|
|
2166
2366
|
if (!loadingOlder) anchorRef.current = null;
|
|
2167
2367
|
}, [loadingOlder]);
|
|
2368
|
+
(0, react.useEffect)(() => {
|
|
2369
|
+
const pending = pendingJumpRef.current;
|
|
2370
|
+
const local = listRef.current;
|
|
2371
|
+
if (pending === null || local === null) return;
|
|
2372
|
+
const el = scrollerOf(local);
|
|
2373
|
+
if (realizePendingJump(local, el, true)) return;
|
|
2374
|
+
if ((firstSeq === null || firstSeq > pending.seq) && hasMore) {
|
|
2375
|
+
if (loadingOlder) return;
|
|
2376
|
+
if (jumpRepageHeadRef.current !== firstSeq) {
|
|
2377
|
+
jumpRepageHeadRef.current = firstSeq;
|
|
2378
|
+
const held = pagingAnchor(local, el);
|
|
2379
|
+
if (held !== null && held.dataset.chatAnchorKey !== void 0) anchorRef.current = {
|
|
2380
|
+
key: held.dataset.chatAnchorKey,
|
|
2381
|
+
top: flowTop(held, el)
|
|
2382
|
+
};
|
|
2383
|
+
loadThrough(pending.seq).finally(() => {
|
|
2384
|
+
setJumpSettleTick((tick) => tick + 1);
|
|
2385
|
+
});
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
for (const row of local.querySelectorAll("[data-chat-turn]:not([hidden])")) {
|
|
2390
|
+
const turn = Number(row.dataset.chatTurn);
|
|
2391
|
+
if (!Number.isSafeInteger(turn) || turn < pending.turn) continue;
|
|
2392
|
+
landOnRowRef.current(local, el, row, turn);
|
|
2393
|
+
break;
|
|
2394
|
+
}
|
|
2395
|
+
pendingJumpRef.current = null;
|
|
2396
|
+
setBusyJumpTurn(null);
|
|
2397
|
+
}, [jumpSettleTick]);
|
|
2398
|
+
(0, react.useEffect)(() => {
|
|
2399
|
+
if (!loadingOlder && pendingJumpRef.current !== null) setJumpSettleTick((tick) => tick + 1);
|
|
2400
|
+
}, [loadingOlder]);
|
|
2168
2401
|
const loadOlderAnchored = () => {
|
|
2169
2402
|
const local = listRef.current;
|
|
2170
2403
|
/* v8 ignore next -- ref-null guard: the paging button renders inside the list tree. */
|
|
@@ -2181,24 +2414,38 @@ window.__ModuleLoader__.load({
|
|
|
2181
2414
|
const navigateToTurn = (0, react.useCallback)((item) => {
|
|
2182
2415
|
const local = listRef.current;
|
|
2183
2416
|
if (local === null) return;
|
|
2184
|
-
const row = anchorElement(local, item.anchorKey);
|
|
2185
|
-
if (row === null) return;
|
|
2186
2417
|
const el = scrollerOf(local);
|
|
2187
|
-
|
|
2188
|
-
|
|
2418
|
+
if (item.anchor.kind === "unloaded") {
|
|
2419
|
+
atBottomRef.current = false;
|
|
2420
|
+
setAtBottom(false);
|
|
2421
|
+
const held = pagingAnchor(local, el);
|
|
2422
|
+
if (held !== null && held.dataset.chatAnchorKey !== void 0) anchorRef.current = {
|
|
2423
|
+
key: held.dataset.chatAnchorKey,
|
|
2424
|
+
top: flowTop(held, el)
|
|
2425
|
+
};
|
|
2426
|
+
pendingJumpRef.current = {
|
|
2427
|
+
turn: item.turn,
|
|
2428
|
+
seq: item.anchor.seq
|
|
2429
|
+
};
|
|
2430
|
+
jumpRepageHeadRef.current = null;
|
|
2431
|
+
jumpLandedRef.current = false;
|
|
2432
|
+
setBusyJumpTurn(item.turn);
|
|
2433
|
+
loadThrough(item.anchor.seq).finally(() => {
|
|
2434
|
+
setJumpSettleTick((tick) => tick + 1);
|
|
2435
|
+
});
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2438
|
+
const row = anchorElement(local, item.anchor.key);
|
|
2439
|
+
if (row === null) return;
|
|
2440
|
+
pendingJumpRef.current = null;
|
|
2441
|
+
setBusyJumpTurn((current) => current === null ? current : null);
|
|
2442
|
+
landOnRowRef.current(local, el, row, item.turn);
|
|
2189
2443
|
const landed = loadingOlder ? pagingAnchor(local, el) : null;
|
|
2190
2444
|
anchorRef.current = landed === null || landed.dataset.chatAnchorKey === void 0 ? null : {
|
|
2191
2445
|
key: landed.dataset.chatAnchorKey,
|
|
2192
2446
|
top: flowTop(landed, el)
|
|
2193
2447
|
};
|
|
2194
|
-
|
|
2195
|
-
atBottomRef.current = isAtBottom;
|
|
2196
|
-
setAtBottom(isAtBottom);
|
|
2197
|
-
setActiveTurn(item.turn);
|
|
2198
|
-
const position = isAtBottom ? null : scrollPosition(local, el);
|
|
2199
|
-
if (isAtBottom) chatScroll.save(null);
|
|
2200
|
-
else if (position !== null) chatScroll.save(position);
|
|
2201
|
-
}, [loadingOlder, chatScroll]);
|
|
2448
|
+
}, [loadingOlder, loadThrough]);
|
|
2202
2449
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
2203
2450
|
className: ChatView_module_css_default.root,
|
|
2204
2451
|
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2206,8 +2453,9 @@ window.__ModuleLoader__.load({
|
|
|
2206
2453
|
className: ChatView_module_css_default.scroll,
|
|
2207
2454
|
children: [
|
|
2208
2455
|
(0, react_jsx_runtime.jsx)(TurnNavigator, {
|
|
2209
|
-
items:
|
|
2456
|
+
items: railItems,
|
|
2210
2457
|
activeTurn,
|
|
2458
|
+
busyTurn: busyJumpTurn,
|
|
2211
2459
|
onNavigate: navigateToTurn,
|
|
2212
2460
|
t
|
|
2213
2461
|
}),
|
|
@@ -2351,6 +2599,7 @@ window.__ModuleLoader__.load({
|
|
|
2351
2599
|
"chat.deepDiving": "深度求索中...",
|
|
2352
2600
|
"chat.turnNavigation.label": "轮次导航",
|
|
2353
2601
|
"chat.turnNavigation.jump": "跳转到第 {turn} 轮",
|
|
2602
|
+
"chat.turnNavigation.jumpLoad": "加载并跳转到第 {turn} 轮",
|
|
2354
2603
|
"chat.turnNavigation.turn": "第 {turn} 轮",
|
|
2355
2604
|
"settings.transcript.title": "对话显示",
|
|
2356
2605
|
"settings.transcript.description": "控制已完成轮次的过程内容",
|
|
@@ -2408,18 +2657,21 @@ window.__ModuleLoader__.load({
|
|
|
2408
2657
|
"message.maxTokens": "已达到输出 token 上限",
|
|
2409
2658
|
"message.maxTokens.hint": "回答被截断,已有输出保留在对话中。发送“继续”可让模型接着输出。",
|
|
2410
2659
|
"message.ranFor": "用时 {duration}",
|
|
2411
|
-
"message.ttft": "首 token {seconds}秒",
|
|
2412
2660
|
"message.tokensPerSecond": "{tps} tok/s",
|
|
2413
2661
|
"message.turnUsage.title": "本轮用量",
|
|
2414
|
-
"message.turnUsage.
|
|
2662
|
+
"message.turnUsage.consumed": "用量 {total}",
|
|
2415
2663
|
"message.turnUsage.model": "提供方 / 模型",
|
|
2664
|
+
"message.turnUsage.cacheHit": "缓存命中",
|
|
2416
2665
|
"message.turnUsage.input": "未缓存输入",
|
|
2417
2666
|
"message.turnUsage.cacheRead": "缓存读取",
|
|
2418
2667
|
"message.turnUsage.cacheWrite": "缓存写入",
|
|
2419
2668
|
"message.turnUsage.output": "输出",
|
|
2420
2669
|
"message.turnUsage.reasoning": "(其中推理 {tokens})",
|
|
2421
|
-
"message.turnUsage.total": "总计",
|
|
2422
2670
|
"message.turnUsage.count": "{count} tok",
|
|
2671
|
+
"message.turnTime.title": "本轮用时和速度",
|
|
2672
|
+
"message.turnTime.duration": "本轮总用时",
|
|
2673
|
+
"message.turnTime.speed": "输出速度(TPS)",
|
|
2674
|
+
"message.turnTime.ttft": "首 token 用时(TTFT)",
|
|
2423
2675
|
"duration.seconds": "{seconds}秒",
|
|
2424
2676
|
"duration.minutes": "{minutes}分{seconds}秒",
|
|
2425
2677
|
"command.running": "执行中…",
|
|
@@ -2460,6 +2712,7 @@ window.__ModuleLoader__.load({
|
|
|
2460
2712
|
"chat.deepDiving": "Deep diving...",
|
|
2461
2713
|
"chat.turnNavigation.label": "Turn navigation",
|
|
2462
2714
|
"chat.turnNavigation.jump": "Jump to turn {turn}",
|
|
2715
|
+
"chat.turnNavigation.jumpLoad": "Load and jump to turn {turn}",
|
|
2463
2716
|
"chat.turnNavigation.turn": "Turn {turn}",
|
|
2464
2717
|
"settings.transcript.title": "Conversation display",
|
|
2465
2718
|
"settings.transcript.description": "Controls process content in completed turns",
|
|
@@ -2517,18 +2770,21 @@ window.__ModuleLoader__.load({
|
|
|
2517
2770
|
"message.maxTokens": "Output token limit reached",
|
|
2518
2771
|
"message.maxTokens.hint": "The reply was cut off; earlier output is preserved in the conversation. Send \"continue\" to let the model resume.",
|
|
2519
2772
|
"message.ranFor": "Ran for {duration}",
|
|
2520
|
-
"message.ttft": "TTFT {seconds}s",
|
|
2521
2773
|
"message.tokensPerSecond": "{tps} tok/s",
|
|
2522
2774
|
"message.turnUsage.title": "Turn usage",
|
|
2523
|
-
"message.turnUsage.
|
|
2775
|
+
"message.turnUsage.consumed": "Usage {total}",
|
|
2524
2776
|
"message.turnUsage.model": "Provider / model",
|
|
2777
|
+
"message.turnUsage.cacheHit": "Cache hit",
|
|
2525
2778
|
"message.turnUsage.input": "Uncached input",
|
|
2526
2779
|
"message.turnUsage.cacheRead": "Cached input",
|
|
2527
2780
|
"message.turnUsage.cacheWrite": "Cache write",
|
|
2528
2781
|
"message.turnUsage.output": "Output",
|
|
2529
2782
|
"message.turnUsage.reasoning": " ({tokens} reasoning)",
|
|
2530
|
-
"message.turnUsage.total": "Total",
|
|
2531
2783
|
"message.turnUsage.count": "{count} tok",
|
|
2784
|
+
"message.turnTime.title": "Turn time and speed",
|
|
2785
|
+
"message.turnTime.duration": "Total run time",
|
|
2786
|
+
"message.turnTime.speed": "Tokens per second (TPS)",
|
|
2787
|
+
"message.turnTime.ttft": "Time to first token (TTFT)",
|
|
2532
2788
|
"duration.seconds": "{seconds}s",
|
|
2533
2789
|
"duration.minutes": "{minutes}m {seconds}s",
|
|
2534
2790
|
"command.running": "Running…",
|
|
@@ -3113,9 +3369,9 @@ window.__ModuleLoader__.load({
|
|
|
3113
3369
|
return `99.${"9".repeat(distinguishingPlaces - 1)}${10 - roundedLoss}`;
|
|
3114
3370
|
}
|
|
3115
3371
|
//#endregion
|
|
3116
|
-
//#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-chat/src/client/chat/
|
|
3117
|
-
const css$4 = ".
|
|
3118
|
-
const tagId$4 = "@prettier-ai/dsh-client-ui-chat/
|
|
3372
|
+
//#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-chat/src/client/chat/TurnUsagePanel.module.css.mjs
|
|
3373
|
+
const css$4 = ".ZVOUHW_root{min-width:0;display:inline-flex}.ZVOUHW_root+.ZVOUHW_root{margin-left:-6px}.ZVOUHW_trigger{min-width:0;height:calc(28px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);font-variant-numeric:tabular-nums;line-height:calc(24px + var(--dsh-content-font-delta,0px));white-space:nowrap;cursor:pointer;background:0 0;border:none;border-radius:28px;align-items:center;gap:4px;padding:6px 8px;display:inline-flex}.ZVOUHW_label{text-overflow:ellipsis;min-width:0;overflow:hidden}.ZVOUHW_trigger svg{width:calc(15px + var(--dsh-content-font-delta,0px));height:calc(15px + var(--dsh-content-font-delta,0px));flex:none}.ZVOUHW_trigger:hover,.ZVOUHW_trigger[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}@media (width<=480px){.ZVOUHW_trigger{width:calc(28px + var(--dsh-content-font-delta,0px));justify-content:center;padding:6px}.ZVOUHW_trigger .ZVOUHW_label{display:none}.ZVOUHW_root+.ZVOUHW_root{margin-left:0}}.ZVOUHW_panel{z-index:1100;box-sizing:border-box;border:1px solid var(--dsw-alias-border-inverted);background:var(--dsw-specific-menu);width:max-content;min-width:min(300px,100vw - 24px);max-width:min(440px,100vw - 24px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-secondary);cursor:default;border-radius:12px;padding:16px;font-size:12px;line-height:18px;position:fixed}.ZVOUHW_title{color:var(--dsw-alias-label-primary);justify-content:space-between;gap:16px;margin-bottom:8px;font-weight:500;display:flex}.ZVOUHW_titleRule{border-top:1px solid var(--dsw-alias-border-l2);margin-bottom:10px}.ZVOUHW_titleValue{font-variant-numeric:tabular-nums}.ZVOUHW_titleLabel{align-items:center;gap:6px;min-width:0;display:inline-flex}.ZVOUHW_titleLabel svg{flex:none;width:14px;height:14px}.ZVOUHW_details{color:var(--dsw-alias-label-tertiary);grid-template-columns:minmax(76px,auto) minmax(0,1fr);gap:6px 16px;margin:0;display:grid}.ZVOUHW_details dt,.ZVOUHW_details dd{min-width:0;margin:0}.ZVOUHW_details dd{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;text-align:right}.ZVOUHW_details .ZVOUHW_route{overflow-wrap:anywhere}.ZVOUHW_reasoning{color:var(--dsw-alias-label-tertiary);white-space:nowrap}";
|
|
3374
|
+
const tagId$4 = "@prettier-ai/dsh-client-ui-chat/TurnUsagePanel.module.css";
|
|
3119
3375
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
|
|
3120
3376
|
const tag = document.createElement("style");
|
|
3121
3377
|
tag.dataset.plugin = "@prettier-ai/dsh-client-ui-chat";
|
|
@@ -3123,81 +3379,194 @@ window.__ModuleLoader__.load({
|
|
|
3123
3379
|
tag.textContent = css$4;
|
|
3124
3380
|
document.head.appendChild(tag);
|
|
3125
3381
|
}
|
|
3126
|
-
var
|
|
3127
|
-
"
|
|
3128
|
-
"
|
|
3129
|
-
"
|
|
3130
|
-
"
|
|
3131
|
-
"
|
|
3132
|
-
"
|
|
3133
|
-
"
|
|
3134
|
-
"
|
|
3135
|
-
"
|
|
3382
|
+
var TurnUsagePanel_module_css_default = {
|
|
3383
|
+
"details": "ZVOUHW_details",
|
|
3384
|
+
"label": "ZVOUHW_label",
|
|
3385
|
+
"panel": "ZVOUHW_panel",
|
|
3386
|
+
"reasoning": "ZVOUHW_reasoning",
|
|
3387
|
+
"root": "ZVOUHW_root",
|
|
3388
|
+
"route": "ZVOUHW_route",
|
|
3389
|
+
"title": "ZVOUHW_title",
|
|
3390
|
+
"titleLabel": "ZVOUHW_titleLabel",
|
|
3391
|
+
"titleRule": "ZVOUHW_titleRule",
|
|
3392
|
+
"titleValue": "ZVOUHW_titleValue",
|
|
3393
|
+
"trigger": "ZVOUHW_trigger"
|
|
3136
3394
|
};
|
|
3137
3395
|
//#endregion
|
|
3138
|
-
//#region lib/types/client/chat/
|
|
3396
|
+
//#region lib/types/client/chat/TurnUsagePanel.js
|
|
3139
3397
|
function formatCompactCount(value, t) {
|
|
3140
3398
|
return t("message.turnUsage.count", { count: formatTokens(value, t) });
|
|
3141
3399
|
}
|
|
3142
3400
|
function formatExactCount(value, t) {
|
|
3143
3401
|
return t("message.turnUsage.count", { count: formatExactTokens(value, t) });
|
|
3144
3402
|
}
|
|
3145
|
-
/**
|
|
3146
|
-
|
|
3403
|
+
/** Viewport margin the placement clamp keeps (the Menu portal margin). */
|
|
3404
|
+
const PANEL_MARGIN = 12;
|
|
3405
|
+
/** Distance between the trigger's top edge and the panel's bottom. */
|
|
3406
|
+
const PANEL_GAP = 8;
|
|
3407
|
+
/**
|
|
3408
|
+
* Unplaced portal panel: hidden but laid out so the clamp measures real
|
|
3409
|
+
* dimensions (the `useAnchoredPosition` measure pass).
|
|
3410
|
+
*/
|
|
3411
|
+
const MEASURE_STYLE = {
|
|
3412
|
+
visibility: "hidden",
|
|
3413
|
+
left: 0,
|
|
3414
|
+
top: 0
|
|
3415
|
+
};
|
|
3416
|
+
/** One trigger-anchored dialog seat: open state, viewport-clamped placement, outside-close. */
|
|
3417
|
+
function useStatDialog() {
|
|
3147
3418
|
const [open, setOpen] = (0, react.useState)(false);
|
|
3419
|
+
const rootRef = (0, react.useRef)(null);
|
|
3420
|
+
const panelRef = (0, react.useRef)(null);
|
|
3421
|
+
const pos = (0, _prettier_ai_dsh_client_ui_primitives.useAnchoredPosition)({
|
|
3422
|
+
open,
|
|
3423
|
+
anchorRef: rootRef,
|
|
3424
|
+
panelRef,
|
|
3425
|
+
side: "top",
|
|
3426
|
+
gap: PANEL_GAP,
|
|
3427
|
+
margin: PANEL_MARGIN
|
|
3428
|
+
});
|
|
3429
|
+
(0, _prettier_ai_dsh_client_ui_primitives.useDismissOnOutsidePointer)(rootRef, open, setOpen, panelRef);
|
|
3430
|
+
(0, react.useEffect)(() => {
|
|
3431
|
+
if (!open) return;
|
|
3432
|
+
const onKeyDown = (e) => {
|
|
3433
|
+
if (e.key === "Escape") setOpen(false);
|
|
3434
|
+
};
|
|
3435
|
+
document.addEventListener("keydown", onKeyDown);
|
|
3436
|
+
return () => {
|
|
3437
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
3438
|
+
};
|
|
3439
|
+
}, [open]);
|
|
3440
|
+
return {
|
|
3441
|
+
open,
|
|
3442
|
+
setOpen,
|
|
3443
|
+
rootRef,
|
|
3444
|
+
panelRef,
|
|
3445
|
+
pos
|
|
3446
|
+
};
|
|
3447
|
+
}
|
|
3448
|
+
/**
|
|
3449
|
+
* Turn-usage IconActions pill with a click-open Turn-usage details dialog.
|
|
3450
|
+
* @param props - Turn usage buckets and locale seat.
|
|
3451
|
+
* @returns The trigger and, while open, its portaled dialog anchored above the trigger.
|
|
3452
|
+
*/
|
|
3453
|
+
function TurnUsagePanel({ usage, t }) {
|
|
3454
|
+
const { open, setOpen, rootRef, panelRef, pos } = useStatDialog();
|
|
3148
3455
|
const cacheHit = usage.cacheReadTokens === void 0 ? null : formatCacheHitPercent(usage.cacheReadTokens, usage.totalTokens - usage.outputTokens, 1);
|
|
3149
3456
|
const total = formatCompactCount(usage.totalTokens, t);
|
|
3150
|
-
const summary = cacheHit === null ? total : t("message.turnUsage.summaryWithCache", {
|
|
3151
|
-
total,
|
|
3152
|
-
percent: cacheHit
|
|
3153
|
-
});
|
|
3154
3457
|
const routes = usage.routes?.map((route) => `${route.provider}/${route.model}`).join(", ") ?? "";
|
|
3155
|
-
return (0, react_jsx_runtime.
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
"data-turn-usage-details": true,
|
|
3458
|
+
return (0, react_jsx_runtime.jsxs)("span", {
|
|
3459
|
+
ref: rootRef,
|
|
3460
|
+
className: TurnUsagePanel_module_css_default.root,
|
|
3461
|
+
children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
3462
|
+
type: "button",
|
|
3463
|
+
className: TurnUsagePanel_module_css_default.trigger,
|
|
3464
|
+
"aria-haspopup": "dialog",
|
|
3465
|
+
"aria-expanded": open,
|
|
3466
|
+
onClick: () => {
|
|
3467
|
+
setOpen(!open);
|
|
3468
|
+
},
|
|
3469
|
+
children: [(0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconDatabaseOutline16, {}), (0, react_jsx_runtime.jsx)("span", {
|
|
3470
|
+
className: TurnUsagePanel_module_css_default.label,
|
|
3471
|
+
children: t("message.turnUsage.consumed", { total })
|
|
3472
|
+
})]
|
|
3473
|
+
}), open && (0, react_dom.createPortal)((0, react_jsx_runtime.jsxs)("div", {
|
|
3474
|
+
ref: panelRef,
|
|
3475
|
+
className: TurnUsagePanel_module_css_default.panel,
|
|
3476
|
+
role: "dialog",
|
|
3477
|
+
"aria-label": t("message.turnUsage.title"),
|
|
3478
|
+
style: pos ?? MEASURE_STYLE,
|
|
3177
3479
|
children: [
|
|
3178
|
-
|
|
3179
|
-
className:
|
|
3180
|
-
children:
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
(0, react_jsx_runtime.jsxs)("dd", { children: [formatExactCount(usage.outputTokens, t), usage.reasoningTokens !== void 0 && (0, react_jsx_runtime.jsx)("span", {
|
|
3188
|
-
className: TurnUsageDisclosure_module_css_default.reasoning,
|
|
3189
|
-
children: t("message.turnUsage.reasoning", { tokens: formatExactCount(usage.reasoningTokens, t) })
|
|
3190
|
-
})] }),
|
|
3191
|
-
(0, react_jsx_runtime.jsx)("dt", {
|
|
3192
|
-
className: TurnUsageDisclosure_module_css_default.totalLabel,
|
|
3193
|
-
children: t("message.turnUsage.total")
|
|
3480
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
3481
|
+
className: TurnUsagePanel_module_css_default.title,
|
|
3482
|
+
children: [(0, react_jsx_runtime.jsxs)("span", {
|
|
3483
|
+
className: TurnUsagePanel_module_css_default.titleLabel,
|
|
3484
|
+
children: [(0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconDatabaseOutline16, {}), t("message.turnUsage.title")]
|
|
3485
|
+
}), (0, react_jsx_runtime.jsx)("span", {
|
|
3486
|
+
className: TurnUsagePanel_module_css_default.titleValue,
|
|
3487
|
+
children: formatExactCount(usage.totalTokens, t)
|
|
3488
|
+
})]
|
|
3194
3489
|
}),
|
|
3195
|
-
(0, react_jsx_runtime.jsx)("
|
|
3196
|
-
className:
|
|
3197
|
-
|
|
3490
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
3491
|
+
className: TurnUsagePanel_module_css_default.titleRule,
|
|
3492
|
+
"aria-hidden": true
|
|
3493
|
+
}),
|
|
3494
|
+
(0, react_jsx_runtime.jsxs)("dl", {
|
|
3495
|
+
className: TurnUsagePanel_module_css_default.details,
|
|
3496
|
+
"data-turn-usage-details": true,
|
|
3497
|
+
children: [
|
|
3498
|
+
routes !== "" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.model") }), (0, react_jsx_runtime.jsx)("dd", {
|
|
3499
|
+
className: TurnUsagePanel_module_css_default.route,
|
|
3500
|
+
children: routes
|
|
3501
|
+
})] }),
|
|
3502
|
+
cacheHit !== null && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.cacheHit") }), (0, react_jsx_runtime.jsx)("dd", { children: `${cacheHit}%` })] }),
|
|
3503
|
+
(0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.input") }),
|
|
3504
|
+
(0, react_jsx_runtime.jsx)("dd", { children: formatExactCount(usage.uncachedInputTokens, t) }),
|
|
3505
|
+
usage.cacheReadTokens !== void 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.cacheRead") }), (0, react_jsx_runtime.jsx)("dd", { children: formatExactCount(usage.cacheReadTokens, t) })] }),
|
|
3506
|
+
usage.cacheWriteTokens !== void 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.cacheWrite") }), (0, react_jsx_runtime.jsx)("dd", { children: formatExactCount(usage.cacheWriteTokens, t) })] }),
|
|
3507
|
+
(0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.output") }),
|
|
3508
|
+
(0, react_jsx_runtime.jsxs)("dd", { children: [formatExactCount(usage.outputTokens, t), usage.reasoningTokens !== void 0 && (0, react_jsx_runtime.jsx)("span", {
|
|
3509
|
+
className: TurnUsagePanel_module_css_default.reasoning,
|
|
3510
|
+
children: t("message.turnUsage.reasoning", { tokens: formatExactCount(usage.reasoningTokens, t) })
|
|
3511
|
+
})] })
|
|
3512
|
+
]
|
|
3198
3513
|
})
|
|
3199
3514
|
]
|
|
3200
|
-
})
|
|
3515
|
+
}), document.body)]
|
|
3516
|
+
});
|
|
3517
|
+
}
|
|
3518
|
+
/**
|
|
3519
|
+
* Turn-time IconActions pill with a click-open Turn-time details dialog.
|
|
3520
|
+
* @param props - Turn timing facts and locale seat.
|
|
3521
|
+
* @returns The clock-and-duration trigger and, while open, its portaled dialog anchored above the trigger.
|
|
3522
|
+
*/
|
|
3523
|
+
function TurnTimePanel({ runMs, tokensPerSecond, ttftMs, t }) {
|
|
3524
|
+
const { open, setOpen, rootRef, panelRef, pos } = useStatDialog();
|
|
3525
|
+
return (0, react_jsx_runtime.jsxs)("span", {
|
|
3526
|
+
ref: rootRef,
|
|
3527
|
+
className: TurnUsagePanel_module_css_default.root,
|
|
3528
|
+
children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
3529
|
+
type: "button",
|
|
3530
|
+
className: TurnUsagePanel_module_css_default.trigger,
|
|
3531
|
+
"aria-haspopup": "dialog",
|
|
3532
|
+
"aria-expanded": open,
|
|
3533
|
+
onClick: () => {
|
|
3534
|
+
setOpen(!open);
|
|
3535
|
+
},
|
|
3536
|
+
children: [(0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconClockOutline16, {}), (0, react_jsx_runtime.jsx)("span", {
|
|
3537
|
+
className: TurnUsagePanel_module_css_default.label,
|
|
3538
|
+
children: t("message.ranFor", { duration: formatRunDuration(runMs, t) })
|
|
3539
|
+
})]
|
|
3540
|
+
}), open && (0, react_dom.createPortal)((0, react_jsx_runtime.jsxs)("div", {
|
|
3541
|
+
ref: panelRef,
|
|
3542
|
+
className: TurnUsagePanel_module_css_default.panel,
|
|
3543
|
+
role: "dialog",
|
|
3544
|
+
"aria-label": t("message.turnTime.title"),
|
|
3545
|
+
style: pos ?? MEASURE_STYLE,
|
|
3546
|
+
children: [
|
|
3547
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
3548
|
+
className: TurnUsagePanel_module_css_default.title,
|
|
3549
|
+
children: (0, react_jsx_runtime.jsxs)("span", {
|
|
3550
|
+
className: TurnUsagePanel_module_css_default.titleLabel,
|
|
3551
|
+
children: [(0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconClockOutline16, {}), t("message.turnTime.title")]
|
|
3552
|
+
})
|
|
3553
|
+
}),
|
|
3554
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
3555
|
+
className: TurnUsagePanel_module_css_default.titleRule,
|
|
3556
|
+
"aria-hidden": true
|
|
3557
|
+
}),
|
|
3558
|
+
(0, react_jsx_runtime.jsxs)("dl", {
|
|
3559
|
+
className: TurnUsagePanel_module_css_default.details,
|
|
3560
|
+
"data-turn-time-details": true,
|
|
3561
|
+
children: [
|
|
3562
|
+
(0, react_jsx_runtime.jsx)("dt", { children: t("message.turnTime.duration") }),
|
|
3563
|
+
(0, react_jsx_runtime.jsx)("dd", { children: formatRunDuration(runMs, t) }),
|
|
3564
|
+
tokensPerSecond !== void 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("message.turnTime.speed") }), (0, react_jsx_runtime.jsx)("dd", { children: t("message.tokensPerSecond", { tps: formatTokensPerSecond(tokensPerSecond) }) })] }),
|
|
3565
|
+
ttftMs !== void 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("message.turnTime.ttft") }), (0, react_jsx_runtime.jsx)("dd", { children: t("duration.seconds", { seconds: formatLatencySeconds(ttftMs) }) })] })
|
|
3566
|
+
]
|
|
3567
|
+
})
|
|
3568
|
+
]
|
|
3569
|
+
}), document.body)]
|
|
3201
3570
|
});
|
|
3202
3571
|
}
|
|
3203
3572
|
//#endregion
|
|
@@ -3212,7 +3581,7 @@ window.__ModuleLoader__.load({
|
|
|
3212
3581
|
}
|
|
3213
3582
|
//#endregion
|
|
3214
3583
|
//#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-chat/src/client/chat/TurnTailNodeView.module.css.mjs
|
|
3215
|
-
const css$3 = ".v8MmmG_root{flex-direction:column;gap:16px;display:flex}.
|
|
3584
|
+
const css$3 = ".v8MmmG_root{flex-direction:column;gap:16px;display:flex}.v8MmmG_actions{margin-left:-6px}";
|
|
3216
3585
|
const tagId$3 = "@prettier-ai/dsh-client-ui-chat/TurnTailNodeView.module.css";
|
|
3217
3586
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
|
|
3218
3587
|
const tag = document.createElement("style");
|
|
@@ -3223,7 +3592,6 @@ window.__ModuleLoader__.load({
|
|
|
3223
3592
|
}
|
|
3224
3593
|
var TurnTailNodeView_module_css_default = {
|
|
3225
3594
|
"actions": "v8MmmG_actions",
|
|
3226
|
-
"footer": "v8MmmG_footer",
|
|
3227
3595
|
"root": "v8MmmG_root"
|
|
3228
3596
|
};
|
|
3229
3597
|
//#endregion
|
|
@@ -3232,6 +3600,7 @@ window.__ModuleLoader__.load({
|
|
|
3232
3600
|
const TurnTailNodeView = (0, react.memo)(function TurnTailNodeView({ node, openFile, forkAt, renderSlot, renderSlotChain, t, useChat }) {
|
|
3233
3601
|
const data = node.data;
|
|
3234
3602
|
const hasLaterChatNode = useChat((snapshot) => snapshot.locations.getTurn(data.turn).at(-1) !== node.key);
|
|
3603
|
+
const isLatestTurn = useChat((snapshot) => snapshot.timeline.turnOrder.at(-1) === data.turn);
|
|
3235
3604
|
const turn = node.location.kind === "turn" || node.location.kind === "step" ? node.location.turn : void 0;
|
|
3236
3605
|
if (turn === void 0) return null;
|
|
3237
3606
|
const closing = data.closing;
|
|
@@ -3250,27 +3619,27 @@ window.__ModuleLoader__.load({
|
|
|
3250
3619
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
3251
3620
|
className: TurnTailNodeView_module_css_default.root,
|
|
3252
3621
|
"data-turn-tail": data.turn,
|
|
3253
|
-
"data-
|
|
3254
|
-
children: [tail, (0, react_jsx_runtime.
|
|
3255
|
-
|
|
3256
|
-
|
|
3622
|
+
"data-actions-reveal": isLatestTurn ? "always" : "hover",
|
|
3623
|
+
children: [tail, (0, react_jsx_runtime.jsx)(MessageIconActions, {
|
|
3624
|
+
text: assistantText(closing.blocks),
|
|
3625
|
+
time: closing.time,
|
|
3626
|
+
clock: "end",
|
|
3627
|
+
onBranch: () => {
|
|
3628
|
+
forkAt(closing.finalNode.seq);
|
|
3629
|
+
},
|
|
3630
|
+
branchUnavailable: data.branchUnavailable || hasLaterChatNode,
|
|
3631
|
+
className: TurnTailNodeView_module_css_default.actions,
|
|
3632
|
+
extraActions: assistantActions,
|
|
3633
|
+
usageAction: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [data.tokenUsage !== void 0 && (0, react_jsx_runtime.jsx)(TurnUsagePanel, {
|
|
3257
3634
|
usage: data.tokenUsage,
|
|
3258
3635
|
t
|
|
3259
|
-
}), (0, react_jsx_runtime.jsx)(
|
|
3260
|
-
text: assistantText(closing.blocks),
|
|
3261
|
-
time: closing.time,
|
|
3636
|
+
}), runMs !== void 0 && (0, react_jsx_runtime.jsx)(TurnTimePanel, {
|
|
3262
3637
|
runMs,
|
|
3263
|
-
ttftMs: data.ttftMs,
|
|
3264
3638
|
tokensPerSecond: data.tokensPerSecond,
|
|
3265
|
-
|
|
3266
|
-
onBranch: () => {
|
|
3267
|
-
forkAt(closing.finalNode.seq);
|
|
3268
|
-
},
|
|
3269
|
-
branchUnavailable: data.branchUnavailable || hasLaterChatNode,
|
|
3270
|
-
className: TurnTailNodeView_module_css_default.actions,
|
|
3271
|
-
extraActions: assistantActions,
|
|
3639
|
+
ttftMs: data.ttftMs,
|
|
3272
3640
|
t
|
|
3273
|
-
})]
|
|
3641
|
+
})] }),
|
|
3642
|
+
t
|
|
3274
3643
|
})]
|
|
3275
3644
|
});
|
|
3276
3645
|
});
|
|
@@ -4252,27 +4621,42 @@ window.__ModuleLoader__.load({
|
|
|
4252
4621
|
//#endregion
|
|
4253
4622
|
//#region lib/types/client/conversation-nodes/turn-navigation.js
|
|
4254
4623
|
/**
|
|
4255
|
-
* Preview
|
|
4256
|
-
*
|
|
4624
|
+
* Preview budgets, sized to the rail card's clamps (one prompt line, up to
|
|
4625
|
+
* three response lines) and mirrored by the turnOutline projection so a turn
|
|
4626
|
+
* shows the same words before and after its events load. Anything past a
|
|
4627
|
+
* budget is invisible; copying whole transcripts into navigation state would
|
|
4257
4628
|
* otherwise grow with the loaded window on every structural update.
|
|
4258
4629
|
*/
|
|
4259
|
-
const
|
|
4260
|
-
|
|
4261
|
-
|
|
4630
|
+
const PROMPT_PREVIEW_LIMIT = 50;
|
|
4631
|
+
const RESPONSE_PREVIEW_LIMIT = 120;
|
|
4632
|
+
/** Join rendered text, collapse whitespace, and cap at `limit` with a trailing ellipsis when clipped. */
|
|
4633
|
+
function preview(parts, limit) {
|
|
4262
4634
|
let text = "";
|
|
4635
|
+
let unread = false;
|
|
4263
4636
|
for (const part of parts) {
|
|
4264
|
-
|
|
4265
|
-
|
|
4637
|
+
if (text.length >= limit * 2) {
|
|
4638
|
+
unread = true;
|
|
4639
|
+
break;
|
|
4640
|
+
}
|
|
4641
|
+
const clipped = part.length > limit * 2;
|
|
4642
|
+
const chunk = clipped ? part.slice(0, limit * 2) : part;
|
|
4643
|
+
text += text === "" ? chunk : ` ${chunk}`;
|
|
4644
|
+
if (clipped) {
|
|
4645
|
+
unread = true;
|
|
4646
|
+
break;
|
|
4647
|
+
}
|
|
4266
4648
|
}
|
|
4267
|
-
|
|
4649
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
4650
|
+
if (normalized.length > limit - 1) return `${normalized.slice(0, limit - 1).trimEnd()}…`;
|
|
4651
|
+
return unread ? `${normalized}…` : normalized;
|
|
4268
4652
|
}
|
|
4269
4653
|
function promptText(node) {
|
|
4270
4654
|
if (node.kind !== "user") return "";
|
|
4271
|
-
return preview(node.data.content.flatMap((block) => block.type === "text" ? [block.text] : []));
|
|
4655
|
+
return preview(node.data.content.flatMap((block) => block.type === "text" ? [block.text] : []), PROMPT_PREVIEW_LIMIT);
|
|
4272
4656
|
}
|
|
4273
4657
|
function responseText(node) {
|
|
4274
4658
|
if (node.kind !== "assistant-step") return "";
|
|
4275
|
-
return preview(node.data.blocks.flatMap((block) => block.kind === "text" ? [block.text] : []));
|
|
4659
|
+
return preview(node.data.blocks.flatMap((block) => block.kind === "text" ? [block.text] : []), RESPONSE_PREVIEW_LIMIT);
|
|
4276
4660
|
}
|
|
4277
4661
|
/**
|
|
4278
4662
|
* Whether two items carry the same rail state, so the reader can keep its array.
|
|
@@ -5128,43 +5512,87 @@ window.__ModuleLoader__.load({
|
|
|
5128
5512
|
}
|
|
5129
5513
|
//#endregion
|
|
5130
5514
|
//#region lib/types/client/conversation-nodes/inbox.js
|
|
5515
|
+
const EMPTY_PENDING = {
|
|
5516
|
+
kind: "snapshot",
|
|
5517
|
+
ids: []
|
|
5518
|
+
};
|
|
5519
|
+
const EMPTY_CURRENT_CLAIMED = /* @__PURE__ */ new Set();
|
|
5520
|
+
function materializePending(state) {
|
|
5521
|
+
const splices = [];
|
|
5522
|
+
let current = state;
|
|
5523
|
+
while (current.kind === "splice") {
|
|
5524
|
+
splices.push(current);
|
|
5525
|
+
current = current.previous;
|
|
5526
|
+
}
|
|
5527
|
+
const pending = [...current.ids];
|
|
5528
|
+
for (const splice of splices.reverse()) pending.splice(splice.start, splice.removedCount, ...splice.inserted);
|
|
5529
|
+
return pending;
|
|
5530
|
+
}
|
|
5531
|
+
function withoutInserted(claimed, inserted) {
|
|
5532
|
+
let next;
|
|
5533
|
+
for (const id of inserted) {
|
|
5534
|
+
if (!claimed.has(id)) continue;
|
|
5535
|
+
next ??= new Set(claimed);
|
|
5536
|
+
next.delete(id);
|
|
5537
|
+
}
|
|
5538
|
+
return next ?? claimed;
|
|
5539
|
+
}
|
|
5540
|
+
/**
|
|
5541
|
+
* Apply one next-step splice under the AgentLoop's durable event ordering.
|
|
5542
|
+
* An entered claim logs its complete message batch before another claim; a
|
|
5543
|
+
* rejected claim logs no messages, so only the current claim can classify a
|
|
5544
|
+
* later `user/message`.
|
|
5545
|
+
*/
|
|
5131
5546
|
function applySplice(previous, splice) {
|
|
5132
|
-
const
|
|
5133
|
-
const
|
|
5134
|
-
const
|
|
5135
|
-
|
|
5136
|
-
|
|
5547
|
+
const priorPending = previous?.state.pending ?? EMPTY_PENDING;
|
|
5548
|
+
const inserted = splice.inserted.map((identity) => identity.id);
|
|
5549
|
+
const removedCount = splice.removedCount ?? 0;
|
|
5550
|
+
if (removedCount > 0 && splice.outcome !== "canceled") {
|
|
5551
|
+
const pending = materializePending(priorPending);
|
|
5552
|
+
const removed = pending.splice(splice.start, removedCount, ...inserted);
|
|
5553
|
+
return {
|
|
5554
|
+
pending: {
|
|
5555
|
+
kind: "snapshot",
|
|
5556
|
+
ids: pending
|
|
5557
|
+
},
|
|
5558
|
+
currentClaimed: new Set(removed)
|
|
5559
|
+
};
|
|
5560
|
+
}
|
|
5561
|
+
const currentClaimed = withoutInserted(previous?.state.currentClaimed ?? EMPTY_CURRENT_CLAIMED, inserted);
|
|
5137
5562
|
return {
|
|
5138
|
-
pending
|
|
5139
|
-
|
|
5563
|
+
pending: {
|
|
5564
|
+
kind: "splice",
|
|
5565
|
+
previous: priorPending,
|
|
5566
|
+
start: splice.start,
|
|
5567
|
+
removedCount,
|
|
5568
|
+
inserted
|
|
5569
|
+
},
|
|
5570
|
+
currentClaimed
|
|
5140
5571
|
};
|
|
5141
5572
|
}
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5573
|
+
const NEXT_STEP_INBOX_KIND = "inbox-next-step";
|
|
5574
|
+
/** Persistent next-step Inbox state used to classify the current claimed batch as steering. */
|
|
5575
|
+
const nextStepInboxDefinition = {
|
|
5576
|
+
kind: NEXT_STEP_INBOX_KIND,
|
|
5577
|
+
match: (event) => {
|
|
5578
|
+
if (event.type === "agent/inbox/spliced" && event.data.target === "next-step") return {
|
|
5147
5579
|
id: String(event.seq),
|
|
5148
5580
|
role: "start"
|
|
5149
|
-
}
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
const nextTurnInboxDefinition = inboxDefinition("next-turn");
|
|
5160
|
-
/** Cumulative next-step inbox splice Definition used to classify steering. */
|
|
5161
|
-
const nextStepInboxDefinition = inboxDefinition("next-step");
|
|
5581
|
+
};
|
|
5582
|
+
return null;
|
|
5583
|
+
},
|
|
5584
|
+
start: (_context, match, reader) => {
|
|
5585
|
+
if (match.event.type !== "agent/inbox/spliced") throw new Error("inbox-next-step start requires agent/inbox/spliced");
|
|
5586
|
+
return applySplice(reader.previous(NEXT_STEP_INBOX_KIND), match.event.data);
|
|
5587
|
+
},
|
|
5588
|
+
update: (context) => context.state,
|
|
5589
|
+
publication: () => "none"
|
|
5590
|
+
};
|
|
5162
5591
|
/**
|
|
5163
|
-
* Register the
|
|
5592
|
+
* Register the next-step Inbox state used by Chat message classification.
|
|
5164
5593
|
* @param ctx - owning UI Conversation context.
|
|
5165
5594
|
*/
|
|
5166
5595
|
function registerInboxConversationNodes(ctx) {
|
|
5167
|
-
ctx.uiConversation.events.register(nextTurnInboxDefinition);
|
|
5168
5596
|
ctx.uiConversation.events.register(nextStepInboxDefinition);
|
|
5169
5597
|
}
|
|
5170
5598
|
//#endregion
|
|
@@ -5194,7 +5622,7 @@ window.__ModuleLoader__.load({
|
|
|
5194
5622
|
provenance: contextProvenance(event.data.source),
|
|
5195
5623
|
form: contextForm(event.data.source)
|
|
5196
5624
|
};
|
|
5197
|
-
return reader.previous("inbox-next-step")?.state.
|
|
5625
|
+
return reader.previous("inbox-next-step")?.state.currentClaimed.has(String(event.data.id)) === true ? {
|
|
5198
5626
|
kind: "steering",
|
|
5199
5627
|
messageId: event.data.id,
|
|
5200
5628
|
seq: event.seq,
|
|
@@ -7534,6 +7962,7 @@ window.__ModuleLoader__.load({
|
|
|
7534
7962
|
loadOlder: () => {
|
|
7535
7963
|
session.loadOlder();
|
|
7536
7964
|
},
|
|
7965
|
+
loadThrough: (seq) => session.loadThrough(seq),
|
|
7537
7966
|
loadImage: Object.assign((attachment) => ctx.uiConversation.imageUrl(sessionId, attachment), { peek: (attachment) => ctx.uiConversation.peekImageUrl(sessionId, attachment) }),
|
|
7538
7967
|
chatScroll: {
|
|
7539
7968
|
save: (position) => {
|