@helpai/elements 0.50.7 → 0.51.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/elements-web-component.esm.js +19 -19
- package/elements-web-component.esm.js.map +3 -3
- package/elements.cjs.js +19 -19
- package/elements.cjs.js.map +3 -3
- package/elements.esm.js +19 -19
- package/elements.esm.js.map +3 -3
- package/elements.js +19 -19
- package/elements.js.map +3 -3
- package/index.d.ts +1 -1
- package/index.mjs +119 -21
- package/package.json +1 -1
- package/schema.d.ts +26 -26
- package/web-component.mjs +119 -21
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.0" : "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.
|
|
@@ -2427,6 +2434,7 @@ 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
|
return { iter, cancel: () => ctrl.abort() };
|
|
2432
2440
|
}
|
|
@@ -2481,6 +2489,15 @@ var AgentTransport = class {
|
|
|
2481
2489
|
log5.debug("resume \u2192 no in-flight stream", { status: res.status });
|
|
2482
2490
|
return;
|
|
2483
2491
|
}
|
|
2492
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
2493
|
+
if (!contentType.includes("text/event-stream")) {
|
|
2494
|
+
const status = await res.json().then((body) => body.status).catch(() => void 0);
|
|
2495
|
+
log5.debug("resume \u2192 no live stream (status response)", { status, attempt });
|
|
2496
|
+
if (status === "pending" && attempt < MAX_RESUME_ATTEMPTS) continue;
|
|
2497
|
+
this.resumeStatus = status === "needs-generation" ? "needs-generation" : "completed";
|
|
2498
|
+
return;
|
|
2499
|
+
}
|
|
2500
|
+
this.resumeStatus = "live";
|
|
2484
2501
|
const before = seenIds.size;
|
|
2485
2502
|
if (yield* this.drain(parseChatStream(res, ctrl.signal), seenIds, ctrl)) return;
|
|
2486
2503
|
if (seenIds.size === before) {
|
|
@@ -2700,6 +2717,7 @@ function toReactive(m) {
|
|
|
2700
2717
|
role: m.role,
|
|
2701
2718
|
createdAt: m.createdAt,
|
|
2702
2719
|
status: m.status,
|
|
2720
|
+
canceled: m.canceled,
|
|
2703
2721
|
errorText: m.errorText,
|
|
2704
2722
|
finishReason: m.finishReason,
|
|
2705
2723
|
serverMessageId: m.serverMessageId,
|
|
@@ -2763,7 +2781,11 @@ function fromWireMessage(w) {
|
|
|
2763
2781
|
// Real send time when the backend provides it; otherwise fall back to now
|
|
2764
2782
|
// (older backends) so the timestamp + day divider still render.
|
|
2765
2783
|
createdAt: parseWireDate(w.createdAt) ?? Date.now(),
|
|
2766
|
-
status:
|
|
2784
|
+
// Map the persisted terminal status onto the bubble's render state: `failed` → an error bubble;
|
|
2785
|
+
// `canceled` → `complete` + the `canceled` flag (an empty canceled turn is hidden on render — see
|
|
2786
|
+
// `message-bubble`); everything else is a normal completed turn.
|
|
2787
|
+
status: w.status === "failed" ? "error" : "complete",
|
|
2788
|
+
canceled: w.status === "canceled",
|
|
2767
2789
|
partsSig: signal(parts)
|
|
2768
2790
|
};
|
|
2769
2791
|
}
|
|
@@ -2773,6 +2795,7 @@ function fromReactive(m) {
|
|
|
2773
2795
|
role: m.role,
|
|
2774
2796
|
createdAt: m.createdAt,
|
|
2775
2797
|
status: m.status,
|
|
2798
|
+
canceled: m.canceled,
|
|
2776
2799
|
errorText: m.errorText,
|
|
2777
2800
|
finishReason: m.finishReason,
|
|
2778
2801
|
serverMessageId: m.serverMessageId,
|
|
@@ -2788,11 +2811,16 @@ function assistantText(m) {
|
|
|
2788
2811
|
}
|
|
2789
2812
|
return out;
|
|
2790
2813
|
}
|
|
2791
|
-
function
|
|
2792
|
-
if (m.role !== "assistant" || m.status === "streaming") return false;
|
|
2814
|
+
function hasNoVisibleAnswer(m) {
|
|
2793
2815
|
if (assistantText(m).trim() !== "") return false;
|
|
2794
2816
|
return !m.partsSig.value.some((p36) => p36.kind === "tool" || p36.kind === "file" || p36.kind === "source");
|
|
2795
2817
|
}
|
|
2818
|
+
function isEmptyAssistantReply(m) {
|
|
2819
|
+
return m.role === "assistant" && m.status !== "streaming" && hasNoVisibleAnswer(m);
|
|
2820
|
+
}
|
|
2821
|
+
function isHiddenCanceledTurn(m) {
|
|
2822
|
+
return m.canceled === true && isEmptyAssistantReply(m);
|
|
2823
|
+
}
|
|
2796
2824
|
function partToReactive(p36) {
|
|
2797
2825
|
if (p36.kind === "text" || p36.kind === "reasoning") {
|
|
2798
2826
|
return { kind: p36.kind, id: p36.id, textSig: signal(p36.text), doneSig: signal(p36.done) };
|
|
@@ -2952,6 +2980,8 @@ var StreamReducer = class {
|
|
|
2952
2980
|
return;
|
|
2953
2981
|
case "data-notification":
|
|
2954
2982
|
return;
|
|
2983
|
+
case "data-conversation-rebind":
|
|
2984
|
+
return;
|
|
2955
2985
|
default: {
|
|
2956
2986
|
const _exhaustive = chunk;
|
|
2957
2987
|
void _exhaustive;
|
|
@@ -5664,6 +5694,7 @@ function MessageBubble({
|
|
|
5664
5694
|
const parts = useComputed5(() => message.partsSig.value);
|
|
5665
5695
|
const partList = parts.value;
|
|
5666
5696
|
const emptyReply = useComputed5(() => isEmptyAssistantReply(message));
|
|
5697
|
+
const hideCanceledHusk = useComputed5(() => isHiddenCanceledTurn(message));
|
|
5667
5698
|
const hasAnswerText = useComputed5(
|
|
5668
5699
|
() => message.partsSig.value.some((part) => part.kind === "text" && part.textSig.value.length > 0)
|
|
5669
5700
|
);
|
|
@@ -5674,6 +5705,7 @@ function MessageBubble({
|
|
|
5674
5705
|
const bufferedHold = responseMode === "buffered" && streaming;
|
|
5675
5706
|
const working = streaming && !hasAnswerText.value;
|
|
5676
5707
|
const showStreamDots = streaming && !bufferedHold && !(reasoningVisible.value && working);
|
|
5708
|
+
if (hideCanceledHusk.value) return null;
|
|
5677
5709
|
const stamp = formatStamp(message.createdAt);
|
|
5678
5710
|
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: [
|
|
5679
5711
|
/* @__PURE__ */ jsxs15("div", { class: `${p17}-bubble`, children: [
|
|
@@ -6016,6 +6048,7 @@ function MessageList({
|
|
|
6016
6048
|
}
|
|
6017
6049
|
};
|
|
6018
6050
|
for (const m of messages.value) {
|
|
6051
|
+
if (m.role === "assistant" && m.id !== lastId && isEmptyAssistantReply(m)) continue;
|
|
6019
6052
|
flushMarkersBefore(m.createdAt);
|
|
6020
6053
|
const day = dayKey(m.createdAt);
|
|
6021
6054
|
if (day && day !== prevDay) {
|
|
@@ -7473,6 +7506,9 @@ function allToolPartsSettled(msg) {
|
|
|
7473
7506
|
return s === "output-available" || s === "output-error" || s === "output-denied" || s === "approval-responded";
|
|
7474
7507
|
});
|
|
7475
7508
|
}
|
|
7509
|
+
function isAbortError(error) {
|
|
7510
|
+
return error instanceof DOMException ? error.name === "AbortError" : error?.name === "AbortError";
|
|
7511
|
+
}
|
|
7476
7512
|
function App({ options, hostElement, bus }) {
|
|
7477
7513
|
const [persistence] = useState14(
|
|
7478
7514
|
() => createPersistence(options.widgetId, options.storage, options.aiAgentDeploymentId)
|
|
@@ -7615,6 +7651,8 @@ function App({ options, hostElement, bus }) {
|
|
|
7615
7651
|
const homeNavSeeded = useRef9(false);
|
|
7616
7652
|
const resumeActiveRef = useRef9(false);
|
|
7617
7653
|
const resumeBubbleRef = useRef9(null);
|
|
7654
|
+
const regenerateDanglingRef = useRef9(null);
|
|
7655
|
+
const regenInFlightRef = useRef9(false);
|
|
7618
7656
|
useEffect16(() => {
|
|
7619
7657
|
if (!conversationReady || homeNavSeeded.current) return;
|
|
7620
7658
|
homeNavSeeded.current = true;
|
|
@@ -7684,6 +7722,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7684
7722
|
} else {
|
|
7685
7723
|
messagesSig.value = withWelcomeRows([], markers);
|
|
7686
7724
|
}
|
|
7725
|
+
if (transport.resumeStatus === "needs-generation") regenerateDanglingRef.current?.();
|
|
7687
7726
|
} catch (err) {
|
|
7688
7727
|
if (isStale()) return;
|
|
7689
7728
|
log16.warn("loadThread failed; resetting conversationId", { err, conversationId });
|
|
@@ -7813,11 +7852,28 @@ function App({ options, hostElement, bus }) {
|
|
|
7813
7852
|
useEffect16(() => {
|
|
7814
7853
|
if (effectiveLocale !== activeLocale) setActiveLocale(effectiveLocale);
|
|
7815
7854
|
}, [effectiveLocale, activeLocale]);
|
|
7855
|
+
const adoptConversationRebind = useCallback6(
|
|
7856
|
+
(chunk) => {
|
|
7857
|
+
if (chunk.type !== "data-conversation-rebind") return false;
|
|
7858
|
+
const next = chunk.data?.conversationId;
|
|
7859
|
+
if (next && next !== conversationIdSig.value) {
|
|
7860
|
+
const previous = conversationIdSig.value;
|
|
7861
|
+
conversationIdSig.value = next;
|
|
7862
|
+
persistence.saveConversationId(next);
|
|
7863
|
+
transport.primeIdentity(void 0, next);
|
|
7864
|
+
log16.info("conversation rebound (in-stream)", { previous, current: next });
|
|
7865
|
+
if (previous) bus.emit("conversationRebound", { previous, current: next, reason: next });
|
|
7866
|
+
}
|
|
7867
|
+
return true;
|
|
7868
|
+
},
|
|
7869
|
+
[conversationIdSig, persistence, transport, bus]
|
|
7870
|
+
);
|
|
7816
7871
|
const runResume = useCallback6(
|
|
7817
7872
|
async (handle) => {
|
|
7818
7873
|
let bubble = null;
|
|
7819
7874
|
try {
|
|
7820
7875
|
for await (const evt of handle.iter) {
|
|
7876
|
+
if (adoptConversationRebind(evt.chunk)) continue;
|
|
7821
7877
|
if (!bubble) {
|
|
7822
7878
|
bubble = makeAssistantMessage();
|
|
7823
7879
|
resumeBubbleRef.current = bubble;
|
|
@@ -7861,7 +7917,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7861
7917
|
}
|
|
7862
7918
|
}
|
|
7863
7919
|
},
|
|
7864
|
-
[reducer, messagesSig, feedback, bus, options, homeNav]
|
|
7920
|
+
[reducer, messagesSig, feedback, bus, options, homeNav, adoptConversationRebind]
|
|
7865
7921
|
);
|
|
7866
7922
|
const resumeHandleRef = useRef9(null);
|
|
7867
7923
|
useEffect16(() => {
|
|
@@ -7886,16 +7942,22 @@ function App({ options, hostElement, bus }) {
|
|
|
7886
7942
|
setFormsReady(true);
|
|
7887
7943
|
}
|
|
7888
7944
|
})();
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
|
|
7945
|
+
void (async () => {
|
|
7946
|
+
const tasks = [];
|
|
7947
|
+
if (pendingThreadRef.current) {
|
|
7948
|
+
pendingThreadRef.current = false;
|
|
7949
|
+
const persisted = conversationIdSig.value;
|
|
7950
|
+
if (persisted) tasks.push(loadThread(persisted));
|
|
7951
|
+
}
|
|
7952
|
+
if (conversationIdSig.value) {
|
|
7953
|
+
const handle = transport.resumeStream();
|
|
7954
|
+
resumeHandleRef.current = handle;
|
|
7955
|
+
tasks.push(runResume(handle));
|
|
7956
|
+
}
|
|
7957
|
+
if (tasks.length === 0) return;
|
|
7958
|
+
await Promise.allSettled(tasks);
|
|
7959
|
+
if (transport.resumeStatus === "needs-generation") regenerateDanglingRef.current?.();
|
|
7960
|
+
})();
|
|
7899
7961
|
}, [activated]);
|
|
7900
7962
|
const formsLocaleRef = useRef9(null);
|
|
7901
7963
|
useEffect16(() => {
|
|
@@ -7968,6 +8030,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7968
8030
|
setActiveCancel(() => handle.cancel);
|
|
7969
8031
|
try {
|
|
7970
8032
|
for await (const evt of handle.iter) {
|
|
8033
|
+
if (adoptConversationRebind(evt.chunk)) continue;
|
|
7971
8034
|
reducer.apply(evt.chunk);
|
|
7972
8035
|
if (evt.chunk.type === "finish" && evt.chunk.canContinue === false) {
|
|
7973
8036
|
setCanSend(false);
|
|
@@ -7988,11 +8051,19 @@ function App({ options, hostElement, bus }) {
|
|
|
7988
8051
|
emitMessage(bus, options, "assistant", assistantText(assistantMsg));
|
|
7989
8052
|
}
|
|
7990
8053
|
} catch (error) {
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
|
|
8054
|
+
if (isAbortError(error)) {
|
|
8055
|
+
if (hasNoVisibleAnswer(assistantMsg)) {
|
|
8056
|
+
messagesSig.value = messagesSig.value.filter((m) => m !== assistantMsg);
|
|
8057
|
+
} else {
|
|
8058
|
+
assistantMsg.status = "complete";
|
|
8059
|
+
}
|
|
8060
|
+
} else {
|
|
8061
|
+
assistantMsg.status = "error";
|
|
8062
|
+
assistantMsg.errorText = errorMessageFor(error, options.strings);
|
|
8063
|
+
feedback.play("error");
|
|
8064
|
+
bus.emit("error", error);
|
|
8065
|
+
options.onError?.(error);
|
|
8066
|
+
}
|
|
7996
8067
|
} finally {
|
|
7997
8068
|
reducer.detach();
|
|
7998
8069
|
setStreaming(false);
|
|
@@ -8000,8 +8071,35 @@ function App({ options, hostElement, bus }) {
|
|
|
8000
8071
|
messagesSig.value = [...messagesSig.value];
|
|
8001
8072
|
}
|
|
8002
8073
|
},
|
|
8003
|
-
[
|
|
8074
|
+
[
|
|
8075
|
+
transport,
|
|
8076
|
+
reducer,
|
|
8077
|
+
messagesSig,
|
|
8078
|
+
conversationIdSig,
|
|
8079
|
+
options,
|
|
8080
|
+
bus,
|
|
8081
|
+
feedback,
|
|
8082
|
+
persistence,
|
|
8083
|
+
visitorId,
|
|
8084
|
+
adoptConversationRebind
|
|
8085
|
+
]
|
|
8004
8086
|
);
|
|
8087
|
+
const regenerateDanglingTurn = useCallback6(() => {
|
|
8088
|
+
if (streaming || regenInFlightRef.current || resumeBubbleRef.current) return;
|
|
8089
|
+
const last = messagesSig.value.filter((m) => !m.ephemeral).at(-1);
|
|
8090
|
+
if (last?.role !== "user") return;
|
|
8091
|
+
regenInFlightRef.current = true;
|
|
8092
|
+
log16.info("regenerating dangling user turn (needs-generation)");
|
|
8093
|
+
const assistantMsg = makeAssistantMessage();
|
|
8094
|
+
messagesSig.value = [...messagesSig.value, assistantMsg];
|
|
8095
|
+
reducer.attach(assistantMsg);
|
|
8096
|
+
void streamAssistant(assistantMsg, { continueExisting: false }).finally(() => {
|
|
8097
|
+
regenInFlightRef.current = false;
|
|
8098
|
+
});
|
|
8099
|
+
}, [streaming, messagesSig, reducer, streamAssistant]);
|
|
8100
|
+
useEffect16(() => {
|
|
8101
|
+
regenerateDanglingRef.current = regenerateDanglingTurn;
|
|
8102
|
+
}, [regenerateDanglingTurn]);
|
|
8005
8103
|
const decideTool = useCallback6(
|
|
8006
8104
|
(toolCallId, mutate) => {
|
|
8007
8105
|
if (streaming) return;
|