@helpai/elements 0.50.8 → 0.51.1
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/elements-web-component.esm.js +22 -22
- package/elements-web-component.esm.js.map +3 -3
- package/elements.cjs.js +22 -22
- package/elements.cjs.js.map +3 -3
- package/elements.esm.js +22 -22
- package/elements.esm.js.map +3 -3
- package/elements.js +22 -22
- package/elements.js.map +3 -3
- package/index.mjs +130 -24
- package/package.json +1 -1
- package/web-component.mjs +130 -24
package/web-component.mjs
CHANGED
|
@@ -1794,7 +1794,7 @@ function createAuth(opts) {
|
|
|
1794
1794
|
}
|
|
1795
1795
|
|
|
1796
1796
|
// src/core/version.ts
|
|
1797
|
-
var ELEMENTS_VERSION = true ? "0.
|
|
1797
|
+
var ELEMENTS_VERSION = true ? "0.51.1" : "0.0.0-dev";
|
|
1798
1798
|
var ELEMENTS_VERSION_PARAM = "_ev";
|
|
1799
1799
|
|
|
1800
1800
|
// src/stream/types.ts
|
|
@@ -2071,6 +2071,13 @@ var AgentTransport = class {
|
|
|
2071
2071
|
// (visitor, conversation) pair.
|
|
2072
2072
|
__publicField(this, "visitorId");
|
|
2073
2073
|
__publicField(this, "conversationId");
|
|
2074
|
+
/**
|
|
2075
|
+
* Outcome of the most recent `resumeStream()` once it found NO replayable SSE — drives the App's
|
|
2076
|
+
* dangling-turn recovery. `live` = a stream was replayed; otherwise the server's JSON recovery status.
|
|
2077
|
+
* `needs-generation` means a durable user turn has no reply (e.g. a fast send→refresh aborted the
|
|
2078
|
+
* deferred LLM run) → the App re-sends to generate it. Reset at the start of each `resumeStream()`.
|
|
2079
|
+
*/
|
|
2080
|
+
__publicField(this, "resumeStatus");
|
|
2074
2081
|
// Host-asserted context (sanitised `userContext` / `pageContext`), refreshed via
|
|
2075
2082
|
// `setContext` and carried as the envelope's optional `context` so the backend
|
|
2076
2083
|
// always sees the visitor's latest context as they navigate.
|
|
@@ -2406,7 +2413,7 @@ var AgentTransport = class {
|
|
|
2406
2413
|
const seenIds = /* @__PURE__ */ new Set();
|
|
2407
2414
|
const iter = this.runStream(body, ctrl, seenIds);
|
|
2408
2415
|
const cancel = () => this.cancelStream(ctrl, body.conversationId);
|
|
2409
|
-
return { iter, cancel };
|
|
2416
|
+
return { iter, cancel, detach: () => ctrl.abort() };
|
|
2410
2417
|
}
|
|
2411
2418
|
/**
|
|
2412
2419
|
* Re-attach to an in-flight reply on a cold page load / refresh.
|
|
@@ -2427,8 +2434,10 @@ var AgentTransport = class {
|
|
|
2427
2434
|
resumeStream() {
|
|
2428
2435
|
const ctrl = new AbortController();
|
|
2429
2436
|
const seenIds = /* @__PURE__ */ new Set();
|
|
2437
|
+
this.resumeStatus = void 0;
|
|
2430
2438
|
const iter = this.resumeStreamGen(ctrl, seenIds, true);
|
|
2431
|
-
|
|
2439
|
+
const stop = () => ctrl.abort();
|
|
2440
|
+
return { iter, cancel: stop, detach: stop };
|
|
2432
2441
|
}
|
|
2433
2442
|
// ---- Stream orchestration ---------------------------------------------
|
|
2434
2443
|
async *runStream(body, ctrl, seenIds) {
|
|
@@ -2481,6 +2490,15 @@ var AgentTransport = class {
|
|
|
2481
2490
|
log5.debug("resume \u2192 no in-flight stream", { status: res.status });
|
|
2482
2491
|
return;
|
|
2483
2492
|
}
|
|
2493
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
2494
|
+
if (!contentType.includes("text/event-stream")) {
|
|
2495
|
+
const status = await res.json().then((body) => body.status).catch(() => void 0);
|
|
2496
|
+
log5.debug("resume \u2192 no live stream (status response)", { status, attempt });
|
|
2497
|
+
if (status === "pending" && attempt < MAX_RESUME_ATTEMPTS) continue;
|
|
2498
|
+
this.resumeStatus = status === "needs-generation" ? "needs-generation" : "completed";
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2501
|
+
this.resumeStatus = "live";
|
|
2484
2502
|
const before = seenIds.size;
|
|
2485
2503
|
if (yield* this.drain(parseChatStream(res, ctrl.signal), seenIds, ctrl)) return;
|
|
2486
2504
|
if (seenIds.size === before) {
|
|
@@ -2700,6 +2718,7 @@ function toReactive(m) {
|
|
|
2700
2718
|
role: m.role,
|
|
2701
2719
|
createdAt: m.createdAt,
|
|
2702
2720
|
status: m.status,
|
|
2721
|
+
canceled: m.canceled,
|
|
2703
2722
|
errorText: m.errorText,
|
|
2704
2723
|
finishReason: m.finishReason,
|
|
2705
2724
|
serverMessageId: m.serverMessageId,
|
|
@@ -2763,7 +2782,11 @@ function fromWireMessage(w) {
|
|
|
2763
2782
|
// Real send time when the backend provides it; otherwise fall back to now
|
|
2764
2783
|
// (older backends) so the timestamp + day divider still render.
|
|
2765
2784
|
createdAt: parseWireDate(w.createdAt) ?? Date.now(),
|
|
2766
|
-
status:
|
|
2785
|
+
// Map the persisted terminal status onto the bubble's render state: `failed` → an error bubble;
|
|
2786
|
+
// `canceled` → `complete` + the `canceled` flag (an empty canceled turn is hidden on render — see
|
|
2787
|
+
// `message-bubble`); everything else is a normal completed turn.
|
|
2788
|
+
status: w.status === "failed" ? "error" : "complete",
|
|
2789
|
+
canceled: w.status === "canceled",
|
|
2767
2790
|
partsSig: signal(parts)
|
|
2768
2791
|
};
|
|
2769
2792
|
}
|
|
@@ -2773,6 +2796,7 @@ function fromReactive(m) {
|
|
|
2773
2796
|
role: m.role,
|
|
2774
2797
|
createdAt: m.createdAt,
|
|
2775
2798
|
status: m.status,
|
|
2799
|
+
canceled: m.canceled,
|
|
2776
2800
|
errorText: m.errorText,
|
|
2777
2801
|
finishReason: m.finishReason,
|
|
2778
2802
|
serverMessageId: m.serverMessageId,
|
|
@@ -2795,6 +2819,9 @@ function hasNoVisibleAnswer(m) {
|
|
|
2795
2819
|
function isEmptyAssistantReply(m) {
|
|
2796
2820
|
return m.role === "assistant" && m.status !== "streaming" && hasNoVisibleAnswer(m);
|
|
2797
2821
|
}
|
|
2822
|
+
function isHiddenCanceledTurn(m) {
|
|
2823
|
+
return m.canceled === true && isEmptyAssistantReply(m);
|
|
2824
|
+
}
|
|
2798
2825
|
function partToReactive(p36) {
|
|
2799
2826
|
if (p36.kind === "text" || p36.kind === "reasoning") {
|
|
2800
2827
|
return { kind: p36.kind, id: p36.id, textSig: signal(p36.text), doneSig: signal(p36.done) };
|
|
@@ -2954,6 +2981,8 @@ var StreamReducer = class {
|
|
|
2954
2981
|
return;
|
|
2955
2982
|
case "data-notification":
|
|
2956
2983
|
return;
|
|
2984
|
+
case "data-conversation-rebind":
|
|
2985
|
+
return;
|
|
2957
2986
|
default: {
|
|
2958
2987
|
const _exhaustive = chunk;
|
|
2959
2988
|
void _exhaustive;
|
|
@@ -5666,6 +5695,7 @@ function MessageBubble({
|
|
|
5666
5695
|
const parts = useComputed5(() => message.partsSig.value);
|
|
5667
5696
|
const partList = parts.value;
|
|
5668
5697
|
const emptyReply = useComputed5(() => isEmptyAssistantReply(message));
|
|
5698
|
+
const hideCanceledHusk = useComputed5(() => isHiddenCanceledTurn(message));
|
|
5669
5699
|
const hasAnswerText = useComputed5(
|
|
5670
5700
|
() => message.partsSig.value.some((part) => part.kind === "text" && part.textSig.value.length > 0)
|
|
5671
5701
|
);
|
|
@@ -5676,6 +5706,7 @@ function MessageBubble({
|
|
|
5676
5706
|
const bufferedHold = responseMode === "buffered" && streaming;
|
|
5677
5707
|
const working = streaming && !hasAnswerText.value;
|
|
5678
5708
|
const showStreamDots = streaming && !bufferedHold && !(reasoningVisible.value && working);
|
|
5709
|
+
if (hideCanceledHusk.value) return null;
|
|
5679
5710
|
const stamp = formatStamp(message.createdAt);
|
|
5680
5711
|
return /* @__PURE__ */ jsx18("div", { class: `${p17}-bubble-row`, "data-role": message.role, "data-testid": tid(TID.messageBubble, message.id), children: /* @__PURE__ */ jsxs15("div", { class: `${p17}-bubble-col`, children: [
|
|
5681
5712
|
/* @__PURE__ */ jsxs15("div", { class: `${p17}-bubble`, children: [
|
|
@@ -7549,6 +7580,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7549
7580
|
const [agent, setAgent] = useState14(null);
|
|
7550
7581
|
const [suggestions, setSuggestions] = useState14([]);
|
|
7551
7582
|
const [activeCancel, setActiveCancel] = useState14(null);
|
|
7583
|
+
const [activeDetach, setActiveDetach] = useState14(null);
|
|
7552
7584
|
const stringsRef = useRef9(options.strings);
|
|
7553
7585
|
const [parsedSite, setParsedSite] = useState14(void 0);
|
|
7554
7586
|
const [parsedBlocks, setParsedBlocks] = useState14(void 0);
|
|
@@ -7621,6 +7653,8 @@ function App({ options, hostElement, bus }) {
|
|
|
7621
7653
|
const homeNavSeeded = useRef9(false);
|
|
7622
7654
|
const resumeActiveRef = useRef9(false);
|
|
7623
7655
|
const resumeBubbleRef = useRef9(null);
|
|
7656
|
+
const regenerateDanglingRef = useRef9(null);
|
|
7657
|
+
const regenInFlightRef = useRef9(false);
|
|
7624
7658
|
useEffect16(() => {
|
|
7625
7659
|
if (!conversationReady || homeNavSeeded.current) return;
|
|
7626
7660
|
homeNavSeeded.current = true;
|
|
@@ -7690,6 +7724,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7690
7724
|
} else {
|
|
7691
7725
|
messagesSig.value = withWelcomeRows([], markers);
|
|
7692
7726
|
}
|
|
7727
|
+
if (transport.resumeStatus === "needs-generation") regenerateDanglingRef.current?.();
|
|
7693
7728
|
} catch (err) {
|
|
7694
7729
|
if (isStale()) return;
|
|
7695
7730
|
log16.warn("loadThread failed; resetting conversationId", { err, conversationId });
|
|
@@ -7819,11 +7854,28 @@ function App({ options, hostElement, bus }) {
|
|
|
7819
7854
|
useEffect16(() => {
|
|
7820
7855
|
if (effectiveLocale !== activeLocale) setActiveLocale(effectiveLocale);
|
|
7821
7856
|
}, [effectiveLocale, activeLocale]);
|
|
7857
|
+
const adoptConversationRebind = useCallback6(
|
|
7858
|
+
(chunk) => {
|
|
7859
|
+
if (chunk.type !== "data-conversation-rebind") return false;
|
|
7860
|
+
const next = chunk.data?.conversationId;
|
|
7861
|
+
if (next && next !== conversationIdSig.value) {
|
|
7862
|
+
const previous = conversationIdSig.value;
|
|
7863
|
+
conversationIdSig.value = next;
|
|
7864
|
+
persistence.saveConversationId(next);
|
|
7865
|
+
transport.primeIdentity(void 0, next);
|
|
7866
|
+
log16.info("conversation rebound (in-stream)", { previous, current: next });
|
|
7867
|
+
if (previous) bus.emit("conversationRebound", { previous, current: next, reason: next });
|
|
7868
|
+
}
|
|
7869
|
+
return true;
|
|
7870
|
+
},
|
|
7871
|
+
[conversationIdSig, persistence, transport, bus]
|
|
7872
|
+
);
|
|
7822
7873
|
const runResume = useCallback6(
|
|
7823
7874
|
async (handle) => {
|
|
7824
7875
|
let bubble = null;
|
|
7825
7876
|
try {
|
|
7826
7877
|
for await (const evt of handle.iter) {
|
|
7878
|
+
if (adoptConversationRebind(evt.chunk)) continue;
|
|
7827
7879
|
if (!bubble) {
|
|
7828
7880
|
bubble = makeAssistantMessage();
|
|
7829
7881
|
resumeBubbleRef.current = bubble;
|
|
@@ -7831,6 +7883,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7831
7883
|
messagesSig.value = [...messagesSig.value, bubble];
|
|
7832
7884
|
setStreaming(true);
|
|
7833
7885
|
setActiveCancel(() => handle.cancel);
|
|
7886
|
+
setActiveDetach(() => handle.detach);
|
|
7834
7887
|
resumeActiveRef.current = true;
|
|
7835
7888
|
const chatTab = chatTabIdRef.current;
|
|
7836
7889
|
if (chatTab) homeNav.switchTab(chatTab);
|
|
@@ -7852,24 +7905,39 @@ function App({ options, hostElement, bus }) {
|
|
|
7852
7905
|
}
|
|
7853
7906
|
} catch (err) {
|
|
7854
7907
|
if (bubble) {
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7908
|
+
if (isAbortError(err)) {
|
|
7909
|
+
if (hasNoVisibleAnswer(bubble)) {
|
|
7910
|
+
messagesSig.value = messagesSig.value.filter((m) => m !== bubble);
|
|
7911
|
+
} else {
|
|
7912
|
+
bubble.status = "complete";
|
|
7913
|
+
}
|
|
7914
|
+
} else {
|
|
7915
|
+
bubble.status = "error";
|
|
7916
|
+
bubble.errorText = errorMessageFor(err, options.strings);
|
|
7917
|
+
feedback.play("error");
|
|
7918
|
+
}
|
|
7858
7919
|
}
|
|
7859
|
-
log16.debug("
|
|
7920
|
+
log16.debug("resume ended without completing", { err });
|
|
7860
7921
|
} finally {
|
|
7861
7922
|
resumeBubbleRef.current = null;
|
|
7862
7923
|
if (bubble) {
|
|
7863
7924
|
reducer.detach();
|
|
7864
7925
|
setStreaming(false);
|
|
7865
7926
|
setActiveCancel(null);
|
|
7927
|
+
setActiveDetach(null);
|
|
7866
7928
|
messagesSig.value = [...messagesSig.value];
|
|
7867
7929
|
}
|
|
7868
7930
|
}
|
|
7869
7931
|
},
|
|
7870
|
-
[reducer, messagesSig, feedback, bus, options, homeNav]
|
|
7932
|
+
[reducer, messagesSig, feedback, bus, options, homeNav, adoptConversationRebind]
|
|
7871
7933
|
);
|
|
7872
7934
|
const resumeHandleRef = useRef9(null);
|
|
7935
|
+
const resumeInFlight = useCallback6(async () => {
|
|
7936
|
+
const handle = transport.resumeStream();
|
|
7937
|
+
resumeHandleRef.current = handle;
|
|
7938
|
+
await runResume(handle);
|
|
7939
|
+
if (transport.resumeStatus === "needs-generation") regenerateDanglingRef.current?.();
|
|
7940
|
+
}, [transport, runResume]);
|
|
7873
7941
|
useEffect16(() => {
|
|
7874
7942
|
const persistedConversation = conversationIdSig.value;
|
|
7875
7943
|
if (persistedConversation) transport.primeIdentity(visitorId, persistedConversation);
|
|
@@ -7892,16 +7960,17 @@ function App({ options, hostElement, bus }) {
|
|
|
7892
7960
|
setFormsReady(true);
|
|
7893
7961
|
}
|
|
7894
7962
|
})();
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7963
|
+
void (async () => {
|
|
7964
|
+
const tasks = [];
|
|
7965
|
+
if (pendingThreadRef.current) {
|
|
7966
|
+
pendingThreadRef.current = false;
|
|
7967
|
+
const persisted = conversationIdSig.value;
|
|
7968
|
+
if (persisted) tasks.push(loadThread(persisted));
|
|
7969
|
+
}
|
|
7970
|
+
if (conversationIdSig.value) tasks.push(resumeInFlight());
|
|
7971
|
+
if (tasks.length === 0) return;
|
|
7972
|
+
await Promise.allSettled(tasks);
|
|
7973
|
+
})();
|
|
7905
7974
|
}, [activated]);
|
|
7906
7975
|
const formsLocaleRef = useRef9(null);
|
|
7907
7976
|
useEffect16(() => {
|
|
@@ -7972,8 +8041,10 @@ function App({ options, hostElement, bus }) {
|
|
|
7972
8041
|
setStreaming(true);
|
|
7973
8042
|
const handle = transport.sendMessage(body);
|
|
7974
8043
|
setActiveCancel(() => handle.cancel);
|
|
8044
|
+
setActiveDetach(() => handle.detach);
|
|
7975
8045
|
try {
|
|
7976
8046
|
for await (const evt of handle.iter) {
|
|
8047
|
+
if (adoptConversationRebind(evt.chunk)) continue;
|
|
7977
8048
|
reducer.apply(evt.chunk);
|
|
7978
8049
|
if (evt.chunk.type === "finish" && evt.chunk.canContinue === false) {
|
|
7979
8050
|
setCanSend(false);
|
|
@@ -8011,11 +8082,39 @@ function App({ options, hostElement, bus }) {
|
|
|
8011
8082
|
reducer.detach();
|
|
8012
8083
|
setStreaming(false);
|
|
8013
8084
|
setActiveCancel(null);
|
|
8085
|
+
setActiveDetach(null);
|
|
8014
8086
|
messagesSig.value = [...messagesSig.value];
|
|
8015
8087
|
}
|
|
8016
8088
|
},
|
|
8017
|
-
[
|
|
8089
|
+
[
|
|
8090
|
+
transport,
|
|
8091
|
+
reducer,
|
|
8092
|
+
messagesSig,
|
|
8093
|
+
conversationIdSig,
|
|
8094
|
+
options,
|
|
8095
|
+
bus,
|
|
8096
|
+
feedback,
|
|
8097
|
+
persistence,
|
|
8098
|
+
visitorId,
|
|
8099
|
+
adoptConversationRebind
|
|
8100
|
+
]
|
|
8018
8101
|
);
|
|
8102
|
+
const regenerateDanglingTurn = useCallback6(() => {
|
|
8103
|
+
if (streaming || regenInFlightRef.current || resumeBubbleRef.current) return;
|
|
8104
|
+
const last = messagesSig.value.filter((m) => !m.ephemeral).at(-1);
|
|
8105
|
+
if (last?.role !== "user") return;
|
|
8106
|
+
regenInFlightRef.current = true;
|
|
8107
|
+
log16.info("regenerating dangling user turn (needs-generation)");
|
|
8108
|
+
const assistantMsg = makeAssistantMessage();
|
|
8109
|
+
messagesSig.value = [...messagesSig.value, assistantMsg];
|
|
8110
|
+
reducer.attach(assistantMsg);
|
|
8111
|
+
void streamAssistant(assistantMsg, { continueExisting: false }).finally(() => {
|
|
8112
|
+
regenInFlightRef.current = false;
|
|
8113
|
+
});
|
|
8114
|
+
}, [streaming, messagesSig, reducer, streamAssistant]);
|
|
8115
|
+
useEffect16(() => {
|
|
8116
|
+
regenerateDanglingRef.current = regenerateDanglingTurn;
|
|
8117
|
+
}, [regenerateDanglingTurn]);
|
|
8019
8118
|
const decideTool = useCallback6(
|
|
8020
8119
|
(toolCallId, mutate) => {
|
|
8021
8120
|
if (streaming) return;
|
|
@@ -8231,7 +8330,7 @@ function App({ options, hostElement, bus }) {
|
|
|
8231
8330
|
}
|
|
8232
8331
|
log16.info("clear \u2192 new conversation", { wasStreaming: streaming });
|
|
8233
8332
|
bus.emit("clear", void 0);
|
|
8234
|
-
if (streaming)
|
|
8333
|
+
if (streaming) activeDetach?.();
|
|
8235
8334
|
batch(() => {
|
|
8236
8335
|
messagesSig.value = [];
|
|
8237
8336
|
conversationIdSig.value = void 0;
|
|
@@ -8240,7 +8339,7 @@ function App({ options, hostElement, bus }) {
|
|
|
8240
8339
|
setCanSend(true);
|
|
8241
8340
|
setFormMarkers([]);
|
|
8242
8341
|
void runHandshake({ newConversation: true });
|
|
8243
|
-
}, [streaming,
|
|
8342
|
+
}, [streaming, activeDetach, messagesSig, conversationIdSig, persistence, runHandshake, bus, canSend, playWelcome]);
|
|
8244
8343
|
const handleNewChat = useCallback6(() => {
|
|
8245
8344
|
handleClear();
|
|
8246
8345
|
setView("chat");
|
|
@@ -8317,8 +8416,9 @@ function App({ options, hostElement, bus }) {
|
|
|
8317
8416
|
);
|
|
8318
8417
|
const handleSelectHistoryConversation = useCallback6(
|
|
8319
8418
|
async (targetConversationId) => {
|
|
8320
|
-
log16.info("selectConversation", { conversationId: targetConversationId });
|
|
8419
|
+
log16.info("selectConversation", { conversationId: targetConversationId, wasStreaming: streaming });
|
|
8321
8420
|
bus.emit("selectConversation", { conversationId: targetConversationId });
|
|
8421
|
+
if (streaming) activeDetach?.();
|
|
8322
8422
|
try {
|
|
8323
8423
|
const [res, markers] = await Promise.all([
|
|
8324
8424
|
transport.loadConversation(targetConversationId),
|
|
@@ -8331,10 +8431,12 @@ function App({ options, hostElement, bus }) {
|
|
|
8331
8431
|
});
|
|
8332
8432
|
setFormMarkers(markers);
|
|
8333
8433
|
persistence.saveConversationId(res.conversationId);
|
|
8434
|
+
transport.primeIdentity(visitorId, res.conversationId);
|
|
8334
8435
|
if (res.agent) setAgent(res.agent);
|
|
8335
8436
|
setCanSend(res.canContinue);
|
|
8336
8437
|
setSuggestions(res.suggestions ?? []);
|
|
8337
8438
|
setView("chat");
|
|
8439
|
+
void resumeInFlight();
|
|
8338
8440
|
await transport.markRead(targetConversationId);
|
|
8339
8441
|
refreshUnread();
|
|
8340
8442
|
} catch (err) {
|
|
@@ -8351,7 +8453,11 @@ function App({ options, hostElement, bus }) {
|
|
|
8351
8453
|
persistence,
|
|
8352
8454
|
refreshUnread,
|
|
8353
8455
|
fetchFormMarkers,
|
|
8354
|
-
withWelcomeRows
|
|
8456
|
+
withWelcomeRows,
|
|
8457
|
+
streaming,
|
|
8458
|
+
activeDetach,
|
|
8459
|
+
visitorId,
|
|
8460
|
+
resumeInFlight
|
|
8355
8461
|
]
|
|
8356
8462
|
);
|
|
8357
8463
|
const pendingOpenFormRef = useRef9(null);
|