@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/index.mjs
CHANGED
|
@@ -29,7 +29,7 @@ var BRAND = {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
// src/core/version.ts
|
|
32
|
-
var ELEMENTS_VERSION = true ? "0.
|
|
32
|
+
var ELEMENTS_VERSION = true ? "0.51.1" : "0.0.0-dev";
|
|
33
33
|
var ELEMENTS_VERSION_PARAM = "_ev";
|
|
34
34
|
|
|
35
35
|
// src/i18n/strings.ts
|
|
@@ -2112,6 +2112,13 @@ var AgentTransport = class {
|
|
|
2112
2112
|
// (visitor, conversation) pair.
|
|
2113
2113
|
__publicField(this, "visitorId");
|
|
2114
2114
|
__publicField(this, "conversationId");
|
|
2115
|
+
/**
|
|
2116
|
+
* Outcome of the most recent `resumeStream()` once it found NO replayable SSE — drives the App's
|
|
2117
|
+
* dangling-turn recovery. `live` = a stream was replayed; otherwise the server's JSON recovery status.
|
|
2118
|
+
* `needs-generation` means a durable user turn has no reply (e.g. a fast send→refresh aborted the
|
|
2119
|
+
* deferred LLM run) → the App re-sends to generate it. Reset at the start of each `resumeStream()`.
|
|
2120
|
+
*/
|
|
2121
|
+
__publicField(this, "resumeStatus");
|
|
2115
2122
|
// Host-asserted context (sanitised `userContext` / `pageContext`), refreshed via
|
|
2116
2123
|
// `setContext` and carried as the envelope's optional `context` so the backend
|
|
2117
2124
|
// always sees the visitor's latest context as they navigate.
|
|
@@ -2447,7 +2454,7 @@ var AgentTransport = class {
|
|
|
2447
2454
|
const seenIds = /* @__PURE__ */ new Set();
|
|
2448
2455
|
const iter = this.runStream(body, ctrl, seenIds);
|
|
2449
2456
|
const cancel = () => this.cancelStream(ctrl, body.conversationId);
|
|
2450
|
-
return { iter, cancel };
|
|
2457
|
+
return { iter, cancel, detach: () => ctrl.abort() };
|
|
2451
2458
|
}
|
|
2452
2459
|
/**
|
|
2453
2460
|
* Re-attach to an in-flight reply on a cold page load / refresh.
|
|
@@ -2468,8 +2475,10 @@ var AgentTransport = class {
|
|
|
2468
2475
|
resumeStream() {
|
|
2469
2476
|
const ctrl = new AbortController();
|
|
2470
2477
|
const seenIds = /* @__PURE__ */ new Set();
|
|
2478
|
+
this.resumeStatus = void 0;
|
|
2471
2479
|
const iter = this.resumeStreamGen(ctrl, seenIds, true);
|
|
2472
|
-
|
|
2480
|
+
const stop = () => ctrl.abort();
|
|
2481
|
+
return { iter, cancel: stop, detach: stop };
|
|
2473
2482
|
}
|
|
2474
2483
|
// ---- Stream orchestration ---------------------------------------------
|
|
2475
2484
|
async *runStream(body, ctrl, seenIds) {
|
|
@@ -2522,6 +2531,15 @@ var AgentTransport = class {
|
|
|
2522
2531
|
log6.debug("resume \u2192 no in-flight stream", { status: res.status });
|
|
2523
2532
|
return;
|
|
2524
2533
|
}
|
|
2534
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
2535
|
+
if (!contentType.includes("text/event-stream")) {
|
|
2536
|
+
const status = await res.json().then((body) => body.status).catch(() => void 0);
|
|
2537
|
+
log6.debug("resume \u2192 no live stream (status response)", { status, attempt });
|
|
2538
|
+
if (status === "pending" && attempt < MAX_RESUME_ATTEMPTS) continue;
|
|
2539
|
+
this.resumeStatus = status === "needs-generation" ? "needs-generation" : "completed";
|
|
2540
|
+
return;
|
|
2541
|
+
}
|
|
2542
|
+
this.resumeStatus = "live";
|
|
2525
2543
|
const before = seenIds.size;
|
|
2526
2544
|
if (yield* this.drain(parseChatStream(res, ctrl.signal), seenIds, ctrl)) return;
|
|
2527
2545
|
if (seenIds.size === before) {
|
|
@@ -2741,6 +2759,7 @@ function toReactive(m) {
|
|
|
2741
2759
|
role: m.role,
|
|
2742
2760
|
createdAt: m.createdAt,
|
|
2743
2761
|
status: m.status,
|
|
2762
|
+
canceled: m.canceled,
|
|
2744
2763
|
errorText: m.errorText,
|
|
2745
2764
|
finishReason: m.finishReason,
|
|
2746
2765
|
serverMessageId: m.serverMessageId,
|
|
@@ -2804,7 +2823,11 @@ function fromWireMessage(w) {
|
|
|
2804
2823
|
// Real send time when the backend provides it; otherwise fall back to now
|
|
2805
2824
|
// (older backends) so the timestamp + day divider still render.
|
|
2806
2825
|
createdAt: parseWireDate(w.createdAt) ?? Date.now(),
|
|
2807
|
-
status:
|
|
2826
|
+
// Map the persisted terminal status onto the bubble's render state: `failed` → an error bubble;
|
|
2827
|
+
// `canceled` → `complete` + the `canceled` flag (an empty canceled turn is hidden on render — see
|
|
2828
|
+
// `message-bubble`); everything else is a normal completed turn.
|
|
2829
|
+
status: w.status === "failed" ? "error" : "complete",
|
|
2830
|
+
canceled: w.status === "canceled",
|
|
2808
2831
|
partsSig: signal(parts)
|
|
2809
2832
|
};
|
|
2810
2833
|
}
|
|
@@ -2814,6 +2837,7 @@ function fromReactive(m) {
|
|
|
2814
2837
|
role: m.role,
|
|
2815
2838
|
createdAt: m.createdAt,
|
|
2816
2839
|
status: m.status,
|
|
2840
|
+
canceled: m.canceled,
|
|
2817
2841
|
errorText: m.errorText,
|
|
2818
2842
|
finishReason: m.finishReason,
|
|
2819
2843
|
serverMessageId: m.serverMessageId,
|
|
@@ -2836,6 +2860,9 @@ function hasNoVisibleAnswer(m) {
|
|
|
2836
2860
|
function isEmptyAssistantReply(m) {
|
|
2837
2861
|
return m.role === "assistant" && m.status !== "streaming" && hasNoVisibleAnswer(m);
|
|
2838
2862
|
}
|
|
2863
|
+
function isHiddenCanceledTurn(m) {
|
|
2864
|
+
return m.canceled === true && isEmptyAssistantReply(m);
|
|
2865
|
+
}
|
|
2839
2866
|
function partToReactive(p36) {
|
|
2840
2867
|
if (p36.kind === "text" || p36.kind === "reasoning") {
|
|
2841
2868
|
return { kind: p36.kind, id: p36.id, textSig: signal(p36.text), doneSig: signal(p36.done) };
|
|
@@ -2995,6 +3022,8 @@ var StreamReducer = class {
|
|
|
2995
3022
|
return;
|
|
2996
3023
|
case "data-notification":
|
|
2997
3024
|
return;
|
|
3025
|
+
case "data-conversation-rebind":
|
|
3026
|
+
return;
|
|
2998
3027
|
default: {
|
|
2999
3028
|
const _exhaustive = chunk;
|
|
3000
3029
|
void _exhaustive;
|
|
@@ -5707,6 +5736,7 @@ function MessageBubble({
|
|
|
5707
5736
|
const parts = useComputed5(() => message.partsSig.value);
|
|
5708
5737
|
const partList = parts.value;
|
|
5709
5738
|
const emptyReply = useComputed5(() => isEmptyAssistantReply(message));
|
|
5739
|
+
const hideCanceledHusk = useComputed5(() => isHiddenCanceledTurn(message));
|
|
5710
5740
|
const hasAnswerText = useComputed5(
|
|
5711
5741
|
() => message.partsSig.value.some((part) => part.kind === "text" && part.textSig.value.length > 0)
|
|
5712
5742
|
);
|
|
@@ -5717,6 +5747,7 @@ function MessageBubble({
|
|
|
5717
5747
|
const bufferedHold = responseMode === "buffered" && streaming;
|
|
5718
5748
|
const working = streaming && !hasAnswerText.value;
|
|
5719
5749
|
const showStreamDots = streaming && !bufferedHold && !(reasoningVisible.value && working);
|
|
5750
|
+
if (hideCanceledHusk.value) return null;
|
|
5720
5751
|
const stamp = formatStamp(message.createdAt);
|
|
5721
5752
|
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: [
|
|
5722
5753
|
/* @__PURE__ */ jsxs15("div", { class: `${p17}-bubble`, children: [
|
|
@@ -7590,6 +7621,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7590
7621
|
const [agent, setAgent] = useState14(null);
|
|
7591
7622
|
const [suggestions, setSuggestions] = useState14([]);
|
|
7592
7623
|
const [activeCancel, setActiveCancel] = useState14(null);
|
|
7624
|
+
const [activeDetach, setActiveDetach] = useState14(null);
|
|
7593
7625
|
const stringsRef = useRef9(options.strings);
|
|
7594
7626
|
const [parsedSite, setParsedSite] = useState14(void 0);
|
|
7595
7627
|
const [parsedBlocks, setParsedBlocks] = useState14(void 0);
|
|
@@ -7662,6 +7694,8 @@ function App({ options, hostElement, bus }) {
|
|
|
7662
7694
|
const homeNavSeeded = useRef9(false);
|
|
7663
7695
|
const resumeActiveRef = useRef9(false);
|
|
7664
7696
|
const resumeBubbleRef = useRef9(null);
|
|
7697
|
+
const regenerateDanglingRef = useRef9(null);
|
|
7698
|
+
const regenInFlightRef = useRef9(false);
|
|
7665
7699
|
useEffect16(() => {
|
|
7666
7700
|
if (!conversationReady || homeNavSeeded.current) return;
|
|
7667
7701
|
homeNavSeeded.current = true;
|
|
@@ -7731,6 +7765,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7731
7765
|
} else {
|
|
7732
7766
|
messagesSig.value = withWelcomeRows([], markers);
|
|
7733
7767
|
}
|
|
7768
|
+
if (transport.resumeStatus === "needs-generation") regenerateDanglingRef.current?.();
|
|
7734
7769
|
} catch (err) {
|
|
7735
7770
|
if (isStale()) return;
|
|
7736
7771
|
log17.warn("loadThread failed; resetting conversationId", { err, conversationId });
|
|
@@ -7860,11 +7895,28 @@ function App({ options, hostElement, bus }) {
|
|
|
7860
7895
|
useEffect16(() => {
|
|
7861
7896
|
if (effectiveLocale !== activeLocale) setActiveLocale(effectiveLocale);
|
|
7862
7897
|
}, [effectiveLocale, activeLocale]);
|
|
7898
|
+
const adoptConversationRebind = useCallback6(
|
|
7899
|
+
(chunk) => {
|
|
7900
|
+
if (chunk.type !== "data-conversation-rebind") return false;
|
|
7901
|
+
const next = chunk.data?.conversationId;
|
|
7902
|
+
if (next && next !== conversationIdSig.value) {
|
|
7903
|
+
const previous = conversationIdSig.value;
|
|
7904
|
+
conversationIdSig.value = next;
|
|
7905
|
+
persistence.saveConversationId(next);
|
|
7906
|
+
transport.primeIdentity(void 0, next);
|
|
7907
|
+
log17.info("conversation rebound (in-stream)", { previous, current: next });
|
|
7908
|
+
if (previous) bus.emit("conversationRebound", { previous, current: next, reason: next });
|
|
7909
|
+
}
|
|
7910
|
+
return true;
|
|
7911
|
+
},
|
|
7912
|
+
[conversationIdSig, persistence, transport, bus]
|
|
7913
|
+
);
|
|
7863
7914
|
const runResume = useCallback6(
|
|
7864
7915
|
async (handle) => {
|
|
7865
7916
|
let bubble = null;
|
|
7866
7917
|
try {
|
|
7867
7918
|
for await (const evt of handle.iter) {
|
|
7919
|
+
if (adoptConversationRebind(evt.chunk)) continue;
|
|
7868
7920
|
if (!bubble) {
|
|
7869
7921
|
bubble = makeAssistantMessage();
|
|
7870
7922
|
resumeBubbleRef.current = bubble;
|
|
@@ -7872,6 +7924,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7872
7924
|
messagesSig.value = [...messagesSig.value, bubble];
|
|
7873
7925
|
setStreaming(true);
|
|
7874
7926
|
setActiveCancel(() => handle.cancel);
|
|
7927
|
+
setActiveDetach(() => handle.detach);
|
|
7875
7928
|
resumeActiveRef.current = true;
|
|
7876
7929
|
const chatTab = chatTabIdRef.current;
|
|
7877
7930
|
if (chatTab) homeNav.switchTab(chatTab);
|
|
@@ -7893,24 +7946,39 @@ function App({ options, hostElement, bus }) {
|
|
|
7893
7946
|
}
|
|
7894
7947
|
} catch (err) {
|
|
7895
7948
|
if (bubble) {
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
|
|
7949
|
+
if (isAbortError(err)) {
|
|
7950
|
+
if (hasNoVisibleAnswer(bubble)) {
|
|
7951
|
+
messagesSig.value = messagesSig.value.filter((m) => m !== bubble);
|
|
7952
|
+
} else {
|
|
7953
|
+
bubble.status = "complete";
|
|
7954
|
+
}
|
|
7955
|
+
} else {
|
|
7956
|
+
bubble.status = "error";
|
|
7957
|
+
bubble.errorText = errorMessageFor(err, options.strings);
|
|
7958
|
+
feedback.play("error");
|
|
7959
|
+
}
|
|
7899
7960
|
}
|
|
7900
|
-
log17.debug("
|
|
7961
|
+
log17.debug("resume ended without completing", { err });
|
|
7901
7962
|
} finally {
|
|
7902
7963
|
resumeBubbleRef.current = null;
|
|
7903
7964
|
if (bubble) {
|
|
7904
7965
|
reducer.detach();
|
|
7905
7966
|
setStreaming(false);
|
|
7906
7967
|
setActiveCancel(null);
|
|
7968
|
+
setActiveDetach(null);
|
|
7907
7969
|
messagesSig.value = [...messagesSig.value];
|
|
7908
7970
|
}
|
|
7909
7971
|
}
|
|
7910
7972
|
},
|
|
7911
|
-
[reducer, messagesSig, feedback, bus, options, homeNav]
|
|
7973
|
+
[reducer, messagesSig, feedback, bus, options, homeNav, adoptConversationRebind]
|
|
7912
7974
|
);
|
|
7913
7975
|
const resumeHandleRef = useRef9(null);
|
|
7976
|
+
const resumeInFlight = useCallback6(async () => {
|
|
7977
|
+
const handle = transport.resumeStream();
|
|
7978
|
+
resumeHandleRef.current = handle;
|
|
7979
|
+
await runResume(handle);
|
|
7980
|
+
if (transport.resumeStatus === "needs-generation") regenerateDanglingRef.current?.();
|
|
7981
|
+
}, [transport, runResume]);
|
|
7914
7982
|
useEffect16(() => {
|
|
7915
7983
|
const persistedConversation = conversationIdSig.value;
|
|
7916
7984
|
if (persistedConversation) transport.primeIdentity(visitorId, persistedConversation);
|
|
@@ -7933,16 +8001,17 @@ function App({ options, hostElement, bus }) {
|
|
|
7933
8001
|
setFormsReady(true);
|
|
7934
8002
|
}
|
|
7935
8003
|
})();
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
8004
|
+
void (async () => {
|
|
8005
|
+
const tasks = [];
|
|
8006
|
+
if (pendingThreadRef.current) {
|
|
8007
|
+
pendingThreadRef.current = false;
|
|
8008
|
+
const persisted = conversationIdSig.value;
|
|
8009
|
+
if (persisted) tasks.push(loadThread(persisted));
|
|
8010
|
+
}
|
|
8011
|
+
if (conversationIdSig.value) tasks.push(resumeInFlight());
|
|
8012
|
+
if (tasks.length === 0) return;
|
|
8013
|
+
await Promise.allSettled(tasks);
|
|
8014
|
+
})();
|
|
7946
8015
|
}, [activated]);
|
|
7947
8016
|
const formsLocaleRef = useRef9(null);
|
|
7948
8017
|
useEffect16(() => {
|
|
@@ -8013,8 +8082,10 @@ function App({ options, hostElement, bus }) {
|
|
|
8013
8082
|
setStreaming(true);
|
|
8014
8083
|
const handle = transport.sendMessage(body);
|
|
8015
8084
|
setActiveCancel(() => handle.cancel);
|
|
8085
|
+
setActiveDetach(() => handle.detach);
|
|
8016
8086
|
try {
|
|
8017
8087
|
for await (const evt of handle.iter) {
|
|
8088
|
+
if (adoptConversationRebind(evt.chunk)) continue;
|
|
8018
8089
|
reducer.apply(evt.chunk);
|
|
8019
8090
|
if (evt.chunk.type === "finish" && evt.chunk.canContinue === false) {
|
|
8020
8091
|
setCanSend(false);
|
|
@@ -8052,11 +8123,39 @@ function App({ options, hostElement, bus }) {
|
|
|
8052
8123
|
reducer.detach();
|
|
8053
8124
|
setStreaming(false);
|
|
8054
8125
|
setActiveCancel(null);
|
|
8126
|
+
setActiveDetach(null);
|
|
8055
8127
|
messagesSig.value = [...messagesSig.value];
|
|
8056
8128
|
}
|
|
8057
8129
|
},
|
|
8058
|
-
[
|
|
8130
|
+
[
|
|
8131
|
+
transport,
|
|
8132
|
+
reducer,
|
|
8133
|
+
messagesSig,
|
|
8134
|
+
conversationIdSig,
|
|
8135
|
+
options,
|
|
8136
|
+
bus,
|
|
8137
|
+
feedback,
|
|
8138
|
+
persistence,
|
|
8139
|
+
visitorId,
|
|
8140
|
+
adoptConversationRebind
|
|
8141
|
+
]
|
|
8059
8142
|
);
|
|
8143
|
+
const regenerateDanglingTurn = useCallback6(() => {
|
|
8144
|
+
if (streaming || regenInFlightRef.current || resumeBubbleRef.current) return;
|
|
8145
|
+
const last = messagesSig.value.filter((m) => !m.ephemeral).at(-1);
|
|
8146
|
+
if (last?.role !== "user") return;
|
|
8147
|
+
regenInFlightRef.current = true;
|
|
8148
|
+
log17.info("regenerating dangling user turn (needs-generation)");
|
|
8149
|
+
const assistantMsg = makeAssistantMessage();
|
|
8150
|
+
messagesSig.value = [...messagesSig.value, assistantMsg];
|
|
8151
|
+
reducer.attach(assistantMsg);
|
|
8152
|
+
void streamAssistant(assistantMsg, { continueExisting: false }).finally(() => {
|
|
8153
|
+
regenInFlightRef.current = false;
|
|
8154
|
+
});
|
|
8155
|
+
}, [streaming, messagesSig, reducer, streamAssistant]);
|
|
8156
|
+
useEffect16(() => {
|
|
8157
|
+
regenerateDanglingRef.current = regenerateDanglingTurn;
|
|
8158
|
+
}, [regenerateDanglingTurn]);
|
|
8060
8159
|
const decideTool = useCallback6(
|
|
8061
8160
|
(toolCallId, mutate) => {
|
|
8062
8161
|
if (streaming) return;
|
|
@@ -8272,7 +8371,7 @@ function App({ options, hostElement, bus }) {
|
|
|
8272
8371
|
}
|
|
8273
8372
|
log17.info("clear \u2192 new conversation", { wasStreaming: streaming });
|
|
8274
8373
|
bus.emit("clear", void 0);
|
|
8275
|
-
if (streaming)
|
|
8374
|
+
if (streaming) activeDetach?.();
|
|
8276
8375
|
batch(() => {
|
|
8277
8376
|
messagesSig.value = [];
|
|
8278
8377
|
conversationIdSig.value = void 0;
|
|
@@ -8281,7 +8380,7 @@ function App({ options, hostElement, bus }) {
|
|
|
8281
8380
|
setCanSend(true);
|
|
8282
8381
|
setFormMarkers([]);
|
|
8283
8382
|
void runHandshake({ newConversation: true });
|
|
8284
|
-
}, [streaming,
|
|
8383
|
+
}, [streaming, activeDetach, messagesSig, conversationIdSig, persistence, runHandshake, bus, canSend, playWelcome]);
|
|
8285
8384
|
const handleNewChat = useCallback6(() => {
|
|
8286
8385
|
handleClear();
|
|
8287
8386
|
setView("chat");
|
|
@@ -8358,8 +8457,9 @@ function App({ options, hostElement, bus }) {
|
|
|
8358
8457
|
);
|
|
8359
8458
|
const handleSelectHistoryConversation = useCallback6(
|
|
8360
8459
|
async (targetConversationId) => {
|
|
8361
|
-
log17.info("selectConversation", { conversationId: targetConversationId });
|
|
8460
|
+
log17.info("selectConversation", { conversationId: targetConversationId, wasStreaming: streaming });
|
|
8362
8461
|
bus.emit("selectConversation", { conversationId: targetConversationId });
|
|
8462
|
+
if (streaming) activeDetach?.();
|
|
8363
8463
|
try {
|
|
8364
8464
|
const [res, markers] = await Promise.all([
|
|
8365
8465
|
transport.loadConversation(targetConversationId),
|
|
@@ -8372,10 +8472,12 @@ function App({ options, hostElement, bus }) {
|
|
|
8372
8472
|
});
|
|
8373
8473
|
setFormMarkers(markers);
|
|
8374
8474
|
persistence.saveConversationId(res.conversationId);
|
|
8475
|
+
transport.primeIdentity(visitorId, res.conversationId);
|
|
8375
8476
|
if (res.agent) setAgent(res.agent);
|
|
8376
8477
|
setCanSend(res.canContinue);
|
|
8377
8478
|
setSuggestions(res.suggestions ?? []);
|
|
8378
8479
|
setView("chat");
|
|
8480
|
+
void resumeInFlight();
|
|
8379
8481
|
await transport.markRead(targetConversationId);
|
|
8380
8482
|
refreshUnread();
|
|
8381
8483
|
} catch (err) {
|
|
@@ -8392,7 +8494,11 @@ function App({ options, hostElement, bus }) {
|
|
|
8392
8494
|
persistence,
|
|
8393
8495
|
refreshUnread,
|
|
8394
8496
|
fetchFormMarkers,
|
|
8395
|
-
withWelcomeRows
|
|
8497
|
+
withWelcomeRows,
|
|
8498
|
+
streaming,
|
|
8499
|
+
activeDetach,
|
|
8500
|
+
visitorId,
|
|
8501
|
+
resumeInFlight
|
|
8396
8502
|
]
|
|
8397
8503
|
);
|
|
8398
8504
|
const pendingOpenFormRef = useRef9(null);
|
package/package.json
CHANGED