@helpai/elements 0.5.0 → 0.7.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 +18 -18
- package/elements-web-component.esm.js.map +4 -4
- package/elements.cjs.js +24 -24
- package/elements.cjs.js.map +4 -4
- package/elements.esm.js +24 -24
- package/elements.esm.js.map +4 -4
- package/elements.js +18 -18
- package/elements.js.map +4 -4
- package/index.d.ts +2 -2
- package/index.mjs +84 -37
- package/package.json +1 -1
- package/schema.d.ts +35 -35
- package/web-component.mjs +84 -37
package/web-component.mjs
CHANGED
|
@@ -46,6 +46,7 @@ var STRINGS_EN = {
|
|
|
46
46
|
dateYesterday: "Yesterday",
|
|
47
47
|
dropZone: "Drop files to attach",
|
|
48
48
|
errorGeneric: "Something went wrong",
|
|
49
|
+
errorRateLimited: "Too many requests \u2014 please try again in a moment.",
|
|
49
50
|
errorRetry: "Retry",
|
|
50
51
|
expand: "Expand",
|
|
51
52
|
expandSidebar: "Expand sidebar",
|
|
@@ -1000,9 +1001,10 @@ function fillRandom(view) {
|
|
|
1000
1001
|
|
|
1001
1002
|
// src/stream/types.ts
|
|
1002
1003
|
var StreamError = class extends Error {
|
|
1003
|
-
constructor(message, code, cause) {
|
|
1004
|
+
constructor(message, code, status, cause) {
|
|
1004
1005
|
super(message);
|
|
1005
1006
|
__publicField(this, "code", code);
|
|
1007
|
+
__publicField(this, "status", status);
|
|
1006
1008
|
__publicField(this, "cause", cause);
|
|
1007
1009
|
this.name = "StreamError";
|
|
1008
1010
|
}
|
|
@@ -1013,7 +1015,7 @@ var log3 = logger.scope("parser");
|
|
|
1013
1015
|
async function* parseChatStream(response, signal6) {
|
|
1014
1016
|
if (!response.ok) {
|
|
1015
1017
|
const text = await response.text().catch(() => "");
|
|
1016
|
-
throw new StreamError(`server responded ${response.status}: ${text.slice(0, 200)}`, "server");
|
|
1018
|
+
throw new StreamError(`server responded ${response.status}: ${text.slice(0, 200)}`, "server", response.status);
|
|
1017
1019
|
}
|
|
1018
1020
|
if (!response.body) throw new StreamError("response has no body", "no-body");
|
|
1019
1021
|
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
@@ -1177,15 +1179,27 @@ function messageToWireParts(m) {
|
|
|
1177
1179
|
var log4 = logger.scope("transport");
|
|
1178
1180
|
var MAX_RESUME_ATTEMPTS = 3;
|
|
1179
1181
|
var RESUME_BACKOFF_MS = 400;
|
|
1182
|
+
var MAX_REQUEST_RETRIES = 3;
|
|
1183
|
+
var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
|
|
1184
|
+
function backoffMs(attempt) {
|
|
1185
|
+
return RESUME_BACKOFF_MS * 2 ** attempt + Math.floor(Math.random() * 250);
|
|
1186
|
+
}
|
|
1187
|
+
function retryAfterMs(headers) {
|
|
1188
|
+
const raw = headers.get("retry-after");
|
|
1189
|
+
if (!raw) return null;
|
|
1190
|
+
const secs = Number(raw);
|
|
1191
|
+
const ms = Number.isFinite(secs) ? secs * 1e3 : Date.parse(raw) - Date.now();
|
|
1192
|
+
return Number.isFinite(ms) && ms >= 0 ? Math.min(ms, 3e4) : null;
|
|
1193
|
+
}
|
|
1180
1194
|
function sleep(ms, signal6) {
|
|
1181
1195
|
return new Promise((resolve) => {
|
|
1182
|
-
if (signal6
|
|
1196
|
+
if (signal6?.aborted) return resolve();
|
|
1183
1197
|
const id = setTimeout(done, ms);
|
|
1184
1198
|
const onAbort = () => done();
|
|
1185
|
-
signal6
|
|
1199
|
+
signal6?.addEventListener("abort", onAbort, { once: true });
|
|
1186
1200
|
function done() {
|
|
1187
1201
|
clearTimeout(id);
|
|
1188
|
-
signal6
|
|
1202
|
+
signal6?.removeEventListener("abort", onAbort);
|
|
1189
1203
|
resolve();
|
|
1190
1204
|
}
|
|
1191
1205
|
});
|
|
@@ -1439,6 +1453,7 @@ var AgentTransport = class {
|
|
|
1439
1453
|
}
|
|
1440
1454
|
} catch (err) {
|
|
1441
1455
|
if (ctrl.signal.aborted) return true;
|
|
1456
|
+
if (err instanceof StreamError && err.status !== void 0) throw err;
|
|
1442
1457
|
log4.debug("stream segment dropped", { err });
|
|
1443
1458
|
}
|
|
1444
1459
|
return false;
|
|
@@ -1483,13 +1498,23 @@ var AgentTransport = class {
|
|
|
1483
1498
|
url.searchParams.set("resumeAt", String(resumeAt));
|
|
1484
1499
|
return url.toString();
|
|
1485
1500
|
}
|
|
1501
|
+
// JSON requests are idempotent (reads, or full-snapshot writes like
|
|
1502
|
+
// update-settings / mark-read / start-session), so they auto-retry transient
|
|
1503
|
+
// failures. The message POST is NOT in here — re-sending could duplicate the
|
|
1504
|
+
// reply, so it surfaces an error for the user to retry (see `openMessageStream`).
|
|
1486
1505
|
async postJson(path, body, label) {
|
|
1487
|
-
const res = await this.
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1506
|
+
const res = await this.fetchWithRetry(
|
|
1507
|
+
() => [
|
|
1508
|
+
this.url(path),
|
|
1509
|
+
{
|
|
1510
|
+
method: "POST",
|
|
1511
|
+
credentials: "omit",
|
|
1512
|
+
headers: { "content-type": "application/json", ...this.opts.auth.headers() },
|
|
1513
|
+
body: JSON.stringify(this.withEnvelope(body))
|
|
1514
|
+
}
|
|
1515
|
+
],
|
|
1516
|
+
label
|
|
1517
|
+
);
|
|
1493
1518
|
return this.assertOk(res, label);
|
|
1494
1519
|
}
|
|
1495
1520
|
async postForm(path, body, label) {
|
|
@@ -1502,16 +1527,44 @@ var AgentTransport = class {
|
|
|
1502
1527
|
return this.assertOk(res, label);
|
|
1503
1528
|
}
|
|
1504
1529
|
async getJson(urlOrPath, label) {
|
|
1505
|
-
const res = await this.
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1530
|
+
const res = await this.fetchWithRetry(
|
|
1531
|
+
() => [
|
|
1532
|
+
this.withEnvelopeQuery(urlOrPath),
|
|
1533
|
+
{ method: "GET", credentials: "omit", headers: { accept: "application/json", ...this.opts.auth.headers() } }
|
|
1534
|
+
],
|
|
1535
|
+
label
|
|
1536
|
+
);
|
|
1510
1537
|
return this.assertOk(res, label);
|
|
1511
1538
|
}
|
|
1512
|
-
/**
|
|
1539
|
+
/**
|
|
1540
|
+
* Fetch with bounded retries for *transient* failures: retryable statuses
|
|
1541
|
+
* (429 / 5xx / …) and network errors, honoring `Retry-After` (else backoff +
|
|
1542
|
+
* jitter). Terminal statuses (4xx) return immediately for the caller to throw.
|
|
1543
|
+
* Use only for idempotent requests.
|
|
1544
|
+
*/
|
|
1545
|
+
async fetchWithRetry(build, label) {
|
|
1546
|
+
for (let attempt = 0; ; attempt++) {
|
|
1547
|
+
const [reqUrl, init] = build();
|
|
1548
|
+
let res;
|
|
1549
|
+
try {
|
|
1550
|
+
res = await this.fetchImpl(reqUrl, init);
|
|
1551
|
+
} catch (err) {
|
|
1552
|
+
if (attempt >= MAX_REQUEST_RETRIES) {
|
|
1553
|
+
throw new StreamError(`${label} failed: network error`, "network", void 0, err);
|
|
1554
|
+
}
|
|
1555
|
+
log4.debug("request network error \u2014 retrying", { label, attempt });
|
|
1556
|
+
await sleep(backoffMs(attempt));
|
|
1557
|
+
continue;
|
|
1558
|
+
}
|
|
1559
|
+
if (res.ok || !RETRYABLE_STATUS.has(res.status) || attempt >= MAX_REQUEST_RETRIES) return res;
|
|
1560
|
+
const wait = retryAfterMs(res.headers) ?? backoffMs(attempt);
|
|
1561
|
+
log4.debug("request retryable status \u2014 retrying", { label, status: res.status, attempt, wait });
|
|
1562
|
+
await sleep(wait);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
/** Throw on non-2xx (with the status attached), otherwise parse the JSON body. */
|
|
1513
1566
|
async assertOk(res, label) {
|
|
1514
|
-
if (!res.ok) throw new StreamError(`${label} failed: ${res.status}`, "server");
|
|
1567
|
+
if (!res.ok) throw new StreamError(`${label} failed: ${res.status}`, "server", res.status);
|
|
1515
1568
|
return await res.json();
|
|
1516
1569
|
}
|
|
1517
1570
|
url(pathOrUrl) {
|
|
@@ -2180,6 +2233,12 @@ var FeedbackBus = class {
|
|
|
2180
2233
|
}
|
|
2181
2234
|
};
|
|
2182
2235
|
|
|
2236
|
+
// src/ui/error-message.ts
|
|
2237
|
+
function errorMessageFor(error, strings) {
|
|
2238
|
+
if (error instanceof StreamError && error.status === 429) return strings.errorRateLimited;
|
|
2239
|
+
return strings.errorGeneric;
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2183
2242
|
// src/ui/launcher.tsx
|
|
2184
2243
|
import { useEffect, useState } from "preact/hooks";
|
|
2185
2244
|
|
|
@@ -4596,6 +4655,7 @@ function ArticleRow({ article, nav }) {
|
|
|
4596
4655
|
function HelpRoot({ transport, strings, config, nav, panelProps }) {
|
|
4597
4656
|
const tags = config.contentTags;
|
|
4598
4657
|
const [state, setState] = useState6("loading");
|
|
4658
|
+
const [errorMsg, setErrorMsg] = useState6(strings.errorGeneric);
|
|
4599
4659
|
const [items, setItems] = useState6([]);
|
|
4600
4660
|
const [query, setQuery] = useState6("");
|
|
4601
4661
|
const [reloadKey, setReloadKey] = useState6(0);
|
|
@@ -4609,6 +4669,7 @@ function HelpRoot({ transport, strings, config, nav, panelProps }) {
|
|
|
4609
4669
|
}).catch((err) => {
|
|
4610
4670
|
if (cancelled) return;
|
|
4611
4671
|
log12.warn("listContent (help) failed", { err });
|
|
4672
|
+
setErrorMsg(errorMessageFor(err, strings));
|
|
4612
4673
|
setState("error");
|
|
4613
4674
|
});
|
|
4614
4675
|
return () => {
|
|
@@ -4623,15 +4684,7 @@ function HelpRoot({ transport, strings, config, nav, panelProps }) {
|
|
|
4623
4684
|
}
|
|
4624
4685
|
if (state === "loading") return /* @__PURE__ */ jsx25(ModuleState, { message: strings.helpLoading, strings });
|
|
4625
4686
|
if (state === "error") {
|
|
4626
|
-
return /* @__PURE__ */ jsx25(
|
|
4627
|
-
ModuleState,
|
|
4628
|
-
{
|
|
4629
|
-
tone: "error",
|
|
4630
|
-
message: strings.errorGeneric,
|
|
4631
|
-
onRetry: () => setReloadKey((k) => k + 1),
|
|
4632
|
-
strings
|
|
4633
|
-
}
|
|
4634
|
-
);
|
|
4687
|
+
return /* @__PURE__ */ jsx25(ModuleState, { tone: "error", message: errorMsg, onRetry: () => setReloadKey((k) => k + 1), strings });
|
|
4635
4688
|
}
|
|
4636
4689
|
if (items.length === 0) return /* @__PURE__ */ jsx25(ModuleState, { message: strings.helpEmpty, strings });
|
|
4637
4690
|
return groupByCategory(items).map(([category, rows]) => /* @__PURE__ */ jsxs20("section", { class: `${p18}-help-group`, children: [
|
|
@@ -4781,6 +4834,7 @@ var log14 = logger.scope("news");
|
|
|
4781
4834
|
function NewsRoot({ transport, strings, config, nav, panelProps }) {
|
|
4782
4835
|
const tags = config.contentTags;
|
|
4783
4836
|
const [state, setState] = useState8("loading");
|
|
4837
|
+
const [errorMsg, setErrorMsg] = useState8(strings.errorGeneric);
|
|
4784
4838
|
const [items, setItems] = useState8([]);
|
|
4785
4839
|
const [reloadKey, setReloadKey] = useState8(0);
|
|
4786
4840
|
useEffect13(() => {
|
|
@@ -4793,6 +4847,7 @@ function NewsRoot({ transport, strings, config, nav, panelProps }) {
|
|
|
4793
4847
|
}).catch((err) => {
|
|
4794
4848
|
if (cancelled) return;
|
|
4795
4849
|
log14.warn("listContent (news) failed", { err });
|
|
4850
|
+
setErrorMsg(errorMessageFor(err, strings));
|
|
4796
4851
|
setState("error");
|
|
4797
4852
|
});
|
|
4798
4853
|
return () => {
|
|
@@ -4802,15 +4857,7 @@ function NewsRoot({ transport, strings, config, nav, panelProps }) {
|
|
|
4802
4857
|
function renderBody() {
|
|
4803
4858
|
if (state === "loading") return /* @__PURE__ */ jsx28(ModuleState, { message: strings.newsLoading, strings });
|
|
4804
4859
|
if (state === "error") {
|
|
4805
|
-
return /* @__PURE__ */ jsx28(
|
|
4806
|
-
ModuleState,
|
|
4807
|
-
{
|
|
4808
|
-
tone: "error",
|
|
4809
|
-
message: strings.errorGeneric,
|
|
4810
|
-
onRetry: () => setReloadKey((k) => k + 1),
|
|
4811
|
-
strings
|
|
4812
|
-
}
|
|
4813
|
-
);
|
|
4860
|
+
return /* @__PURE__ */ jsx28(ModuleState, { tone: "error", message: errorMsg, onRetry: () => setReloadKey((k) => k + 1), strings });
|
|
4814
4861
|
}
|
|
4815
4862
|
if (items.length === 0) return /* @__PURE__ */ jsx28(ModuleState, { message: strings.newsEmpty, strings });
|
|
4816
4863
|
return /* @__PURE__ */ jsx28("div", { class: `${p21}-news-list`, children: items.map((item) => /* @__PURE__ */ jsxs22(
|
|
@@ -5517,7 +5564,7 @@ function App({ options, hostElement, bus }) {
|
|
|
5517
5564
|
emitMessage(bus, options, "assistant", assistantText(assistantMsg));
|
|
5518
5565
|
} catch (error) {
|
|
5519
5566
|
assistantMsg.status = "error";
|
|
5520
|
-
assistantMsg.errorText = error
|
|
5567
|
+
assistantMsg.errorText = errorMessageFor(error, options.strings);
|
|
5521
5568
|
feedback.play("error");
|
|
5522
5569
|
bus.emit("error", error);
|
|
5523
5570
|
options.onError?.(error);
|