@noodleseed/assistant 1.19.0 → 1.21.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/README.md +90 -0
- package/dist/{chunk-RFETV5KQ.js → chunk-LEKEIHJV.js} +68 -40
- package/dist/{chunk-RFETV5KQ.js.map → chunk-LEKEIHJV.js.map} +1 -1
- package/dist/{chunk-S373FCAI.js → chunk-ZPZHPXIQ.js} +137 -85
- package/dist/chunk-ZPZHPXIQ.js.map +1 -0
- package/dist/{client-BQ30Fq-c.d.cts → client-BZ7xPwfW.d.cts} +20 -9
- package/dist/{client-B6pnBmF8.d.ts → client-BkJlpN5N.d.ts} +20 -9
- package/dist/client.cjs +135 -83
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/embed.global.js +65 -65
- package/dist/index.cjs +201 -121
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/react/client.cjs +135 -83
- package/dist/react/client.cjs.map +1 -1
- package/dist/react/client.d.cts +1 -1
- package/dist/react/client.d.ts +1 -1
- package/dist/react/client.js +1 -1
- package/dist/react.cjs +202 -121
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +8 -1
- package/dist/react.d.ts +8 -1
- package/dist/react.js +3 -2
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +48 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +75 -7
- package/dist/server.d.ts +75 -7
- package/dist/server.js +46 -1
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-S373FCAI.js.map +0 -1
package/README.md
CHANGED
|
@@ -202,6 +202,81 @@ claims, or tool input. The endpoint key must match the authored name. Noodle val
|
|
|
202
202
|
only in private short-lived session state, and an omitted route leaves only dependent tools unavailable.
|
|
203
203
|
Mint a new session when the user's selected account or cluster changes.
|
|
204
204
|
|
|
205
|
+
#### Public website embeds
|
|
206
|
+
|
|
207
|
+
Everything above serves a signed-in user through your backend. A `publicWebsite(...)` surface serves the
|
|
208
|
+
same assistant to anonymous visitors — a marketing site, docs, or landing page — with a **required**
|
|
209
|
+
capability allowlist, per-surface daily budgets, and a kill switch. `noodle deploy` provisions a stable
|
|
210
|
+
embed id and prints a one-line snippet; the embed id is deliberately not a credential:
|
|
211
|
+
|
|
212
|
+
```html
|
|
213
|
+
<script src="https://cloud.noodleseed.dev/v1/assistant/embed.js"
|
|
214
|
+
data-embed-id="pub_7f2q4k9x" async></script>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
In a bundled app, the same public surface mounts through the element or React wrapper with `embedId`
|
|
218
|
+
instead of `sessionEndpoint` — the two are mutually exclusive, and a public embed needs no backend route:
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
<AssistantWidget embedId="pub_7f2q4k9x" serviceUrl="https://cloud.noodleseed.dev" />
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
A public embed opens its session on **first open**, never on mount — every mount would otherwise spend
|
|
225
|
+
one of the surface's daily admission budget. Add `signIn: true` to the surface and it becomes **mixed**:
|
|
226
|
+
anonymous visitors start immediately, and identity-dependent capabilities raise the sign-in flow below.
|
|
227
|
+
Before shipping, preflight the host with `noodle assistant embed --check --surface public` (or `mixed`) —
|
|
228
|
+
it verifies `script-src` too, the one CSP directive whose failure runs no widget code at all.
|
|
229
|
+
|
|
230
|
+
#### Mid-conversation sign-in (mixed surfaces)
|
|
231
|
+
|
|
232
|
+
On a `publicWebsite({ signIn: true })` surface, an anonymous visitor who reaches an identity-dependent
|
|
233
|
+
capability sees a "Sign in to continue" card, and the widget raises `assistant-sign-in-requested` with a
|
|
234
|
+
single-use `signInTicket` in its detail. The page signs the visitor in however it already does, then its
|
|
235
|
+
backend spends the ticket with the **same** helper and its own client credentials — same endpoint, same
|
|
236
|
+
`user` shape, plus the ticket:
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
const session = await createAssistantSession({
|
|
240
|
+
serviceUrl: process.env.NOODLE_SERVICE_URL!,
|
|
241
|
+
clientId: process.env.NOODLE_ASSISTANT_CLIENT_ID!,
|
|
242
|
+
clientSecret: process.env.NOODLE_ASSISTANT_CLIENT_SECRET!,
|
|
243
|
+
origin: process.env.PUBLIC_APP_ORIGIN!,
|
|
244
|
+
user: { id: user.id, email: user.email, roles: user.roles },
|
|
245
|
+
signInTicket, // from the widget's assistant-sign-in-requested event, via your page
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The visitor keeps the same conversation with a new token; the anonymous token dies at that moment —
|
|
250
|
+
and by default **the assistant answers the pending question itself**: the service re-attempts the
|
|
251
|
+
intercepted tool under the signed-in principal and streams the result as the session's first turn,
|
|
252
|
+
so the panel's first content after sign-in is the answer the visitor asked for. It is one-shot
|
|
253
|
+
(consumed by that turn, or mooted the moment the visitor types first), confirm-gated tools stop at
|
|
254
|
+
their normal confirmation card — sign-in is never implicit consent for a write — and the turn counts
|
|
255
|
+
against the surface's daily budget like any other. Pass `resume: false` beside the ticket if your
|
|
256
|
+
application provides its own post-login affordance.
|
|
257
|
+
`signInTicket` and `context` are mutually exclusive — an elevation continues an existing session.
|
|
258
|
+
`routing` IS accepted here, and this is the moment to send it: elevation is the first authenticated
|
|
259
|
+
exchange, so it is the only chance a routed connector's session gets its backend-verified customer
|
|
260
|
+
routes. Possession of a ticket alone elevates nothing:
|
|
261
|
+
spending it also requires your client credentials, and the service checks your tenant owns that
|
|
262
|
+
conversation. (The ticket is deliberately **not** called a continuation: the server-held interaction
|
|
263
|
+
continuation described below must never reach browser code, while this value's whole job is to travel
|
|
264
|
+
through the page.)
|
|
265
|
+
|
|
266
|
+
A refused spend throws `AssistantSessionExchangeError`; branch on `error.elevationRefusal`:
|
|
267
|
+
|
|
268
|
+
| Code | What happened | What to do |
|
|
269
|
+
| :-- | :-- | :-- |
|
|
270
|
+
| `elevation_ticket_expired` | The visitor took too long (tickets live 10 minutes) | Re-prompt; the widget raises a fresh ticket when they retry the action |
|
|
271
|
+
| `elevation_ticket_invalid` | Stale, spent, or double-submitted ticket | Same recovery; log it — a burst is a replay signal |
|
|
272
|
+
| `elevation_tenant_mismatch` | Your credentials do not own that conversation | Alert someone; never retry |
|
|
273
|
+
| `elevation_already_signed_in` / `elevation_session_unavailable` | The conversation moved on | Ask the visitor to refresh |
|
|
274
|
+
|
|
275
|
+
`error.detail.serviceCode === "elevation_unavailable"` (a 503) means the deployment has no elevation
|
|
276
|
+
store configured — page the operator, not the visitor. Failures without a code (fresh-mint refusals,
|
|
277
|
+
proxy errors) carry `error.detail.status`, and `error.detail.retryable` is `true` only for
|
|
278
|
+
infrastructure 5xx responses.
|
|
279
|
+
|
|
205
280
|
Then add the framework-neutral element:
|
|
206
281
|
|
|
207
282
|
```ts
|
|
@@ -426,6 +501,21 @@ element.addEventListener("assistant-view-available", (event) => {
|
|
|
426
501
|
});
|
|
427
502
|
```
|
|
428
503
|
|
|
504
|
+
#### Readiness and call ordering
|
|
505
|
+
|
|
506
|
+
`assistant-ready` fires **once per element lifetime**, when the element's shadow DOM is rendered and its
|
|
507
|
+
imperative API (`sendMessage`, `confirmTool`, …) is callable. It does **not** mean a session exists — that
|
|
508
|
+
is the `assistant-event` with `event: 'session_started'`. A DOM move (router reparent, portal) re-runs the
|
|
509
|
+
element's connection lifecycle but never re-fires the event. The React wrapper's `onReady` mirrors this:
|
|
510
|
+
at most once per component instance, whether the element upgraded before or after the component mounted;
|
|
511
|
+
the first callback identity wins, and a `console.debug('[noodle-assistant] ready')` breadcrumb marks the
|
|
512
|
+
moment for field diagnosis.
|
|
513
|
+
|
|
514
|
+
`sendMessage` awaits an in-flight eager session exchange and never opens a second session — a message sent
|
|
515
|
+
while the panel is still connecting is delivered on the session that exchange produces. It does **not**
|
|
516
|
+
queue: a `sendMessage` issued while another send or response is in flight rejects with a retryable
|
|
517
|
+
`request_in_progress` error before touching the transcript.
|
|
518
|
+
|
|
429
519
|
Assistant text renders as provider deltas arrive. If a turn finds an expired session, the component calls
|
|
430
520
|
the same authenticated session endpoint and retries that unprocessed message once. Confirmations never
|
|
431
521
|
replay across sessions. React applications may observe recovery and structured failures:
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
copyAssistantPageContext,
|
|
5
5
|
createAssistantClient,
|
|
6
6
|
sessionSourceKey
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-ZPZHPXIQ.js";
|
|
8
8
|
import {
|
|
9
9
|
_enum,
|
|
10
10
|
_null,
|
|
@@ -6824,6 +6824,11 @@ var AssistantElementEventController = class {
|
|
|
6824
6824
|
this.#completedTool = void 0;
|
|
6825
6825
|
return;
|
|
6826
6826
|
}
|
|
6827
|
+
if (name === "resume_started") {
|
|
6828
|
+
this.#startStream("message");
|
|
6829
|
+
this.#host.element.dispatchEvent(new CustomEvent("assistant-message-started"));
|
|
6830
|
+
return;
|
|
6831
|
+
}
|
|
6827
6832
|
if (name === "interaction_completed") {
|
|
6828
6833
|
if (this.#streamKind !== "interaction") return;
|
|
6829
6834
|
this.#finishInteraction(String(data.id ?? ""), String(data.action ?? "accept"));
|
|
@@ -6844,11 +6849,11 @@ var AssistantElementEventController = class {
|
|
|
6844
6849
|
);
|
|
6845
6850
|
return;
|
|
6846
6851
|
}
|
|
6847
|
-
if (name === "auth_requested" && typeof data.
|
|
6852
|
+
if (name === "auth_requested" && typeof data.signInTicket === "string") {
|
|
6848
6853
|
this.#appendSignInRequest(
|
|
6849
6854
|
String(data.id ?? ""),
|
|
6850
6855
|
String(data.tool ?? "this"),
|
|
6851
|
-
data.
|
|
6856
|
+
data.signInTicket,
|
|
6852
6857
|
String(data.expiresAt ?? "")
|
|
6853
6858
|
);
|
|
6854
6859
|
return;
|
|
@@ -6941,15 +6946,15 @@ var AssistantElementEventController = class {
|
|
|
6941
6946
|
/**
|
|
6942
6947
|
* The host application owns the login. This renders the prompt and raises
|
|
6943
6948
|
* `assistant-sign-in-requested`; the page signs the visitor in however it already does, then its
|
|
6944
|
-
* backend spends the
|
|
6949
|
+
* backend spends the sign-in ticket. Nothing here talks to an identity provider.
|
|
6945
6950
|
*/
|
|
6946
|
-
#appendSignInRequest(id, tool,
|
|
6951
|
+
#appendSignInRequest(id, tool, signInTicket, expiresAt) {
|
|
6947
6952
|
const messages = this.#host.messages();
|
|
6948
6953
|
if (!messages) return;
|
|
6949
6954
|
this.#proposalCards.get(id)?.remove();
|
|
6950
6955
|
const card = createSignInCard({
|
|
6951
6956
|
tool,
|
|
6952
|
-
|
|
6957
|
+
signInTicket,
|
|
6953
6958
|
expiresAt,
|
|
6954
6959
|
labels: {
|
|
6955
6960
|
heading: "Sign in to continue",
|
|
@@ -6957,7 +6962,7 @@ var AssistantElementEventController = class {
|
|
|
6957
6962
|
},
|
|
6958
6963
|
onSignIn: () => this.#host.element.dispatchEvent(
|
|
6959
6964
|
new CustomEvent("assistant-sign-in-requested", {
|
|
6960
|
-
detail: { id, tool,
|
|
6965
|
+
detail: { id, tool, signInTicket, expiresAt },
|
|
6961
6966
|
bubbles: true,
|
|
6962
6967
|
composed: true
|
|
6963
6968
|
})
|
|
@@ -6968,7 +6973,7 @@ var AssistantElementEventController = class {
|
|
|
6968
6973
|
this.#host.revealLatest();
|
|
6969
6974
|
this.#host.element.dispatchEvent(
|
|
6970
6975
|
new CustomEvent("assistant-sign-in-required", {
|
|
6971
|
-
detail: { id, tool,
|
|
6976
|
+
detail: { id, tool, signInTicket, expiresAt }
|
|
6972
6977
|
})
|
|
6973
6978
|
);
|
|
6974
6979
|
}
|
|
@@ -7042,6 +7047,36 @@ function queryRequired(root, selector) {
|
|
|
7042
7047
|
if (!element) throw new Error(`assistant renderer is missing ${selector}`);
|
|
7043
7048
|
return element;
|
|
7044
7049
|
}
|
|
7050
|
+
function assistantElementMarkup(appearance, open, styles) {
|
|
7051
|
+
return `${styles}
|
|
7052
|
+
<button class="launcher" type="button" aria-label="${escapeAttribute(appearance.labels.open)}" aria-controls="assistant-panel" aria-expanded="${String(open)}"><slot name="launcher-icon"></slot><span class="visually-hidden" data-session-status aria-live="polite" hidden></span></button>
|
|
7053
|
+
<section class="panel" id="assistant-panel" role="dialog" aria-label="${escapeAttribute(appearance.brand.name)}">
|
|
7054
|
+
<header><slot name="header-leading"></slot><img class="brand-logo" alt=""><strong></strong><slot name="header-actions"></slot><button class="close" type="button" aria-label="${escapeAttribute(appearance.labels.close)}"></button></header>
|
|
7055
|
+
<div class="welcome"><slot name="empty-state"></slot></div>
|
|
7056
|
+
<div class="suggested-prompts"></div>
|
|
7057
|
+
<div class="messages-region">
|
|
7058
|
+
<div class="messages" aria-live="polite"></div>
|
|
7059
|
+
<button class="new-messages" type="button" aria-label="${escapeAttribute(appearance.labels.newMessages)}" hidden></button>
|
|
7060
|
+
</div>
|
|
7061
|
+
<form><slot name="composer-leading"></slot><textarea rows="1"></textarea><button class="send" type="submit"></button><slot name="composer-trailing"></slot></form>
|
|
7062
|
+
<nav class="legal" aria-label="Legal"></nav>
|
|
7063
|
+
<slot name="conversation-footer"></slot>
|
|
7064
|
+
</section>`;
|
|
7065
|
+
}
|
|
7066
|
+
function appendLegalLinks(legal, appearance) {
|
|
7067
|
+
for (const [label, href] of [
|
|
7068
|
+
["Privacy", appearance.privacyUrl],
|
|
7069
|
+
["Terms", appearance.termsUrl]
|
|
7070
|
+
]) {
|
|
7071
|
+
if (!href) continue;
|
|
7072
|
+
const link = document.createElement("a");
|
|
7073
|
+
link.href = href;
|
|
7074
|
+
link.target = "_blank";
|
|
7075
|
+
link.rel = "noopener noreferrer";
|
|
7076
|
+
link.textContent = label;
|
|
7077
|
+
legal.append(link);
|
|
7078
|
+
}
|
|
7079
|
+
}
|
|
7045
7080
|
|
|
7046
7081
|
// src/element-page-context-controller.ts
|
|
7047
7082
|
var AssistantElementPageContextController = class {
|
|
@@ -7829,6 +7864,8 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
7829
7864
|
#pendingUserEcho;
|
|
7830
7865
|
#sessionState = "idle";
|
|
7831
7866
|
#startOpenApplied = false;
|
|
7867
|
+
#readyDispatched = false;
|
|
7868
|
+
#turnInFlight = false;
|
|
7832
7869
|
#hostAppearance;
|
|
7833
7870
|
#appearanceWarningKeys = /* @__PURE__ */ new Set();
|
|
7834
7871
|
#appearanceStyleSnapshot = /* @__PURE__ */ new Map();
|
|
@@ -7900,7 +7937,10 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
7900
7937
|
document.addEventListener("pointerdown", this.#handleDocumentPointerDown);
|
|
7901
7938
|
if (this.#appearance.behavior.startOpen) this.#applyStartOpenPolicy();
|
|
7902
7939
|
this.#render();
|
|
7903
|
-
|
|
7940
|
+
if (!this.#readyDispatched) {
|
|
7941
|
+
this.#readyDispatched = true;
|
|
7942
|
+
this.dispatchEvent(new CustomEvent("assistant-ready"));
|
|
7943
|
+
}
|
|
7904
7944
|
if (this.hasAttribute("start-open")) {
|
|
7905
7945
|
this.setAttribute("open", "");
|
|
7906
7946
|
this.#startOpenApplied = true;
|
|
@@ -8014,7 +8054,14 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
8014
8054
|
async sendMessage(text2) {
|
|
8015
8055
|
const message = text2.trim();
|
|
8016
8056
|
if (!message) return;
|
|
8057
|
+
if (this.#turnInFlight) {
|
|
8058
|
+
throw new AssistantClientError(
|
|
8059
|
+
{ code: "request_in_progress", retryable: true },
|
|
8060
|
+
"an assistant request is already in progress"
|
|
8061
|
+
);
|
|
8062
|
+
}
|
|
8017
8063
|
const generation = this.#conversationGeneration;
|
|
8064
|
+
this.#turnInFlight = true;
|
|
8018
8065
|
this.#pendingUserEcho = message;
|
|
8019
8066
|
this.#appendMessage("user", message);
|
|
8020
8067
|
this.#setBusy(true);
|
|
@@ -8031,6 +8078,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
8031
8078
|
this.#dispatchClientError(error, "turn_failed");
|
|
8032
8079
|
throw error;
|
|
8033
8080
|
} finally {
|
|
8081
|
+
this.#turnInFlight = false;
|
|
8034
8082
|
if (this.#pendingUserEcho === message) this.#pendingUserEcho = void 0;
|
|
8035
8083
|
if (generation === this.#conversationGeneration) this.#setBusy(false);
|
|
8036
8084
|
}
|
|
@@ -8099,6 +8147,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
8099
8147
|
#primeSession() {
|
|
8100
8148
|
if (!this.sessionEndpoint && !this.embedId) return;
|
|
8101
8149
|
if (this.#sessionBootstrap || this.#client?.hasSession()) return;
|
|
8150
|
+
if (this.#client?.isBusy?.() === true) return;
|
|
8102
8151
|
if (this.embedId && !this.#pageContext) void this.#automaticPageContext.refresh();
|
|
8103
8152
|
const client = this.#ensureClient();
|
|
8104
8153
|
const generation = this.#conversationGeneration;
|
|
@@ -8128,6 +8177,8 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
8128
8177
|
if (event.event === "content" || event.event === "message_completed" || event.event === "interaction_completed" || event.event === "error") {
|
|
8129
8178
|
this.#removeThinking();
|
|
8130
8179
|
}
|
|
8180
|
+
if (event.event === "resume_started") this.#setBusy(true);
|
|
8181
|
+
if (event.event === "message_completed" || event.event === "error") this.#setBusy(false);
|
|
8131
8182
|
}
|
|
8132
8183
|
#setBusy(busy) {
|
|
8133
8184
|
this.toggleAttribute("busy", busy);
|
|
@@ -8336,7 +8387,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
8336
8387
|
queryRequired(this.shadowRoot, "header").hidden = !appearance.behavior.showHeader;
|
|
8337
8388
|
const legal = queryRequired(this.shadowRoot, ".legal");
|
|
8338
8389
|
legal.replaceChildren();
|
|
8339
|
-
|
|
8390
|
+
appendLegalLinks(legal, this.#appearance);
|
|
8340
8391
|
this.#applyTheme();
|
|
8341
8392
|
}
|
|
8342
8393
|
#render() {
|
|
@@ -8352,20 +8403,11 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
8352
8403
|
this.dataset.position = appearance.layout.position;
|
|
8353
8404
|
this.dataset.density = appearance.layout.density;
|
|
8354
8405
|
this.toggleAttribute("mobile-fullscreen", appearance.layout.mobileFullscreen);
|
|
8355
|
-
this.shadowRoot.innerHTML =
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
<div class="suggested-prompts"></div>
|
|
8361
|
-
<div class="messages-region">
|
|
8362
|
-
<div class="messages" aria-live="polite"></div>
|
|
8363
|
-
<button class="new-messages" type="button" aria-label="${escapeAttribute(appearance.labels.newMessages)}" hidden></button>
|
|
8364
|
-
</div>
|
|
8365
|
-
<form><slot name="composer-leading"></slot><textarea rows="1"></textarea><button class="send" type="submit"></button><slot name="composer-trailing"></slot></form>
|
|
8366
|
-
<nav class="legal" aria-label="Legal"></nav>
|
|
8367
|
-
<slot name="conversation-footer"></slot>
|
|
8368
|
-
</section>`;
|
|
8406
|
+
this.shadowRoot.innerHTML = assistantElementMarkup(
|
|
8407
|
+
appearance,
|
|
8408
|
+
this.hasAttribute("open"),
|
|
8409
|
+
`${ASSISTANT_ELEMENT_STYLES}${presentationStyles}`
|
|
8410
|
+
);
|
|
8369
8411
|
queryRequired(this.shadowRoot, "strong").textContent = appearance.brand.name;
|
|
8370
8412
|
const logo = queryRequired(this.shadowRoot, ".brand-logo");
|
|
8371
8413
|
const logoUrl = appearance.brand.logo[this.#resolvedMode()];
|
|
@@ -8392,7 +8434,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
8392
8434
|
if (!appearance.behavior.showHeader)
|
|
8393
8435
|
queryRequired(this.shadowRoot, "header").hidden = true;
|
|
8394
8436
|
const legal = queryRequired(this.shadowRoot, ".legal");
|
|
8395
|
-
|
|
8437
|
+
appendLegalLinks(legal, this.#appearance);
|
|
8396
8438
|
this.#messages = this.shadowRoot.querySelector(".messages") ?? void 0;
|
|
8397
8439
|
this.#messages?.append(...conversation);
|
|
8398
8440
|
this.#thinking = this.#messages?.querySelector(".thinking") ?? void 0;
|
|
@@ -8431,20 +8473,6 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
8431
8473
|
});
|
|
8432
8474
|
this.#applyTheme();
|
|
8433
8475
|
}
|
|
8434
|
-
#appendLegalLinks(legal) {
|
|
8435
|
-
for (const [label, href] of [
|
|
8436
|
-
["Privacy", this.#appearance.privacyUrl],
|
|
8437
|
-
["Terms", this.#appearance.termsUrl]
|
|
8438
|
-
]) {
|
|
8439
|
-
if (!href) continue;
|
|
8440
|
-
const link = document.createElement("a");
|
|
8441
|
-
link.href = href;
|
|
8442
|
-
link.target = "_blank";
|
|
8443
|
-
link.rel = "noopener noreferrer";
|
|
8444
|
-
link.textContent = label;
|
|
8445
|
-
legal.append(link);
|
|
8446
|
-
}
|
|
8447
|
-
}
|
|
8448
8476
|
};
|
|
8449
8477
|
function registerNoodleAssistant() {
|
|
8450
8478
|
if (globalThis.customElements && !customElements.get(ASSISTANT_TAG_NAME)) {
|
|
@@ -8465,4 +8493,4 @@ export {
|
|
|
8465
8493
|
dompurify/dist/purify.es.mjs:
|
|
8466
8494
|
(*! @license DOMPurify 3.4.11 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.11/LICENSE *)
|
|
8467
8495
|
*/
|
|
8468
|
-
//# sourceMappingURL=chunk-
|
|
8496
|
+
//# sourceMappingURL=chunk-LEKEIHJV.js.map
|