@aparte/core 0.13.0 → 0.14.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/elicitation/aparte-elicitation.d.ts.map +1 -1
- 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 +37 -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 +6484 -6103
- 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 +539 -172
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +609 -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-DrO_oR32.js → is-awaiting-reply-BZ_iJ3s3.js} +188 -1017
- 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 +18 -0
- 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/index.d.ts +1 -1
- 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-DrO_oR32.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) => {
|
|
@@ -731,11 +752,17 @@ class AparteElicitation extends HTMLElement {
|
|
|
731
752
|
return new Promise((resolve, reject) => {
|
|
732
753
|
let done = false;
|
|
733
754
|
const slot = {};
|
|
755
|
+
const focusedBefore = document.activeElement;
|
|
734
756
|
const close = () => {
|
|
735
757
|
if (done) return false;
|
|
736
758
|
done = true;
|
|
737
759
|
this._pending = null;
|
|
760
|
+
const active = document.activeElement;
|
|
761
|
+
const focusWasInPanel = active instanceof Node && composer.contains(active);
|
|
738
762
|
composer.hidePanel(slot.token);
|
|
763
|
+
if (focusWasInPanel && focusedBefore instanceof HTMLElement && focusedBefore.isConnected) {
|
|
764
|
+
focusedBefore.focus();
|
|
765
|
+
}
|
|
739
766
|
return true;
|
|
740
767
|
};
|
|
741
768
|
const settle = (result) => {
|
|
@@ -827,12 +854,12 @@ class AparteElicitation extends HTMLElement {
|
|
|
827
854
|
* resolves `undefined` on three wrappers out of four.
|
|
828
855
|
*/
|
|
829
856
|
_pendingTargetId() {
|
|
830
|
-
let
|
|
831
|
-
while (
|
|
832
|
-
const tag =
|
|
833
|
-
const isHost = tag === "aparte-chat" ||
|
|
834
|
-
if (isHost &&
|
|
835
|
-
|
|
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;
|
|
836
863
|
}
|
|
837
864
|
return void 0;
|
|
838
865
|
}
|
|
@@ -858,7 +885,7 @@ class AparteElicitation extends HTMLElement {
|
|
|
858
885
|
const composer = node.querySelector("aparte-composer");
|
|
859
886
|
if (composer && typeof composer.showPanel === "function") return composer;
|
|
860
887
|
const tag = node.tagName?.toLowerCase();
|
|
861
|
-
const isChatBoundary = tag === "aparte-chat" ||
|
|
888
|
+
const isChatBoundary = tag === "aparte-chat" || node.hasAttribute?.("data-aparte-chat");
|
|
862
889
|
if (isChatBoundary) break;
|
|
863
890
|
node = node.parentElement;
|
|
864
891
|
}
|
|
@@ -996,10 +1023,10 @@ function warnMissingRenderer(type) {
|
|
|
996
1023
|
function unrenderedSegment(segment) {
|
|
997
1024
|
const fallback = typeof segment.fallback === "string" && segment.fallback.trim() ? segment.fallback : null;
|
|
998
1025
|
if (!fallback) warnMissingRenderer(segment.type);
|
|
999
|
-
const
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
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;
|
|
1003
1030
|
}
|
|
1004
1031
|
function resolveSegmentRenderer(type, config) {
|
|
1005
1032
|
const renderer = getSegmentRenderer(type, config);
|
|
@@ -1037,7 +1064,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1037
1064
|
/** The live inline editor (the composer's contenteditable primitive), present only while `_editing`. */
|
|
1038
1065
|
_editInput = null;
|
|
1039
1066
|
static get observedAttributes() {
|
|
1040
|
-
return ["
|
|
1067
|
+
return ["data-role", "content", "timestamp", "message-id", "streaming", "name"];
|
|
1041
1068
|
}
|
|
1042
1069
|
constructor() {
|
|
1043
1070
|
super();
|
|
@@ -1077,11 +1104,11 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1077
1104
|
for (const segment of this._segments) {
|
|
1078
1105
|
const renderer = resolveSegmentRenderer(segment.type, this._cfg);
|
|
1079
1106
|
if (!renderer?.relabel) continue;
|
|
1080
|
-
const
|
|
1107
|
+
const el2 = this._segmentsEl.querySelector(
|
|
1081
1108
|
`:scope > [data-segment-id="${cssEscape(segment.id)}"]`
|
|
1082
1109
|
);
|
|
1083
|
-
if (!
|
|
1084
|
-
runWithConfig(this._cfg, () => renderer.relabel(
|
|
1110
|
+
if (!el2) continue;
|
|
1111
|
+
runWithConfig(this._cfg, () => renderer.relabel(el2, segment));
|
|
1085
1112
|
}
|
|
1086
1113
|
}
|
|
1087
1114
|
/**
|
|
@@ -1128,9 +1155,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1128
1155
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
1129
1156
|
if (oldValue === newValue) return;
|
|
1130
1157
|
switch (name) {
|
|
1131
|
-
case "role":
|
|
1132
1158
|
case "data-role":
|
|
1133
|
-
if (newValue === "article") return;
|
|
1134
1159
|
if (newValue === "user" || newValue === "assistant") {
|
|
1135
1160
|
this._role = newValue;
|
|
1136
1161
|
this._updateRole();
|
|
@@ -1196,7 +1221,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1196
1221
|
}
|
|
1197
1222
|
/** Update a specific segment */
|
|
1198
1223
|
updateSegment(segmentId, updates) {
|
|
1199
|
-
const index = this._segments.findIndex((
|
|
1224
|
+
const index = this._segments.findIndex((s2) => s2.id === segmentId);
|
|
1200
1225
|
if (index !== -1) {
|
|
1201
1226
|
const updated = mergeSegmentUpdate(this._segments[index], updates);
|
|
1202
1227
|
this._segments[index] = updated;
|
|
@@ -1205,7 +1230,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1205
1230
|
}
|
|
1206
1231
|
/** Append content to a segment */
|
|
1207
1232
|
appendToSegment(segmentId, content) {
|
|
1208
|
-
const segment = this._segments.find((
|
|
1233
|
+
const segment = this._segments.find((s2) => s2.id === segmentId);
|
|
1209
1234
|
if (segment && "content" in segment) {
|
|
1210
1235
|
segment.content += content;
|
|
1211
1236
|
this._applySegmentUpdate(segmentId, segment, { content: segment.content });
|
|
@@ -1231,18 +1256,19 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1231
1256
|
* the human-in-the-loop control.
|
|
1232
1257
|
*/
|
|
1233
1258
|
removeSegment(segmentId) {
|
|
1234
|
-
const index = this._segments.findIndex((
|
|
1259
|
+
const index = this._segments.findIndex((s2) => s2.id === segmentId);
|
|
1235
1260
|
if (index !== -1) {
|
|
1236
1261
|
this._segments.splice(index, 1);
|
|
1237
1262
|
}
|
|
1238
|
-
const
|
|
1239
|
-
|
|
1263
|
+
const el2 = this._segmentsEl?.querySelector(`:scope > [data-segment-id="${cssEscape(segmentId)}"]`);
|
|
1264
|
+
el2?.remove();
|
|
1240
1265
|
this._updateWaiting();
|
|
1241
1266
|
}
|
|
1242
1267
|
/** Set attachments (chips shown above message content, user role only) */
|
|
1243
1268
|
setAttachments(attachments) {
|
|
1244
1269
|
this._attachments = attachments;
|
|
1245
1270
|
this._updateAttachments();
|
|
1271
|
+
this._updateWaiting();
|
|
1246
1272
|
}
|
|
1247
1273
|
/**
|
|
1248
1274
|
* Set token usage + timing for this message (assistant only).
|
|
@@ -1294,6 +1320,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1294
1320
|
if ("attachments" in updates) {
|
|
1295
1321
|
this._attachments = updates.attachments ?? [];
|
|
1296
1322
|
this._updateAttachments();
|
|
1323
|
+
this._updateWaiting();
|
|
1297
1324
|
}
|
|
1298
1325
|
}
|
|
1299
1326
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -1306,10 +1333,10 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1306
1333
|
}
|
|
1307
1334
|
const renderer = resolveSegmentRenderer(segment.type, this._cfg);
|
|
1308
1335
|
if (renderer) {
|
|
1309
|
-
const
|
|
1310
|
-
if (
|
|
1311
|
-
this._segmentsEl.appendChild(
|
|
1312
|
-
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));
|
|
1313
1340
|
}
|
|
1314
1341
|
} else {
|
|
1315
1342
|
this._segmentsEl.appendChild(unrenderedSegment(segment));
|
|
@@ -1318,27 +1345,27 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1318
1345
|
this._reflectError();
|
|
1319
1346
|
}
|
|
1320
1347
|
_applySegmentUpdate(segmentId, segment, updates) {
|
|
1321
|
-
const
|
|
1322
|
-
if (!
|
|
1348
|
+
const el2 = this._segmentsEl?.querySelector(`:scope > [data-segment-id="${cssEscape(segmentId)}"]`);
|
|
1349
|
+
if (!el2) {
|
|
1323
1350
|
this._renderSegments();
|
|
1324
1351
|
return;
|
|
1325
1352
|
}
|
|
1326
1353
|
const renderer = resolveSegmentRenderer(segment.type, this._cfg);
|
|
1327
1354
|
if (!renderer) return;
|
|
1328
1355
|
if (renderer.update) {
|
|
1329
|
-
runWithConfig(this._cfg, () => renderer.update(
|
|
1356
|
+
runWithConfig(this._cfg, () => renderer.update(el2, segment));
|
|
1330
1357
|
} else {
|
|
1331
1358
|
const newEl = segmentRenderResultToElement(runWithConfig(this._cfg, () => renderer.render(segment)));
|
|
1332
1359
|
if (newEl) {
|
|
1333
|
-
|
|
1360
|
+
el2.replaceWith(newEl);
|
|
1334
1361
|
runWithConfig(this._cfg, () => renderer.setup?.(newEl, segment));
|
|
1335
1362
|
}
|
|
1336
1363
|
}
|
|
1337
1364
|
if ("collapsed" in updates) {
|
|
1338
1365
|
if (updates.collapsed) {
|
|
1339
|
-
|
|
1366
|
+
el2.removeAttribute("open");
|
|
1340
1367
|
} else {
|
|
1341
|
-
|
|
1368
|
+
el2.setAttribute("open", "");
|
|
1342
1369
|
}
|
|
1343
1370
|
}
|
|
1344
1371
|
}
|
|
@@ -1354,8 +1381,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1354
1381
|
}
|
|
1355
1382
|
_render() {
|
|
1356
1383
|
const dataRole = this.getAttribute("data-role");
|
|
1357
|
-
const
|
|
1358
|
-
const role = dataRole && dataRole !== "article" ? dataRole : legacyRole && legacyRole !== "article" ? legacyRole : "assistant";
|
|
1384
|
+
const role = dataRole === "user" || dataRole === "assistant" ? dataRole : "assistant";
|
|
1359
1385
|
this._role = role;
|
|
1360
1386
|
if (this.getAttribute("role") !== "article") {
|
|
1361
1387
|
this.setAttribute("role", "article");
|
|
@@ -1414,6 +1440,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1414
1440
|
this._actionBarEl = this.querySelector(".aparte-action-bar");
|
|
1415
1441
|
this._branchPickerEl = this.querySelector(".aparte-branch-picker");
|
|
1416
1442
|
this._footerEl = this.querySelector(".aparte-footer");
|
|
1443
|
+
this._transcriptBusy = !!this.closest("aparte-chat-viewport")?.hasAttribute("data-busy");
|
|
1417
1444
|
this._updateActionBar();
|
|
1418
1445
|
this._renderAvatar();
|
|
1419
1446
|
if (this._streaming) this._updateStreaming(true);
|
|
@@ -1434,12 +1461,16 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1434
1461
|
const waiting = this._streaming && this._role !== "user" && empty;
|
|
1435
1462
|
const box = this.querySelector(".aparte-message-content");
|
|
1436
1463
|
if (box) box.hidden = empty && !waiting;
|
|
1437
|
-
const
|
|
1438
|
-
if (
|
|
1439
|
-
|
|
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;
|
|
1440
1471
|
if (!waiting) return;
|
|
1441
1472
|
const label = this._cfg.getLocale().typing;
|
|
1442
|
-
const sr =
|
|
1473
|
+
const sr = el2.querySelector(".aparte-sr-only");
|
|
1443
1474
|
if (sr && sr.textContent !== label) sr.textContent = label;
|
|
1444
1475
|
}
|
|
1445
1476
|
/**
|
|
@@ -1541,10 +1572,10 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1541
1572
|
for (const segment of this._segments) {
|
|
1542
1573
|
const renderer = resolveSegmentRenderer(segment.type, this._cfg);
|
|
1543
1574
|
if (renderer) {
|
|
1544
|
-
const
|
|
1545
|
-
if (
|
|
1546
|
-
this._segmentsEl.appendChild(
|
|
1547
|
-
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));
|
|
1548
1579
|
}
|
|
1549
1580
|
} else {
|
|
1550
1581
|
this._segmentsEl.appendChild(unrenderedSegment(segment));
|
|
@@ -1565,7 +1596,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1565
1596
|
_reflectError() {
|
|
1566
1597
|
const message = this.querySelector(".aparte-message");
|
|
1567
1598
|
if (!message) return;
|
|
1568
|
-
const hasError = this._segments.some((
|
|
1599
|
+
const hasError = this._segments.some((s2) => s2.type === "error");
|
|
1569
1600
|
if (hasError) message.setAttribute("data-error", "");
|
|
1570
1601
|
else message.removeAttribute("data-error");
|
|
1571
1602
|
}
|
|
@@ -1600,18 +1631,18 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1600
1631
|
const customAttachment = this._cfg.getAttachmentRenderer?.();
|
|
1601
1632
|
if (customAttachment) {
|
|
1602
1633
|
this._attachmentsEl.replaceChildren();
|
|
1603
|
-
for (const
|
|
1604
|
-
const
|
|
1605
|
-
if (
|
|
1634
|
+
for (const a2 of this._attachments) {
|
|
1635
|
+
const el2 = segmentRenderResultToElement(runWithConfig(this._cfg, () => customAttachment(a2)));
|
|
1636
|
+
if (el2) this._attachmentsEl.appendChild(el2);
|
|
1606
1637
|
}
|
|
1607
1638
|
return;
|
|
1608
1639
|
}
|
|
1609
|
-
this._attachmentsEl.innerHTML = this._attachments.map((
|
|
1610
|
-
const name = escapeHtml(
|
|
1611
|
-
if (
|
|
1612
|
-
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>`;
|
|
1613
1644
|
}
|
|
1614
|
-
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>`;
|
|
1615
1646
|
}).join("");
|
|
1616
1647
|
if (!this._cfg.getHostHandlers().attachmentPreview) return;
|
|
1617
1648
|
this._attachmentsEl.querySelectorAll(".aparte-thumb--image").forEach((tile) => {
|
|
@@ -1676,6 +1707,25 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1676
1707
|
detail
|
|
1677
1708
|
}));
|
|
1678
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
|
+
}
|
|
1679
1729
|
_updateBranchPicker() {
|
|
1680
1730
|
if (!this._branchPickerEl) return;
|
|
1681
1731
|
if (this._siblingCount <= 1 || this._role !== "assistant") {
|
|
@@ -1703,8 +1753,8 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1703
1753
|
}
|
|
1704
1754
|
const prevBtn = this._branchPickerEl.querySelector(".aparte-branch-prev");
|
|
1705
1755
|
const nextBtn = this._branchPickerEl.querySelector(".aparte-branch-next");
|
|
1706
|
-
if (prevBtn) prevBtn.disabled = this._siblingIndex === 0;
|
|
1707
|
-
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;
|
|
1708
1758
|
}
|
|
1709
1759
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1710
1760
|
// Action Bar
|
|
@@ -1721,14 +1771,14 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1721
1771
|
const buttons = [];
|
|
1722
1772
|
if (this._role === "user") {
|
|
1723
1773
|
if (config.user) {
|
|
1724
|
-
for (const
|
|
1774
|
+
for (const a2 of config.user) buttons.push(this._actionButtonHtml(a2, icons, locale));
|
|
1725
1775
|
} else {
|
|
1726
1776
|
if (config.copy) buttons.push(this._actionButtonHtml("copy", icons, locale));
|
|
1727
1777
|
if (config.edit) buttons.push(this._actionButtonHtml("edit", icons, locale));
|
|
1728
1778
|
}
|
|
1729
1779
|
} else if (this._role === "assistant") {
|
|
1730
1780
|
if (config.assistant) {
|
|
1731
|
-
for (const
|
|
1781
|
+
for (const a2 of config.assistant) buttons.push(this._actionButtonHtml(a2, icons, locale));
|
|
1732
1782
|
} else {
|
|
1733
1783
|
if (config.copy) buttons.push(this._actionButtonHtml("copy", icons, locale));
|
|
1734
1784
|
if (config.retry) buttons.push(this._actionButtonHtml("retry", icons, locale));
|
|
@@ -1741,6 +1791,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1741
1791
|
}
|
|
1742
1792
|
this._actionBarEl.innerHTML = buttons.join("");
|
|
1743
1793
|
this._appendCustomActions(icons);
|
|
1794
|
+
this._applyTranscriptBusy();
|
|
1744
1795
|
this._actionBarEl.querySelectorAll(".aparte-action-btn").forEach((btn) => {
|
|
1745
1796
|
btn.addEventListener("click", (e2) => this._handleActionClick(e2));
|
|
1746
1797
|
});
|
|
@@ -1759,20 +1810,21 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1759
1810
|
const barEmpty = !this._actionBarEl || this._actionBarEl.hidden;
|
|
1760
1811
|
const pickerHidden = !this._branchPickerEl || this._branchPickerEl.hidden;
|
|
1761
1812
|
this._footerEl.hidden = barEmpty && pickerHidden;
|
|
1813
|
+
this.querySelector(".aparte-message")?.toggleAttribute("data-branches", !pickerHidden);
|
|
1762
1814
|
}
|
|
1763
1815
|
/** Append the registered custom action buttons for this bubble's role. */
|
|
1764
1816
|
_appendCustomActions(icons) {
|
|
1765
1817
|
if (!this._actionBarEl) return;
|
|
1766
|
-
for (const
|
|
1767
|
-
const roles =
|
|
1818
|
+
for (const a2 of this._cfg.getActions("bubble")) {
|
|
1819
|
+
const roles = a2.bubble?.roles ?? ["user", "assistant"];
|
|
1768
1820
|
if (!roles.includes(this._role)) continue;
|
|
1769
1821
|
const btn = document.createElement("button");
|
|
1770
1822
|
btn.className = "aparte-btn aparte-btn--icon aparte-action-btn aparte-action-custom";
|
|
1771
|
-
btn.dataset["action"] = `custom:${
|
|
1772
|
-
btn.setAttribute("aria-label",
|
|
1773
|
-
btn.setAttribute("title",
|
|
1774
|
-
const fromProvider = icons[
|
|
1775
|
-
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 ?? "";
|
|
1776
1828
|
this._actionBarEl.appendChild(btn);
|
|
1777
1829
|
}
|
|
1778
1830
|
}
|
|
@@ -1843,7 +1895,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1843
1895
|
}
|
|
1844
1896
|
switch (action) {
|
|
1845
1897
|
case "copy": {
|
|
1846
|
-
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");
|
|
1847
1899
|
const icons = this._cfg.getIconProvider();
|
|
1848
1900
|
const locale = this._cfg.getLocale();
|
|
1849
1901
|
navigator.clipboard.writeText(text).then(() => {
|
|
@@ -1977,12 +2029,12 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1977
2029
|
}
|
|
1978
2030
|
}
|
|
1979
2031
|
_resolveTargetId() {
|
|
1980
|
-
let
|
|
1981
|
-
while (
|
|
1982
|
-
const tag =
|
|
1983
|
-
const isHost = tag === "aparte-chat" ||
|
|
1984
|
-
if (isHost &&
|
|
1985
|
-
|
|
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;
|
|
1986
2038
|
}
|
|
1987
2039
|
return void 0;
|
|
1988
2040
|
}
|
|
@@ -2152,6 +2204,19 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2152
2204
|
_layoutTransitionMs = 0;
|
|
2153
2205
|
_repo = new AparteMessageRepository();
|
|
2154
2206
|
_isAutoScrollEnabled = true;
|
|
2207
|
+
/** The last scroll position we saw, so growth can be told from a gesture. */
|
|
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", " "]);
|
|
2155
2220
|
_scrollThreshold = 50;
|
|
2156
2221
|
/** When true, the next _autoScroll() call uses smooth instead of instant, then resets. */
|
|
2157
2222
|
_smoothScrollOnce = false;
|
|
@@ -2162,8 +2227,6 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2162
2227
|
* snapshot stay intact; only the oldest rendered bubbles are dropped from view.
|
|
2163
2228
|
*/
|
|
2164
2229
|
_maxRenderedBubbles = 1e3;
|
|
2165
|
-
/** One-time guard for the deprecated `maxMessages` warning. */
|
|
2166
|
-
_warnedMaxMessagesDeprecation = false;
|
|
2167
2230
|
_resizeObserver = null;
|
|
2168
2231
|
_mutationObserver = null;
|
|
2169
2232
|
_boundResetHandler = null;
|
|
@@ -2174,7 +2237,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2174
2237
|
*/
|
|
2175
2238
|
_frameworkManagedDOM = false;
|
|
2176
2239
|
static get observedAttributes() {
|
|
2177
|
-
return ["scroll-threshold", "max-rendered-bubbles"
|
|
2240
|
+
return ["scroll-threshold", "max-rendered-bubbles"];
|
|
2178
2241
|
}
|
|
2179
2242
|
constructor() {
|
|
2180
2243
|
super();
|
|
@@ -2198,6 +2261,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2198
2261
|
const detail = e2.detail;
|
|
2199
2262
|
if (detail?.config && detail.config !== resolveConfig(this)) return;
|
|
2200
2263
|
this._applyDirection();
|
|
2264
|
+
this._scrollBtn?.setAttribute("aria-label", resolveConfig(this).t("scrollToBottom"));
|
|
2201
2265
|
};
|
|
2202
2266
|
/** Mirror `locale.direction` onto the scroll container. */
|
|
2203
2267
|
_applyDirection() {
|
|
@@ -2225,11 +2289,6 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2225
2289
|
this._maxRenderedBubbles = parseInt(newValue || "1000", 10);
|
|
2226
2290
|
this._pruneRenderedBubbles();
|
|
2227
2291
|
break;
|
|
2228
|
-
case "max-messages":
|
|
2229
|
-
this._warnMaxMessagesDeprecated();
|
|
2230
|
-
this._maxRenderedBubbles = parseInt(newValue || "1000", 10);
|
|
2231
|
-
this._pruneRenderedBubbles();
|
|
2232
|
-
break;
|
|
2233
2292
|
}
|
|
2234
2293
|
}
|
|
2235
2294
|
/**
|
|
@@ -2243,11 +2302,6 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2243
2302
|
this._maxRenderedBubbles = config.maxRenderedBubbles;
|
|
2244
2303
|
this._pruneRenderedBubbles();
|
|
2245
2304
|
}
|
|
2246
|
-
if (config.maxMessages !== void 0) {
|
|
2247
|
-
this._warnMaxMessagesDeprecated();
|
|
2248
|
-
this._maxRenderedBubbles = config.maxMessages;
|
|
2249
|
-
this._pruneRenderedBubbles();
|
|
2250
|
-
}
|
|
2251
2305
|
if (config.layoutTransitionMs !== void 0) {
|
|
2252
2306
|
this._layoutTransitionMs = config.layoutTransitionMs;
|
|
2253
2307
|
}
|
|
@@ -2281,7 +2335,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2281
2335
|
if (!message.segments) {
|
|
2282
2336
|
message.segments = [];
|
|
2283
2337
|
}
|
|
2284
|
-
const index = message.segments.findIndex((
|
|
2338
|
+
const index = message.segments.findIndex((s2) => s2.id === segmentId);
|
|
2285
2339
|
const segment = index === -1 ? void 0 : message.segments[index];
|
|
2286
2340
|
if (segment && "content" in segment) {
|
|
2287
2341
|
message.segments[index] = {
|
|
@@ -2323,6 +2377,24 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2323
2377
|
}
|
|
2324
2378
|
return null;
|
|
2325
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
|
+
}
|
|
2326
2398
|
addSegment(messageIdOrSegment, maybeSegment) {
|
|
2327
2399
|
const messageId = typeof messageIdOrSegment === "string" ? messageIdOrSegment : this._activeMessageId();
|
|
2328
2400
|
const segment = typeof messageIdOrSegment === "string" ? maybeSegment : messageIdOrSegment;
|
|
@@ -2343,15 +2415,15 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2343
2415
|
this._notifyBubble(messageId, "addSegment", stamped);
|
|
2344
2416
|
this._autoScroll();
|
|
2345
2417
|
}
|
|
2346
|
-
updateSegment(
|
|
2418
|
+
updateSegment(a2, b2, c2) {
|
|
2347
2419
|
const clientForm = c2 === void 0 && typeof b2 === "object";
|
|
2348
|
-
const messageId = clientForm ? this._activeMessageId() :
|
|
2349
|
-
const segmentId = clientForm ?
|
|
2420
|
+
const messageId = clientForm ? this._activeMessageId() : a2;
|
|
2421
|
+
const segmentId = clientForm ? a2 : b2;
|
|
2350
2422
|
const updates = clientForm ? b2 : c2;
|
|
2351
2423
|
if (!messageId) return;
|
|
2352
2424
|
const message = this._repo.getMessageById(messageId);
|
|
2353
2425
|
if (!message?.segments) return;
|
|
2354
|
-
const segmentIndex = message.segments.findIndex((
|
|
2426
|
+
const segmentIndex = message.segments.findIndex((s2) => s2.id === segmentId);
|
|
2355
2427
|
if (segmentIndex !== -1) {
|
|
2356
2428
|
const current = message.segments[segmentIndex];
|
|
2357
2429
|
const stamped = stampSegmentOnUpdate(current, updates);
|
|
@@ -2359,14 +2431,14 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2359
2431
|
this._notifyBubble(messageId, "updateSegment", { segmentId, updates: stamped });
|
|
2360
2432
|
}
|
|
2361
2433
|
}
|
|
2362
|
-
removeSegment(
|
|
2434
|
+
removeSegment(a2, b2) {
|
|
2363
2435
|
const clientForm = b2 === void 0;
|
|
2364
|
-
const messageId = clientForm ? this._activeMessageId() :
|
|
2365
|
-
const segmentId = clientForm ?
|
|
2436
|
+
const messageId = clientForm ? this._activeMessageId() : a2;
|
|
2437
|
+
const segmentId = clientForm ? a2 : b2;
|
|
2366
2438
|
if (!messageId || !segmentId) return;
|
|
2367
2439
|
const message = this._repo.getMessageById(messageId);
|
|
2368
2440
|
if (message?.segments) {
|
|
2369
|
-
const idx = message.segments.findIndex((
|
|
2441
|
+
const idx = message.segments.findIndex((s2) => s2.id === segmentId);
|
|
2370
2442
|
if (idx !== -1) {
|
|
2371
2443
|
message.segments.splice(idx, 1);
|
|
2372
2444
|
renumberSegments(message.segments);
|
|
@@ -2412,6 +2484,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2412
2484
|
message.status = "completed";
|
|
2413
2485
|
this._settleSegments(messageId, message);
|
|
2414
2486
|
this._notifyBubble(messageId, "complete", { status: "completed" });
|
|
2487
|
+
this._syncBusy();
|
|
2415
2488
|
this._recalculateSpacer();
|
|
2416
2489
|
}
|
|
2417
2490
|
}
|
|
@@ -2443,6 +2516,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2443
2516
|
if (isTerminalStatus(updates.status)) this._settleSegments(messageId, message);
|
|
2444
2517
|
}
|
|
2445
2518
|
this._notifyBubble(messageId, "update", updates);
|
|
2519
|
+
if (updates.status) this._syncBusy();
|
|
2446
2520
|
this._autoScroll();
|
|
2447
2521
|
}
|
|
2448
2522
|
/**
|
|
@@ -2457,6 +2531,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2457
2531
|
addMessage(message) {
|
|
2458
2532
|
this._repo.addOrUpdateMessage(this._repo.headId, adoptMessageSegments({ ...message }));
|
|
2459
2533
|
this._pruneRenderedBubbles();
|
|
2534
|
+
this._syncBusy();
|
|
2460
2535
|
this._autoScroll();
|
|
2461
2536
|
}
|
|
2462
2537
|
/**
|
|
@@ -2479,13 +2554,14 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2479
2554
|
},
|
|
2480
2555
|
[]
|
|
2481
2556
|
) } : { ...message };
|
|
2557
|
+
if (!options?.historical && isAwaitingReply(message)) stored.isStreaming = true;
|
|
2482
2558
|
this._repo.addOrUpdateMessage(this._repo.headId, stored);
|
|
2483
2559
|
if (!this._frameworkManagedDOM) {
|
|
2484
2560
|
const wrapper = this.querySelector(".aparte-messages-wrapper");
|
|
2485
2561
|
if (wrapper) {
|
|
2486
2562
|
const bubble = document.createElement("aparte-chat-bubble");
|
|
2487
2563
|
bubble.setAttribute("message-id", message.id);
|
|
2488
|
-
bubble.setAttribute("role", message.role);
|
|
2564
|
+
bubble.setAttribute("data-role", message.role);
|
|
2489
2565
|
if (message.timestamp) bubble.setAttribute("timestamp", String(message.timestamp));
|
|
2490
2566
|
if (message.content) bubble.setAttribute("content", message.content);
|
|
2491
2567
|
if (isAwaitingReply(message)) {
|
|
@@ -2500,10 +2576,13 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2500
2576
|
}
|
|
2501
2577
|
}
|
|
2502
2578
|
this._pruneRenderedBubbles();
|
|
2579
|
+
this._syncBusy();
|
|
2503
2580
|
this._recalculateSpacer();
|
|
2504
2581
|
if (message.role === "user") {
|
|
2505
2582
|
this._isAutoScrollEnabled = true;
|
|
2506
|
-
requestAnimationFrame(() =>
|
|
2583
|
+
requestAnimationFrame(() => {
|
|
2584
|
+
if (this._isAutoScrollEnabled) this._smoothScrollToBottom();
|
|
2585
|
+
});
|
|
2507
2586
|
} else {
|
|
2508
2587
|
this._autoScroll();
|
|
2509
2588
|
}
|
|
@@ -2562,7 +2641,9 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2562
2641
|
const meta = this._repo.getMessage(existingId);
|
|
2563
2642
|
if (!meta) return null;
|
|
2564
2643
|
const parentId = meta.message.role === "user" ? existingId : meta.parentId;
|
|
2565
|
-
|
|
2644
|
+
const stored = { ...newMessage };
|
|
2645
|
+
if (isAwaitingReply(newMessage)) stored.isStreaming = true;
|
|
2646
|
+
this._repo.addOrUpdateMessage(parentId, stored);
|
|
2566
2647
|
this._repo.switchToBranch(newMessage.id);
|
|
2567
2648
|
this._reRenderActivePath();
|
|
2568
2649
|
return newMessage.id;
|
|
@@ -2572,6 +2653,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2572
2653
|
* Triggers a full re-render of the active path.
|
|
2573
2654
|
*/
|
|
2574
2655
|
navigateBranch(messageId, direction) {
|
|
2656
|
+
if (this._streamingMessageId() !== null) return;
|
|
2575
2657
|
const siblings = this._repo.getBranches(messageId);
|
|
2576
2658
|
const currentIdx = siblings.indexOf(messageId);
|
|
2577
2659
|
if (currentIdx === -1) return;
|
|
@@ -2676,6 +2758,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2676
2758
|
}
|
|
2677
2759
|
}
|
|
2678
2760
|
this._repo.clear();
|
|
2761
|
+
this._syncBusy();
|
|
2679
2762
|
if (!this._frameworkManagedDOM) {
|
|
2680
2763
|
const wrapper = this.querySelector(".aparte-messages-wrapper");
|
|
2681
2764
|
if (wrapper) {
|
|
@@ -2825,7 +2908,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2825
2908
|
const sibInfo = siblingsInfo[i2];
|
|
2826
2909
|
const bubble = document.createElement("aparte-chat-bubble");
|
|
2827
2910
|
bubble.setAttribute("message-id", message.id);
|
|
2828
|
-
bubble.setAttribute("role", message.role);
|
|
2911
|
+
bubble.setAttribute("data-role", message.role);
|
|
2829
2912
|
if (message.timestamp) bubble.setAttribute("timestamp", String(message.timestamp));
|
|
2830
2913
|
if (isAwaitingReply(message)) {
|
|
2831
2914
|
bubble.setAttribute("streaming", "");
|
|
@@ -2834,6 +2917,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2834
2917
|
populateBubbleFromMessage(bubble, message, sibInfo);
|
|
2835
2918
|
}
|
|
2836
2919
|
this._dispatchPathChanged(activeMessages, siblingsInfo);
|
|
2920
|
+
this._syncBusy();
|
|
2837
2921
|
this._recalculateSpacer();
|
|
2838
2922
|
}
|
|
2839
2923
|
_dispatchPathChanged(messages, siblings) {
|
|
@@ -2848,9 +2932,13 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2848
2932
|
if (this._isAutoScrollEnabled) {
|
|
2849
2933
|
if (this._smoothScrollOnce) {
|
|
2850
2934
|
this._smoothScrollOnce = false;
|
|
2851
|
-
requestAnimationFrame(() =>
|
|
2935
|
+
requestAnimationFrame(() => {
|
|
2936
|
+
if (this._isAutoScrollEnabled) this._smoothScrollToBottom();
|
|
2937
|
+
});
|
|
2852
2938
|
} else {
|
|
2853
|
-
requestAnimationFrame(() =>
|
|
2939
|
+
requestAnimationFrame(() => {
|
|
2940
|
+
if (this._isAutoScrollEnabled) this._scrollToBottom();
|
|
2941
|
+
});
|
|
2854
2942
|
}
|
|
2855
2943
|
}
|
|
2856
2944
|
this._pruneRenderedBubbles();
|
|
@@ -2895,7 +2983,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2895
2983
|
this._scrollBtn = document.createElement("button");
|
|
2896
2984
|
this._scrollBtn.className = "aparte-btn aparte-btn--surface aparte-btn--circle aparte-btn--lg aparte-scroll-btn aparte-scroll-btn--hidden";
|
|
2897
2985
|
this._scrollBtn.setAttribute("type", "button");
|
|
2898
|
-
this._scrollBtn.setAttribute("aria-label", "
|
|
2986
|
+
this._scrollBtn.setAttribute("aria-label", resolveConfig(this).t("scrollToBottom"));
|
|
2899
2987
|
const scrollIcon = resolveConfig(this).getIcon("scrollDown");
|
|
2900
2988
|
this._scrollBtn.innerHTML = scrollIcon;
|
|
2901
2989
|
this.appendChild(this._scrollBtn);
|
|
@@ -2928,7 +3016,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2928
3016
|
const scrollBtn = document.createElement("button");
|
|
2929
3017
|
scrollBtn.className = "aparte-btn aparte-btn--surface aparte-btn--circle aparte-btn--lg aparte-scroll-btn aparte-scroll-btn--hidden";
|
|
2930
3018
|
scrollBtn.setAttribute("type", "button");
|
|
2931
|
-
scrollBtn.setAttribute("aria-label", "
|
|
3019
|
+
scrollBtn.setAttribute("aria-label", resolveConfig(this).t("scrollToBottom"));
|
|
2932
3020
|
const scrollIcon = resolveConfig(this).getIcon("scrollDown");
|
|
2933
3021
|
scrollBtn.innerHTML = scrollIcon;
|
|
2934
3022
|
this.appendChild(scrollBtn);
|
|
@@ -2973,6 +3061,27 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2973
3061
|
this._smoothScrollToBottom();
|
|
2974
3062
|
this._updateScrollButton();
|
|
2975
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
|
+
};
|
|
2976
3085
|
_onBranchNavigate = (e2) => {
|
|
2977
3086
|
const evt = e2;
|
|
2978
3087
|
evt.stopPropagation();
|
|
@@ -2980,6 +3089,9 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2980
3089
|
};
|
|
2981
3090
|
_setupEventListeners() {
|
|
2982
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
|
+
}
|
|
2983
3095
|
this._scrollBtn?.addEventListener("click", this._onScrollBtnClick);
|
|
2984
3096
|
this.addEventListener("aparte-branch-navigate", this._onBranchNavigate);
|
|
2985
3097
|
}
|
|
@@ -2989,6 +3101,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2989
3101
|
this._scrollToBottom();
|
|
2990
3102
|
}
|
|
2991
3103
|
this._recalculateSpacer();
|
|
3104
|
+
this._updateScrollButton();
|
|
2992
3105
|
});
|
|
2993
3106
|
const wrapper = this.querySelector(".aparte-messages-wrapper");
|
|
2994
3107
|
if (this._container) {
|
|
@@ -3001,7 +3114,9 @@ class AparteChatViewport extends HTMLElement {
|
|
|
3001
3114
|
this._mutationObserver = new MutationObserver(() => {
|
|
3002
3115
|
if (this._frameworkManagedDOM) this._keepScrollButtonLast();
|
|
3003
3116
|
if (this._isAutoScrollEnabled) {
|
|
3004
|
-
requestAnimationFrame(() =>
|
|
3117
|
+
requestAnimationFrame(() => {
|
|
3118
|
+
if (this._isAutoScrollEnabled) this._scrollToBottom();
|
|
3119
|
+
});
|
|
3005
3120
|
}
|
|
3006
3121
|
this._scheduleSpacerUpdate();
|
|
3007
3122
|
});
|
|
@@ -3021,11 +3136,25 @@ class AparteChatViewport extends HTMLElement {
|
|
|
3021
3136
|
}
|
|
3022
3137
|
_handleScroll() {
|
|
3023
3138
|
if (!this._container) return;
|
|
3024
|
-
|
|
3139
|
+
const top = this._container.scrollTop;
|
|
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;
|
|
3146
|
+
this._lastScrollTop = top;
|
|
3147
|
+
this._lastScrollHeight = height;
|
|
3148
|
+
if (this._isAtBottom()) {
|
|
3149
|
+
this._isAutoScrollEnabled = true;
|
|
3150
|
+
} else if (readerWentUp) {
|
|
3151
|
+
this._isAutoScrollEnabled = false;
|
|
3152
|
+
}
|
|
3025
3153
|
this._updateScrollButton();
|
|
3026
3154
|
}
|
|
3027
3155
|
_scrollToBottom() {
|
|
3028
3156
|
if (!this._container) return;
|
|
3157
|
+
this._ownScrollAt = performance.now();
|
|
3029
3158
|
this._container.scrollTop = this._container.scrollHeight;
|
|
3030
3159
|
this._settleAtBottom(4);
|
|
3031
3160
|
}
|
|
@@ -3059,12 +3188,14 @@ class AparteChatViewport extends HTMLElement {
|
|
|
3059
3188
|
if (!this._container || !this._isAutoScrollEnabled) return;
|
|
3060
3189
|
const max = this._container.scrollHeight - this._container.clientHeight;
|
|
3061
3190
|
if (max - this._container.scrollTop <= 1) return;
|
|
3191
|
+
this._ownScrollAt = performance.now();
|
|
3062
3192
|
this._container.scrollTop = max;
|
|
3063
3193
|
this._settleAtBottom(framesLeft - 1);
|
|
3064
3194
|
});
|
|
3065
3195
|
}
|
|
3066
3196
|
_smoothScrollToBottom() {
|
|
3067
3197
|
if (!this._container) return;
|
|
3198
|
+
this._ownScrollAt = performance.now();
|
|
3068
3199
|
if (typeof this._container.scrollTo === "function" && !this._prefersReducedMotion()) {
|
|
3069
3200
|
this._container.scrollTo({ top: this._container.scrollHeight, behavior: "smooth" });
|
|
3070
3201
|
} else {
|
|
@@ -3083,7 +3214,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
3083
3214
|
* fold"). Mirroring the flag made the button lie whenever the two diverged —
|
|
3084
3215
|
* `navigateBranch` deliberately disarms auto-follow, so swapping a branch while
|
|
3085
3216
|
* already at the bottom of a scrollable transcript left the button offering to
|
|
3086
|
-
* scroll nowhere (reported from
|
|
3217
|
+
* scroll nowhere (reported from a React consumer). Re-derived on scroll, on the
|
|
3087
3218
|
* post-mutation frame and after a path swap, so it converges to the truth
|
|
3088
3219
|
* whatever a framework's render timing does in between.
|
|
3089
3220
|
*/
|
|
@@ -3110,7 +3241,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
3110
3241
|
this._setSpacerHeight(0);
|
|
3111
3242
|
return;
|
|
3112
3243
|
}
|
|
3113
|
-
const lastUserBubble = [...allBubbles].reverse().find((b2) => b2.getAttribute("role") === "user");
|
|
3244
|
+
const lastUserBubble = [...allBubbles].reverse().find((b2) => b2.getAttribute("data-role") === "user");
|
|
3114
3245
|
if (!lastUserBubble) {
|
|
3115
3246
|
this._setSpacerHeight(0);
|
|
3116
3247
|
return;
|
|
@@ -3169,15 +3300,11 @@ class AparteChatViewport extends HTMLElement {
|
|
|
3169
3300
|
bubbles[i2]?.remove();
|
|
3170
3301
|
}
|
|
3171
3302
|
}
|
|
3172
|
-
_warnMaxMessagesDeprecated() {
|
|
3173
|
-
if (this._warnedMaxMessagesDeprecation) return;
|
|
3174
|
-
this._warnedMaxMessagesDeprecation = true;
|
|
3175
|
-
console.warn(
|
|
3176
|
-
"[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."
|
|
3177
|
-
);
|
|
3178
|
-
}
|
|
3179
3303
|
_cleanup() {
|
|
3180
3304
|
this._container?.removeEventListener("scroll", this._handleScroll);
|
|
3305
|
+
for (const type of ["wheel", "touchmove", "pointerdown", "keydown"]) {
|
|
3306
|
+
this._container?.removeEventListener(type, this._noteReaderInput);
|
|
3307
|
+
}
|
|
3181
3308
|
this._scrollBtn?.removeEventListener("click", this._onScrollBtnClick);
|
|
3182
3309
|
this.removeEventListener("aparte-branch-navigate", this._onBranchNavigate);
|
|
3183
3310
|
this._resizeObserver?.disconnect();
|
|
@@ -3282,6 +3409,7 @@ class AparteComposer extends HTMLElement {
|
|
|
3282
3409
|
window.addEventListener("aparte-message-error", this._onMessageDone);
|
|
3283
3410
|
window.addEventListener("aparte-message-aborted", this._onMessageDone);
|
|
3284
3411
|
window.addEventListener("aparte-config-change", this._onConfigChangeEvent);
|
|
3412
|
+
queueMicrotask(() => this._warnIfUnattached());
|
|
3285
3413
|
this._evaluateModelGate();
|
|
3286
3414
|
this._applyDirection();
|
|
3287
3415
|
}
|
|
@@ -3297,6 +3425,11 @@ class AparteComposer extends HTMLElement {
|
|
|
3297
3425
|
if (name === "disabled") {
|
|
3298
3426
|
this._emit("disabled-change", { disabled: value !== null });
|
|
3299
3427
|
}
|
|
3428
|
+
if (name === "placeholder") {
|
|
3429
|
+
for (const input of this.querySelectorAll("aparte-composer-input")) {
|
|
3430
|
+
input.syncPlaceholder?.();
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3300
3433
|
}
|
|
3301
3434
|
// ── Public API ─────────────────────────────────────────────────────────
|
|
3302
3435
|
get value() {
|
|
@@ -3414,10 +3547,13 @@ class AparteComposer extends HTMLElement {
|
|
|
3414
3547
|
*/
|
|
3415
3548
|
showPanel(panel, options) {
|
|
3416
3549
|
this._evictPanel();
|
|
3550
|
+
const host = this.querySelector("[data-aparte-panel-host]");
|
|
3417
3551
|
const inputEl = this.querySelector("aparte-composer-input");
|
|
3418
3552
|
this.setAttribute("data-panel-active", "");
|
|
3419
3553
|
panel.dataset["apartePanel"] = "true";
|
|
3420
|
-
if (
|
|
3554
|
+
if (host) {
|
|
3555
|
+
host.appendChild(panel);
|
|
3556
|
+
} else if (inputEl) {
|
|
3421
3557
|
inputEl.insertAdjacentElement("afterend", panel);
|
|
3422
3558
|
} else {
|
|
3423
3559
|
this.appendChild(panel);
|
|
@@ -3627,15 +3763,26 @@ class AparteComposer extends HTMLElement {
|
|
|
3627
3763
|
* for the reason written there — Angular's wrapper root IS `<aparte-chat>`, the
|
|
3628
3764
|
* other three render a `[data-aparte-chat]` div.
|
|
3629
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
|
+
}
|
|
3630
3777
|
_ownTargetId() {
|
|
3631
3778
|
const attr = this.targetId;
|
|
3632
3779
|
if (attr) return attr;
|
|
3633
|
-
let
|
|
3634
|
-
while (
|
|
3635
|
-
const tag =
|
|
3636
|
-
const isHost = tag === "aparte-chat" ||
|
|
3637
|
-
if (isHost &&
|
|
3638
|
-
|
|
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;
|
|
3639
3786
|
}
|
|
3640
3787
|
return void 0;
|
|
3641
3788
|
}
|
|
@@ -3723,6 +3870,14 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3723
3870
|
this._adjustHeight();
|
|
3724
3871
|
this._getRoot()?.setValue("");
|
|
3725
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
|
+
}
|
|
3726
3881
|
/** Focus the inner contenteditable rather than the host element. */
|
|
3727
3882
|
focus() {
|
|
3728
3883
|
this._editor?.focus();
|
|
@@ -3760,7 +3915,7 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3760
3915
|
role="textbox"
|
|
3761
3916
|
aria-multiline="true"
|
|
3762
3917
|
aria-label="${placeholder}"
|
|
3763
|
-
tabindex="0"
|
|
3918
|
+
tabindex="${escapeAttr(String(disabled ? -1 : 0))}"
|
|
3764
3919
|
aria-disabled="${disabled}"
|
|
3765
3920
|
data-placeholder="${placeholder}"
|
|
3766
3921
|
></div>`;
|
|
@@ -3785,11 +3940,6 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3785
3940
|
else this.setValue(value);
|
|
3786
3941
|
})
|
|
3787
3942
|
);
|
|
3788
|
-
this._unsubscribes.push(
|
|
3789
|
-
root._on("streaming-change", ({ streaming }) => {
|
|
3790
|
-
this._updateDisabled(streaming || root.disabled);
|
|
3791
|
-
})
|
|
3792
|
-
);
|
|
3793
3943
|
}
|
|
3794
3944
|
_handleInput() {
|
|
3795
3945
|
this._adjustHeight();
|
|
@@ -3809,6 +3959,7 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3809
3959
|
e2.preventDefault();
|
|
3810
3960
|
const root = this._getRoot();
|
|
3811
3961
|
if (root) {
|
|
3962
|
+
if (root.streaming) return;
|
|
3812
3963
|
root.submit();
|
|
3813
3964
|
} else {
|
|
3814
3965
|
this.dispatchEvent(new CustomEvent("aparte-composer-submit", { bubbles: true }));
|
|
@@ -3866,9 +4017,9 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3866
4017
|
}
|
|
3867
4018
|
_updatePlaceholder() {
|
|
3868
4019
|
if (this._editor) {
|
|
3869
|
-
const
|
|
3870
|
-
this._editor.setAttribute("data-placeholder",
|
|
3871
|
-
this._editor.setAttribute("aria-label",
|
|
4020
|
+
const p2 = this._getPlaceholder();
|
|
4021
|
+
this._editor.setAttribute("data-placeholder", p2);
|
|
4022
|
+
this._editor.setAttribute("aria-label", p2);
|
|
3872
4023
|
}
|
|
3873
4024
|
}
|
|
3874
4025
|
_updatePlaceholderVisibility() {
|
|
@@ -3877,6 +4028,8 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3877
4028
|
if (!this._editor) return;
|
|
3878
4029
|
this._editor.setAttribute("contenteditable", String(!disabled));
|
|
3879
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();
|
|
3880
4033
|
}
|
|
3881
4034
|
/** Escape a value before it lands in a double-quoted HTML attribute. */
|
|
3882
4035
|
}
|
|
@@ -4231,9 +4384,8 @@ class AparteComposerAddAttachment extends HTMLElement {
|
|
|
4231
4384
|
* Re-read the label and the icon on the button that already exists.
|
|
4232
4385
|
*
|
|
4233
4386
|
* Deliberately not a re-render: `_render()` returns early once the button is
|
|
4234
|
-
* there, and
|
|
4235
|
-
*
|
|
4236
|
-
* 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.
|
|
4237
4389
|
* The native file input and the drag listeners live outside this element
|
|
4238
4390
|
* (on `document.body` and on the composer root), so they are untouched either way.
|
|
4239
4391
|
*/
|
|
@@ -4270,7 +4422,7 @@ class AparteComposerAddAttachment extends HTMLElement {
|
|
|
4270
4422
|
);
|
|
4271
4423
|
this._unsubscribes.push(
|
|
4272
4424
|
root._on("streaming-change", ({ streaming }) => {
|
|
4273
|
-
if (this._button) this._button.disabled =
|
|
4425
|
+
if (this._button) this._button.disabled = root.disabled;
|
|
4274
4426
|
})
|
|
4275
4427
|
);
|
|
4276
4428
|
}
|
|
@@ -4579,10 +4731,10 @@ class AparteConversationList extends HTMLElement {
|
|
|
4579
4731
|
/** Update active class without full re-render (perf optimisation). */
|
|
4580
4732
|
_updateActiveState() {
|
|
4581
4733
|
const items = this.querySelectorAll("[data-conv-id]");
|
|
4582
|
-
items.forEach((
|
|
4583
|
-
const isActive =
|
|
4584
|
-
|
|
4585
|
-
|
|
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");
|
|
4586
4738
|
});
|
|
4587
4739
|
}
|
|
4588
4740
|
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
@@ -4591,6 +4743,279 @@ class AparteConversationList extends HTMLElement {
|
|
|
4591
4743
|
}
|
|
4592
4744
|
}
|
|
4593
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
|
+
}
|
|
4594
5019
|
function registerAllComponents() {
|
|
4595
5020
|
const _chat = customElements.get("aparte-chat");
|
|
4596
5021
|
const _viewport = customElements.get("aparte-chat-viewport");
|
|
@@ -4607,16 +5032,15 @@ export {
|
|
|
4607
5032
|
c as APARTE_DEFAULT_HOST_HANDLERS,
|
|
4608
5033
|
d as APARTE_DEFAULT_ICON_FALLBACKS,
|
|
4609
5034
|
e as APARTE_DEFAULT_LOCALE,
|
|
4610
|
-
f as
|
|
4611
|
-
g as
|
|
4612
|
-
h as
|
|
4613
|
-
i as AparteBackendTransport,
|
|
5035
|
+
f as APARTE_DEFAULT_UI_EVENTS,
|
|
5036
|
+
g as APARTE_HOST_ATTR,
|
|
5037
|
+
h as AparteBackendTransport,
|
|
4614
5038
|
AparteChat,
|
|
4615
5039
|
AparteChatBubble,
|
|
4616
|
-
|
|
5040
|
+
i as AparteChatHost,
|
|
4617
5041
|
AparteChatStatus,
|
|
4618
5042
|
AparteChatViewport,
|
|
4619
|
-
|
|
5043
|
+
j as AparteClient,
|
|
4620
5044
|
AparteComposer,
|
|
4621
5045
|
AparteComposerAction,
|
|
4622
5046
|
AparteComposerAddAttachment,
|
|
@@ -4625,62 +5049,64 @@ export {
|
|
|
4625
5049
|
AparteComposerInput,
|
|
4626
5050
|
AparteComposerSend,
|
|
4627
5051
|
AparteComposerToolbar,
|
|
4628
|
-
|
|
4629
|
-
|
|
5052
|
+
k as AparteConfig,
|
|
5053
|
+
AparteContext,
|
|
5054
|
+
l as AparteConversationController,
|
|
4630
5055
|
AparteConversationList,
|
|
4631
|
-
|
|
4632
|
-
|
|
5056
|
+
m as AparteConversationManager,
|
|
5057
|
+
n as AparteDirectTransport,
|
|
4633
5058
|
AparteElicitation,
|
|
4634
5059
|
AparteElicitationAbortError,
|
|
4635
|
-
|
|
4636
|
-
|
|
5060
|
+
p as AparteError,
|
|
5061
|
+
q as AparteErrorCode,
|
|
4637
5062
|
AparteIcon,
|
|
4638
5063
|
AparteMessageRepository,
|
|
4639
5064
|
AparteOptgroup,
|
|
4640
5065
|
AparteOption,
|
|
4641
5066
|
AparteProgressSpinner,
|
|
4642
5067
|
AparteSelect,
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
5068
|
+
s as AparteStreamParser,
|
|
5069
|
+
AparteSuggestions,
|
|
5070
|
+
t as aparteGlobalConfig,
|
|
5071
|
+
u as applyElementProps,
|
|
5072
|
+
v as attachConfig,
|
|
4647
5073
|
buildApprovalPanel,
|
|
4648
5074
|
buildElicitationPanel,
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
5075
|
+
y as collectRendererStyles,
|
|
5076
|
+
z as contentToText,
|
|
5077
|
+
B as contextConfig,
|
|
5078
|
+
C as createAparteChatHandler,
|
|
5079
|
+
D as createStreamAdapter,
|
|
4654
5080
|
cssEscape,
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
5081
|
+
F as declineDefaultRenderers,
|
|
5082
|
+
G as defaultSanitizer,
|
|
5083
|
+
deriveArtifactKind,
|
|
5084
|
+
H as detachConfig,
|
|
4659
5085
|
escapeAttr,
|
|
4660
5086
|
escapeHtml,
|
|
4661
|
-
|
|
4662
|
-
|
|
5087
|
+
K as filesToAttachments,
|
|
5088
|
+
L as getAllRenderers,
|
|
4663
5089
|
getSegmentRenderer,
|
|
4664
5090
|
installDefaultRenderersOnce,
|
|
4665
5091
|
isAwaitingReply,
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
5092
|
+
P as isFormatAdapter,
|
|
5093
|
+
Q as isSafeUrl,
|
|
5094
|
+
R as isSegmentSettled,
|
|
5095
|
+
S as parseAparteEventStream,
|
|
5096
|
+
T as parseMarkdownToSegments,
|
|
4671
5097
|
populateBubbleFromMessage,
|
|
4672
|
-
|
|
5098
|
+
V as readableToAsyncIterable,
|
|
4673
5099
|
registerAllComponents,
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
5100
|
+
W as registerDefaultRenderers,
|
|
5101
|
+
X as registerSegmentRenderer,
|
|
5102
|
+
Y as requestUserInput,
|
|
4677
5103
|
resolveConfig,
|
|
4678
5104
|
revokeAttachmentUrls,
|
|
4679
5105
|
runWithConfig,
|
|
4680
|
-
|
|
4681
|
-
|
|
5106
|
+
a0 as segmentDuration,
|
|
5107
|
+
a1 as segmentTiming,
|
|
4682
5108
|
subscribeConfigChange,
|
|
4683
|
-
|
|
5109
|
+
a3 as unregisterSegmentRenderer,
|
|
4684
5110
|
uuid
|
|
4685
5111
|
};
|
|
4686
5112
|
//# sourceMappingURL=index.js.map
|