@aparte/core 0.10.0 → 0.12.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/dist/client/aparte-client.d.ts +54 -33
- package/dist/client/aparte-client.d.ts.map +1 -1
- package/dist/client/stream-adapter.d.ts +6 -1
- package/dist/client/stream-adapter.d.ts.map +1 -1
- package/dist/components/bubble/aparte-chat-bubble.d.ts +25 -4
- package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
- package/dist/components/chat/aparte-chat.d.ts +8 -4
- package/dist/components/chat/aparte-chat.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-action.d.ts +15 -6
- package/dist/components/composer/aparte-composer-action.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-add-attachment.d.ts +5 -4
- package/dist/components/composer/aparte-composer-add-attachment.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-attachments.d.ts +5 -3
- package/dist/components/composer/aparte-composer-attachments.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-cancel.d.ts +2 -2
- package/dist/components/composer/aparte-composer-input.d.ts +8 -5
- package/dist/components/composer/aparte-composer-input.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-send.d.ts +2 -2
- package/dist/components/composer/aparte-composer-toolbar.d.ts +3 -3
- package/dist/components/composer/aparte-composer.d.ts +81 -10
- package/dist/components/composer/aparte-composer.d.ts.map +1 -1
- package/dist/components/conversation-list/aparte-conversation-list.d.ts +19 -12
- package/dist/components/conversation-list/aparte-conversation-list.d.ts.map +1 -1
- package/dist/components/elicitation/aparte-elicitation.d.ts +14 -2
- package/dist/components/elicitation/aparte-elicitation.d.ts.map +1 -1
- package/dist/components/status/aparte-chat-status.d.ts +9 -0
- package/dist/components/status/aparte-chat-status.d.ts.map +1 -1
- package/dist/components/viewport/aparte-chat-viewport.d.ts +15 -4
- package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
- package/dist/config/aparte-config.d.ts +72 -15
- package/dist/config/aparte-config.d.ts.map +1 -1
- package/dist/config/locale.d.ts +43 -0
- package/dist/config/locale.d.ts.map +1 -1
- package/dist/custom-elements.json +2824 -1801
- package/dist/elicitation/approval-panel.d.ts +42 -0
- package/dist/elicitation/approval-panel.d.ts.map +1 -0
- package/dist/elicitation/index.d.ts +4 -1
- package/dist/elicitation/index.d.ts.map +1 -1
- package/dist/elicitation/panel.d.ts +1 -1
- package/dist/elicitation/panel.d.ts.map +1 -1
- package/dist/elicitation/types.d.ts +116 -9
- package/dist/elicitation/types.d.ts.map +1 -1
- package/dist/generated/element-attributes.d.ts +177 -0
- package/dist/generated/element-attributes.d.ts.map +1 -0
- package/dist/index.css +621 -92
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +368 -255
- package/dist/index.js.map +1 -1
- package/dist/index.node.d.ts +4 -3
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +46 -44
- package/dist/index.node.js.map +1 -1
- package/dist/interop/element-props.d.ts +40 -2
- package/dist/interop/element-props.d.ts.map +1 -1
- package/dist/{index-BnKBciDG.js → is-awaiting-reply-CxWRISBC.js} +949 -957
- package/dist/is-awaiting-reply-CxWRISBC.js.map +1 -0
- package/dist/primitives/select/aparte-optgroup.d.ts +3 -0
- package/dist/primitives/select/aparte-optgroup.d.ts.map +1 -1
- package/dist/primitives/select/aparte-option.d.ts +1 -0
- package/dist/primitives/select/aparte-option.d.ts.map +1 -1
- package/dist/primitives/select/aparte-select.d.ts +22 -18
- 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/artifact/card.d.ts.map +1 -1
- package/dist/renderers/segments/pipeline-waiting.d.ts.map +1 -1
- package/dist/renderers/segments/tool-call.d.ts.map +1 -1
- package/dist/types/element-attributes.d.ts +42 -0
- package/dist/types/element-attributes.d.ts.map +1 -0
- package/dist/types/event-map.d.ts +1 -2
- package/dist/types/event-map.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/segments.d.ts +1 -8
- package/dist/types/segments.d.ts.map +1 -1
- package/dist/types/tools.d.ts +26 -23
- package/dist/types/tools.d.ts.map +1 -1
- package/dist/utils/chat-boundary.d.ts +22 -0
- package/dist/utils/chat-boundary.d.ts.map +1 -0
- package/dist/utils/segments.d.ts +8 -0
- package/dist/utils/segments.d.ts.map +1 -1
- package/package.json +3 -2
- package/dist/index-BnKBciDG.js.map +0 -1
- package/dist/renderers/segments/progress.d.ts +0 -3
- package/dist/renderers/segments/progress.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { A, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o,
|
|
1
|
+
import { a4 as subscribeConfigChange, $ as resolveConfig, a1 as runWithConfig, p as AparteElicitationAbortError, x as buildApprovalPanel, y as buildElicitationPanel, K as escapeAttr, F as cssEscape, a7 as mergeSegmentUpdate, L as escapeHtml, O as getSegmentRenderer, P as installDefaultRenderersOnce, s as AparteMessageRepository, a8 as stampSegmentActivity, a9 as stampSegmentOnInsert, aa as stampSegmentOnUpdate, ab as renumberSegments, ac as openSegmentIds, ad as isTerminalStatus, ae as adoptMessageSegments, Q as isAwaitingReply, W as populateBubbleFromMessage, a6 as uuid, a0 as revokeAttachmentUrls } from "./is-awaiting-reply-CxWRISBC.js";
|
|
2
|
+
import { A, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, q, r, t, u, v, w, z, B, C, D, E, G, H, I, J, M, N, R, S, T, U, V, X, Y, Z, _, a2, a3, a5 } from "./is-awaiting-reply-CxWRISBC.js";
|
|
3
3
|
class AparteOption extends HTMLElement {
|
|
4
4
|
static get observedAttributes() {
|
|
5
5
|
return ["value", "disabled", "selected", "data-status"];
|
|
@@ -631,6 +631,221 @@ class AparteProgressSpinner extends HTMLElement {
|
|
|
631
631
|
if (!customElements.get("aparte-progress-spinner")) {
|
|
632
632
|
customElements.define("aparte-progress-spinner", AparteProgressSpinner);
|
|
633
633
|
}
|
|
634
|
+
class AparteElicitation extends HTMLElement {
|
|
635
|
+
_pending = null;
|
|
636
|
+
/**
|
|
637
|
+
* A turn ended — cancel the open question only if it was OUR turn.
|
|
638
|
+
*
|
|
639
|
+
* These two listeners sit on `window` and had no instance filter at all, so a
|
|
640
|
+
* Stop (or an error) in one chat cancelled the question a DIFFERENT chat was
|
|
641
|
+
* waiting on — and that chat's model was told the user had refused a question
|
|
642
|
+
* the user was still looking at. Same defect as the `compact()` handler that
|
|
643
|
+
* emptied both chats, which its four sibling handlers in `AparteClient` already
|
|
644
|
+
* guarded against.
|
|
645
|
+
*
|
|
646
|
+
* The leniency rule is the composer's, deliberately: an event with no
|
|
647
|
+
* `targetId` is for everyone (a single-chat app never sets one), and a chat we
|
|
648
|
+
* cannot identify accepts everything rather than becoming deaf. Only a
|
|
649
|
+
* MISMATCH is ignored.
|
|
650
|
+
*/
|
|
651
|
+
_onTurnEnd = (e2) => {
|
|
652
|
+
const evtTargetId = e2.detail?.targetId;
|
|
653
|
+
const own = this._pendingTargetId();
|
|
654
|
+
if (evtTargetId && own && evtTargetId !== own) return;
|
|
655
|
+
this._cancelPending();
|
|
656
|
+
};
|
|
657
|
+
_unsubscribeConfig = null;
|
|
658
|
+
connectedCallback() {
|
|
659
|
+
this.style.display = "none";
|
|
660
|
+
this._unsubscribeConfig = subscribeConfigChange(this, () => this._relabelPending());
|
|
661
|
+
resolveConfig(this).setElicitationPresenter(this._present, this);
|
|
662
|
+
window.addEventListener("aparte-message-aborted", this._onTurnEnd);
|
|
663
|
+
window.addEventListener("aparte-message-error", this._onTurnEnd);
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* The boundary above us appeared, changed, or went away — move the
|
|
667
|
+
* registration with it.
|
|
668
|
+
*
|
|
669
|
+
* `connectedCallback` alone is not enough and cannot be: registering is a
|
|
670
|
+
* WRITE, and under all four wrappers it happens before `attachConfig` runs, so
|
|
671
|
+
* it lands on the global singleton. `requestUserInput()` then resolves the
|
|
672
|
+
* instance config, finds nothing, and rejects the request — the model
|
|
673
|
+
* hears the user refuse a question the user never saw.
|
|
674
|
+
*
|
|
675
|
+
* See {@link AparteConfigAware}.
|
|
676
|
+
*/
|
|
677
|
+
aparteConfigChanged(next, previous) {
|
|
678
|
+
previous.removeElicitationPresenter(this._present);
|
|
679
|
+
next.setElicitationPresenter(this._present, this);
|
|
680
|
+
}
|
|
681
|
+
disconnectedCallback() {
|
|
682
|
+
resolveConfig(this).removeElicitationPresenter(this._present);
|
|
683
|
+
window.removeEventListener("aparte-message-aborted", this._onTurnEnd);
|
|
684
|
+
window.removeEventListener("aparte-message-error", this._onTurnEnd);
|
|
685
|
+
this._unsubscribeConfig?.();
|
|
686
|
+
this._unsubscribeConfig = null;
|
|
687
|
+
this._cancelPending();
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Re-apply the open question's strings, in place.
|
|
691
|
+
*
|
|
692
|
+
* Two owners, and both have to move or the panel goes bilingual: the panel's own
|
|
693
|
+
* defaults (`relabel`), and the Skip button, which this file builds and this file
|
|
694
|
+
* therefore has to re-text. The composer's one button is a third, and it already
|
|
695
|
+
* follows — `aparte-composer-send` remembers the panel state it was given.
|
|
696
|
+
*/
|
|
697
|
+
_relabelPending() {
|
|
698
|
+
if (!this._pending) return;
|
|
699
|
+
const cfg = resolveConfig(this);
|
|
700
|
+
runWithConfig(cfg, () => this._pending.relabel());
|
|
701
|
+
}
|
|
702
|
+
_present = (request) => {
|
|
703
|
+
const composer = this._getComposer();
|
|
704
|
+
if (!composer) return Promise.reject(new AparteElicitationAbortError("no-presenter"));
|
|
705
|
+
return new Promise((resolve, reject) => {
|
|
706
|
+
let done = false;
|
|
707
|
+
const slot = {};
|
|
708
|
+
const close = () => {
|
|
709
|
+
if (done) return false;
|
|
710
|
+
done = true;
|
|
711
|
+
this._pending = null;
|
|
712
|
+
composer.hidePanel(slot.token);
|
|
713
|
+
return true;
|
|
714
|
+
};
|
|
715
|
+
const settle = (result) => {
|
|
716
|
+
if (close()) resolve(result);
|
|
717
|
+
};
|
|
718
|
+
const fail = (reason = "aborted") => {
|
|
719
|
+
if (close()) reject(new AparteElicitationAbortError(reason));
|
|
720
|
+
};
|
|
721
|
+
if (request.signal) {
|
|
722
|
+
if (request.signal.aborted) {
|
|
723
|
+
fail();
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
request.signal.addEventListener("abort", () => fail(), { once: true });
|
|
727
|
+
}
|
|
728
|
+
const cfg = resolveConfig(this);
|
|
729
|
+
if (request.kind === "approval") {
|
|
730
|
+
const panel2 = runWithConfig(cfg, () => buildApprovalPanel(request.message, request.options ?? [], () => {
|
|
731
|
+
composer.setPanelSubmitEnabled(panel2.isComplete(), "submit");
|
|
732
|
+
}));
|
|
733
|
+
panel2.onSettle((answer) => settle({ action: "accept", content: answer }));
|
|
734
|
+
this._pending = { abort: () => fail(), composer, relabel: () => panel2.relabel() };
|
|
735
|
+
slot.token = composer.showPanel(panel2.el, {
|
|
736
|
+
submitEnabled: panel2.isComplete(),
|
|
737
|
+
mode: "submit",
|
|
738
|
+
// The composer's button carries the INSTRUCTION, which is written
|
|
739
|
+
// text — exactly the act that button already means. The options
|
|
740
|
+
// never route through it: a decision is its own click.
|
|
741
|
+
onSubmit: () => {
|
|
742
|
+
if (panel2.isComplete()) settle({ action: "accept", content: panel2.getContent() });
|
|
743
|
+
},
|
|
744
|
+
onEvict: () => fail()
|
|
745
|
+
});
|
|
746
|
+
panel2.focus();
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
const schema = request.schema ?? { type: "string" };
|
|
750
|
+
const panel = runWithConfig(cfg, () => buildElicitationPanel(request.message, schema, () => {
|
|
751
|
+
composer.setPanelSubmitEnabled(panel.canProceed(), panel.mode());
|
|
752
|
+
}));
|
|
753
|
+
const skip = document.createElement("button");
|
|
754
|
+
skip.type = "button";
|
|
755
|
+
skip.className = "aparte-elic-skip";
|
|
756
|
+
skip.textContent = cfg.t("elicitationSkip");
|
|
757
|
+
skip.addEventListener("click", () => settle({ action: "decline" }));
|
|
758
|
+
panel.dismiss.appendChild(skip);
|
|
759
|
+
this._pending = {
|
|
760
|
+
abort: () => fail(),
|
|
761
|
+
composer,
|
|
762
|
+
relabel: () => {
|
|
763
|
+
panel.relabel();
|
|
764
|
+
skip.textContent = resolveConfig(this).t("elicitationSkip");
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
slot.token = composer.showPanel(panel.el, {
|
|
768
|
+
submitEnabled: panel.canProceed(),
|
|
769
|
+
mode: panel.mode(),
|
|
770
|
+
/*
|
|
771
|
+
* Something else took the slot — another request, a conversation switch,
|
|
772
|
+
* or a turn ending. The composer tears the panel down either way; only
|
|
773
|
+
* this callback can settle the promise, and without it the request hung
|
|
774
|
+
* AND `_pending` stayed set, so every later question was short-circuited
|
|
775
|
+
* for the life of the page. `cancel`, not `decline`: nobody declined
|
|
776
|
+
* anything, the question was taken away.
|
|
777
|
+
*/
|
|
778
|
+
onEvict: () => fail(),
|
|
779
|
+
onSubmit: () => {
|
|
780
|
+
if (panel.mode() === "advance") {
|
|
781
|
+
panel.proceed();
|
|
782
|
+
composer.setPanelSubmitEnabled(panel.canProceed(), panel.mode());
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
if (panel.isComplete()) settle({ action: "accept", content: panel.getContent() });
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
panel.focus();
|
|
789
|
+
});
|
|
790
|
+
};
|
|
791
|
+
_cancelPending() {
|
|
792
|
+
this._pending?.abort();
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* The host id of the chat whose composer holds the open panel.
|
|
796
|
+
*
|
|
797
|
+
* Walks up from the composer rather than from `this`, because the panel lives in
|
|
798
|
+
* the composer and that is what the turn belongs to. Matches the hosts
|
|
799
|
+
* `aparte-chat-bubble._resolveTargetId()` matches, for the reason written there:
|
|
800
|
+
* Angular's wrapper root IS the `<aparte-chat>` element, while the plain-root
|
|
801
|
+
* wrappers render a `[data-aparte-chat]` div instead, so matching only the tag
|
|
802
|
+
* resolves `undefined` on three wrappers out of four.
|
|
803
|
+
*/
|
|
804
|
+
_pendingTargetId() {
|
|
805
|
+
let el = this._pending?.composer ?? null;
|
|
806
|
+
while (el) {
|
|
807
|
+
const tag = el.tagName?.toLowerCase();
|
|
808
|
+
const isHost = tag === "aparte-chat" || tag === "aparte-chat-component" || el.hasAttribute?.("data-aparte-chat");
|
|
809
|
+
if (isHost && el.id) return el.id;
|
|
810
|
+
el = el.parentElement;
|
|
811
|
+
}
|
|
812
|
+
return void 0;
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* The composer to present in: the nearest one in an ancestor subtree, and
|
|
816
|
+
* nothing else.
|
|
817
|
+
*
|
|
818
|
+
* There used to be a `document.querySelector('aparte-composer')` fallback, which
|
|
819
|
+
* is the "first chat on the page" bug this repo has now fixed in four other
|
|
820
|
+
* places: on a page with two chats, an elicitation that could not find its own
|
|
821
|
+
* composer mounted its panel in the OTHER chat's — so one conversation's question
|
|
822
|
+
* appeared under the other conversation, and answering it resolved a tool call
|
|
823
|
+
* belonging to a chat the user was not looking at.
|
|
824
|
+
*
|
|
825
|
+
* Returning `null` instead REJECTS the request, which is honest: nothing was
|
|
826
|
+
* shown, so nothing was answered. The warning names the fix, because this is a
|
|
827
|
+
* setup mistake and only the developer can correct it — the guide's own example
|
|
828
|
+
* puts `<aparte-elicitation>` inside `<aparte-chat>`.
|
|
829
|
+
*/
|
|
830
|
+
_getComposer() {
|
|
831
|
+
let node = this.parentElement;
|
|
832
|
+
while (node) {
|
|
833
|
+
const composer = node.querySelector("aparte-composer");
|
|
834
|
+
if (composer && typeof composer.showPanel === "function") return composer;
|
|
835
|
+
const tag = node.tagName?.toLowerCase();
|
|
836
|
+
const isChatBoundary = tag === "aparte-chat" || tag === "aparte-chat-component" || node.hasAttribute?.("data-aparte-chat");
|
|
837
|
+
if (isChatBoundary) break;
|
|
838
|
+
node = node.parentElement;
|
|
839
|
+
}
|
|
840
|
+
console.warn(
|
|
841
|
+
"[aparte-elicitation] No <aparte-composer> in this element's subtree, so the request could not be shown, so it REJECTED and the turn halted. Nothing was told to the model — there is nothing true to tell it. Move <aparte-elicitation> inside the <aparte-chat> it belongs to. It is deliberately NOT borrowing another chat's composer: on a page with two chats that put the question under the wrong one."
|
|
842
|
+
);
|
|
843
|
+
return null;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
if (typeof customElements !== "undefined" && !customElements.get("aparte-elicitation")) {
|
|
847
|
+
customElements.define("aparte-elicitation", AparteElicitation);
|
|
848
|
+
}
|
|
634
849
|
class AparteChat extends HTMLElement {
|
|
635
850
|
static get observedAttributes() {
|
|
636
851
|
return ["placeholder", "disabled", "center-empty", "attachments"];
|
|
@@ -683,6 +898,7 @@ class AparteChat extends HTMLElement {
|
|
|
683
898
|
const attachments = this.hasAttribute("attachments");
|
|
684
899
|
this.innerHTML = `
|
|
685
900
|
<aparte-chat-viewport></aparte-chat-viewport>
|
|
901
|
+
<aparte-elicitation></aparte-elicitation>
|
|
686
902
|
<aparte-composer${composerAttrs}>
|
|
687
903
|
<div class="aparte-composer-shell">
|
|
688
904
|
${attachments ? "<aparte-composer-attachments></aparte-composer-attachments>" : ""}
|
|
@@ -935,7 +1151,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
935
1151
|
}
|
|
936
1152
|
/** Update a specific segment */
|
|
937
1153
|
updateSegment(segmentId, updates) {
|
|
938
|
-
const index = this._segments.findIndex((
|
|
1154
|
+
const index = this._segments.findIndex((s) => s.id === segmentId);
|
|
939
1155
|
if (index !== -1) {
|
|
940
1156
|
const updated = mergeSegmentUpdate(this._segments[index], updates);
|
|
941
1157
|
this._segments[index] = updated;
|
|
@@ -944,7 +1160,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
944
1160
|
}
|
|
945
1161
|
/** Append content to a segment */
|
|
946
1162
|
appendToSegment(segmentId, content) {
|
|
947
|
-
const segment = this._segments.find((
|
|
1163
|
+
const segment = this._segments.find((s) => s.id === segmentId);
|
|
948
1164
|
if (segment && "content" in segment) {
|
|
949
1165
|
segment.content += content;
|
|
950
1166
|
this._applySegmentUpdate(segmentId, segment, { content: segment.content });
|
|
@@ -970,7 +1186,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
970
1186
|
* the human-in-the-loop control.
|
|
971
1187
|
*/
|
|
972
1188
|
removeSegment(segmentId) {
|
|
973
|
-
const index = this._segments.findIndex((
|
|
1189
|
+
const index = this._segments.findIndex((s) => s.id === segmentId);
|
|
974
1190
|
if (index !== -1) {
|
|
975
1191
|
this._segments.splice(index, 1);
|
|
976
1192
|
}
|
|
@@ -1053,7 +1269,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1053
1269
|
} else {
|
|
1054
1270
|
warnMissingRenderer(segment.type);
|
|
1055
1271
|
const fallback = document.createElement("div");
|
|
1056
|
-
fallback.className = "segment segment-unknown";
|
|
1272
|
+
fallback.className = "aparte-segment aparte-segment-unknown";
|
|
1057
1273
|
fallback.textContent = `[Unknown segment type: ${segment.type}]`;
|
|
1058
1274
|
this._segmentsEl.appendChild(fallback);
|
|
1059
1275
|
}
|
|
@@ -1282,7 +1498,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1282
1498
|
} else {
|
|
1283
1499
|
warnMissingRenderer(segment.type);
|
|
1284
1500
|
const fallback = document.createElement("div");
|
|
1285
|
-
fallback.className = "segment segment-unknown";
|
|
1501
|
+
fallback.className = "aparte-segment aparte-segment-unknown";
|
|
1286
1502
|
fallback.textContent = `[Unknown segment type: ${segment.type}]`;
|
|
1287
1503
|
this._segmentsEl.appendChild(fallback);
|
|
1288
1504
|
}
|
|
@@ -1302,7 +1518,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1302
1518
|
_reflectError() {
|
|
1303
1519
|
const message = this.querySelector(".aparte-message");
|
|
1304
1520
|
if (!message) return;
|
|
1305
|
-
const hasError = this._segments.some((
|
|
1521
|
+
const hasError = this._segments.some((s) => s.type === "error");
|
|
1306
1522
|
if (hasError) message.setAttribute("data-error", "");
|
|
1307
1523
|
else message.removeAttribute("data-error");
|
|
1308
1524
|
}
|
|
@@ -1337,18 +1553,18 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1337
1553
|
const customAttachment = this._cfg.getAttachmentRenderer?.();
|
|
1338
1554
|
if (customAttachment) {
|
|
1339
1555
|
this._attachmentsEl.replaceChildren();
|
|
1340
|
-
for (const
|
|
1341
|
-
const el = segmentRenderResultToElement(runWithConfig(this._cfg, () => customAttachment(
|
|
1556
|
+
for (const a4 of this._attachments) {
|
|
1557
|
+
const el = segmentRenderResultToElement(runWithConfig(this._cfg, () => customAttachment(a4)));
|
|
1342
1558
|
if (el) this._attachmentsEl.appendChild(el);
|
|
1343
1559
|
}
|
|
1344
1560
|
return;
|
|
1345
1561
|
}
|
|
1346
|
-
this._attachmentsEl.innerHTML = this._attachments.map((
|
|
1347
|
-
const name = escapeHtml(
|
|
1348
|
-
if (
|
|
1349
|
-
return `<div class="aparte-thumb aparte-thumb--image" title="${name}"><img class="aparte-thumb__img" src="${escapeHtml(
|
|
1562
|
+
this._attachmentsEl.innerHTML = this._attachments.map((a4) => {
|
|
1563
|
+
const name = escapeHtml(a4.name);
|
|
1564
|
+
if (a4.type.startsWith("image/")) {
|
|
1565
|
+
return `<div class="aparte-thumb aparte-thumb--image" title="${name}"><img class="aparte-thumb__img" src="${escapeHtml(a4.url)}" alt="${name}" loading="lazy" /><span class="aparte-thumb__name">${name}</span></div>`;
|
|
1350
1566
|
}
|
|
1351
|
-
return `<div class="aparte-thumb aparte-thumb--file" title="${name}"><span class="aparte-thumb__ext">${escapeHtml(this._fileExt(
|
|
1567
|
+
return `<div class="aparte-thumb aparte-thumb--file" title="${name}"><span class="aparte-thumb__ext">${escapeHtml(this._fileExt(a4.name))}</span><span class="aparte-thumb__name">${name}</span></div>`;
|
|
1352
1568
|
}).join("");
|
|
1353
1569
|
if (!this._cfg.getHostHandlers().attachmentPreview) return;
|
|
1354
1570
|
this._attachmentsEl.querySelectorAll(".aparte-thumb--image").forEach((tile) => {
|
|
@@ -1453,14 +1669,14 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1453
1669
|
const buttons = [];
|
|
1454
1670
|
if (this._role === "user") {
|
|
1455
1671
|
if (config.user) {
|
|
1456
|
-
for (const
|
|
1672
|
+
for (const a4 of config.user) buttons.push(this._actionButtonHtml(a4, icons, locale));
|
|
1457
1673
|
} else {
|
|
1458
1674
|
if (config.copy) buttons.push(this._actionButtonHtml("copy", icons, locale));
|
|
1459
1675
|
if (config.edit) buttons.push(this._actionButtonHtml("edit", icons, locale));
|
|
1460
1676
|
}
|
|
1461
1677
|
} else if (this._role === "assistant") {
|
|
1462
1678
|
if (config.assistant) {
|
|
1463
|
-
for (const
|
|
1679
|
+
for (const a4 of config.assistant) buttons.push(this._actionButtonHtml(a4, icons, locale));
|
|
1464
1680
|
} else {
|
|
1465
1681
|
if (config.copy) buttons.push(this._actionButtonHtml("copy", icons, locale));
|
|
1466
1682
|
if (config.retry) buttons.push(this._actionButtonHtml("retry", icons, locale));
|
|
@@ -1495,16 +1711,16 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1495
1711
|
/** Append the registered custom action buttons for this bubble's role. */
|
|
1496
1712
|
_appendCustomActions(icons) {
|
|
1497
1713
|
if (!this._actionBarEl) return;
|
|
1498
|
-
for (const
|
|
1499
|
-
const roles =
|
|
1714
|
+
for (const a4 of this._cfg.getActions("bubble")) {
|
|
1715
|
+
const roles = a4.bubble?.roles ?? ["user", "assistant"];
|
|
1500
1716
|
if (!roles.includes(this._role)) continue;
|
|
1501
1717
|
const btn = document.createElement("button");
|
|
1502
1718
|
btn.className = "aparte-action-btn aparte-action-custom";
|
|
1503
|
-
btn.dataset["action"] = `custom:${
|
|
1504
|
-
btn.setAttribute("aria-label",
|
|
1505
|
-
btn.setAttribute("title",
|
|
1506
|
-
const fromProvider = icons[
|
|
1507
|
-
btn.innerHTML =
|
|
1719
|
+
btn.dataset["action"] = `custom:${a4.id}`;
|
|
1720
|
+
btn.setAttribute("aria-label", a4.label);
|
|
1721
|
+
btn.setAttribute("title", a4.label);
|
|
1722
|
+
const fromProvider = icons[a4.icon];
|
|
1723
|
+
btn.innerHTML = a4.icon.startsWith("<") ? a4.icon : typeof fromProvider === "function" ? fromProvider() : a4.iconFallback ?? "";
|
|
1508
1724
|
this._actionBarEl.appendChild(btn);
|
|
1509
1725
|
}
|
|
1510
1726
|
}
|
|
@@ -1570,12 +1786,12 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1570
1786
|
composed: true,
|
|
1571
1787
|
detail
|
|
1572
1788
|
}));
|
|
1573
|
-
this._cfg.getActions("bubble").find((
|
|
1789
|
+
this._cfg.getActions("bubble").find((x) => x.id === actionId)?.onClick?.(e2);
|
|
1574
1790
|
return;
|
|
1575
1791
|
}
|
|
1576
1792
|
switch (action) {
|
|
1577
1793
|
case "copy": {
|
|
1578
|
-
const text = this._content || this._segments.map((
|
|
1794
|
+
const text = this._content || this._segments.map((s) => s.content ?? "").join("\n");
|
|
1579
1795
|
const icons = this._cfg.getIconProvider();
|
|
1580
1796
|
const locale = this._cfg.getLocale();
|
|
1581
1797
|
navigator.clipboard.writeText(text).then(() => {
|
|
@@ -2013,7 +2229,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2013
2229
|
if (!message.segments) {
|
|
2014
2230
|
message.segments = [];
|
|
2015
2231
|
}
|
|
2016
|
-
const index = message.segments.findIndex((
|
|
2232
|
+
const index = message.segments.findIndex((s) => s.id === segmentId);
|
|
2017
2233
|
const segment = index === -1 ? void 0 : message.segments[index];
|
|
2018
2234
|
if (segment && "content" in segment) {
|
|
2019
2235
|
message.segments[index] = {
|
|
@@ -2075,15 +2291,15 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2075
2291
|
this._notifyBubble(messageId, "addSegment", stamped);
|
|
2076
2292
|
this._autoScroll();
|
|
2077
2293
|
}
|
|
2078
|
-
updateSegment(
|
|
2294
|
+
updateSegment(a4, b2, c2) {
|
|
2079
2295
|
const clientForm = c2 === void 0 && typeof b2 === "object";
|
|
2080
|
-
const messageId = clientForm ? this._activeMessageId() :
|
|
2081
|
-
const segmentId = clientForm ?
|
|
2296
|
+
const messageId = clientForm ? this._activeMessageId() : a4;
|
|
2297
|
+
const segmentId = clientForm ? a4 : b2;
|
|
2082
2298
|
const updates = clientForm ? b2 : c2;
|
|
2083
2299
|
if (!messageId) return;
|
|
2084
2300
|
const message = this._repo.getMessageById(messageId);
|
|
2085
2301
|
if (!message?.segments) return;
|
|
2086
|
-
const segmentIndex = message.segments.findIndex((
|
|
2302
|
+
const segmentIndex = message.segments.findIndex((s) => s.id === segmentId);
|
|
2087
2303
|
if (segmentIndex !== -1) {
|
|
2088
2304
|
const current = message.segments[segmentIndex];
|
|
2089
2305
|
const stamped = stampSegmentOnUpdate(current, updates);
|
|
@@ -2091,14 +2307,14 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2091
2307
|
this._notifyBubble(messageId, "updateSegment", { segmentId, updates: stamped });
|
|
2092
2308
|
}
|
|
2093
2309
|
}
|
|
2094
|
-
removeSegment(
|
|
2310
|
+
removeSegment(a4, b2) {
|
|
2095
2311
|
const clientForm = b2 === void 0;
|
|
2096
|
-
const messageId = clientForm ? this._activeMessageId() :
|
|
2097
|
-
const segmentId = clientForm ?
|
|
2312
|
+
const messageId = clientForm ? this._activeMessageId() : a4;
|
|
2313
|
+
const segmentId = clientForm ? a4 : b2;
|
|
2098
2314
|
if (!messageId || !segmentId) return;
|
|
2099
2315
|
const message = this._repo.getMessageById(messageId);
|
|
2100
2316
|
if (message?.segments) {
|
|
2101
|
-
const idx = message.segments.findIndex((
|
|
2317
|
+
const idx = message.segments.findIndex((s) => s.id === segmentId);
|
|
2102
2318
|
if (idx !== -1) {
|
|
2103
2319
|
message.segments.splice(idx, 1);
|
|
2104
2320
|
renumberSegments(message.segments);
|
|
@@ -2930,6 +3146,20 @@ class AparteComposer extends HTMLElement {
|
|
|
2930
3146
|
_panelMode = "submit";
|
|
2931
3147
|
_panelSubmitEnabled = false;
|
|
2932
3148
|
_panelOnSubmit = null;
|
|
3149
|
+
/**
|
|
3150
|
+
* Who owns the one panel slot, and how to tell them they lost it.
|
|
3151
|
+
*
|
|
3152
|
+
* There is exactly one slot, `showPanel` empties it unconditionally, and nothing
|
|
3153
|
+
* used to say whose it was. Three paths therefore closed a panel whose owner was
|
|
3154
|
+
* still awaiting an answer, and none of them told the owner: a second `showPanel`,
|
|
3155
|
+
* the owner-of-record's own `hidePanel`, and — the one that actually bit —
|
|
3156
|
+
* `_handleMessageDone`, which fires on EVERY turn end. A question still open when a
|
|
3157
|
+
* turn finished lost its panel while `<aparte-elicitation>` kept `_pending` set,
|
|
3158
|
+
* so the request never settled AND every later request was short-circuited for the
|
|
3159
|
+
* life of the page.
|
|
3160
|
+
*/
|
|
3161
|
+
_panelToken = null;
|
|
3162
|
+
_panelOnEvict = null;
|
|
2933
3163
|
// Internal bus events that represent an observable STATE change — these are
|
|
2934
3164
|
// mirrored to the public `aparte-composer-change` DOM event. `submit`/`cancel`
|
|
2935
3165
|
// are actions, not state, and are covered by `aparte-send`/`aparte-cancel`.
|
|
@@ -3097,9 +3327,14 @@ class AparteComposer extends HTMLElement {
|
|
|
3097
3327
|
* used to set on a child: an attribute is themeable, is visible to a consumer's
|
|
3098
3328
|
* own rules, and does not clobber a `display` the consumer had set (the restore
|
|
3099
3329
|
* wrote `''`, not the previous value).
|
|
3330
|
+
*
|
|
3331
|
+
* Returns the TOKEN for this panel. Pass it back to `hidePanel` so a presenter
|
|
3332
|
+
* that has already lost the slot cannot close the panel that replaced it, and
|
|
3333
|
+
* supply `onEvict` to be told when that happens — an owner that is not told is an
|
|
3334
|
+
* owner whose promise nobody can settle.
|
|
3100
3335
|
*/
|
|
3101
3336
|
showPanel(panel, options) {
|
|
3102
|
-
this.
|
|
3337
|
+
this._evictPanel();
|
|
3103
3338
|
const inputEl = this.querySelector("aparte-composer-input");
|
|
3104
3339
|
this.setAttribute("data-panel-active", "");
|
|
3105
3340
|
panel.dataset["apartePanel"] = "true";
|
|
@@ -3112,10 +3347,58 @@ class AparteComposer extends HTMLElement {
|
|
|
3112
3347
|
this._panelSubmitEnabled = options?.submitEnabled ?? false;
|
|
3113
3348
|
this._panelMode = options?.mode ?? "submit";
|
|
3114
3349
|
this._panelOnSubmit = options?.onSubmit ?? null;
|
|
3350
|
+
this._panelOnEvict = options?.onEvict ?? null;
|
|
3351
|
+
const token = Symbol("aparte-composer-panel");
|
|
3352
|
+
this._panelToken = token;
|
|
3115
3353
|
this._emit("panel-change", { active: true, submitEnabled: this._panelSubmitEnabled, mode: this._panelMode });
|
|
3354
|
+
return token;
|
|
3116
3355
|
}
|
|
3117
|
-
/**
|
|
3118
|
-
|
|
3356
|
+
/**
|
|
3357
|
+
* Remove the panel, tell its owner, and restore the composer's own controls.
|
|
3358
|
+
*
|
|
3359
|
+
* With a `token`, this closes the panel only if that token still owns the slot —
|
|
3360
|
+
* so a presenter settling late cannot tear down the panel that replaced it. With
|
|
3361
|
+
* no token it closes whatever is there, which is what a consumer driving the
|
|
3362
|
+
* composer directly means.
|
|
3363
|
+
*
|
|
3364
|
+
* BOTH forms notify. The no-token form used to call `_teardownPanel` directly,
|
|
3365
|
+
* which nulls `_panelOnEvict` without ever calling it — so the documented public
|
|
3366
|
+
* `hidePanel()` closed an open approval panel and left its request pending
|
|
3367
|
+
* forever. The turn hung on "waiting for you", and because
|
|
3368
|
+
* `AparteConfig.requestUserInput` chains each request on the previous one, NO
|
|
3369
|
+
* further question or approval on that config was ever presented again, for the
|
|
3370
|
+
* life of the page.
|
|
3371
|
+
*
|
|
3372
|
+
* The old JSDoc justified the silent branch as "what `reset()` needs". It was not:
|
|
3373
|
+
* `reset()` calls `_evictPanel()`, which notifies. The branch had no consumer and
|
|
3374
|
+
* one failure mode.
|
|
3375
|
+
*
|
|
3376
|
+
* The two forms differ on purpose, and the difference is who already knows:
|
|
3377
|
+
*
|
|
3378
|
+
* - **With a matching token** the OWNER is closing its own panel, which is what
|
|
3379
|
+
* `<aparte-elicitation>`'s `close()` does right after it resolves. It must NOT be
|
|
3380
|
+
* notified — telling it "you were evicted" for a request it just settled would
|
|
3381
|
+
* fire `onEvict` against a finished promise.
|
|
3382
|
+
* - **With no token** somebody else is closing a panel they do not own, so the owner
|
|
3383
|
+
* cannot know and has to be told. That includes the presenter's own defensive
|
|
3384
|
+
* `hidePanel(undefined)` when it settles before `showPanel` ran: whatever is open
|
|
3385
|
+
* then belongs to another request, and that request must not orphan.
|
|
3386
|
+
*/
|
|
3387
|
+
hidePanel(token) {
|
|
3388
|
+
if (token !== void 0) {
|
|
3389
|
+
if (token !== this._panelToken) return;
|
|
3390
|
+
this._teardownPanel();
|
|
3391
|
+
return;
|
|
3392
|
+
}
|
|
3393
|
+
this._evictPanel();
|
|
3394
|
+
}
|
|
3395
|
+
/** Close the panel AND tell its owner, so a pending request never orphans. */
|
|
3396
|
+
_evictPanel() {
|
|
3397
|
+
const onEvict = this._panelOnEvict;
|
|
3398
|
+
this._teardownPanel();
|
|
3399
|
+
onEvict?.();
|
|
3400
|
+
}
|
|
3401
|
+
_teardownPanel() {
|
|
3119
3402
|
const existing = this.querySelector("[data-aparte-panel]");
|
|
3120
3403
|
if (existing) existing.remove();
|
|
3121
3404
|
this.removeAttribute("data-panel-active");
|
|
@@ -3123,6 +3406,8 @@ class AparteComposer extends HTMLElement {
|
|
|
3123
3406
|
this._panelSubmitEnabled = false;
|
|
3124
3407
|
this._panelMode = "submit";
|
|
3125
3408
|
this._panelOnSubmit = null;
|
|
3409
|
+
this._panelOnEvict = null;
|
|
3410
|
+
this._panelToken = null;
|
|
3126
3411
|
this._emit("panel-change", { active: false, submitEnabled: false, mode: "submit" });
|
|
3127
3412
|
this.focus();
|
|
3128
3413
|
}
|
|
@@ -3188,7 +3473,7 @@ class AparteComposer extends HTMLElement {
|
|
|
3188
3473
|
cancel() {
|
|
3189
3474
|
this._emit("cancel", {});
|
|
3190
3475
|
this.dispatchEvent(new CustomEvent("aparte-cancel", { bubbles: true, composed: true }));
|
|
3191
|
-
const abortDetail = { targetId: this.
|
|
3476
|
+
const abortDetail = { targetId: this._ownTargetId() };
|
|
3192
3477
|
window.dispatchEvent(new CustomEvent("aparte-abort", { bubbles: false, detail: abortDetail }));
|
|
3193
3478
|
window.dispatchEvent(new CustomEvent("aparte-message-aborted", { bubbles: false, detail: abortDetail }));
|
|
3194
3479
|
}
|
|
@@ -3200,7 +3485,7 @@ class AparteComposer extends HTMLElement {
|
|
|
3200
3485
|
reset() {
|
|
3201
3486
|
this.setValue("");
|
|
3202
3487
|
this.clearAttachments();
|
|
3203
|
-
if (this._panelActive) this.
|
|
3488
|
+
if (this._panelActive) this._evictPanel();
|
|
3204
3489
|
}
|
|
3205
3490
|
/** Focus the input primitive inside this composer. */
|
|
3206
3491
|
focus() {
|
|
@@ -3269,7 +3554,7 @@ class AparteComposer extends HTMLElement {
|
|
|
3269
3554
|
if (!this._isForThisComposer(e2)) return;
|
|
3270
3555
|
this._streaming = false;
|
|
3271
3556
|
this._emit("streaming-change", { streaming: false });
|
|
3272
|
-
if (this._panelActive) this.
|
|
3557
|
+
if (this._panelActive) this._evictPanel();
|
|
3273
3558
|
}
|
|
3274
3559
|
}
|
|
3275
3560
|
if (!customElements.get("aparte-composer")) {
|
|
@@ -3431,10 +3716,10 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3431
3716
|
}
|
|
3432
3717
|
}
|
|
3433
3718
|
_handleFocus() {
|
|
3434
|
-
this.classList.add("is-focused");
|
|
3719
|
+
this.classList.add("aparte-is-focused");
|
|
3435
3720
|
}
|
|
3436
3721
|
_handleBlur() {
|
|
3437
|
-
this.classList.remove("is-focused");
|
|
3722
|
+
this.classList.remove("aparte-is-focused");
|
|
3438
3723
|
}
|
|
3439
3724
|
_handlePaste(e2) {
|
|
3440
3725
|
e2.preventDefault();
|
|
@@ -3477,9 +3762,9 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3477
3762
|
}
|
|
3478
3763
|
_updatePlaceholder() {
|
|
3479
3764
|
if (this._editor) {
|
|
3480
|
-
const
|
|
3481
|
-
this._editor.setAttribute("data-placeholder",
|
|
3482
|
-
this._editor.setAttribute("aria-label",
|
|
3765
|
+
const p = this._getPlaceholder();
|
|
3766
|
+
this._editor.setAttribute("data-placeholder", p);
|
|
3767
|
+
this._editor.setAttribute("aria-label", p);
|
|
3483
3768
|
}
|
|
3484
3769
|
}
|
|
3485
3770
|
_updatePlaceholderVisibility() {
|
|
@@ -3605,7 +3890,7 @@ class AparteComposerSend extends HTMLElement {
|
|
|
3605
3890
|
const label = advancing ? cfg.t("elicitationNext") || "Next" : cfg.t("submitButton") || "Submit";
|
|
3606
3891
|
this._button.setAttribute("aria-label", label);
|
|
3607
3892
|
this._button.setAttribute("title", label);
|
|
3608
|
-
this._button.classList.remove("is-streaming");
|
|
3893
|
+
this._button.classList.remove("aparte-is-streaming");
|
|
3609
3894
|
}
|
|
3610
3895
|
_handleClick(e2) {
|
|
3611
3896
|
e2.preventDefault();
|
|
@@ -3621,7 +3906,7 @@ class AparteComposerSend extends HTMLElement {
|
|
|
3621
3906
|
const label = resolveConfig(this).t("sendButton") || "Send";
|
|
3622
3907
|
this._button.setAttribute("aria-label", label);
|
|
3623
3908
|
this._button.setAttribute("title", label);
|
|
3624
|
-
this._button.classList.remove("is-streaming");
|
|
3909
|
+
this._button.classList.remove("aparte-is-streaming");
|
|
3625
3910
|
}
|
|
3626
3911
|
_syncStreamingState(streaming) {
|
|
3627
3912
|
if (!this._button) return;
|
|
@@ -3631,7 +3916,7 @@ class AparteComposerSend extends HTMLElement {
|
|
|
3631
3916
|
const label = resolveConfig(this).t("stopButton") || "Stop";
|
|
3632
3917
|
this._button.setAttribute("aria-label", label);
|
|
3633
3918
|
this._button.setAttribute("title", label);
|
|
3634
|
-
this._button.classList.add("is-streaming");
|
|
3919
|
+
this._button.classList.add("aparte-is-streaming");
|
|
3635
3920
|
} else {
|
|
3636
3921
|
this._syncState();
|
|
3637
3922
|
}
|
|
@@ -3906,15 +4191,15 @@ class AparteComposerAddAttachment extends HTMLElement {
|
|
|
3906
4191
|
const onDragOver = (e2) => {
|
|
3907
4192
|
if (root.disabled) return;
|
|
3908
4193
|
prevent(e2);
|
|
3909
|
-
root.classList.add("is-dragover");
|
|
4194
|
+
root.classList.add("aparte-is-dragover");
|
|
3910
4195
|
};
|
|
3911
4196
|
const onDragLeave = (e2) => {
|
|
3912
4197
|
prevent(e2);
|
|
3913
|
-
root.classList.remove("is-dragover");
|
|
4198
|
+
root.classList.remove("aparte-is-dragover");
|
|
3914
4199
|
};
|
|
3915
4200
|
const onDrop = (e2) => {
|
|
3916
4201
|
prevent(e2);
|
|
3917
|
-
root.classList.remove("is-dragover");
|
|
4202
|
+
root.classList.remove("aparte-is-dragover");
|
|
3918
4203
|
if (root.disabled) return;
|
|
3919
4204
|
const files = e2.dataTransfer?.files;
|
|
3920
4205
|
if (files?.length) this._getRoot()?.addAttachments(files);
|
|
@@ -4207,180 +4492,6 @@ class AparteConversationList extends HTMLElement {
|
|
|
4207
4492
|
}
|
|
4208
4493
|
}
|
|
4209
4494
|
if (!customElements.get("aparte-conversation-list")) customElements.define("aparte-conversation-list", AparteConversationList);
|
|
4210
|
-
class AparteElicitation extends HTMLElement {
|
|
4211
|
-
_pending = null;
|
|
4212
|
-
/**
|
|
4213
|
-
* A turn ended — cancel the open question only if it was OUR turn.
|
|
4214
|
-
*
|
|
4215
|
-
* These two listeners sit on `window` and had no instance filter at all, so a
|
|
4216
|
-
* Stop (or an error) in one chat cancelled the question a DIFFERENT chat was
|
|
4217
|
-
* waiting on — and that chat's model was told the user had refused a question
|
|
4218
|
-
* the user was still looking at. Same defect as the `compact()` handler that
|
|
4219
|
-
* emptied both chats, which its four sibling handlers in `AparteClient` already
|
|
4220
|
-
* guarded against.
|
|
4221
|
-
*
|
|
4222
|
-
* The leniency rule is the composer's, deliberately: an event with no
|
|
4223
|
-
* `targetId` is for everyone (a single-chat app never sets one), and a chat we
|
|
4224
|
-
* cannot identify accepts everything rather than becoming deaf. Only a
|
|
4225
|
-
* MISMATCH is ignored.
|
|
4226
|
-
*/
|
|
4227
|
-
_onTurnEnd = (e2) => {
|
|
4228
|
-
const evtTargetId = e2.detail?.targetId;
|
|
4229
|
-
const own = this._pendingTargetId();
|
|
4230
|
-
if (evtTargetId && own && evtTargetId !== own) return;
|
|
4231
|
-
this._cancelPending();
|
|
4232
|
-
};
|
|
4233
|
-
_unsubscribeConfig = null;
|
|
4234
|
-
connectedCallback() {
|
|
4235
|
-
this.style.display = "none";
|
|
4236
|
-
this._unsubscribeConfig = subscribeConfigChange(this, () => this._relabelPending());
|
|
4237
|
-
resolveConfig(this).setElicitationPresenter(this._present);
|
|
4238
|
-
window.addEventListener("aparte-message-aborted", this._onTurnEnd);
|
|
4239
|
-
window.addEventListener("aparte-message-error", this._onTurnEnd);
|
|
4240
|
-
}
|
|
4241
|
-
/**
|
|
4242
|
-
* The boundary above us appeared, changed, or went away — move the
|
|
4243
|
-
* registration with it.
|
|
4244
|
-
*
|
|
4245
|
-
* `connectedCallback` alone is not enough and cannot be: registering is a
|
|
4246
|
-
* WRITE, and under all four wrappers it happens before `attachConfig` runs, so
|
|
4247
|
-
* it lands on the global singleton. `requestUserInput()` then resolves the
|
|
4248
|
-
* instance config, finds nothing, and answers the model `cancel` — the model
|
|
4249
|
-
* hears the user refuse a question the user never saw.
|
|
4250
|
-
*
|
|
4251
|
-
* See {@link AparteConfigAware}.
|
|
4252
|
-
*/
|
|
4253
|
-
aparteConfigChanged(next, previous) {
|
|
4254
|
-
if (previous.getElicitationPresenter() === this._present) previous.setElicitationPresenter(null);
|
|
4255
|
-
next.setElicitationPresenter(this._present);
|
|
4256
|
-
}
|
|
4257
|
-
disconnectedCallback() {
|
|
4258
|
-
const cfg = resolveConfig(this);
|
|
4259
|
-
if (cfg.getElicitationPresenter() === this._present) cfg.setElicitationPresenter(null);
|
|
4260
|
-
window.removeEventListener("aparte-message-aborted", this._onTurnEnd);
|
|
4261
|
-
window.removeEventListener("aparte-message-error", this._onTurnEnd);
|
|
4262
|
-
this._unsubscribeConfig?.();
|
|
4263
|
-
this._unsubscribeConfig = null;
|
|
4264
|
-
this._cancelPending();
|
|
4265
|
-
}
|
|
4266
|
-
/**
|
|
4267
|
-
* Re-apply the open question's strings, in place.
|
|
4268
|
-
*
|
|
4269
|
-
* Two owners, and both have to move or the panel goes bilingual: the panel's own
|
|
4270
|
-
* defaults (`relabel`), and the Skip button, which this file builds and this file
|
|
4271
|
-
* therefore has to re-text. The composer's one button is a third, and it already
|
|
4272
|
-
* follows — `aparte-composer-send` remembers the panel state it was given.
|
|
4273
|
-
*/
|
|
4274
|
-
_relabelPending() {
|
|
4275
|
-
if (!this._pending) return;
|
|
4276
|
-
const cfg = resolveConfig(this);
|
|
4277
|
-
runWithConfig(cfg, () => this._pending.panel.relabel());
|
|
4278
|
-
this._pending.skip.textContent = cfg.t("elicitationSkip");
|
|
4279
|
-
}
|
|
4280
|
-
_present = (request) => {
|
|
4281
|
-
if (this._pending) return Promise.resolve({ action: "cancel" });
|
|
4282
|
-
const composer = this._getComposer();
|
|
4283
|
-
if (!composer) return Promise.resolve({ action: "cancel" });
|
|
4284
|
-
return new Promise((resolve) => {
|
|
4285
|
-
let done = false;
|
|
4286
|
-
const settle = (result) => {
|
|
4287
|
-
if (done) return;
|
|
4288
|
-
done = true;
|
|
4289
|
-
this._pending = null;
|
|
4290
|
-
composer.hidePanel();
|
|
4291
|
-
resolve(result);
|
|
4292
|
-
};
|
|
4293
|
-
if (request.signal) {
|
|
4294
|
-
if (request.signal.aborted) {
|
|
4295
|
-
settle({ action: "cancel" });
|
|
4296
|
-
return;
|
|
4297
|
-
}
|
|
4298
|
-
request.signal.addEventListener("abort", () => settle({ action: "cancel" }), { once: true });
|
|
4299
|
-
}
|
|
4300
|
-
const cfg = resolveConfig(this);
|
|
4301
|
-
const panel = runWithConfig(cfg, () => buildElicitationPanel(request.message, request.schema, () => {
|
|
4302
|
-
composer.setPanelSubmitEnabled(panel.canProceed(), panel.mode());
|
|
4303
|
-
}));
|
|
4304
|
-
const skip = document.createElement("button");
|
|
4305
|
-
skip.type = "button";
|
|
4306
|
-
skip.className = "aparte-elic-skip";
|
|
4307
|
-
skip.textContent = cfg.t("elicitationSkip");
|
|
4308
|
-
skip.addEventListener("click", () => settle({ action: "decline" }));
|
|
4309
|
-
panel.dismiss.appendChild(skip);
|
|
4310
|
-
this._pending = { settle, composer, panel, skip };
|
|
4311
|
-
composer.showPanel(panel.el, {
|
|
4312
|
-
submitEnabled: panel.canProceed(),
|
|
4313
|
-
mode: panel.mode(),
|
|
4314
|
-
onSubmit: () => {
|
|
4315
|
-
if (panel.mode() === "advance") {
|
|
4316
|
-
panel.proceed();
|
|
4317
|
-
composer.setPanelSubmitEnabled(panel.canProceed(), panel.mode());
|
|
4318
|
-
return;
|
|
4319
|
-
}
|
|
4320
|
-
if (panel.isComplete()) settle({ action: "accept", content: panel.getContent() });
|
|
4321
|
-
}
|
|
4322
|
-
});
|
|
4323
|
-
panel.focus();
|
|
4324
|
-
});
|
|
4325
|
-
};
|
|
4326
|
-
_cancelPending() {
|
|
4327
|
-
this._pending?.settle({ action: "cancel" });
|
|
4328
|
-
}
|
|
4329
|
-
/**
|
|
4330
|
-
* The host id of the chat whose composer holds the open panel.
|
|
4331
|
-
*
|
|
4332
|
-
* Walks up from the composer rather than from `this`, because the panel lives in
|
|
4333
|
-
* the composer and that is what the turn belongs to. Matches the hosts
|
|
4334
|
-
* `aparte-chat-bubble._resolveTargetId()` matches, for the reason written there:
|
|
4335
|
-
* Angular's wrapper root IS the `<aparte-chat>` element, while the plain-root
|
|
4336
|
-
* wrappers render a `[data-aparte-chat]` div instead, so matching only the tag
|
|
4337
|
-
* resolves `undefined` on three wrappers out of four.
|
|
4338
|
-
*/
|
|
4339
|
-
_pendingTargetId() {
|
|
4340
|
-
let el = this._pending?.composer ?? null;
|
|
4341
|
-
while (el) {
|
|
4342
|
-
const tag = el.tagName?.toLowerCase();
|
|
4343
|
-
const isHost = tag === "aparte-chat" || tag === "aparte-chat-component" || el.hasAttribute?.("data-aparte-chat");
|
|
4344
|
-
if (isHost && el.id) return el.id;
|
|
4345
|
-
el = el.parentElement;
|
|
4346
|
-
}
|
|
4347
|
-
return void 0;
|
|
4348
|
-
}
|
|
4349
|
-
/**
|
|
4350
|
-
* The composer to present in: the nearest one in an ancestor subtree, and
|
|
4351
|
-
* nothing else.
|
|
4352
|
-
*
|
|
4353
|
-
* There used to be a `document.querySelector('aparte-composer')` fallback, which
|
|
4354
|
-
* is the "first chat on the page" bug this repo has now fixed in four other
|
|
4355
|
-
* places: on a page with two chats, an elicitation that could not find its own
|
|
4356
|
-
* composer mounted its panel in the OTHER chat's — so one conversation's question
|
|
4357
|
-
* appeared under the other conversation, and answering it resolved a tool call
|
|
4358
|
-
* belonging to a chat the user was not looking at.
|
|
4359
|
-
*
|
|
4360
|
-
* Returning `null` instead resolves `cancel`, which is honest: nothing was
|
|
4361
|
-
* shown, so nothing was answered. The warning names the fix, because this is a
|
|
4362
|
-
* setup mistake and only the developer can correct it — the guide's own example
|
|
4363
|
-
* puts `<aparte-elicitation>` inside `<aparte-chat>`.
|
|
4364
|
-
*/
|
|
4365
|
-
_getComposer() {
|
|
4366
|
-
let node = this.parentElement;
|
|
4367
|
-
while (node) {
|
|
4368
|
-
const composer = node.querySelector("aparte-composer");
|
|
4369
|
-
if (composer && typeof composer.showPanel === "function") return composer;
|
|
4370
|
-
const tag = node.tagName?.toLowerCase();
|
|
4371
|
-
const isChatBoundary = tag === "aparte-chat" || tag === "aparte-chat-component" || node.hasAttribute?.("data-aparte-chat");
|
|
4372
|
-
if (isChatBoundary) break;
|
|
4373
|
-
node = node.parentElement;
|
|
4374
|
-
}
|
|
4375
|
-
console.warn(
|
|
4376
|
-
"[aparte-elicitation] No <aparte-composer> in this element's subtree, so the request was cancelled — the model will read that as a refusal. Move <aparte-elicitation> inside the <aparte-chat> it belongs to. It is deliberately NOT borrowing another chat's composer: on a page with two chats that put the question under the wrong one."
|
|
4377
|
-
);
|
|
4378
|
-
return null;
|
|
4379
|
-
}
|
|
4380
|
-
}
|
|
4381
|
-
if (typeof customElements !== "undefined" && !customElements.get("aparte-elicitation")) {
|
|
4382
|
-
customElements.define("aparte-elicitation", AparteElicitation);
|
|
4383
|
-
}
|
|
4384
4495
|
function registerAllComponents() {
|
|
4385
4496
|
const _chat = customElements.get("aparte-chat");
|
|
4386
4497
|
const _viewport = customElements.get("aparte-chat-viewport");
|
|
@@ -4421,53 +4532,55 @@ export {
|
|
|
4421
4532
|
n as AparteConversationManager,
|
|
4422
4533
|
o as AparteDirectTransport,
|
|
4423
4534
|
AparteElicitation,
|
|
4424
|
-
|
|
4425
|
-
q as
|
|
4535
|
+
AparteElicitationAbortError,
|
|
4536
|
+
q as AparteError,
|
|
4537
|
+
r as AparteErrorCode,
|
|
4426
4538
|
AparteMessageRepository,
|
|
4427
4539
|
AparteOptgroup,
|
|
4428
4540
|
AparteOption,
|
|
4429
4541
|
AparteProgressSpinner,
|
|
4430
4542
|
AparteSelect,
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4543
|
+
t as AparteStreamParser,
|
|
4544
|
+
u as aparteGlobalConfig,
|
|
4545
|
+
v as applyElementProps,
|
|
4546
|
+
w as attachConfig,
|
|
4547
|
+
buildApprovalPanel,
|
|
4435
4548
|
buildElicitationPanel,
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4549
|
+
z as collectRendererStyles,
|
|
4550
|
+
B as contentToText,
|
|
4551
|
+
C as contextConfig,
|
|
4552
|
+
D as createAparteChatHandler,
|
|
4553
|
+
E as createStreamAdapter,
|
|
4441
4554
|
cssEscape,
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4555
|
+
G as declineDefaultRenderers,
|
|
4556
|
+
H as defaultSanitizer,
|
|
4557
|
+
I as deriveArtifactKind,
|
|
4558
|
+
J as detachConfig,
|
|
4446
4559
|
escapeAttr,
|
|
4447
4560
|
escapeHtml,
|
|
4448
|
-
|
|
4449
|
-
|
|
4561
|
+
M as filesToAttachments,
|
|
4562
|
+
N as getAllRenderers,
|
|
4450
4563
|
getSegmentRenderer,
|
|
4451
4564
|
installDefaultRenderersOnce,
|
|
4452
4565
|
isAwaitingReply,
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4566
|
+
R as isFormatAdapter,
|
|
4567
|
+
S as isSafeUrl,
|
|
4568
|
+
T as isSegmentSettled,
|
|
4569
|
+
U as parseAparteEventStream,
|
|
4570
|
+
V as parseMarkdownToSegments,
|
|
4458
4571
|
populateBubbleFromMessage,
|
|
4459
|
-
|
|
4572
|
+
X as readableToAsyncIterable,
|
|
4460
4573
|
registerAllComponents,
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4574
|
+
Y as registerDefaultRenderers,
|
|
4575
|
+
Z as registerSegmentRenderer,
|
|
4576
|
+
_ as requestUserInput,
|
|
4464
4577
|
resolveConfig,
|
|
4465
4578
|
revokeAttachmentUrls,
|
|
4466
4579
|
runWithConfig,
|
|
4467
|
-
|
|
4468
|
-
|
|
4580
|
+
a2 as segmentDuration,
|
|
4581
|
+
a3 as segmentTiming,
|
|
4469
4582
|
subscribeConfigChange,
|
|
4470
|
-
|
|
4583
|
+
a5 as unregisterSegmentRenderer,
|
|
4471
4584
|
uuid
|
|
4472
4585
|
};
|
|
4473
4586
|
//# sourceMappingURL=index.js.map
|