@aparte/core 0.13.1 → 0.15.0
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.md +2 -2
- package/dist/client/aparte-client.d.ts +36 -35
- package/dist/client/aparte-client.d.ts.map +1 -1
- package/dist/client/stream-adapter.d.ts +37 -145
- package/dist/client/stream-adapter.d.ts.map +1 -1
- package/dist/components/bubble/aparte-chat-bubble.d.ts +10 -2
- package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-add-attachment.d.ts +6 -6
- package/dist/components/composer/aparte-composer-add-attachment.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-input.d.ts +9 -1
- package/dist/components/composer/aparte-composer-input.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer.d.ts +12 -3
- package/dist/components/composer/aparte-composer.d.ts.map +1 -1
- package/dist/components/context/aparte-context.d.ts +81 -0
- package/dist/components/context/aparte-context.d.ts.map +1 -0
- package/dist/components/context/index.d.ts +3 -0
- package/dist/components/context/index.d.ts.map +1 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/suggestions/aparte-suggestions.d.ts +95 -0
- package/dist/components/suggestions/aparte-suggestions.d.ts.map +1 -0
- package/dist/components/suggestions/index.d.ts +3 -0
- package/dist/components/suggestions/index.d.ts.map +1 -0
- package/dist/components/viewport/aparte-chat-viewport.d.ts +35 -7
- package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
- package/dist/config/aparte-config.d.ts +1 -21
- package/dist/config/aparte-config.d.ts.map +1 -1
- package/dist/config/index.d.ts +0 -2
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/locale.d.ts +30 -1
- package/dist/config/locale.d.ts.map +1 -1
- package/dist/conversations/index.d.ts +1 -1
- package/dist/conversations/index.d.ts.map +1 -1
- package/dist/conversations/types.d.ts +9 -57
- package/dist/conversations/types.d.ts.map +1 -1
- package/dist/custom-elements.json +5543 -5196
- package/dist/generated/element-attributes.d.ts +34 -6
- package/dist/generated/element-attributes.d.ts.map +1 -1
- package/dist/{glyphs-QXfbL3n_.js → glyphs-B3BZ2TIb.js} +21 -21
- package/dist/glyphs-B3BZ2TIb.js.map +1 -0
- package/dist/host/aparte-chat-host.d.ts +1 -1
- package/dist/host/aparte-chat-host.d.ts.map +1 -1
- package/dist/icons/extended.d.ts +87 -85
- package/dist/icons/extended.d.ts.map +1 -1
- package/dist/icons/glyphs.d.ts +53 -50
- package/dist/icons/glyphs.d.ts.map +1 -1
- package/dist/icons.js +41 -41
- package/dist/icons.js.map +1 -1
- package/dist/index.css +532 -171
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +593 -183
- package/dist/index.js.map +1 -1
- package/dist/index.node.d.ts +6 -4
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +56 -56
- package/dist/index.node.js.map +1 -1
- package/dist/{is-awaiting-reply-CWp1ffHh.js → is-awaiting-reply-BZ_iJ3s3.js} +175 -994
- package/dist/is-awaiting-reply-BZ_iJ3s3.js.map +1 -0
- package/dist/parsers/aparte-stream-parser.d.ts +6 -17
- package/dist/parsers/aparte-stream-parser.d.ts.map +1 -1
- package/dist/primitives/select/aparte-select.d.ts +2 -2
- package/dist/primitives/select/aparte-select.d.ts.map +1 -1
- package/dist/renderers/segment-renderers.d.ts.map +1 -1
- package/dist/renderers/segments/tool-call.d.ts.map +1 -1
- package/dist/runtime/message-repository.d.ts +7 -0
- package/dist/runtime/message-repository.d.ts.map +1 -1
- package/dist/transport/types.d.ts +8 -0
- package/dist/transport/types.d.ts.map +1 -1
- package/dist/types/chat.d.ts +32 -38
- package/dist/types/chat.d.ts.map +1 -1
- package/dist/types/errors.d.ts +16 -1
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/event-map.d.ts +4 -0
- package/dist/types/event-map.d.ts.map +1 -1
- package/dist/types/events.d.ts +15 -0
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/imperative-api.d.ts +10 -1
- package/dist/types/imperative-api.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/models.d.ts +1 -8
- package/dist/types/models.d.ts.map +1 -1
- package/dist/types/segments.d.ts +1 -13
- package/dist/types/segments.d.ts.map +1 -1
- package/dist/types/tools.d.ts +1 -1
- package/dist/utils/chat-boundary.d.ts +8 -4
- package/dist/utils/chat-boundary.d.ts.map +1 -1
- package/package.json +5 -2
- package/dist/client/xml-artifact-feed.d.ts +0 -76
- package/dist/client/xml-artifact-feed.d.ts.map +0 -1
- package/dist/config/skeleton-provider.d.ts +0 -26
- package/dist/config/skeleton-provider.d.ts.map +0 -1
- package/dist/glyphs-QXfbL3n_.js.map +0 -1
- package/dist/is-awaiting-reply-CWp1ffHh.js.map +0 -1
- package/dist/renderers/segments/pipeline-waiting.d.ts +0 -3
- package/dist/renderers/segments/pipeline-waiting.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { A, a, b, c, d, e, f, g, h, i, j, k, l, m, n,
|
|
1
|
+
import { Z as resolveConfig, a2 as subscribeConfigChange, $ as runWithConfig, o as AparteElicitationAbortError, w as buildApprovalPanel, x as buildElicitationPanel, I as escapeAttr, E as cssEscape, a5 as mergeSegmentUpdate, J as escapeHtml, a6 as writeStreamedMarkdown, M as getSegmentRenderer, N as installDefaultRenderersOnce, r as AparteMessageRepository, a7 as stampSegmentActivity, a8 as stampSegmentOnInsert, a9 as stampSegmentOnUpdate, aa as renumberSegments, ab as openSegmentIds, ac as isTerminalStatus, ad as adoptMessageSegments, O as isAwaitingReply, U as populateBubbleFromMessage, a4 as uuid, _ as revokeAttachmentUrls } from "./is-awaiting-reply-BZ_iJ3s3.js";
|
|
2
|
+
import { A, a, b, c, d, e, f, g, h, i, j, k, l, m, n, p, q, s, t, u, v, y, z, B, C, D, F, G, H, K, L, P, Q, R, S, T, V, W, X, Y, a0, a1, a3 } from "./is-awaiting-reply-BZ_iJ3s3.js";
|
|
3
|
+
import { deriveArtifactKind } from "@aparte/engine";
|
|
3
4
|
class AparteOption extends HTMLElement {
|
|
4
5
|
static get observedAttributes() {
|
|
5
6
|
return ["value", "disabled", "selected", "data-status"];
|
|
@@ -286,7 +287,13 @@ class AparteSelect extends HTMLElement {
|
|
|
286
287
|
trigger.setAttribute("aria-label", this.getAttribute("aria-label") || placeholder);
|
|
287
288
|
const labelSpan = document.createElement("span");
|
|
288
289
|
labelSpan.className = "aparte-select-label";
|
|
289
|
-
|
|
290
|
+
const labelText = document.createElement("span");
|
|
291
|
+
labelText.className = "aparte-select-label-text";
|
|
292
|
+
labelText.textContent = placeholder;
|
|
293
|
+
const sizer = document.createElement("span");
|
|
294
|
+
sizer.className = "aparte-select-label-sizer";
|
|
295
|
+
sizer.setAttribute("aria-hidden", "true");
|
|
296
|
+
labelSpan.append(labelText, sizer);
|
|
290
297
|
const chevronSpan = document.createElement("span");
|
|
291
298
|
chevronSpan.className = "aparte-select-chevron";
|
|
292
299
|
chevronSpan.innerHTML = resolveConfig(this).getIcon("expand");
|
|
@@ -329,6 +336,7 @@ class AparteSelect extends HTMLElement {
|
|
|
329
336
|
_setupMutationObserver() {
|
|
330
337
|
this._observer = new MutationObserver(() => {
|
|
331
338
|
this._updateDropdownContent();
|
|
339
|
+
this._updateTriggerLabel();
|
|
332
340
|
this._restoreActive();
|
|
333
341
|
});
|
|
334
342
|
this._observer.observe(this, { childList: true, subtree: true });
|
|
@@ -458,7 +466,7 @@ class AparteSelect extends HTMLElement {
|
|
|
458
466
|
/** Highlight the option at `index` (clamped) and point aria-activedescendant at it. */
|
|
459
467
|
_setActive(index) {
|
|
460
468
|
const all = this.querySelectorAll("aparte-option");
|
|
461
|
-
all.forEach((
|
|
469
|
+
all.forEach((o) => o.removeAttribute("data-active"));
|
|
462
470
|
const opts = this._visibleOptions();
|
|
463
471
|
if (opts.length === 0) {
|
|
464
472
|
this._activeIndex = -1;
|
|
@@ -477,7 +485,7 @@ class AparteSelect extends HTMLElement {
|
|
|
477
485
|
_clearActive() {
|
|
478
486
|
this._activeIndex = -1;
|
|
479
487
|
this._trigger?.removeAttribute("aria-activedescendant");
|
|
480
|
-
this.querySelectorAll("aparte-option").forEach((
|
|
488
|
+
this.querySelectorAll("aparte-option").forEach((o) => o.removeAttribute("data-active"));
|
|
481
489
|
}
|
|
482
490
|
// ─────────────────────────────────────────────────────────────────────────
|
|
483
491
|
// Actions
|
|
@@ -498,7 +506,7 @@ class AparteSelect extends HTMLElement {
|
|
|
498
506
|
this._updatePosition();
|
|
499
507
|
this._searchInput?.focus();
|
|
500
508
|
const opts = this._visibleOptions();
|
|
501
|
-
const selectedIdx = opts.findIndex((
|
|
509
|
+
const selectedIdx = opts.findIndex((o) => o.getAttribute("value") === this._value);
|
|
502
510
|
this._setActive(selectedIdx >= 0 ? selectedIdx : 0);
|
|
503
511
|
this.dispatchEvent(new CustomEvent("aparte-select-open", { bubbles: true }));
|
|
504
512
|
}
|
|
@@ -551,10 +559,23 @@ class AparteSelect extends HTMLElement {
|
|
|
551
559
|
this._trigger?.focus();
|
|
552
560
|
}
|
|
553
561
|
_updateTriggerLabel() {
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
562
|
+
const placeholder = this.getAttribute("placeholder") || "Select...";
|
|
563
|
+
const labelText = this._trigger?.querySelector(".aparte-select-label-text");
|
|
564
|
+
if (labelText) {
|
|
565
|
+
const next = this._getSelectedLabel() || placeholder;
|
|
566
|
+
if (labelText.textContent !== next) labelText.textContent = next;
|
|
567
|
+
}
|
|
568
|
+
const sizer = this._trigger?.querySelector(".aparte-select-label-sizer");
|
|
569
|
+
if (sizer) {
|
|
570
|
+
const labels = [placeholder, ...Array.from(this.querySelectorAll("aparte-option"), (o) => o.textContent?.trim() || "")];
|
|
571
|
+
const current = Array.from(sizer.children, (s2) => s2.textContent ?? "");
|
|
572
|
+
if (labels.length !== current.length || labels.some((l2, i2) => l2 !== current[i2])) {
|
|
573
|
+
sizer.replaceChildren(...labels.map((text) => {
|
|
574
|
+
const span = document.createElement("span");
|
|
575
|
+
span.textContent = text;
|
|
576
|
+
return span;
|
|
577
|
+
}));
|
|
578
|
+
}
|
|
558
579
|
}
|
|
559
580
|
const options = this.querySelectorAll("aparte-option");
|
|
560
581
|
options.forEach((opt) => {
|
|
@@ -833,12 +854,12 @@ class AparteElicitation extends HTMLElement {
|
|
|
833
854
|
* resolves `undefined` on three wrappers out of four.
|
|
834
855
|
*/
|
|
835
856
|
_pendingTargetId() {
|
|
836
|
-
let
|
|
837
|
-
while (
|
|
838
|
-
const tag =
|
|
839
|
-
const isHost = tag === "aparte-chat" ||
|
|
840
|
-
if (isHost &&
|
|
841
|
-
|
|
857
|
+
let el2 = this._pending?.composer ?? null;
|
|
858
|
+
while (el2) {
|
|
859
|
+
const tag = el2.tagName?.toLowerCase();
|
|
860
|
+
const isHost = tag === "aparte-chat" || el2.hasAttribute?.("data-aparte-chat");
|
|
861
|
+
if (isHost && el2.id) return el2.id;
|
|
862
|
+
el2 = el2.parentElement;
|
|
842
863
|
}
|
|
843
864
|
return void 0;
|
|
844
865
|
}
|
|
@@ -864,7 +885,7 @@ class AparteElicitation extends HTMLElement {
|
|
|
864
885
|
const composer = node.querySelector("aparte-composer");
|
|
865
886
|
if (composer && typeof composer.showPanel === "function") return composer;
|
|
866
887
|
const tag = node.tagName?.toLowerCase();
|
|
867
|
-
const isChatBoundary = tag === "aparte-chat" ||
|
|
888
|
+
const isChatBoundary = tag === "aparte-chat" || node.hasAttribute?.("data-aparte-chat");
|
|
868
889
|
if (isChatBoundary) break;
|
|
869
890
|
node = node.parentElement;
|
|
870
891
|
}
|
|
@@ -1002,10 +1023,10 @@ function warnMissingRenderer(type) {
|
|
|
1002
1023
|
function unrenderedSegment(segment) {
|
|
1003
1024
|
const fallback = typeof segment.fallback === "string" && segment.fallback.trim() ? segment.fallback : null;
|
|
1004
1025
|
if (!fallback) warnMissingRenderer(segment.type);
|
|
1005
|
-
const
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
return
|
|
1026
|
+
const el2 = document.createElement("div");
|
|
1027
|
+
el2.className = fallback ? "aparte-segment aparte-segment-fallback" : "aparte-segment aparte-segment-unknown";
|
|
1028
|
+
el2.textContent = fallback ?? `[Unknown segment type: ${segment.type}]`;
|
|
1029
|
+
return el2;
|
|
1009
1030
|
}
|
|
1010
1031
|
function resolveSegmentRenderer(type, config) {
|
|
1011
1032
|
const renderer = getSegmentRenderer(type, config);
|
|
@@ -1043,7 +1064,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1043
1064
|
/** The live inline editor (the composer's contenteditable primitive), present only while `_editing`. */
|
|
1044
1065
|
_editInput = null;
|
|
1045
1066
|
static get observedAttributes() {
|
|
1046
|
-
return ["
|
|
1067
|
+
return ["data-role", "content", "timestamp", "message-id", "streaming", "name"];
|
|
1047
1068
|
}
|
|
1048
1069
|
constructor() {
|
|
1049
1070
|
super();
|
|
@@ -1083,11 +1104,11 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1083
1104
|
for (const segment of this._segments) {
|
|
1084
1105
|
const renderer = resolveSegmentRenderer(segment.type, this._cfg);
|
|
1085
1106
|
if (!renderer?.relabel) continue;
|
|
1086
|
-
const
|
|
1107
|
+
const el2 = this._segmentsEl.querySelector(
|
|
1087
1108
|
`:scope > [data-segment-id="${cssEscape(segment.id)}"]`
|
|
1088
1109
|
);
|
|
1089
|
-
if (!
|
|
1090
|
-
runWithConfig(this._cfg, () => renderer.relabel(
|
|
1110
|
+
if (!el2) continue;
|
|
1111
|
+
runWithConfig(this._cfg, () => renderer.relabel(el2, segment));
|
|
1091
1112
|
}
|
|
1092
1113
|
}
|
|
1093
1114
|
/**
|
|
@@ -1134,9 +1155,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1134
1155
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
1135
1156
|
if (oldValue === newValue) return;
|
|
1136
1157
|
switch (name) {
|
|
1137
|
-
case "role":
|
|
1138
1158
|
case "data-role":
|
|
1139
|
-
if (newValue === "article") return;
|
|
1140
1159
|
if (newValue === "user" || newValue === "assistant") {
|
|
1141
1160
|
this._role = newValue;
|
|
1142
1161
|
this._updateRole();
|
|
@@ -1202,7 +1221,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1202
1221
|
}
|
|
1203
1222
|
/** Update a specific segment */
|
|
1204
1223
|
updateSegment(segmentId, updates) {
|
|
1205
|
-
const index = this._segments.findIndex((
|
|
1224
|
+
const index = this._segments.findIndex((s2) => s2.id === segmentId);
|
|
1206
1225
|
if (index !== -1) {
|
|
1207
1226
|
const updated = mergeSegmentUpdate(this._segments[index], updates);
|
|
1208
1227
|
this._segments[index] = updated;
|
|
@@ -1211,7 +1230,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1211
1230
|
}
|
|
1212
1231
|
/** Append content to a segment */
|
|
1213
1232
|
appendToSegment(segmentId, content) {
|
|
1214
|
-
const segment = this._segments.find((
|
|
1233
|
+
const segment = this._segments.find((s2) => s2.id === segmentId);
|
|
1215
1234
|
if (segment && "content" in segment) {
|
|
1216
1235
|
segment.content += content;
|
|
1217
1236
|
this._applySegmentUpdate(segmentId, segment, { content: segment.content });
|
|
@@ -1237,18 +1256,19 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1237
1256
|
* the human-in-the-loop control.
|
|
1238
1257
|
*/
|
|
1239
1258
|
removeSegment(segmentId) {
|
|
1240
|
-
const index = this._segments.findIndex((
|
|
1259
|
+
const index = this._segments.findIndex((s2) => s2.id === segmentId);
|
|
1241
1260
|
if (index !== -1) {
|
|
1242
1261
|
this._segments.splice(index, 1);
|
|
1243
1262
|
}
|
|
1244
|
-
const
|
|
1245
|
-
|
|
1263
|
+
const el2 = this._segmentsEl?.querySelector(`:scope > [data-segment-id="${cssEscape(segmentId)}"]`);
|
|
1264
|
+
el2?.remove();
|
|
1246
1265
|
this._updateWaiting();
|
|
1247
1266
|
}
|
|
1248
1267
|
/** Set attachments (chips shown above message content, user role only) */
|
|
1249
1268
|
setAttachments(attachments) {
|
|
1250
1269
|
this._attachments = attachments;
|
|
1251
1270
|
this._updateAttachments();
|
|
1271
|
+
this._updateWaiting();
|
|
1252
1272
|
}
|
|
1253
1273
|
/**
|
|
1254
1274
|
* Set token usage + timing for this message (assistant only).
|
|
@@ -1300,6 +1320,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1300
1320
|
if ("attachments" in updates) {
|
|
1301
1321
|
this._attachments = updates.attachments ?? [];
|
|
1302
1322
|
this._updateAttachments();
|
|
1323
|
+
this._updateWaiting();
|
|
1303
1324
|
}
|
|
1304
1325
|
}
|
|
1305
1326
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -1312,10 +1333,10 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1312
1333
|
}
|
|
1313
1334
|
const renderer = resolveSegmentRenderer(segment.type, this._cfg);
|
|
1314
1335
|
if (renderer) {
|
|
1315
|
-
const
|
|
1316
|
-
if (
|
|
1317
|
-
this._segmentsEl.appendChild(
|
|
1318
|
-
runWithConfig(this._cfg, () => renderer.setup?.(
|
|
1336
|
+
const el2 = segmentRenderResultToElement(runWithConfig(this._cfg, () => renderer.render(segment)));
|
|
1337
|
+
if (el2) {
|
|
1338
|
+
this._segmentsEl.appendChild(el2);
|
|
1339
|
+
runWithConfig(this._cfg, () => renderer.setup?.(el2, segment));
|
|
1319
1340
|
}
|
|
1320
1341
|
} else {
|
|
1321
1342
|
this._segmentsEl.appendChild(unrenderedSegment(segment));
|
|
@@ -1324,27 +1345,27 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1324
1345
|
this._reflectError();
|
|
1325
1346
|
}
|
|
1326
1347
|
_applySegmentUpdate(segmentId, segment, updates) {
|
|
1327
|
-
const
|
|
1328
|
-
if (!
|
|
1348
|
+
const el2 = this._segmentsEl?.querySelector(`:scope > [data-segment-id="${cssEscape(segmentId)}"]`);
|
|
1349
|
+
if (!el2) {
|
|
1329
1350
|
this._renderSegments();
|
|
1330
1351
|
return;
|
|
1331
1352
|
}
|
|
1332
1353
|
const renderer = resolveSegmentRenderer(segment.type, this._cfg);
|
|
1333
1354
|
if (!renderer) return;
|
|
1334
1355
|
if (renderer.update) {
|
|
1335
|
-
runWithConfig(this._cfg, () => renderer.update(
|
|
1356
|
+
runWithConfig(this._cfg, () => renderer.update(el2, segment));
|
|
1336
1357
|
} else {
|
|
1337
1358
|
const newEl = segmentRenderResultToElement(runWithConfig(this._cfg, () => renderer.render(segment)));
|
|
1338
1359
|
if (newEl) {
|
|
1339
|
-
|
|
1360
|
+
el2.replaceWith(newEl);
|
|
1340
1361
|
runWithConfig(this._cfg, () => renderer.setup?.(newEl, segment));
|
|
1341
1362
|
}
|
|
1342
1363
|
}
|
|
1343
1364
|
if ("collapsed" in updates) {
|
|
1344
1365
|
if (updates.collapsed) {
|
|
1345
|
-
|
|
1366
|
+
el2.removeAttribute("open");
|
|
1346
1367
|
} else {
|
|
1347
|
-
|
|
1368
|
+
el2.setAttribute("open", "");
|
|
1348
1369
|
}
|
|
1349
1370
|
}
|
|
1350
1371
|
}
|
|
@@ -1360,8 +1381,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1360
1381
|
}
|
|
1361
1382
|
_render() {
|
|
1362
1383
|
const dataRole = this.getAttribute("data-role");
|
|
1363
|
-
const
|
|
1364
|
-
const role = dataRole && dataRole !== "article" ? dataRole : legacyRole && legacyRole !== "article" ? legacyRole : "assistant";
|
|
1384
|
+
const role = dataRole === "user" || dataRole === "assistant" ? dataRole : "assistant";
|
|
1365
1385
|
this._role = role;
|
|
1366
1386
|
if (this.getAttribute("role") !== "article") {
|
|
1367
1387
|
this.setAttribute("role", "article");
|
|
@@ -1420,6 +1440,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1420
1440
|
this._actionBarEl = this.querySelector(".aparte-action-bar");
|
|
1421
1441
|
this._branchPickerEl = this.querySelector(".aparte-branch-picker");
|
|
1422
1442
|
this._footerEl = this.querySelector(".aparte-footer");
|
|
1443
|
+
this._transcriptBusy = !!this.closest("aparte-chat-viewport")?.hasAttribute("data-busy");
|
|
1423
1444
|
this._updateActionBar();
|
|
1424
1445
|
this._renderAvatar();
|
|
1425
1446
|
if (this._streaming) this._updateStreaming(true);
|
|
@@ -1440,12 +1461,16 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1440
1461
|
const waiting = this._streaming && this._role !== "user" && empty;
|
|
1441
1462
|
const box = this.querySelector(".aparte-message-content");
|
|
1442
1463
|
if (box) box.hidden = empty && !waiting;
|
|
1443
|
-
const
|
|
1444
|
-
if (
|
|
1445
|
-
|
|
1464
|
+
const message = this.querySelector(".aparte-message");
|
|
1465
|
+
if (message) {
|
|
1466
|
+
message.toggleAttribute("data-empty", empty && !this._streaming && this._attachments.length === 0);
|
|
1467
|
+
}
|
|
1468
|
+
const el2 = this.querySelector(".aparte-waiting");
|
|
1469
|
+
if (!el2) return;
|
|
1470
|
+
el2.hidden = !waiting;
|
|
1446
1471
|
if (!waiting) return;
|
|
1447
1472
|
const label = this._cfg.getLocale().typing;
|
|
1448
|
-
const sr =
|
|
1473
|
+
const sr = el2.querySelector(".aparte-sr-only");
|
|
1449
1474
|
if (sr && sr.textContent !== label) sr.textContent = label;
|
|
1450
1475
|
}
|
|
1451
1476
|
/**
|
|
@@ -1547,10 +1572,10 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1547
1572
|
for (const segment of this._segments) {
|
|
1548
1573
|
const renderer = resolveSegmentRenderer(segment.type, this._cfg);
|
|
1549
1574
|
if (renderer) {
|
|
1550
|
-
const
|
|
1551
|
-
if (
|
|
1552
|
-
this._segmentsEl.appendChild(
|
|
1553
|
-
runWithConfig(this._cfg, () => renderer.setup?.(
|
|
1575
|
+
const el2 = segmentRenderResultToElement(runWithConfig(this._cfg, () => renderer.render(segment)));
|
|
1576
|
+
if (el2) {
|
|
1577
|
+
this._segmentsEl.appendChild(el2);
|
|
1578
|
+
runWithConfig(this._cfg, () => renderer.setup?.(el2, segment));
|
|
1554
1579
|
}
|
|
1555
1580
|
} else {
|
|
1556
1581
|
this._segmentsEl.appendChild(unrenderedSegment(segment));
|
|
@@ -1571,7 +1596,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1571
1596
|
_reflectError() {
|
|
1572
1597
|
const message = this.querySelector(".aparte-message");
|
|
1573
1598
|
if (!message) return;
|
|
1574
|
-
const hasError = this._segments.some((
|
|
1599
|
+
const hasError = this._segments.some((s2) => s2.type === "error");
|
|
1575
1600
|
if (hasError) message.setAttribute("data-error", "");
|
|
1576
1601
|
else message.removeAttribute("data-error");
|
|
1577
1602
|
}
|
|
@@ -1606,18 +1631,18 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1606
1631
|
const customAttachment = this._cfg.getAttachmentRenderer?.();
|
|
1607
1632
|
if (customAttachment) {
|
|
1608
1633
|
this._attachmentsEl.replaceChildren();
|
|
1609
|
-
for (const
|
|
1610
|
-
const
|
|
1611
|
-
if (
|
|
1634
|
+
for (const a2 of this._attachments) {
|
|
1635
|
+
const el2 = segmentRenderResultToElement(runWithConfig(this._cfg, () => customAttachment(a2)));
|
|
1636
|
+
if (el2) this._attachmentsEl.appendChild(el2);
|
|
1612
1637
|
}
|
|
1613
1638
|
return;
|
|
1614
1639
|
}
|
|
1615
|
-
this._attachmentsEl.innerHTML = this._attachments.map((
|
|
1616
|
-
const name = escapeHtml(
|
|
1617
|
-
if (
|
|
1618
|
-
return `<div class="aparte-thumb aparte-thumb--image" title="${name}"><img class="aparte-thumb__img" src="${escapeHtml(
|
|
1640
|
+
this._attachmentsEl.innerHTML = this._attachments.map((a2) => {
|
|
1641
|
+
const name = escapeHtml(a2.name);
|
|
1642
|
+
if (a2.type.startsWith("image/")) {
|
|
1643
|
+
return `<div class="aparte-thumbnail aparte-thumb aparte-thumb--image" title="${name}"><img class="aparte-thumb__img" src="${escapeHtml(a2.url)}" alt="${name}" loading="lazy" /><span class="aparte-thumb__name">${name}</span></div>`;
|
|
1619
1644
|
}
|
|
1620
|
-
return `<div class="aparte-thumb aparte-thumb--file" title="${name}"><span class="aparte-thumb__ext">${escapeHtml(this._fileExt(
|
|
1645
|
+
return `<div class="aparte-thumbnail aparte-thumb aparte-thumb--file" title="${name}"><span class="aparte-thumb__ext">${escapeHtml(this._fileExt(a2.name))}</span><span class="aparte-thumb__name">${name}</span></div>`;
|
|
1621
1646
|
}).join("");
|
|
1622
1647
|
if (!this._cfg.getHostHandlers().attachmentPreview) return;
|
|
1623
1648
|
this._attachmentsEl.querySelectorAll(".aparte-thumb--image").forEach((tile) => {
|
|
@@ -1682,6 +1707,25 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1682
1707
|
detail
|
|
1683
1708
|
}));
|
|
1684
1709
|
};
|
|
1710
|
+
/**
|
|
1711
|
+
* Whether a reply is streaming somewhere in this bubble's transcript. Set by the
|
|
1712
|
+
* viewport (`data-busy` on it, pushed here), read once on connect for a bubble
|
|
1713
|
+
* mounted while the flag was already up. While busy, the branch arrows and the
|
|
1714
|
+
* retry/edit actions are disabled — the transcript is read-only except for Stop.
|
|
1715
|
+
*/
|
|
1716
|
+
_transcriptBusy = false;
|
|
1717
|
+
setTranscriptBusy(busy) {
|
|
1718
|
+
if (this._transcriptBusy === busy) return;
|
|
1719
|
+
this._transcriptBusy = busy;
|
|
1720
|
+
this._applyTranscriptBusy();
|
|
1721
|
+
this._updateBranchPicker();
|
|
1722
|
+
}
|
|
1723
|
+
_applyTranscriptBusy() {
|
|
1724
|
+
if (!this._actionBarEl) return;
|
|
1725
|
+
for (const btn of this._actionBarEl.querySelectorAll('[data-action="retry"], [data-action="edit"]')) {
|
|
1726
|
+
btn.disabled = this._transcriptBusy;
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1685
1729
|
_updateBranchPicker() {
|
|
1686
1730
|
if (!this._branchPickerEl) return;
|
|
1687
1731
|
if (this._siblingCount <= 1 || this._role !== "assistant") {
|
|
@@ -1709,8 +1753,8 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1709
1753
|
}
|
|
1710
1754
|
const prevBtn = this._branchPickerEl.querySelector(".aparte-branch-prev");
|
|
1711
1755
|
const nextBtn = this._branchPickerEl.querySelector(".aparte-branch-next");
|
|
1712
|
-
if (prevBtn) prevBtn.disabled = this._siblingIndex === 0;
|
|
1713
|
-
if (nextBtn) nextBtn.disabled = this._siblingIndex === this._siblingCount - 1;
|
|
1756
|
+
if (prevBtn) prevBtn.disabled = this._transcriptBusy || this._siblingIndex === 0;
|
|
1757
|
+
if (nextBtn) nextBtn.disabled = this._transcriptBusy || this._siblingIndex === this._siblingCount - 1;
|
|
1714
1758
|
}
|
|
1715
1759
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1716
1760
|
// Action Bar
|
|
@@ -1727,14 +1771,14 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1727
1771
|
const buttons = [];
|
|
1728
1772
|
if (this._role === "user") {
|
|
1729
1773
|
if (config.user) {
|
|
1730
|
-
for (const
|
|
1774
|
+
for (const a2 of config.user) buttons.push(this._actionButtonHtml(a2, icons, locale));
|
|
1731
1775
|
} else {
|
|
1732
1776
|
if (config.copy) buttons.push(this._actionButtonHtml("copy", icons, locale));
|
|
1733
1777
|
if (config.edit) buttons.push(this._actionButtonHtml("edit", icons, locale));
|
|
1734
1778
|
}
|
|
1735
1779
|
} else if (this._role === "assistant") {
|
|
1736
1780
|
if (config.assistant) {
|
|
1737
|
-
for (const
|
|
1781
|
+
for (const a2 of config.assistant) buttons.push(this._actionButtonHtml(a2, icons, locale));
|
|
1738
1782
|
} else {
|
|
1739
1783
|
if (config.copy) buttons.push(this._actionButtonHtml("copy", icons, locale));
|
|
1740
1784
|
if (config.retry) buttons.push(this._actionButtonHtml("retry", icons, locale));
|
|
@@ -1747,6 +1791,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1747
1791
|
}
|
|
1748
1792
|
this._actionBarEl.innerHTML = buttons.join("");
|
|
1749
1793
|
this._appendCustomActions(icons);
|
|
1794
|
+
this._applyTranscriptBusy();
|
|
1750
1795
|
this._actionBarEl.querySelectorAll(".aparte-action-btn").forEach((btn) => {
|
|
1751
1796
|
btn.addEventListener("click", (e2) => this._handleActionClick(e2));
|
|
1752
1797
|
});
|
|
@@ -1765,20 +1810,21 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1765
1810
|
const barEmpty = !this._actionBarEl || this._actionBarEl.hidden;
|
|
1766
1811
|
const pickerHidden = !this._branchPickerEl || this._branchPickerEl.hidden;
|
|
1767
1812
|
this._footerEl.hidden = barEmpty && pickerHidden;
|
|
1813
|
+
this.querySelector(".aparte-message")?.toggleAttribute("data-branches", !pickerHidden);
|
|
1768
1814
|
}
|
|
1769
1815
|
/** Append the registered custom action buttons for this bubble's role. */
|
|
1770
1816
|
_appendCustomActions(icons) {
|
|
1771
1817
|
if (!this._actionBarEl) return;
|
|
1772
|
-
for (const
|
|
1773
|
-
const roles =
|
|
1818
|
+
for (const a2 of this._cfg.getActions("bubble")) {
|
|
1819
|
+
const roles = a2.bubble?.roles ?? ["user", "assistant"];
|
|
1774
1820
|
if (!roles.includes(this._role)) continue;
|
|
1775
1821
|
const btn = document.createElement("button");
|
|
1776
1822
|
btn.className = "aparte-btn aparte-btn--icon aparte-action-btn aparte-action-custom";
|
|
1777
|
-
btn.dataset["action"] = `custom:${
|
|
1778
|
-
btn.setAttribute("aria-label",
|
|
1779
|
-
btn.setAttribute("title",
|
|
1780
|
-
const fromProvider = icons[
|
|
1781
|
-
btn.innerHTML =
|
|
1823
|
+
btn.dataset["action"] = `custom:${a2.id}`;
|
|
1824
|
+
btn.setAttribute("aria-label", a2.label);
|
|
1825
|
+
btn.setAttribute("title", a2.label);
|
|
1826
|
+
const fromProvider = icons[a2.icon];
|
|
1827
|
+
btn.innerHTML = a2.icon.startsWith("<") ? a2.icon : typeof fromProvider === "function" ? fromProvider() : a2.iconFallback ?? "";
|
|
1782
1828
|
this._actionBarEl.appendChild(btn);
|
|
1783
1829
|
}
|
|
1784
1830
|
}
|
|
@@ -1849,7 +1895,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1849
1895
|
}
|
|
1850
1896
|
switch (action) {
|
|
1851
1897
|
case "copy": {
|
|
1852
|
-
const text = this._content || this._segments.map((
|
|
1898
|
+
const text = this._content || this._segments.filter((s2) => s2.type !== "thinking").map((s2) => s2.content ?? "").join("\n");
|
|
1853
1899
|
const icons = this._cfg.getIconProvider();
|
|
1854
1900
|
const locale = this._cfg.getLocale();
|
|
1855
1901
|
navigator.clipboard.writeText(text).then(() => {
|
|
@@ -1983,12 +2029,12 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1983
2029
|
}
|
|
1984
2030
|
}
|
|
1985
2031
|
_resolveTargetId() {
|
|
1986
|
-
let
|
|
1987
|
-
while (
|
|
1988
|
-
const tag =
|
|
1989
|
-
const isHost = tag === "aparte-chat" ||
|
|
1990
|
-
if (isHost &&
|
|
1991
|
-
|
|
2032
|
+
let el2 = this.parentElement;
|
|
2033
|
+
while (el2) {
|
|
2034
|
+
const tag = el2.tagName?.toLowerCase();
|
|
2035
|
+
const isHost = tag === "aparte-chat" || el2.hasAttribute?.("data-aparte-chat");
|
|
2036
|
+
if (isHost && el2.id) return el2.id;
|
|
2037
|
+
el2 = el2.parentElement;
|
|
1992
2038
|
}
|
|
1993
2039
|
return void 0;
|
|
1994
2040
|
}
|
|
@@ -2160,6 +2206,17 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2160
2206
|
_isAutoScrollEnabled = true;
|
|
2161
2207
|
/** The last scroll position we saw, so growth can be told from a gesture. */
|
|
2162
2208
|
_lastScrollTop = 0;
|
|
2209
|
+
/** The scroll height at the last scroll event: how much it moved since bounds what churn can do. */
|
|
2210
|
+
_lastScrollHeight = 0;
|
|
2211
|
+
/** When this component last moved `scrollTop` itself — see `_handleScroll`. "Never" is
|
|
2212
|
+
* -Infinity rather than 0: `performance.now()` can be under a second old. */
|
|
2213
|
+
_ownScrollAt = Number.NEGATIVE_INFINITY;
|
|
2214
|
+
/** When the reader last touched the transcript (wheel, touch, pointer, key). */
|
|
2215
|
+
_readerInputAt = Number.NEGATIVE_INFINITY;
|
|
2216
|
+
/** A decrease inside this window after our own scroll, with no gesture, is the browser's. */
|
|
2217
|
+
_ownScrollWindowMs = 1e3;
|
|
2218
|
+
/** The keys that scroll a focused scroll container — the only keydowns that are a gesture. */
|
|
2219
|
+
_scrollKeys = /* @__PURE__ */ new Set(["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End", " "]);
|
|
2163
2220
|
_scrollThreshold = 50;
|
|
2164
2221
|
/** When true, the next _autoScroll() call uses smooth instead of instant, then resets. */
|
|
2165
2222
|
_smoothScrollOnce = false;
|
|
@@ -2170,8 +2227,6 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2170
2227
|
* snapshot stay intact; only the oldest rendered bubbles are dropped from view.
|
|
2171
2228
|
*/
|
|
2172
2229
|
_maxRenderedBubbles = 1e3;
|
|
2173
|
-
/** One-time guard for the deprecated `maxMessages` warning. */
|
|
2174
|
-
_warnedMaxMessagesDeprecation = false;
|
|
2175
2230
|
_resizeObserver = null;
|
|
2176
2231
|
_mutationObserver = null;
|
|
2177
2232
|
_boundResetHandler = null;
|
|
@@ -2182,7 +2237,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2182
2237
|
*/
|
|
2183
2238
|
_frameworkManagedDOM = false;
|
|
2184
2239
|
static get observedAttributes() {
|
|
2185
|
-
return ["scroll-threshold", "max-rendered-bubbles"
|
|
2240
|
+
return ["scroll-threshold", "max-rendered-bubbles"];
|
|
2186
2241
|
}
|
|
2187
2242
|
constructor() {
|
|
2188
2243
|
super();
|
|
@@ -2206,6 +2261,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2206
2261
|
const detail = e2.detail;
|
|
2207
2262
|
if (detail?.config && detail.config !== resolveConfig(this)) return;
|
|
2208
2263
|
this._applyDirection();
|
|
2264
|
+
this._scrollBtn?.setAttribute("aria-label", resolveConfig(this).t("scrollToBottom"));
|
|
2209
2265
|
};
|
|
2210
2266
|
/** Mirror `locale.direction` onto the scroll container. */
|
|
2211
2267
|
_applyDirection() {
|
|
@@ -2233,11 +2289,6 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2233
2289
|
this._maxRenderedBubbles = parseInt(newValue || "1000", 10);
|
|
2234
2290
|
this._pruneRenderedBubbles();
|
|
2235
2291
|
break;
|
|
2236
|
-
case "max-messages":
|
|
2237
|
-
this._warnMaxMessagesDeprecated();
|
|
2238
|
-
this._maxRenderedBubbles = parseInt(newValue || "1000", 10);
|
|
2239
|
-
this._pruneRenderedBubbles();
|
|
2240
|
-
break;
|
|
2241
2292
|
}
|
|
2242
2293
|
}
|
|
2243
2294
|
/**
|
|
@@ -2251,11 +2302,6 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2251
2302
|
this._maxRenderedBubbles = config.maxRenderedBubbles;
|
|
2252
2303
|
this._pruneRenderedBubbles();
|
|
2253
2304
|
}
|
|
2254
|
-
if (config.maxMessages !== void 0) {
|
|
2255
|
-
this._warnMaxMessagesDeprecated();
|
|
2256
|
-
this._maxRenderedBubbles = config.maxMessages;
|
|
2257
|
-
this._pruneRenderedBubbles();
|
|
2258
|
-
}
|
|
2259
2305
|
if (config.layoutTransitionMs !== void 0) {
|
|
2260
2306
|
this._layoutTransitionMs = config.layoutTransitionMs;
|
|
2261
2307
|
}
|
|
@@ -2289,7 +2335,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2289
2335
|
if (!message.segments) {
|
|
2290
2336
|
message.segments = [];
|
|
2291
2337
|
}
|
|
2292
|
-
const index = message.segments.findIndex((
|
|
2338
|
+
const index = message.segments.findIndex((s2) => s2.id === segmentId);
|
|
2293
2339
|
const segment = index === -1 ? void 0 : message.segments[index];
|
|
2294
2340
|
if (segment && "content" in segment) {
|
|
2295
2341
|
message.segments[index] = {
|
|
@@ -2331,6 +2377,24 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2331
2377
|
}
|
|
2332
2378
|
return null;
|
|
2333
2379
|
}
|
|
2380
|
+
/**
|
|
2381
|
+
* While a reply streams, the transcript is read-only except for Stop.
|
|
2382
|
+
*
|
|
2383
|
+
* The streaming message's own footer was already hidden; every OTHER message kept
|
|
2384
|
+
* its branch picker and its retry/edit buttons live. Swapping a branch mid-stream
|
|
2385
|
+
* re-rendered the active path under the reply being written, and a retry cut that
|
|
2386
|
+
* reply off to start another — both seen on the landing page. The state that
|
|
2387
|
+
* decides it is the whole transcript's, so it lives on the viewport (`data-busy`)
|
|
2388
|
+
* and is pushed to the bubbles it holds; a bubble mounted later, under a
|
|
2389
|
+
* framework's DOM, reads the attribute itself when it connects.
|
|
2390
|
+
*/
|
|
2391
|
+
_syncBusy() {
|
|
2392
|
+
const busy = this._streamingMessageId() !== null;
|
|
2393
|
+
this.toggleAttribute("data-busy", busy);
|
|
2394
|
+
for (const bubble of this.querySelectorAll("aparte-chat-bubble")) {
|
|
2395
|
+
bubble.setTranscriptBusy?.(busy);
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2334
2398
|
addSegment(messageIdOrSegment, maybeSegment) {
|
|
2335
2399
|
const messageId = typeof messageIdOrSegment === "string" ? messageIdOrSegment : this._activeMessageId();
|
|
2336
2400
|
const segment = typeof messageIdOrSegment === "string" ? maybeSegment : messageIdOrSegment;
|
|
@@ -2351,15 +2415,15 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2351
2415
|
this._notifyBubble(messageId, "addSegment", stamped);
|
|
2352
2416
|
this._autoScroll();
|
|
2353
2417
|
}
|
|
2354
|
-
updateSegment(
|
|
2418
|
+
updateSegment(a2, b2, c2) {
|
|
2355
2419
|
const clientForm = c2 === void 0 && typeof b2 === "object";
|
|
2356
|
-
const messageId = clientForm ? this._activeMessageId() :
|
|
2357
|
-
const segmentId = clientForm ?
|
|
2420
|
+
const messageId = clientForm ? this._activeMessageId() : a2;
|
|
2421
|
+
const segmentId = clientForm ? a2 : b2;
|
|
2358
2422
|
const updates = clientForm ? b2 : c2;
|
|
2359
2423
|
if (!messageId) return;
|
|
2360
2424
|
const message = this._repo.getMessageById(messageId);
|
|
2361
2425
|
if (!message?.segments) return;
|
|
2362
|
-
const segmentIndex = message.segments.findIndex((
|
|
2426
|
+
const segmentIndex = message.segments.findIndex((s2) => s2.id === segmentId);
|
|
2363
2427
|
if (segmentIndex !== -1) {
|
|
2364
2428
|
const current = message.segments[segmentIndex];
|
|
2365
2429
|
const stamped = stampSegmentOnUpdate(current, updates);
|
|
@@ -2367,14 +2431,14 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2367
2431
|
this._notifyBubble(messageId, "updateSegment", { segmentId, updates: stamped });
|
|
2368
2432
|
}
|
|
2369
2433
|
}
|
|
2370
|
-
removeSegment(
|
|
2434
|
+
removeSegment(a2, b2) {
|
|
2371
2435
|
const clientForm = b2 === void 0;
|
|
2372
|
-
const messageId = clientForm ? this._activeMessageId() :
|
|
2373
|
-
const segmentId = clientForm ?
|
|
2436
|
+
const messageId = clientForm ? this._activeMessageId() : a2;
|
|
2437
|
+
const segmentId = clientForm ? a2 : b2;
|
|
2374
2438
|
if (!messageId || !segmentId) return;
|
|
2375
2439
|
const message = this._repo.getMessageById(messageId);
|
|
2376
2440
|
if (message?.segments) {
|
|
2377
|
-
const idx = message.segments.findIndex((
|
|
2441
|
+
const idx = message.segments.findIndex((s2) => s2.id === segmentId);
|
|
2378
2442
|
if (idx !== -1) {
|
|
2379
2443
|
message.segments.splice(idx, 1);
|
|
2380
2444
|
renumberSegments(message.segments);
|
|
@@ -2420,6 +2484,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2420
2484
|
message.status = "completed";
|
|
2421
2485
|
this._settleSegments(messageId, message);
|
|
2422
2486
|
this._notifyBubble(messageId, "complete", { status: "completed" });
|
|
2487
|
+
this._syncBusy();
|
|
2423
2488
|
this._recalculateSpacer();
|
|
2424
2489
|
}
|
|
2425
2490
|
}
|
|
@@ -2451,6 +2516,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2451
2516
|
if (isTerminalStatus(updates.status)) this._settleSegments(messageId, message);
|
|
2452
2517
|
}
|
|
2453
2518
|
this._notifyBubble(messageId, "update", updates);
|
|
2519
|
+
if (updates.status) this._syncBusy();
|
|
2454
2520
|
this._autoScroll();
|
|
2455
2521
|
}
|
|
2456
2522
|
/**
|
|
@@ -2465,6 +2531,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2465
2531
|
addMessage(message) {
|
|
2466
2532
|
this._repo.addOrUpdateMessage(this._repo.headId, adoptMessageSegments({ ...message }));
|
|
2467
2533
|
this._pruneRenderedBubbles();
|
|
2534
|
+
this._syncBusy();
|
|
2468
2535
|
this._autoScroll();
|
|
2469
2536
|
}
|
|
2470
2537
|
/**
|
|
@@ -2487,13 +2554,14 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2487
2554
|
},
|
|
2488
2555
|
[]
|
|
2489
2556
|
) } : { ...message };
|
|
2557
|
+
if (!options?.historical && isAwaitingReply(message)) stored.isStreaming = true;
|
|
2490
2558
|
this._repo.addOrUpdateMessage(this._repo.headId, stored);
|
|
2491
2559
|
if (!this._frameworkManagedDOM) {
|
|
2492
2560
|
const wrapper = this.querySelector(".aparte-messages-wrapper");
|
|
2493
2561
|
if (wrapper) {
|
|
2494
2562
|
const bubble = document.createElement("aparte-chat-bubble");
|
|
2495
2563
|
bubble.setAttribute("message-id", message.id);
|
|
2496
|
-
bubble.setAttribute("role", message.role);
|
|
2564
|
+
bubble.setAttribute("data-role", message.role);
|
|
2497
2565
|
if (message.timestamp) bubble.setAttribute("timestamp", String(message.timestamp));
|
|
2498
2566
|
if (message.content) bubble.setAttribute("content", message.content);
|
|
2499
2567
|
if (isAwaitingReply(message)) {
|
|
@@ -2508,10 +2576,13 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2508
2576
|
}
|
|
2509
2577
|
}
|
|
2510
2578
|
this._pruneRenderedBubbles();
|
|
2579
|
+
this._syncBusy();
|
|
2511
2580
|
this._recalculateSpacer();
|
|
2512
2581
|
if (message.role === "user") {
|
|
2513
2582
|
this._isAutoScrollEnabled = true;
|
|
2514
|
-
requestAnimationFrame(() =>
|
|
2583
|
+
requestAnimationFrame(() => {
|
|
2584
|
+
if (this._isAutoScrollEnabled) this._smoothScrollToBottom();
|
|
2585
|
+
});
|
|
2515
2586
|
} else {
|
|
2516
2587
|
this._autoScroll();
|
|
2517
2588
|
}
|
|
@@ -2570,7 +2641,9 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2570
2641
|
const meta = this._repo.getMessage(existingId);
|
|
2571
2642
|
if (!meta) return null;
|
|
2572
2643
|
const parentId = meta.message.role === "user" ? existingId : meta.parentId;
|
|
2573
|
-
|
|
2644
|
+
const stored = { ...newMessage };
|
|
2645
|
+
if (isAwaitingReply(newMessage)) stored.isStreaming = true;
|
|
2646
|
+
this._repo.addOrUpdateMessage(parentId, stored);
|
|
2574
2647
|
this._repo.switchToBranch(newMessage.id);
|
|
2575
2648
|
this._reRenderActivePath();
|
|
2576
2649
|
return newMessage.id;
|
|
@@ -2580,6 +2653,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2580
2653
|
* Triggers a full re-render of the active path.
|
|
2581
2654
|
*/
|
|
2582
2655
|
navigateBranch(messageId, direction) {
|
|
2656
|
+
if (this._streamingMessageId() !== null) return;
|
|
2583
2657
|
const siblings = this._repo.getBranches(messageId);
|
|
2584
2658
|
const currentIdx = siblings.indexOf(messageId);
|
|
2585
2659
|
if (currentIdx === -1) return;
|
|
@@ -2684,6 +2758,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2684
2758
|
}
|
|
2685
2759
|
}
|
|
2686
2760
|
this._repo.clear();
|
|
2761
|
+
this._syncBusy();
|
|
2687
2762
|
if (!this._frameworkManagedDOM) {
|
|
2688
2763
|
const wrapper = this.querySelector(".aparte-messages-wrapper");
|
|
2689
2764
|
if (wrapper) {
|
|
@@ -2833,7 +2908,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2833
2908
|
const sibInfo = siblingsInfo[i2];
|
|
2834
2909
|
const bubble = document.createElement("aparte-chat-bubble");
|
|
2835
2910
|
bubble.setAttribute("message-id", message.id);
|
|
2836
|
-
bubble.setAttribute("role", message.role);
|
|
2911
|
+
bubble.setAttribute("data-role", message.role);
|
|
2837
2912
|
if (message.timestamp) bubble.setAttribute("timestamp", String(message.timestamp));
|
|
2838
2913
|
if (isAwaitingReply(message)) {
|
|
2839
2914
|
bubble.setAttribute("streaming", "");
|
|
@@ -2842,6 +2917,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2842
2917
|
populateBubbleFromMessage(bubble, message, sibInfo);
|
|
2843
2918
|
}
|
|
2844
2919
|
this._dispatchPathChanged(activeMessages, siblingsInfo);
|
|
2920
|
+
this._syncBusy();
|
|
2845
2921
|
this._recalculateSpacer();
|
|
2846
2922
|
}
|
|
2847
2923
|
_dispatchPathChanged(messages, siblings) {
|
|
@@ -2856,9 +2932,13 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2856
2932
|
if (this._isAutoScrollEnabled) {
|
|
2857
2933
|
if (this._smoothScrollOnce) {
|
|
2858
2934
|
this._smoothScrollOnce = false;
|
|
2859
|
-
requestAnimationFrame(() =>
|
|
2935
|
+
requestAnimationFrame(() => {
|
|
2936
|
+
if (this._isAutoScrollEnabled) this._smoothScrollToBottom();
|
|
2937
|
+
});
|
|
2860
2938
|
} else {
|
|
2861
|
-
requestAnimationFrame(() =>
|
|
2939
|
+
requestAnimationFrame(() => {
|
|
2940
|
+
if (this._isAutoScrollEnabled) this._scrollToBottom();
|
|
2941
|
+
});
|
|
2862
2942
|
}
|
|
2863
2943
|
}
|
|
2864
2944
|
this._pruneRenderedBubbles();
|
|
@@ -2903,7 +2983,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2903
2983
|
this._scrollBtn = document.createElement("button");
|
|
2904
2984
|
this._scrollBtn.className = "aparte-btn aparte-btn--surface aparte-btn--circle aparte-btn--lg aparte-scroll-btn aparte-scroll-btn--hidden";
|
|
2905
2985
|
this._scrollBtn.setAttribute("type", "button");
|
|
2906
|
-
this._scrollBtn.setAttribute("aria-label", "
|
|
2986
|
+
this._scrollBtn.setAttribute("aria-label", resolveConfig(this).t("scrollToBottom"));
|
|
2907
2987
|
const scrollIcon = resolveConfig(this).getIcon("scrollDown");
|
|
2908
2988
|
this._scrollBtn.innerHTML = scrollIcon;
|
|
2909
2989
|
this.appendChild(this._scrollBtn);
|
|
@@ -2936,7 +3016,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2936
3016
|
const scrollBtn = document.createElement("button");
|
|
2937
3017
|
scrollBtn.className = "aparte-btn aparte-btn--surface aparte-btn--circle aparte-btn--lg aparte-scroll-btn aparte-scroll-btn--hidden";
|
|
2938
3018
|
scrollBtn.setAttribute("type", "button");
|
|
2939
|
-
scrollBtn.setAttribute("aria-label", "
|
|
3019
|
+
scrollBtn.setAttribute("aria-label", resolveConfig(this).t("scrollToBottom"));
|
|
2940
3020
|
const scrollIcon = resolveConfig(this).getIcon("scrollDown");
|
|
2941
3021
|
scrollBtn.innerHTML = scrollIcon;
|
|
2942
3022
|
this.appendChild(scrollBtn);
|
|
@@ -2981,6 +3061,27 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2981
3061
|
this._smoothScrollToBottom();
|
|
2982
3062
|
this._updateScrollButton();
|
|
2983
3063
|
};
|
|
3064
|
+
/**
|
|
3065
|
+
* Only the inputs that can SCROLL count as the reader's hand: a wheel notch, a
|
|
3066
|
+
* touch, a navigation key, a press in the scrollbar's gutter. A press on a control
|
|
3067
|
+
* inside the transcript — a branch arrow, a copy button — is not a scroll gesture,
|
|
3068
|
+
* and counting it disarmed the follow through the swap it triggered (react-webkit,
|
|
3069
|
+
* 1 run in 6: the rebuild's height churn moved scrollTop within the second after
|
|
3070
|
+
* the click, and the click made that look like the reader's).
|
|
3071
|
+
*/
|
|
3072
|
+
_noteReaderInput = (e2) => {
|
|
3073
|
+
if (e2.type === "keydown" && !this._scrollKeys.has(e2.key)) return;
|
|
3074
|
+
if (e2.type === "pointerdown") {
|
|
3075
|
+
const container = this._container;
|
|
3076
|
+
if (!container) return;
|
|
3077
|
+
const { clientX } = e2;
|
|
3078
|
+
const rect = container.getBoundingClientRect();
|
|
3079
|
+
const rtl = getComputedStyle(container).direction === "rtl";
|
|
3080
|
+
const inGutter = rtl ? clientX <= rect.right - container.clientWidth : clientX >= rect.left + container.clientWidth;
|
|
3081
|
+
if (!inGutter) return;
|
|
3082
|
+
}
|
|
3083
|
+
this._readerInputAt = performance.now();
|
|
3084
|
+
};
|
|
2984
3085
|
_onBranchNavigate = (e2) => {
|
|
2985
3086
|
const evt = e2;
|
|
2986
3087
|
evt.stopPropagation();
|
|
@@ -2988,6 +3089,9 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2988
3089
|
};
|
|
2989
3090
|
_setupEventListeners() {
|
|
2990
3091
|
this._container?.addEventListener("scroll", this._handleScroll, { passive: true });
|
|
3092
|
+
for (const type of ["wheel", "touchmove", "pointerdown", "keydown"]) {
|
|
3093
|
+
this._container?.addEventListener(type, this._noteReaderInput, { passive: true });
|
|
3094
|
+
}
|
|
2991
3095
|
this._scrollBtn?.addEventListener("click", this._onScrollBtnClick);
|
|
2992
3096
|
this.addEventListener("aparte-branch-navigate", this._onBranchNavigate);
|
|
2993
3097
|
}
|
|
@@ -3010,7 +3114,9 @@ class AparteChatViewport extends HTMLElement {
|
|
|
3010
3114
|
this._mutationObserver = new MutationObserver(() => {
|
|
3011
3115
|
if (this._frameworkManagedDOM) this._keepScrollButtonLast();
|
|
3012
3116
|
if (this._isAutoScrollEnabled) {
|
|
3013
|
-
requestAnimationFrame(() =>
|
|
3117
|
+
requestAnimationFrame(() => {
|
|
3118
|
+
if (this._isAutoScrollEnabled) this._scrollToBottom();
|
|
3119
|
+
});
|
|
3014
3120
|
}
|
|
3015
3121
|
this._scheduleSpacerUpdate();
|
|
3016
3122
|
});
|
|
@@ -3031,8 +3137,14 @@ class AparteChatViewport extends HTMLElement {
|
|
|
3031
3137
|
_handleScroll() {
|
|
3032
3138
|
if (!this._container) return;
|
|
3033
3139
|
const top = this._container.scrollTop;
|
|
3034
|
-
const
|
|
3140
|
+
const height = this._container.scrollHeight;
|
|
3141
|
+
const drop = this._lastScrollTop - top;
|
|
3142
|
+
const churn = Math.abs(height - this._lastScrollHeight);
|
|
3143
|
+
const now = performance.now();
|
|
3144
|
+
const settlingOurs = drop <= churn + 2 && now - this._ownScrollAt < this._ownScrollWindowMs && now - this._readerInputAt >= this._ownScrollWindowMs;
|
|
3145
|
+
const readerWentUp = drop > 1 && !settlingOurs;
|
|
3035
3146
|
this._lastScrollTop = top;
|
|
3147
|
+
this._lastScrollHeight = height;
|
|
3036
3148
|
if (this._isAtBottom()) {
|
|
3037
3149
|
this._isAutoScrollEnabled = true;
|
|
3038
3150
|
} else if (readerWentUp) {
|
|
@@ -3042,6 +3154,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
3042
3154
|
}
|
|
3043
3155
|
_scrollToBottom() {
|
|
3044
3156
|
if (!this._container) return;
|
|
3157
|
+
this._ownScrollAt = performance.now();
|
|
3045
3158
|
this._container.scrollTop = this._container.scrollHeight;
|
|
3046
3159
|
this._settleAtBottom(4);
|
|
3047
3160
|
}
|
|
@@ -3075,12 +3188,14 @@ class AparteChatViewport extends HTMLElement {
|
|
|
3075
3188
|
if (!this._container || !this._isAutoScrollEnabled) return;
|
|
3076
3189
|
const max = this._container.scrollHeight - this._container.clientHeight;
|
|
3077
3190
|
if (max - this._container.scrollTop <= 1) return;
|
|
3191
|
+
this._ownScrollAt = performance.now();
|
|
3078
3192
|
this._container.scrollTop = max;
|
|
3079
3193
|
this._settleAtBottom(framesLeft - 1);
|
|
3080
3194
|
});
|
|
3081
3195
|
}
|
|
3082
3196
|
_smoothScrollToBottom() {
|
|
3083
3197
|
if (!this._container) return;
|
|
3198
|
+
this._ownScrollAt = performance.now();
|
|
3084
3199
|
if (typeof this._container.scrollTo === "function" && !this._prefersReducedMotion()) {
|
|
3085
3200
|
this._container.scrollTo({ top: this._container.scrollHeight, behavior: "smooth" });
|
|
3086
3201
|
} else {
|
|
@@ -3099,7 +3214,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
3099
3214
|
* fold"). Mirroring the flag made the button lie whenever the two diverged —
|
|
3100
3215
|
* `navigateBranch` deliberately disarms auto-follow, so swapping a branch while
|
|
3101
3216
|
* already at the bottom of a scrollable transcript left the button offering to
|
|
3102
|
-
* scroll nowhere (reported from
|
|
3217
|
+
* scroll nowhere (reported from a React consumer). Re-derived on scroll, on the
|
|
3103
3218
|
* post-mutation frame and after a path swap, so it converges to the truth
|
|
3104
3219
|
* whatever a framework's render timing does in between.
|
|
3105
3220
|
*/
|
|
@@ -3126,7 +3241,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
3126
3241
|
this._setSpacerHeight(0);
|
|
3127
3242
|
return;
|
|
3128
3243
|
}
|
|
3129
|
-
const lastUserBubble = [...allBubbles].reverse().find((b2) => b2.getAttribute("role") === "user");
|
|
3244
|
+
const lastUserBubble = [...allBubbles].reverse().find((b2) => b2.getAttribute("data-role") === "user");
|
|
3130
3245
|
if (!lastUserBubble) {
|
|
3131
3246
|
this._setSpacerHeight(0);
|
|
3132
3247
|
return;
|
|
@@ -3185,15 +3300,11 @@ class AparteChatViewport extends HTMLElement {
|
|
|
3185
3300
|
bubbles[i2]?.remove();
|
|
3186
3301
|
}
|
|
3187
3302
|
}
|
|
3188
|
-
_warnMaxMessagesDeprecated() {
|
|
3189
|
-
if (this._warnedMaxMessagesDeprecation) return;
|
|
3190
|
-
this._warnedMaxMessagesDeprecation = true;
|
|
3191
|
-
console.warn(
|
|
3192
|
-
"[Aparte] `maxMessages` / `max-messages` on aparte-chat-viewport is deprecated: it used to silently evict messages from the conversation model. It now only caps rendered bubbles in the DOM — use `maxRenderedBubbles` / `max-rendered-bubbles`. For actual history retention, configure it on your AparteConversationManager instead."
|
|
3193
|
-
);
|
|
3194
|
-
}
|
|
3195
3303
|
_cleanup() {
|
|
3196
3304
|
this._container?.removeEventListener("scroll", this._handleScroll);
|
|
3305
|
+
for (const type of ["wheel", "touchmove", "pointerdown", "keydown"]) {
|
|
3306
|
+
this._container?.removeEventListener(type, this._noteReaderInput);
|
|
3307
|
+
}
|
|
3197
3308
|
this._scrollBtn?.removeEventListener("click", this._onScrollBtnClick);
|
|
3198
3309
|
this.removeEventListener("aparte-branch-navigate", this._onBranchNavigate);
|
|
3199
3310
|
this._resizeObserver?.disconnect();
|
|
@@ -3298,6 +3409,7 @@ class AparteComposer extends HTMLElement {
|
|
|
3298
3409
|
window.addEventListener("aparte-message-error", this._onMessageDone);
|
|
3299
3410
|
window.addEventListener("aparte-message-aborted", this._onMessageDone);
|
|
3300
3411
|
window.addEventListener("aparte-config-change", this._onConfigChangeEvent);
|
|
3412
|
+
queueMicrotask(() => this._warnIfUnattached());
|
|
3301
3413
|
this._evaluateModelGate();
|
|
3302
3414
|
this._applyDirection();
|
|
3303
3415
|
}
|
|
@@ -3313,6 +3425,11 @@ class AparteComposer extends HTMLElement {
|
|
|
3313
3425
|
if (name === "disabled") {
|
|
3314
3426
|
this._emit("disabled-change", { disabled: value !== null });
|
|
3315
3427
|
}
|
|
3428
|
+
if (name === "placeholder") {
|
|
3429
|
+
for (const input of this.querySelectorAll("aparte-composer-input")) {
|
|
3430
|
+
input.syncPlaceholder?.();
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3316
3433
|
}
|
|
3317
3434
|
// ── Public API ─────────────────────────────────────────────────────────
|
|
3318
3435
|
get value() {
|
|
@@ -3430,10 +3547,13 @@ class AparteComposer extends HTMLElement {
|
|
|
3430
3547
|
*/
|
|
3431
3548
|
showPanel(panel, options) {
|
|
3432
3549
|
this._evictPanel();
|
|
3550
|
+
const host = this.querySelector("[data-aparte-panel-host]");
|
|
3433
3551
|
const inputEl = this.querySelector("aparte-composer-input");
|
|
3434
3552
|
this.setAttribute("data-panel-active", "");
|
|
3435
3553
|
panel.dataset["apartePanel"] = "true";
|
|
3436
|
-
if (
|
|
3554
|
+
if (host) {
|
|
3555
|
+
host.appendChild(panel);
|
|
3556
|
+
} else if (inputEl) {
|
|
3437
3557
|
inputEl.insertAdjacentElement("afterend", panel);
|
|
3438
3558
|
} else {
|
|
3439
3559
|
this.appendChild(panel);
|
|
@@ -3643,15 +3763,26 @@ class AparteComposer extends HTMLElement {
|
|
|
3643
3763
|
* for the reason written there — Angular's wrapper root IS `<aparte-chat>`, the
|
|
3644
3764
|
* other three render a `[data-aparte-chat]` div.
|
|
3645
3765
|
*/
|
|
3766
|
+
/**
|
|
3767
|
+
* A signal, not a guard: on a page with several chats, a composer attached to none
|
|
3768
|
+
* answers to every chat's lifecycle events (see `_ownTargetId`), and the symptom —
|
|
3769
|
+
* one chat's Stop evicting another's open question — is nowhere near its cause.
|
|
3770
|
+
* Said once, at the console, where the developer is.
|
|
3771
|
+
*/
|
|
3772
|
+
_warnIfUnattached() {
|
|
3773
|
+
if (!this.isConnected || this._ownTargetId()) return;
|
|
3774
|
+
if (document.querySelectorAll("aparte-chat, [data-aparte-chat]").length < 2) return;
|
|
3775
|
+
console.warn(`[aparte-composer] this composer belongs to no chat while the page has several: give it target="<chat id>" or place it under a chat host with an id, or it will answer to every chat's events.`);
|
|
3776
|
+
}
|
|
3646
3777
|
_ownTargetId() {
|
|
3647
3778
|
const attr = this.targetId;
|
|
3648
3779
|
if (attr) return attr;
|
|
3649
|
-
let
|
|
3650
|
-
while (
|
|
3651
|
-
const tag =
|
|
3652
|
-
const isHost = tag === "aparte-chat" ||
|
|
3653
|
-
if (isHost &&
|
|
3654
|
-
|
|
3780
|
+
let el2 = this.parentElement;
|
|
3781
|
+
while (el2) {
|
|
3782
|
+
const tag = el2.tagName?.toLowerCase();
|
|
3783
|
+
const isHost = tag === "aparte-chat" || el2.hasAttribute?.("data-aparte-chat");
|
|
3784
|
+
if (isHost && el2.id) return el2.id;
|
|
3785
|
+
el2 = el2.parentElement;
|
|
3655
3786
|
}
|
|
3656
3787
|
return void 0;
|
|
3657
3788
|
}
|
|
@@ -3739,6 +3870,14 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3739
3870
|
this._adjustHeight();
|
|
3740
3871
|
this._getRoot()?.setValue("");
|
|
3741
3872
|
}
|
|
3873
|
+
/**
|
|
3874
|
+
* Re-read the placeholder fallback chain. Called by the composer when ITS
|
|
3875
|
+
* `placeholder` changes — this element observes only its own attribute, and the
|
|
3876
|
+
* composer's is one of the three sources it falls back to.
|
|
3877
|
+
*/
|
|
3878
|
+
syncPlaceholder() {
|
|
3879
|
+
this._updatePlaceholder();
|
|
3880
|
+
}
|
|
3742
3881
|
/** Focus the inner contenteditable rather than the host element. */
|
|
3743
3882
|
focus() {
|
|
3744
3883
|
this._editor?.focus();
|
|
@@ -3776,7 +3915,7 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3776
3915
|
role="textbox"
|
|
3777
3916
|
aria-multiline="true"
|
|
3778
3917
|
aria-label="${placeholder}"
|
|
3779
|
-
tabindex="0"
|
|
3918
|
+
tabindex="${escapeAttr(String(disabled ? -1 : 0))}"
|
|
3780
3919
|
aria-disabled="${disabled}"
|
|
3781
3920
|
data-placeholder="${placeholder}"
|
|
3782
3921
|
></div>`;
|
|
@@ -3801,11 +3940,6 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3801
3940
|
else this.setValue(value);
|
|
3802
3941
|
})
|
|
3803
3942
|
);
|
|
3804
|
-
this._unsubscribes.push(
|
|
3805
|
-
root._on("streaming-change", ({ streaming }) => {
|
|
3806
|
-
this._updateDisabled(streaming || root.disabled);
|
|
3807
|
-
})
|
|
3808
|
-
);
|
|
3809
3943
|
}
|
|
3810
3944
|
_handleInput() {
|
|
3811
3945
|
this._adjustHeight();
|
|
@@ -3825,6 +3959,7 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3825
3959
|
e2.preventDefault();
|
|
3826
3960
|
const root = this._getRoot();
|
|
3827
3961
|
if (root) {
|
|
3962
|
+
if (root.streaming) return;
|
|
3828
3963
|
root.submit();
|
|
3829
3964
|
} else {
|
|
3830
3965
|
this.dispatchEvent(new CustomEvent("aparte-composer-submit", { bubbles: true }));
|
|
@@ -3882,9 +4017,9 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3882
4017
|
}
|
|
3883
4018
|
_updatePlaceholder() {
|
|
3884
4019
|
if (this._editor) {
|
|
3885
|
-
const
|
|
3886
|
-
this._editor.setAttribute("data-placeholder",
|
|
3887
|
-
this._editor.setAttribute("aria-label",
|
|
4020
|
+
const p2 = this._getPlaceholder();
|
|
4021
|
+
this._editor.setAttribute("data-placeholder", p2);
|
|
4022
|
+
this._editor.setAttribute("aria-label", p2);
|
|
3888
4023
|
}
|
|
3889
4024
|
}
|
|
3890
4025
|
_updatePlaceholderVisibility() {
|
|
@@ -3893,6 +4028,8 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3893
4028
|
if (!this._editor) return;
|
|
3894
4029
|
this._editor.setAttribute("contenteditable", String(!disabled));
|
|
3895
4030
|
this._editor.setAttribute("aria-disabled", String(disabled));
|
|
4031
|
+
this._editor.setAttribute("tabindex", disabled ? "-1" : "0");
|
|
4032
|
+
if (disabled && this.ownerDocument?.activeElement === this._editor) this._editor.blur();
|
|
3896
4033
|
}
|
|
3897
4034
|
/** Escape a value before it lands in a double-quoted HTML attribute. */
|
|
3898
4035
|
}
|
|
@@ -4247,9 +4384,8 @@ class AparteComposerAddAttachment extends HTMLElement {
|
|
|
4247
4384
|
* Re-read the label and the icon on the button that already exists.
|
|
4248
4385
|
*
|
|
4249
4386
|
* Deliberately not a re-render: `_render()` returns early once the button is
|
|
4250
|
-
* there, and
|
|
4251
|
-
*
|
|
4252
|
-
* re-enable the attach button mid-turn, and drop focus if the user were on it.
|
|
4387
|
+
* there, and rebuilding it would drop focus if the user were on it — a locale or
|
|
4388
|
+
* icon change is not a reason to lose the keyboard.
|
|
4253
4389
|
* The native file input and the drag listeners live outside this element
|
|
4254
4390
|
* (on `document.body` and on the composer root), so they are untouched either way.
|
|
4255
4391
|
*/
|
|
@@ -4286,7 +4422,7 @@ class AparteComposerAddAttachment extends HTMLElement {
|
|
|
4286
4422
|
);
|
|
4287
4423
|
this._unsubscribes.push(
|
|
4288
4424
|
root._on("streaming-change", ({ streaming }) => {
|
|
4289
|
-
if (this._button) this._button.disabled =
|
|
4425
|
+
if (this._button) this._button.disabled = root.disabled;
|
|
4290
4426
|
})
|
|
4291
4427
|
);
|
|
4292
4428
|
}
|
|
@@ -4595,10 +4731,10 @@ class AparteConversationList extends HTMLElement {
|
|
|
4595
4731
|
/** Update active class without full re-render (perf optimisation). */
|
|
4596
4732
|
_updateActiveState() {
|
|
4597
4733
|
const items = this.querySelectorAll("[data-conv-id]");
|
|
4598
|
-
items.forEach((
|
|
4599
|
-
const isActive =
|
|
4600
|
-
|
|
4601
|
-
|
|
4734
|
+
items.forEach((el2) => {
|
|
4735
|
+
const isActive = el2.dataset["convId"] === this._activeId;
|
|
4736
|
+
el2.classList.toggle("aparte-conv-item--active", isActive);
|
|
4737
|
+
el2.setAttribute("aria-current", isActive ? "page" : "false");
|
|
4602
4738
|
});
|
|
4603
4739
|
}
|
|
4604
4740
|
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
@@ -4607,6 +4743,279 @@ class AparteConversationList extends HTMLElement {
|
|
|
4607
4743
|
}
|
|
4608
4744
|
}
|
|
4609
4745
|
if (!customElements.get("aparte-conversation-list")) customElements.define("aparte-conversation-list", AparteConversationList);
|
|
4746
|
+
const isSuggestion = (value) => typeof value === "string" || typeof value === "object" && value !== null && typeof value.label === "string";
|
|
4747
|
+
const labelOf = (s2) => typeof s2 === "string" ? s2 : s2.label;
|
|
4748
|
+
const promptOf = (s2) => typeof s2 === "string" ? s2 : s2.prompt ?? s2.label;
|
|
4749
|
+
class AparteSuggestions extends HTMLElement {
|
|
4750
|
+
static get observedAttributes() {
|
|
4751
|
+
return ["suggestions", "target"];
|
|
4752
|
+
}
|
|
4753
|
+
_suggestions = [];
|
|
4754
|
+
_composer = null;
|
|
4755
|
+
_onSend = () => {
|
|
4756
|
+
if (this.hasAttribute("empty-only")) this.hidden = true;
|
|
4757
|
+
};
|
|
4758
|
+
_onConfigChange = () => {
|
|
4759
|
+
const row = this.querySelector(".aparte-suggestions");
|
|
4760
|
+
if (row) row.setAttribute("aria-label", this._groupLabel());
|
|
4761
|
+
};
|
|
4762
|
+
/** The starters. Setting it re-renders; the `suggestions` attribute is the JSON form. */
|
|
4763
|
+
get suggestions() {
|
|
4764
|
+
return this._suggestions;
|
|
4765
|
+
}
|
|
4766
|
+
set suggestions(value) {
|
|
4767
|
+
this._suggestions = Array.isArray(value) ? value.filter(isSuggestion) : [];
|
|
4768
|
+
if (this.isConnected) this._render();
|
|
4769
|
+
}
|
|
4770
|
+
connectedCallback() {
|
|
4771
|
+
if (this.hasAttribute("suggestions")) this._readAttribute();
|
|
4772
|
+
this._render();
|
|
4773
|
+
this._watchComposer();
|
|
4774
|
+
window.addEventListener("aparte-config-change", this._onConfigChange);
|
|
4775
|
+
}
|
|
4776
|
+
disconnectedCallback() {
|
|
4777
|
+
this._composer?.removeEventListener("aparte-send", this._onSend);
|
|
4778
|
+
this._composer = null;
|
|
4779
|
+
window.removeEventListener("aparte-config-change", this._onConfigChange);
|
|
4780
|
+
}
|
|
4781
|
+
attributeChangedCallback(name) {
|
|
4782
|
+
if (!this.isConnected) return;
|
|
4783
|
+
if (name === "suggestions") {
|
|
4784
|
+
this._readAttribute();
|
|
4785
|
+
this._render();
|
|
4786
|
+
} else if (name === "target") {
|
|
4787
|
+
this._watchComposer();
|
|
4788
|
+
}
|
|
4789
|
+
}
|
|
4790
|
+
_groupLabel() {
|
|
4791
|
+
return resolveConfig(this).t("suggestionsLabel") || "Suggested prompts";
|
|
4792
|
+
}
|
|
4793
|
+
_parsed = null;
|
|
4794
|
+
/**
|
|
4795
|
+
* Parse the `suggestions` attribute once per value. The attribute callback and
|
|
4796
|
+
* `connectedCallback` both arrive for a parsed element, in an order the parser
|
|
4797
|
+
* decides; without this, an invalid value warned twice.
|
|
4798
|
+
*/
|
|
4799
|
+
_readAttribute() {
|
|
4800
|
+
const raw = this.getAttribute("suggestions");
|
|
4801
|
+
if (raw === null || raw === this._parsed) return;
|
|
4802
|
+
this._parsed = raw;
|
|
4803
|
+
try {
|
|
4804
|
+
const parsed = JSON.parse(raw);
|
|
4805
|
+
this._suggestions = Array.isArray(parsed) ? parsed.filter(isSuggestion) : [];
|
|
4806
|
+
} catch {
|
|
4807
|
+
console.warn('[aparte-suggestions] `suggestions` is not a JSON array. Use ["…"] or [{"label": "…", "prompt": "…"}].');
|
|
4808
|
+
this._suggestions = [];
|
|
4809
|
+
}
|
|
4810
|
+
}
|
|
4811
|
+
/**
|
|
4812
|
+
* The composer the click goes through, resolved the way the composer's own parts
|
|
4813
|
+
* resolve it — by ancestry first. Outside a composer, `target` names the chat and the
|
|
4814
|
+
* composer serving it is either inside that chat or points at it; the first composer
|
|
4815
|
+
* on the page is the last resort, so the one-chat page needs no attribute at all.
|
|
4816
|
+
*/
|
|
4817
|
+
_resolveComposer() {
|
|
4818
|
+
const nearest = this.closest("aparte-composer");
|
|
4819
|
+
if (nearest) return nearest;
|
|
4820
|
+
const target = this.getAttribute("target");
|
|
4821
|
+
if (target) {
|
|
4822
|
+
const scope = document.getElementById(target);
|
|
4823
|
+
const inside = scope?.querySelector("aparte-composer");
|
|
4824
|
+
if (inside) return inside;
|
|
4825
|
+
for (const composer of document.querySelectorAll("aparte-composer[target]")) {
|
|
4826
|
+
if (composer.getAttribute("target") === target) return composer;
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4829
|
+
return document.querySelector("aparte-composer");
|
|
4830
|
+
}
|
|
4831
|
+
_watchComposer() {
|
|
4832
|
+
this._composer?.removeEventListener("aparte-send", this._onSend);
|
|
4833
|
+
this._composer = this._resolveComposer();
|
|
4834
|
+
this._composer?.addEventListener("aparte-send", this._onSend);
|
|
4835
|
+
}
|
|
4836
|
+
_render() {
|
|
4837
|
+
this.replaceChildren();
|
|
4838
|
+
if (this._suggestions.length === 0) {
|
|
4839
|
+
this.setAttribute("data-empty", "");
|
|
4840
|
+
return;
|
|
4841
|
+
}
|
|
4842
|
+
this.removeAttribute("data-empty");
|
|
4843
|
+
const row = document.createElement("div");
|
|
4844
|
+
row.className = "aparte-suggestions";
|
|
4845
|
+
row.setAttribute("role", "group");
|
|
4846
|
+
row.setAttribute("aria-label", this._groupLabel());
|
|
4847
|
+
for (const suggestion of this._suggestions) {
|
|
4848
|
+
const button = document.createElement("button");
|
|
4849
|
+
button.type = "button";
|
|
4850
|
+
button.className = "aparte-btn aparte-btn--surface aparte-btn--pill aparte-suggestion";
|
|
4851
|
+
button.textContent = labelOf(suggestion);
|
|
4852
|
+
const prompt = promptOf(suggestion);
|
|
4853
|
+
if (prompt !== button.textContent) button.title = prompt;
|
|
4854
|
+
button.addEventListener("click", () => this._activate(suggestion));
|
|
4855
|
+
row.appendChild(button);
|
|
4856
|
+
}
|
|
4857
|
+
this.appendChild(row);
|
|
4858
|
+
}
|
|
4859
|
+
_activate(suggestion) {
|
|
4860
|
+
const detail = { label: labelOf(suggestion), prompt: promptOf(suggestion) };
|
|
4861
|
+
const proceed = this.dispatchEvent(new CustomEvent("aparte-suggestion", {
|
|
4862
|
+
detail,
|
|
4863
|
+
bubbles: true,
|
|
4864
|
+
composed: true,
|
|
4865
|
+
cancelable: true
|
|
4866
|
+
}));
|
|
4867
|
+
if (!proceed) return;
|
|
4868
|
+
const composer = this._composer ?? this._resolveComposer();
|
|
4869
|
+
if (!composer) {
|
|
4870
|
+
console.warn("[aparte-suggestions] no <aparte-composer> to send through — put the element inside one, or point `target` at the chat it serves.");
|
|
4871
|
+
return;
|
|
4872
|
+
}
|
|
4873
|
+
if (composer !== this._composer) this._watchComposer();
|
|
4874
|
+
composer.setValue(detail.prompt);
|
|
4875
|
+
if (this.getAttribute("mode") === "fill") composer.focus();
|
|
4876
|
+
else composer.submit();
|
|
4877
|
+
}
|
|
4878
|
+
}
|
|
4879
|
+
if (!customElements.get("aparte-suggestions")) {
|
|
4880
|
+
customElements.define("aparte-suggestions", AparteSuggestions);
|
|
4881
|
+
}
|
|
4882
|
+
const fraction = (raw, fallback) => {
|
|
4883
|
+
const n2 = Number(raw);
|
|
4884
|
+
return raw !== null && Number.isFinite(n2) && n2 > 0 && n2 <= 1 ? n2 : fallback;
|
|
4885
|
+
};
|
|
4886
|
+
const el = (tag, className) => {
|
|
4887
|
+
const node = document.createElement(tag);
|
|
4888
|
+
node.className = className;
|
|
4889
|
+
return node;
|
|
4890
|
+
};
|
|
4891
|
+
class AparteContext extends HTMLElement {
|
|
4892
|
+
static get observedAttributes() {
|
|
4893
|
+
return ["window", "warn", "danger", "target"];
|
|
4894
|
+
}
|
|
4895
|
+
_used = null;
|
|
4896
|
+
_level = null;
|
|
4897
|
+
_compactRequested = false;
|
|
4898
|
+
_onDone = (e2) => {
|
|
4899
|
+
const detail = e2.detail;
|
|
4900
|
+
if (!detail?.usage || !this._isMine(detail.targetId)) return;
|
|
4901
|
+
const { usage } = detail;
|
|
4902
|
+
this._used = usage.totalTokens ?? usage.inputTokens + usage.outputTokens;
|
|
4903
|
+
this._render();
|
|
4904
|
+
};
|
|
4905
|
+
/**
|
|
4906
|
+
* After a real compaction the context is smaller by an amount nobody has measured
|
|
4907
|
+
* yet — the next turn's usage will say. Until then the gauge shows nothing rather
|
|
4908
|
+
* than a number it knows to be wrong. (`aparte-compact-done` names no chat: on a
|
|
4909
|
+
* multi-chat page every gauge resets, and each is right again one turn later.)
|
|
4910
|
+
*/
|
|
4911
|
+
_onCompacted = (e2) => {
|
|
4912
|
+
const detail = e2.detail;
|
|
4913
|
+
if (detail?.skipped) return;
|
|
4914
|
+
this._used = null;
|
|
4915
|
+
this._compactRequested = false;
|
|
4916
|
+
this._render();
|
|
4917
|
+
};
|
|
4918
|
+
_onRerender = () => this._render();
|
|
4919
|
+
/** Tokens the last turn reported, or `null` before the first turn. */
|
|
4920
|
+
get used() {
|
|
4921
|
+
return this._used;
|
|
4922
|
+
}
|
|
4923
|
+
/** The window in force — the attribute, else the current model's. */
|
|
4924
|
+
get window() {
|
|
4925
|
+
return this._window();
|
|
4926
|
+
}
|
|
4927
|
+
/** The current level, or `null` while nothing is shown. */
|
|
4928
|
+
get level() {
|
|
4929
|
+
return this._level;
|
|
4930
|
+
}
|
|
4931
|
+
connectedCallback() {
|
|
4932
|
+
window.addEventListener("aparte-message-done", this._onDone);
|
|
4933
|
+
window.addEventListener("aparte-compact-done", this._onCompacted);
|
|
4934
|
+
window.addEventListener("aparte-config-change", this._onRerender);
|
|
4935
|
+
this._render();
|
|
4936
|
+
}
|
|
4937
|
+
disconnectedCallback() {
|
|
4938
|
+
window.removeEventListener("aparte-message-done", this._onDone);
|
|
4939
|
+
window.removeEventListener("aparte-compact-done", this._onCompacted);
|
|
4940
|
+
window.removeEventListener("aparte-config-change", this._onRerender);
|
|
4941
|
+
}
|
|
4942
|
+
attributeChangedCallback() {
|
|
4943
|
+
if (this.isConnected) this._render();
|
|
4944
|
+
}
|
|
4945
|
+
_window() {
|
|
4946
|
+
const attr = Number(this.getAttribute("window"));
|
|
4947
|
+
if (this.hasAttribute("window") && Number.isFinite(attr) && attr > 0) return attr;
|
|
4948
|
+
const model = resolveConfig(this).getCurrentModel();
|
|
4949
|
+
return model?.contextWindow && model.contextWindow > 0 ? model.contextWindow : null;
|
|
4950
|
+
}
|
|
4951
|
+
/** The chat this gauge watches: `target`, else the chat host above it — the composer's rule. */
|
|
4952
|
+
_ownTargetId() {
|
|
4953
|
+
const attr = this.getAttribute("target");
|
|
4954
|
+
if (attr) return attr;
|
|
4955
|
+
let node = this.parentElement;
|
|
4956
|
+
while (node) {
|
|
4957
|
+
const isHost = node.tagName.toLowerCase() === "aparte-chat" || node.hasAttribute("data-aparte-chat");
|
|
4958
|
+
if (isHost && node.id) return node.id;
|
|
4959
|
+
node = node.parentElement;
|
|
4960
|
+
}
|
|
4961
|
+
return void 0;
|
|
4962
|
+
}
|
|
4963
|
+
_isMine(targetId) {
|
|
4964
|
+
const own = this._ownTargetId();
|
|
4965
|
+
return !targetId || !own || targetId === own;
|
|
4966
|
+
}
|
|
4967
|
+
_render() {
|
|
4968
|
+
const capacity = this._window();
|
|
4969
|
+
if (capacity === null || this._used === null) {
|
|
4970
|
+
this.setAttribute("data-empty", "");
|
|
4971
|
+
this.removeAttribute("data-level");
|
|
4972
|
+
this.replaceChildren();
|
|
4973
|
+
this._level = null;
|
|
4974
|
+
return;
|
|
4975
|
+
}
|
|
4976
|
+
this.removeAttribute("data-empty");
|
|
4977
|
+
const used = this._used;
|
|
4978
|
+
const ratio = Math.min(1, used / capacity);
|
|
4979
|
+
const warn = fraction(this.getAttribute("warn"), 0.75);
|
|
4980
|
+
const danger = fraction(this.getAttribute("danger"), 0.9);
|
|
4981
|
+
const level = ratio >= danger ? "danger" : ratio >= warn ? "warn" : "ok";
|
|
4982
|
+
this.setAttribute("data-level", level);
|
|
4983
|
+
let bar = this.querySelector(".aparte-progress");
|
|
4984
|
+
let text = this.querySelector(".aparte-context__text");
|
|
4985
|
+
if (!bar || !text) {
|
|
4986
|
+
const root = el("div", "aparte-context");
|
|
4987
|
+
bar = el("div", "aparte-progress");
|
|
4988
|
+
bar.setAttribute("role", "meter");
|
|
4989
|
+
bar.appendChild(el("div", "aparte-progress__bar"));
|
|
4990
|
+
text = el("span", "aparte-context__text");
|
|
4991
|
+
root.append(bar, text);
|
|
4992
|
+
this.replaceChildren(root);
|
|
4993
|
+
}
|
|
4994
|
+
const format = new Intl.NumberFormat(void 0, { notation: "compact", maximumFractionDigits: 1 });
|
|
4995
|
+
const reading = `${format.format(used)} / ${format.format(capacity)}`;
|
|
4996
|
+
const label = resolveConfig(this).t("contextLabel") || "Context window";
|
|
4997
|
+
bar.style.setProperty("--aparte-progress-value", String(Math.round(ratio * 100)));
|
|
4998
|
+
bar.setAttribute("aria-valuemin", "0");
|
|
4999
|
+
bar.setAttribute("aria-valuemax", String(capacity));
|
|
5000
|
+
bar.setAttribute("aria-valuenow", String(used));
|
|
5001
|
+
bar.setAttribute("aria-label", `${label}: ${reading}`);
|
|
5002
|
+
text.textContent = reading;
|
|
5003
|
+
if (level !== this._level) {
|
|
5004
|
+
this._level = level;
|
|
5005
|
+
const targetId = this._ownTargetId();
|
|
5006
|
+
const detail = { level, used, window: capacity, ratio, targetId };
|
|
5007
|
+
this.dispatchEvent(new CustomEvent("aparte-context-threshold", { detail, bubbles: true, composed: true }));
|
|
5008
|
+
if (level === "danger" && this.hasAttribute("auto-compact") && !this._compactRequested) {
|
|
5009
|
+
this._compactRequested = true;
|
|
5010
|
+
window.dispatchEvent(new CustomEvent("aparte-compact", { detail: { targetId } }));
|
|
5011
|
+
}
|
|
5012
|
+
if (level !== "danger") this._compactRequested = false;
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
}
|
|
5016
|
+
if (!customElements.get("aparte-context")) {
|
|
5017
|
+
customElements.define("aparte-context", AparteContext);
|
|
5018
|
+
}
|
|
4610
5019
|
function registerAllComponents() {
|
|
4611
5020
|
const _chat = customElements.get("aparte-chat");
|
|
4612
5021
|
const _viewport = customElements.get("aparte-chat-viewport");
|
|
@@ -4623,16 +5032,15 @@ export {
|
|
|
4623
5032
|
c as APARTE_DEFAULT_HOST_HANDLERS,
|
|
4624
5033
|
d as APARTE_DEFAULT_ICON_FALLBACKS,
|
|
4625
5034
|
e as APARTE_DEFAULT_LOCALE,
|
|
4626
|
-
f as
|
|
4627
|
-
g as
|
|
4628
|
-
h as
|
|
4629
|
-
i as AparteBackendTransport,
|
|
5035
|
+
f as APARTE_DEFAULT_UI_EVENTS,
|
|
5036
|
+
g as APARTE_HOST_ATTR,
|
|
5037
|
+
h as AparteBackendTransport,
|
|
4630
5038
|
AparteChat,
|
|
4631
5039
|
AparteChatBubble,
|
|
4632
|
-
|
|
5040
|
+
i as AparteChatHost,
|
|
4633
5041
|
AparteChatStatus,
|
|
4634
5042
|
AparteChatViewport,
|
|
4635
|
-
|
|
5043
|
+
j as AparteClient,
|
|
4636
5044
|
AparteComposer,
|
|
4637
5045
|
AparteComposerAction,
|
|
4638
5046
|
AparteComposerAddAttachment,
|
|
@@ -4641,62 +5049,64 @@ export {
|
|
|
4641
5049
|
AparteComposerInput,
|
|
4642
5050
|
AparteComposerSend,
|
|
4643
5051
|
AparteComposerToolbar,
|
|
4644
|
-
|
|
4645
|
-
|
|
5052
|
+
k as AparteConfig,
|
|
5053
|
+
AparteContext,
|
|
5054
|
+
l as AparteConversationController,
|
|
4646
5055
|
AparteConversationList,
|
|
4647
|
-
|
|
4648
|
-
|
|
5056
|
+
m as AparteConversationManager,
|
|
5057
|
+
n as AparteDirectTransport,
|
|
4649
5058
|
AparteElicitation,
|
|
4650
5059
|
AparteElicitationAbortError,
|
|
4651
|
-
|
|
4652
|
-
|
|
5060
|
+
p as AparteError,
|
|
5061
|
+
q as AparteErrorCode,
|
|
4653
5062
|
AparteIcon,
|
|
4654
5063
|
AparteMessageRepository,
|
|
4655
5064
|
AparteOptgroup,
|
|
4656
5065
|
AparteOption,
|
|
4657
5066
|
AparteProgressSpinner,
|
|
4658
5067
|
AparteSelect,
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
5068
|
+
s as AparteStreamParser,
|
|
5069
|
+
AparteSuggestions,
|
|
5070
|
+
t as aparteGlobalConfig,
|
|
5071
|
+
u as applyElementProps,
|
|
5072
|
+
v as attachConfig,
|
|
4663
5073
|
buildApprovalPanel,
|
|
4664
5074
|
buildElicitationPanel,
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
5075
|
+
y as collectRendererStyles,
|
|
5076
|
+
z as contentToText,
|
|
5077
|
+
B as contextConfig,
|
|
5078
|
+
C as createAparteChatHandler,
|
|
5079
|
+
D as createStreamAdapter,
|
|
4670
5080
|
cssEscape,
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
5081
|
+
F as declineDefaultRenderers,
|
|
5082
|
+
G as defaultSanitizer,
|
|
5083
|
+
deriveArtifactKind,
|
|
5084
|
+
H as detachConfig,
|
|
4675
5085
|
escapeAttr,
|
|
4676
5086
|
escapeHtml,
|
|
4677
|
-
|
|
4678
|
-
|
|
5087
|
+
K as filesToAttachments,
|
|
5088
|
+
L as getAllRenderers,
|
|
4679
5089
|
getSegmentRenderer,
|
|
4680
5090
|
installDefaultRenderersOnce,
|
|
4681
5091
|
isAwaitingReply,
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
5092
|
+
P as isFormatAdapter,
|
|
5093
|
+
Q as isSafeUrl,
|
|
5094
|
+
R as isSegmentSettled,
|
|
5095
|
+
S as parseAparteEventStream,
|
|
5096
|
+
T as parseMarkdownToSegments,
|
|
4687
5097
|
populateBubbleFromMessage,
|
|
4688
|
-
|
|
5098
|
+
V as readableToAsyncIterable,
|
|
4689
5099
|
registerAllComponents,
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
5100
|
+
W as registerDefaultRenderers,
|
|
5101
|
+
X as registerSegmentRenderer,
|
|
5102
|
+
Y as requestUserInput,
|
|
4693
5103
|
resolveConfig,
|
|
4694
5104
|
revokeAttachmentUrls,
|
|
4695
5105
|
runWithConfig,
|
|
4696
|
-
|
|
4697
|
-
|
|
5106
|
+
a0 as segmentDuration,
|
|
5107
|
+
a1 as segmentTiming,
|
|
4698
5108
|
subscribeConfigChange,
|
|
4699
|
-
|
|
5109
|
+
a3 as unregisterSegmentRenderer,
|
|
4700
5110
|
uuid
|
|
4701
5111
|
};
|
|
4702
5112
|
//# sourceMappingURL=index.js.map
|