@aparte/core 0.10.0 → 0.11.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 +5 -5
- 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 +54 -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 +12 -0
- 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 +20 -1
- 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 +8517 -7601
- 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 +173 -0
- package/dist/generated/element-attributes.d.ts.map +1 -0
- package/dist/index.css +615 -92
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +341 -254
- package/dist/index.js.map +1 -1
- package/dist/index.node.d.ts +3 -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 +26 -1
- package/dist/interop/element-props.d.ts.map +1 -1
- package/dist/{index-BnKBciDG.js → is-awaiting-reply-Cb7H3YfN.js} +1448 -1544
- package/dist/is-awaiting-reply-Cb7H3YfN.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 +15 -21
- package/dist/types/tools.d.ts.map +1 -1
- 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-Cb7H3YfN.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-Cb7H3YfN.js";
|
|
3
3
|
class AparteOption extends HTMLElement {
|
|
4
4
|
static get observedAttributes() {
|
|
5
5
|
return ["value", "disabled", "selected", "data-status"];
|
|
@@ -631,6 +631,222 @@ 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);
|
|
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 answers the model `cancel` — the model
|
|
673
|
+
* hears the user refuse a question the user never saw.
|
|
674
|
+
*
|
|
675
|
+
* See {@link AparteConfigAware}.
|
|
676
|
+
*/
|
|
677
|
+
aparteConfigChanged(next, previous) {
|
|
678
|
+
if (previous.getElicitationPresenter() === this._present) previous.setElicitationPresenter(null);
|
|
679
|
+
next.setElicitationPresenter(this._present);
|
|
680
|
+
}
|
|
681
|
+
disconnectedCallback() {
|
|
682
|
+
const cfg = resolveConfig(this);
|
|
683
|
+
if (cfg.getElicitationPresenter() === this._present) cfg.setElicitationPresenter(null);
|
|
684
|
+
window.removeEventListener("aparte-message-aborted", this._onTurnEnd);
|
|
685
|
+
window.removeEventListener("aparte-message-error", this._onTurnEnd);
|
|
686
|
+
this._unsubscribeConfig?.();
|
|
687
|
+
this._unsubscribeConfig = null;
|
|
688
|
+
this._cancelPending();
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Re-apply the open question's strings, in place.
|
|
692
|
+
*
|
|
693
|
+
* Two owners, and both have to move or the panel goes bilingual: the panel's own
|
|
694
|
+
* defaults (`relabel`), and the Skip button, which this file builds and this file
|
|
695
|
+
* therefore has to re-text. The composer's one button is a third, and it already
|
|
696
|
+
* follows — `aparte-composer-send` remembers the panel state it was given.
|
|
697
|
+
*/
|
|
698
|
+
_relabelPending() {
|
|
699
|
+
if (!this._pending) return;
|
|
700
|
+
const cfg = resolveConfig(this);
|
|
701
|
+
runWithConfig(cfg, () => this._pending.relabel());
|
|
702
|
+
}
|
|
703
|
+
_present = (request) => {
|
|
704
|
+
const composer = this._getComposer();
|
|
705
|
+
if (!composer) return Promise.reject(new AparteElicitationAbortError("no-presenter"));
|
|
706
|
+
return new Promise((resolve, reject) => {
|
|
707
|
+
let done = false;
|
|
708
|
+
const slot = {};
|
|
709
|
+
const close = () => {
|
|
710
|
+
if (done) return false;
|
|
711
|
+
done = true;
|
|
712
|
+
this._pending = null;
|
|
713
|
+
composer.hidePanel(slot.token);
|
|
714
|
+
return true;
|
|
715
|
+
};
|
|
716
|
+
const settle = (result) => {
|
|
717
|
+
if (close()) resolve(result);
|
|
718
|
+
};
|
|
719
|
+
const fail = (reason = "aborted") => {
|
|
720
|
+
if (close()) reject(new AparteElicitationAbortError(reason));
|
|
721
|
+
};
|
|
722
|
+
if (request.signal) {
|
|
723
|
+
if (request.signal.aborted) {
|
|
724
|
+
fail();
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
request.signal.addEventListener("abort", () => fail(), { once: true });
|
|
728
|
+
}
|
|
729
|
+
const cfg = resolveConfig(this);
|
|
730
|
+
if (request.kind === "approval") {
|
|
731
|
+
const panel2 = runWithConfig(cfg, () => buildApprovalPanel(request.message, request.options ?? [], () => {
|
|
732
|
+
composer.setPanelSubmitEnabled(panel2.isComplete(), "submit");
|
|
733
|
+
}));
|
|
734
|
+
panel2.onSettle((answer) => settle({ action: "accept", content: answer }));
|
|
735
|
+
this._pending = { abort: () => fail(), composer, relabel: () => panel2.relabel() };
|
|
736
|
+
slot.token = composer.showPanel(panel2.el, {
|
|
737
|
+
submitEnabled: panel2.isComplete(),
|
|
738
|
+
mode: "submit",
|
|
739
|
+
// The composer's button carries the INSTRUCTION, which is written
|
|
740
|
+
// text — exactly the act that button already means. The options
|
|
741
|
+
// never route through it: a decision is its own click.
|
|
742
|
+
onSubmit: () => {
|
|
743
|
+
if (panel2.isComplete()) settle({ action: "accept", content: panel2.getContent() });
|
|
744
|
+
},
|
|
745
|
+
onEvict: () => fail()
|
|
746
|
+
});
|
|
747
|
+
panel2.focus();
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
const schema = request.schema ?? { type: "string" };
|
|
751
|
+
const panel = runWithConfig(cfg, () => buildElicitationPanel(request.message, schema, () => {
|
|
752
|
+
composer.setPanelSubmitEnabled(panel.canProceed(), panel.mode());
|
|
753
|
+
}));
|
|
754
|
+
const skip = document.createElement("button");
|
|
755
|
+
skip.type = "button";
|
|
756
|
+
skip.className = "aparte-elic-skip";
|
|
757
|
+
skip.textContent = cfg.t("elicitationSkip");
|
|
758
|
+
skip.addEventListener("click", () => settle({ action: "decline" }));
|
|
759
|
+
panel.dismiss.appendChild(skip);
|
|
760
|
+
this._pending = {
|
|
761
|
+
abort: () => fail(),
|
|
762
|
+
composer,
|
|
763
|
+
relabel: () => {
|
|
764
|
+
panel.relabel();
|
|
765
|
+
skip.textContent = resolveConfig(this).t("elicitationSkip");
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
slot.token = composer.showPanel(panel.el, {
|
|
769
|
+
submitEnabled: panel.canProceed(),
|
|
770
|
+
mode: panel.mode(),
|
|
771
|
+
/*
|
|
772
|
+
* Something else took the slot — another request, a conversation switch,
|
|
773
|
+
* or a turn ending. The composer tears the panel down either way; only
|
|
774
|
+
* this callback can settle the promise, and without it the request hung
|
|
775
|
+
* AND `_pending` stayed set, so every later question was short-circuited
|
|
776
|
+
* for the life of the page. `cancel`, not `decline`: nobody declined
|
|
777
|
+
* anything, the question was taken away.
|
|
778
|
+
*/
|
|
779
|
+
onEvict: () => fail(),
|
|
780
|
+
onSubmit: () => {
|
|
781
|
+
if (panel.mode() === "advance") {
|
|
782
|
+
panel.proceed();
|
|
783
|
+
composer.setPanelSubmitEnabled(panel.canProceed(), panel.mode());
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
if (panel.isComplete()) settle({ action: "accept", content: panel.getContent() });
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
panel.focus();
|
|
790
|
+
});
|
|
791
|
+
};
|
|
792
|
+
_cancelPending() {
|
|
793
|
+
this._pending?.abort();
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* The host id of the chat whose composer holds the open panel.
|
|
797
|
+
*
|
|
798
|
+
* Walks up from the composer rather than from `this`, because the panel lives in
|
|
799
|
+
* the composer and that is what the turn belongs to. Matches the hosts
|
|
800
|
+
* `aparte-chat-bubble._resolveTargetId()` matches, for the reason written there:
|
|
801
|
+
* Angular's wrapper root IS the `<aparte-chat>` element, while the plain-root
|
|
802
|
+
* wrappers render a `[data-aparte-chat]` div instead, so matching only the tag
|
|
803
|
+
* resolves `undefined` on three wrappers out of four.
|
|
804
|
+
*/
|
|
805
|
+
_pendingTargetId() {
|
|
806
|
+
let el = this._pending?.composer ?? null;
|
|
807
|
+
while (el) {
|
|
808
|
+
const tag = el.tagName?.toLowerCase();
|
|
809
|
+
const isHost = tag === "aparte-chat" || tag === "aparte-chat-component" || el.hasAttribute?.("data-aparte-chat");
|
|
810
|
+
if (isHost && el.id) return el.id;
|
|
811
|
+
el = el.parentElement;
|
|
812
|
+
}
|
|
813
|
+
return void 0;
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* The composer to present in: the nearest one in an ancestor subtree, and
|
|
817
|
+
* nothing else.
|
|
818
|
+
*
|
|
819
|
+
* There used to be a `document.querySelector('aparte-composer')` fallback, which
|
|
820
|
+
* is the "first chat on the page" bug this repo has now fixed in four other
|
|
821
|
+
* places: on a page with two chats, an elicitation that could not find its own
|
|
822
|
+
* composer mounted its panel in the OTHER chat's — so one conversation's question
|
|
823
|
+
* appeared under the other conversation, and answering it resolved a tool call
|
|
824
|
+
* belonging to a chat the user was not looking at.
|
|
825
|
+
*
|
|
826
|
+
* Returning `null` instead resolves `cancel`, which is honest: nothing was
|
|
827
|
+
* shown, so nothing was answered. The warning names the fix, because this is a
|
|
828
|
+
* setup mistake and only the developer can correct it — the guide's own example
|
|
829
|
+
* puts `<aparte-elicitation>` inside `<aparte-chat>`.
|
|
830
|
+
*/
|
|
831
|
+
_getComposer() {
|
|
832
|
+
let node = this.parentElement;
|
|
833
|
+
while (node) {
|
|
834
|
+
const composer = node.querySelector("aparte-composer");
|
|
835
|
+
if (composer && typeof composer.showPanel === "function") return composer;
|
|
836
|
+
const tag = node.tagName?.toLowerCase();
|
|
837
|
+
const isChatBoundary = tag === "aparte-chat" || tag === "aparte-chat-component" || node.hasAttribute?.("data-aparte-chat");
|
|
838
|
+
if (isChatBoundary) break;
|
|
839
|
+
node = node.parentElement;
|
|
840
|
+
}
|
|
841
|
+
console.warn(
|
|
842
|
+
"[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."
|
|
843
|
+
);
|
|
844
|
+
return null;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
if (typeof customElements !== "undefined" && !customElements.get("aparte-elicitation")) {
|
|
848
|
+
customElements.define("aparte-elicitation", AparteElicitation);
|
|
849
|
+
}
|
|
634
850
|
class AparteChat extends HTMLElement {
|
|
635
851
|
static get observedAttributes() {
|
|
636
852
|
return ["placeholder", "disabled", "center-empty", "attachments"];
|
|
@@ -683,6 +899,7 @@ class AparteChat extends HTMLElement {
|
|
|
683
899
|
const attachments = this.hasAttribute("attachments");
|
|
684
900
|
this.innerHTML = `
|
|
685
901
|
<aparte-chat-viewport></aparte-chat-viewport>
|
|
902
|
+
<aparte-elicitation></aparte-elicitation>
|
|
686
903
|
<aparte-composer${composerAttrs}>
|
|
687
904
|
<div class="aparte-composer-shell">
|
|
688
905
|
${attachments ? "<aparte-composer-attachments></aparte-composer-attachments>" : ""}
|
|
@@ -935,7 +1152,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
935
1152
|
}
|
|
936
1153
|
/** Update a specific segment */
|
|
937
1154
|
updateSegment(segmentId, updates) {
|
|
938
|
-
const index = this._segments.findIndex((
|
|
1155
|
+
const index = this._segments.findIndex((s) => s.id === segmentId);
|
|
939
1156
|
if (index !== -1) {
|
|
940
1157
|
const updated = mergeSegmentUpdate(this._segments[index], updates);
|
|
941
1158
|
this._segments[index] = updated;
|
|
@@ -944,7 +1161,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
944
1161
|
}
|
|
945
1162
|
/** Append content to a segment */
|
|
946
1163
|
appendToSegment(segmentId, content) {
|
|
947
|
-
const segment = this._segments.find((
|
|
1164
|
+
const segment = this._segments.find((s) => s.id === segmentId);
|
|
948
1165
|
if (segment && "content" in segment) {
|
|
949
1166
|
segment.content += content;
|
|
950
1167
|
this._applySegmentUpdate(segmentId, segment, { content: segment.content });
|
|
@@ -970,7 +1187,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
970
1187
|
* the human-in-the-loop control.
|
|
971
1188
|
*/
|
|
972
1189
|
removeSegment(segmentId) {
|
|
973
|
-
const index = this._segments.findIndex((
|
|
1190
|
+
const index = this._segments.findIndex((s) => s.id === segmentId);
|
|
974
1191
|
if (index !== -1) {
|
|
975
1192
|
this._segments.splice(index, 1);
|
|
976
1193
|
}
|
|
@@ -1053,7 +1270,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1053
1270
|
} else {
|
|
1054
1271
|
warnMissingRenderer(segment.type);
|
|
1055
1272
|
const fallback = document.createElement("div");
|
|
1056
|
-
fallback.className = "segment segment-unknown";
|
|
1273
|
+
fallback.className = "segment aparte-segment-unknown";
|
|
1057
1274
|
fallback.textContent = `[Unknown segment type: ${segment.type}]`;
|
|
1058
1275
|
this._segmentsEl.appendChild(fallback);
|
|
1059
1276
|
}
|
|
@@ -1282,7 +1499,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1282
1499
|
} else {
|
|
1283
1500
|
warnMissingRenderer(segment.type);
|
|
1284
1501
|
const fallback = document.createElement("div");
|
|
1285
|
-
fallback.className = "segment segment-unknown";
|
|
1502
|
+
fallback.className = "segment aparte-segment-unknown";
|
|
1286
1503
|
fallback.textContent = `[Unknown segment type: ${segment.type}]`;
|
|
1287
1504
|
this._segmentsEl.appendChild(fallback);
|
|
1288
1505
|
}
|
|
@@ -1302,7 +1519,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1302
1519
|
_reflectError() {
|
|
1303
1520
|
const message = this.querySelector(".aparte-message");
|
|
1304
1521
|
if (!message) return;
|
|
1305
|
-
const hasError = this._segments.some((
|
|
1522
|
+
const hasError = this._segments.some((s) => s.type === "error");
|
|
1306
1523
|
if (hasError) message.setAttribute("data-error", "");
|
|
1307
1524
|
else message.removeAttribute("data-error");
|
|
1308
1525
|
}
|
|
@@ -1337,18 +1554,18 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1337
1554
|
const customAttachment = this._cfg.getAttachmentRenderer?.();
|
|
1338
1555
|
if (customAttachment) {
|
|
1339
1556
|
this._attachmentsEl.replaceChildren();
|
|
1340
|
-
for (const
|
|
1341
|
-
const el = segmentRenderResultToElement(runWithConfig(this._cfg, () => customAttachment(
|
|
1557
|
+
for (const a4 of this._attachments) {
|
|
1558
|
+
const el = segmentRenderResultToElement(runWithConfig(this._cfg, () => customAttachment(a4)));
|
|
1342
1559
|
if (el) this._attachmentsEl.appendChild(el);
|
|
1343
1560
|
}
|
|
1344
1561
|
return;
|
|
1345
1562
|
}
|
|
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(
|
|
1563
|
+
this._attachmentsEl.innerHTML = this._attachments.map((a4) => {
|
|
1564
|
+
const name = escapeHtml(a4.name);
|
|
1565
|
+
if (a4.type.startsWith("image/")) {
|
|
1566
|
+
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
1567
|
}
|
|
1351
|
-
return `<div class="aparte-thumb aparte-thumb--file" title="${name}"><span class="aparte-thumb__ext">${escapeHtml(this._fileExt(
|
|
1568
|
+
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
1569
|
}).join("");
|
|
1353
1570
|
if (!this._cfg.getHostHandlers().attachmentPreview) return;
|
|
1354
1571
|
this._attachmentsEl.querySelectorAll(".aparte-thumb--image").forEach((tile) => {
|
|
@@ -1453,14 +1670,14 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1453
1670
|
const buttons = [];
|
|
1454
1671
|
if (this._role === "user") {
|
|
1455
1672
|
if (config.user) {
|
|
1456
|
-
for (const
|
|
1673
|
+
for (const a4 of config.user) buttons.push(this._actionButtonHtml(a4, icons, locale));
|
|
1457
1674
|
} else {
|
|
1458
1675
|
if (config.copy) buttons.push(this._actionButtonHtml("copy", icons, locale));
|
|
1459
1676
|
if (config.edit) buttons.push(this._actionButtonHtml("edit", icons, locale));
|
|
1460
1677
|
}
|
|
1461
1678
|
} else if (this._role === "assistant") {
|
|
1462
1679
|
if (config.assistant) {
|
|
1463
|
-
for (const
|
|
1680
|
+
for (const a4 of config.assistant) buttons.push(this._actionButtonHtml(a4, icons, locale));
|
|
1464
1681
|
} else {
|
|
1465
1682
|
if (config.copy) buttons.push(this._actionButtonHtml("copy", icons, locale));
|
|
1466
1683
|
if (config.retry) buttons.push(this._actionButtonHtml("retry", icons, locale));
|
|
@@ -1495,16 +1712,16 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1495
1712
|
/** Append the registered custom action buttons for this bubble's role. */
|
|
1496
1713
|
_appendCustomActions(icons) {
|
|
1497
1714
|
if (!this._actionBarEl) return;
|
|
1498
|
-
for (const
|
|
1499
|
-
const roles =
|
|
1715
|
+
for (const a4 of this._cfg.getActions("bubble")) {
|
|
1716
|
+
const roles = a4.bubble?.roles ?? ["user", "assistant"];
|
|
1500
1717
|
if (!roles.includes(this._role)) continue;
|
|
1501
1718
|
const btn = document.createElement("button");
|
|
1502
1719
|
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 =
|
|
1720
|
+
btn.dataset["action"] = `custom:${a4.id}`;
|
|
1721
|
+
btn.setAttribute("aria-label", a4.label);
|
|
1722
|
+
btn.setAttribute("title", a4.label);
|
|
1723
|
+
const fromProvider = icons[a4.icon];
|
|
1724
|
+
btn.innerHTML = a4.icon.startsWith("<") ? a4.icon : typeof fromProvider === "function" ? fromProvider() : a4.iconFallback ?? "";
|
|
1508
1725
|
this._actionBarEl.appendChild(btn);
|
|
1509
1726
|
}
|
|
1510
1727
|
}
|
|
@@ -1570,12 +1787,12 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1570
1787
|
composed: true,
|
|
1571
1788
|
detail
|
|
1572
1789
|
}));
|
|
1573
|
-
this._cfg.getActions("bubble").find((
|
|
1790
|
+
this._cfg.getActions("bubble").find((x) => x.id === actionId)?.onClick?.(e2);
|
|
1574
1791
|
return;
|
|
1575
1792
|
}
|
|
1576
1793
|
switch (action) {
|
|
1577
1794
|
case "copy": {
|
|
1578
|
-
const text = this._content || this._segments.map((
|
|
1795
|
+
const text = this._content || this._segments.map((s) => s.content ?? "").join("\n");
|
|
1579
1796
|
const icons = this._cfg.getIconProvider();
|
|
1580
1797
|
const locale = this._cfg.getLocale();
|
|
1581
1798
|
navigator.clipboard.writeText(text).then(() => {
|
|
@@ -2013,7 +2230,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2013
2230
|
if (!message.segments) {
|
|
2014
2231
|
message.segments = [];
|
|
2015
2232
|
}
|
|
2016
|
-
const index = message.segments.findIndex((
|
|
2233
|
+
const index = message.segments.findIndex((s) => s.id === segmentId);
|
|
2017
2234
|
const segment = index === -1 ? void 0 : message.segments[index];
|
|
2018
2235
|
if (segment && "content" in segment) {
|
|
2019
2236
|
message.segments[index] = {
|
|
@@ -2075,15 +2292,15 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2075
2292
|
this._notifyBubble(messageId, "addSegment", stamped);
|
|
2076
2293
|
this._autoScroll();
|
|
2077
2294
|
}
|
|
2078
|
-
updateSegment(
|
|
2295
|
+
updateSegment(a4, b2, c2) {
|
|
2079
2296
|
const clientForm = c2 === void 0 && typeof b2 === "object";
|
|
2080
|
-
const messageId = clientForm ? this._activeMessageId() :
|
|
2081
|
-
const segmentId = clientForm ?
|
|
2297
|
+
const messageId = clientForm ? this._activeMessageId() : a4;
|
|
2298
|
+
const segmentId = clientForm ? a4 : b2;
|
|
2082
2299
|
const updates = clientForm ? b2 : c2;
|
|
2083
2300
|
if (!messageId) return;
|
|
2084
2301
|
const message = this._repo.getMessageById(messageId);
|
|
2085
2302
|
if (!message?.segments) return;
|
|
2086
|
-
const segmentIndex = message.segments.findIndex((
|
|
2303
|
+
const segmentIndex = message.segments.findIndex((s) => s.id === segmentId);
|
|
2087
2304
|
if (segmentIndex !== -1) {
|
|
2088
2305
|
const current = message.segments[segmentIndex];
|
|
2089
2306
|
const stamped = stampSegmentOnUpdate(current, updates);
|
|
@@ -2091,14 +2308,14 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2091
2308
|
this._notifyBubble(messageId, "updateSegment", { segmentId, updates: stamped });
|
|
2092
2309
|
}
|
|
2093
2310
|
}
|
|
2094
|
-
removeSegment(
|
|
2311
|
+
removeSegment(a4, b2) {
|
|
2095
2312
|
const clientForm = b2 === void 0;
|
|
2096
|
-
const messageId = clientForm ? this._activeMessageId() :
|
|
2097
|
-
const segmentId = clientForm ?
|
|
2313
|
+
const messageId = clientForm ? this._activeMessageId() : a4;
|
|
2314
|
+
const segmentId = clientForm ? a4 : b2;
|
|
2098
2315
|
if (!messageId || !segmentId) return;
|
|
2099
2316
|
const message = this._repo.getMessageById(messageId);
|
|
2100
2317
|
if (message?.segments) {
|
|
2101
|
-
const idx = message.segments.findIndex((
|
|
2318
|
+
const idx = message.segments.findIndex((s) => s.id === segmentId);
|
|
2102
2319
|
if (idx !== -1) {
|
|
2103
2320
|
message.segments.splice(idx, 1);
|
|
2104
2321
|
renumberSegments(message.segments);
|
|
@@ -2930,6 +3147,20 @@ class AparteComposer extends HTMLElement {
|
|
|
2930
3147
|
_panelMode = "submit";
|
|
2931
3148
|
_panelSubmitEnabled = false;
|
|
2932
3149
|
_panelOnSubmit = null;
|
|
3150
|
+
/**
|
|
3151
|
+
* Who owns the one panel slot, and how to tell them they lost it.
|
|
3152
|
+
*
|
|
3153
|
+
* There is exactly one slot, `showPanel` empties it unconditionally, and nothing
|
|
3154
|
+
* used to say whose it was. Three paths therefore closed a panel whose owner was
|
|
3155
|
+
* still awaiting an answer, and none of them told the owner: a second `showPanel`,
|
|
3156
|
+
* the owner-of-record's own `hidePanel`, and — the one that actually bit —
|
|
3157
|
+
* `_handleMessageDone`, which fires on EVERY turn end. A question still open when a
|
|
3158
|
+
* turn finished lost its panel while `<aparte-elicitation>` kept `_pending` set,
|
|
3159
|
+
* so the request never settled AND every later request was short-circuited for the
|
|
3160
|
+
* life of the page.
|
|
3161
|
+
*/
|
|
3162
|
+
_panelToken = null;
|
|
3163
|
+
_panelOnEvict = null;
|
|
2933
3164
|
// Internal bus events that represent an observable STATE change — these are
|
|
2934
3165
|
// mirrored to the public `aparte-composer-change` DOM event. `submit`/`cancel`
|
|
2935
3166
|
// are actions, not state, and are covered by `aparte-send`/`aparte-cancel`.
|
|
@@ -3097,9 +3328,14 @@ class AparteComposer extends HTMLElement {
|
|
|
3097
3328
|
* used to set on a child: an attribute is themeable, is visible to a consumer's
|
|
3098
3329
|
* own rules, and does not clobber a `display` the consumer had set (the restore
|
|
3099
3330
|
* wrote `''`, not the previous value).
|
|
3331
|
+
*
|
|
3332
|
+
* Returns the TOKEN for this panel. Pass it back to `hidePanel` so a presenter
|
|
3333
|
+
* that has already lost the slot cannot close the panel that replaced it, and
|
|
3334
|
+
* supply `onEvict` to be told when that happens — an owner that is not told is an
|
|
3335
|
+
* owner whose promise nobody can settle.
|
|
3100
3336
|
*/
|
|
3101
3337
|
showPanel(panel, options) {
|
|
3102
|
-
this.
|
|
3338
|
+
this._evictPanel();
|
|
3103
3339
|
const inputEl = this.querySelector("aparte-composer-input");
|
|
3104
3340
|
this.setAttribute("data-panel-active", "");
|
|
3105
3341
|
panel.dataset["apartePanel"] = "true";
|
|
@@ -3112,10 +3348,31 @@ class AparteComposer extends HTMLElement {
|
|
|
3112
3348
|
this._panelSubmitEnabled = options?.submitEnabled ?? false;
|
|
3113
3349
|
this._panelMode = options?.mode ?? "submit";
|
|
3114
3350
|
this._panelOnSubmit = options?.onSubmit ?? null;
|
|
3351
|
+
this._panelOnEvict = options?.onEvict ?? null;
|
|
3352
|
+
const token = Symbol("aparte-composer-panel");
|
|
3353
|
+
this._panelToken = token;
|
|
3115
3354
|
this._emit("panel-change", { active: true, submitEnabled: this._panelSubmitEnabled, mode: this._panelMode });
|
|
3355
|
+
return token;
|
|
3116
3356
|
}
|
|
3117
|
-
/**
|
|
3118
|
-
|
|
3357
|
+
/**
|
|
3358
|
+
* Remove the panel and restore the composer's own controls.
|
|
3359
|
+
*
|
|
3360
|
+
* With a `token`, this closes the panel only if that token still owns the slot —
|
|
3361
|
+
* so a presenter settling late cannot tear down the panel that replaced it. With
|
|
3362
|
+
* no token it closes whatever is there, which is what a consumer driving the
|
|
3363
|
+
* composer directly means, and what `reset()` needs.
|
|
3364
|
+
*/
|
|
3365
|
+
hidePanel(token) {
|
|
3366
|
+
if (token !== void 0 && token !== this._panelToken) return;
|
|
3367
|
+
this._teardownPanel();
|
|
3368
|
+
}
|
|
3369
|
+
/** Close the panel AND tell its owner, so a pending request never orphans. */
|
|
3370
|
+
_evictPanel() {
|
|
3371
|
+
const onEvict = this._panelOnEvict;
|
|
3372
|
+
this._teardownPanel();
|
|
3373
|
+
onEvict?.();
|
|
3374
|
+
}
|
|
3375
|
+
_teardownPanel() {
|
|
3119
3376
|
const existing = this.querySelector("[data-aparte-panel]");
|
|
3120
3377
|
if (existing) existing.remove();
|
|
3121
3378
|
this.removeAttribute("data-panel-active");
|
|
@@ -3123,6 +3380,8 @@ class AparteComposer extends HTMLElement {
|
|
|
3123
3380
|
this._panelSubmitEnabled = false;
|
|
3124
3381
|
this._panelMode = "submit";
|
|
3125
3382
|
this._panelOnSubmit = null;
|
|
3383
|
+
this._panelOnEvict = null;
|
|
3384
|
+
this._panelToken = null;
|
|
3126
3385
|
this._emit("panel-change", { active: false, submitEnabled: false, mode: "submit" });
|
|
3127
3386
|
this.focus();
|
|
3128
3387
|
}
|
|
@@ -3200,7 +3459,7 @@ class AparteComposer extends HTMLElement {
|
|
|
3200
3459
|
reset() {
|
|
3201
3460
|
this.setValue("");
|
|
3202
3461
|
this.clearAttachments();
|
|
3203
|
-
if (this._panelActive) this.
|
|
3462
|
+
if (this._panelActive) this._evictPanel();
|
|
3204
3463
|
}
|
|
3205
3464
|
/** Focus the input primitive inside this composer. */
|
|
3206
3465
|
focus() {
|
|
@@ -3269,7 +3528,7 @@ class AparteComposer extends HTMLElement {
|
|
|
3269
3528
|
if (!this._isForThisComposer(e2)) return;
|
|
3270
3529
|
this._streaming = false;
|
|
3271
3530
|
this._emit("streaming-change", { streaming: false });
|
|
3272
|
-
if (this._panelActive) this.
|
|
3531
|
+
if (this._panelActive) this._evictPanel();
|
|
3273
3532
|
}
|
|
3274
3533
|
}
|
|
3275
3534
|
if (!customElements.get("aparte-composer")) {
|
|
@@ -3431,10 +3690,10 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3431
3690
|
}
|
|
3432
3691
|
}
|
|
3433
3692
|
_handleFocus() {
|
|
3434
|
-
this.classList.add("is-focused");
|
|
3693
|
+
this.classList.add("aparte-is-focused");
|
|
3435
3694
|
}
|
|
3436
3695
|
_handleBlur() {
|
|
3437
|
-
this.classList.remove("is-focused");
|
|
3696
|
+
this.classList.remove("aparte-is-focused");
|
|
3438
3697
|
}
|
|
3439
3698
|
_handlePaste(e2) {
|
|
3440
3699
|
e2.preventDefault();
|
|
@@ -3477,9 +3736,9 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3477
3736
|
}
|
|
3478
3737
|
_updatePlaceholder() {
|
|
3479
3738
|
if (this._editor) {
|
|
3480
|
-
const
|
|
3481
|
-
this._editor.setAttribute("data-placeholder",
|
|
3482
|
-
this._editor.setAttribute("aria-label",
|
|
3739
|
+
const p = this._getPlaceholder();
|
|
3740
|
+
this._editor.setAttribute("data-placeholder", p);
|
|
3741
|
+
this._editor.setAttribute("aria-label", p);
|
|
3483
3742
|
}
|
|
3484
3743
|
}
|
|
3485
3744
|
_updatePlaceholderVisibility() {
|
|
@@ -3605,7 +3864,7 @@ class AparteComposerSend extends HTMLElement {
|
|
|
3605
3864
|
const label = advancing ? cfg.t("elicitationNext") || "Next" : cfg.t("submitButton") || "Submit";
|
|
3606
3865
|
this._button.setAttribute("aria-label", label);
|
|
3607
3866
|
this._button.setAttribute("title", label);
|
|
3608
|
-
this._button.classList.remove("is-streaming");
|
|
3867
|
+
this._button.classList.remove("aparte-is-streaming");
|
|
3609
3868
|
}
|
|
3610
3869
|
_handleClick(e2) {
|
|
3611
3870
|
e2.preventDefault();
|
|
@@ -3621,7 +3880,7 @@ class AparteComposerSend extends HTMLElement {
|
|
|
3621
3880
|
const label = resolveConfig(this).t("sendButton") || "Send";
|
|
3622
3881
|
this._button.setAttribute("aria-label", label);
|
|
3623
3882
|
this._button.setAttribute("title", label);
|
|
3624
|
-
this._button.classList.remove("is-streaming");
|
|
3883
|
+
this._button.classList.remove("aparte-is-streaming");
|
|
3625
3884
|
}
|
|
3626
3885
|
_syncStreamingState(streaming) {
|
|
3627
3886
|
if (!this._button) return;
|
|
@@ -3631,7 +3890,7 @@ class AparteComposerSend extends HTMLElement {
|
|
|
3631
3890
|
const label = resolveConfig(this).t("stopButton") || "Stop";
|
|
3632
3891
|
this._button.setAttribute("aria-label", label);
|
|
3633
3892
|
this._button.setAttribute("title", label);
|
|
3634
|
-
this._button.classList.add("is-streaming");
|
|
3893
|
+
this._button.classList.add("aparte-is-streaming");
|
|
3635
3894
|
} else {
|
|
3636
3895
|
this._syncState();
|
|
3637
3896
|
}
|
|
@@ -3906,15 +4165,15 @@ class AparteComposerAddAttachment extends HTMLElement {
|
|
|
3906
4165
|
const onDragOver = (e2) => {
|
|
3907
4166
|
if (root.disabled) return;
|
|
3908
4167
|
prevent(e2);
|
|
3909
|
-
root.classList.add("is-dragover");
|
|
4168
|
+
root.classList.add("aparte-is-dragover");
|
|
3910
4169
|
};
|
|
3911
4170
|
const onDragLeave = (e2) => {
|
|
3912
4171
|
prevent(e2);
|
|
3913
|
-
root.classList.remove("is-dragover");
|
|
4172
|
+
root.classList.remove("aparte-is-dragover");
|
|
3914
4173
|
};
|
|
3915
4174
|
const onDrop = (e2) => {
|
|
3916
4175
|
prevent(e2);
|
|
3917
|
-
root.classList.remove("is-dragover");
|
|
4176
|
+
root.classList.remove("aparte-is-dragover");
|
|
3918
4177
|
if (root.disabled) return;
|
|
3919
4178
|
const files = e2.dataTransfer?.files;
|
|
3920
4179
|
if (files?.length) this._getRoot()?.addAttachments(files);
|
|
@@ -4207,180 +4466,6 @@ class AparteConversationList extends HTMLElement {
|
|
|
4207
4466
|
}
|
|
4208
4467
|
}
|
|
4209
4468
|
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
4469
|
function registerAllComponents() {
|
|
4385
4470
|
const _chat = customElements.get("aparte-chat");
|
|
4386
4471
|
const _viewport = customElements.get("aparte-chat-viewport");
|
|
@@ -4421,53 +4506,55 @@ export {
|
|
|
4421
4506
|
n as AparteConversationManager,
|
|
4422
4507
|
o as AparteDirectTransport,
|
|
4423
4508
|
AparteElicitation,
|
|
4424
|
-
|
|
4425
|
-
q as
|
|
4509
|
+
AparteElicitationAbortError,
|
|
4510
|
+
q as AparteError,
|
|
4511
|
+
r as AparteErrorCode,
|
|
4426
4512
|
AparteMessageRepository,
|
|
4427
4513
|
AparteOptgroup,
|
|
4428
4514
|
AparteOption,
|
|
4429
4515
|
AparteProgressSpinner,
|
|
4430
4516
|
AparteSelect,
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4517
|
+
t as AparteStreamParser,
|
|
4518
|
+
u as aparteGlobalConfig,
|
|
4519
|
+
v as applyElementProps,
|
|
4520
|
+
w as attachConfig,
|
|
4521
|
+
buildApprovalPanel,
|
|
4435
4522
|
buildElicitationPanel,
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4523
|
+
z as collectRendererStyles,
|
|
4524
|
+
B as contentToText,
|
|
4525
|
+
C as contextConfig,
|
|
4526
|
+
D as createAparteChatHandler,
|
|
4527
|
+
E as createStreamAdapter,
|
|
4441
4528
|
cssEscape,
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4529
|
+
G as declineDefaultRenderers,
|
|
4530
|
+
H as defaultSanitizer,
|
|
4531
|
+
I as deriveArtifactKind,
|
|
4532
|
+
J as detachConfig,
|
|
4446
4533
|
escapeAttr,
|
|
4447
4534
|
escapeHtml,
|
|
4448
|
-
|
|
4449
|
-
|
|
4535
|
+
M as filesToAttachments,
|
|
4536
|
+
N as getAllRenderers,
|
|
4450
4537
|
getSegmentRenderer,
|
|
4451
4538
|
installDefaultRenderersOnce,
|
|
4452
4539
|
isAwaitingReply,
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4540
|
+
R as isFormatAdapter,
|
|
4541
|
+
S as isSafeUrl,
|
|
4542
|
+
T as isSegmentSettled,
|
|
4543
|
+
U as parseAparteEventStream,
|
|
4544
|
+
V as parseMarkdownToSegments,
|
|
4458
4545
|
populateBubbleFromMessage,
|
|
4459
|
-
|
|
4546
|
+
X as readableToAsyncIterable,
|
|
4460
4547
|
registerAllComponents,
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4548
|
+
Y as registerDefaultRenderers,
|
|
4549
|
+
Z as registerSegmentRenderer,
|
|
4550
|
+
_ as requestUserInput,
|
|
4464
4551
|
resolveConfig,
|
|
4465
4552
|
revokeAttachmentUrls,
|
|
4466
4553
|
runWithConfig,
|
|
4467
|
-
|
|
4468
|
-
|
|
4554
|
+
a2 as segmentDuration,
|
|
4555
|
+
a3 as segmentTiming,
|
|
4469
4556
|
subscribeConfigChange,
|
|
4470
|
-
|
|
4557
|
+
a5 as unregisterSegmentRenderer,
|
|
4471
4558
|
uuid
|
|
4472
4559
|
};
|
|
4473
4560
|
//# sourceMappingURL=index.js.map
|