@helpai/elements 0.24.0 → 0.25.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 +1 -1
- package/configurator.mjs +15 -20
- package/elements-web-component.esm.js +27 -27
- package/elements-web-component.esm.js.map +3 -3
- package/elements.cjs.js +27 -27
- package/elements.cjs.js.map +3 -3
- package/elements.esm.js +27 -27
- package/elements.esm.js.map +3 -3
- package/elements.js +27 -27
- package/elements.js.map +3 -3
- package/index.d.ts +33 -39
- package/index.mjs +169 -50
- package/package.json +1 -1
- package/schema.d.ts +2 -2
- package/schema.json +18 -396
- package/schema.mjs +15 -20
- package/web-component.mjs +169 -50
package/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as Asset, B as BlocksConfig, C as ConnectionConfig, a as ConnectionConfigPartial, L as Link, S as ServerConfig, b as SiteConfig, c as StartConversationResponse, W as WidgetConfig, d as WidgetConfigPartial, e as WidgetSettings, f as WidgetSettingsPartial } from './deployment-
|
|
1
|
+
export { A as Asset, B as BlocksConfig, C as ConnectionConfig, a as ConnectionConfigPartial, L as Link, S as ServerConfig, b as SiteConfig, c as StartConversationResponse, W as WidgetConfig, d as WidgetConfigPartial, e as WidgetSettings, f as WidgetSettingsPartial } from './deployment-B0uvvJYK.js';
|
|
2
2
|
import 'zod';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -180,27 +180,6 @@ type WirePart = {
|
|
|
180
180
|
errorText?: string;
|
|
181
181
|
approval?: ToolApproval;
|
|
182
182
|
};
|
|
183
|
-
/**
|
|
184
|
-
* One recorded form submission, returned on `/ai/agent/start-conversation`
|
|
185
|
-
* (alongside `messages`) so the widget can re-place the "form submitted /
|
|
186
|
-
* skipped" marker at its chronological spot in the timeline.
|
|
187
|
-
*/
|
|
188
|
-
interface FormSubmissionRecord {
|
|
189
|
-
formId: string;
|
|
190
|
-
/** The trigger kind that surfaced the form. */
|
|
191
|
-
trigger: string;
|
|
192
|
-
/** True when the visitor skipped instead of answering. */
|
|
193
|
-
skipped?: boolean;
|
|
194
|
-
/** ISO-8601 — same format + name as {@link WireMessage.createdAt}. */
|
|
195
|
-
createdAt?: string;
|
|
196
|
-
/**
|
|
197
|
-
* The visitor's own answers, echoed back so the submitted marker can offer
|
|
198
|
-
* a read-only review after a reload. Scoped to the same `visitorId` that
|
|
199
|
-
* submitted them — this is the visitor seeing their own data, independent
|
|
200
|
-
* of `submit.toContext` (which governs mirroring into request context).
|
|
201
|
-
*/
|
|
202
|
-
values?: Record<string, string>;
|
|
203
|
-
}
|
|
204
183
|
/** One-shot bot persona shown in the panel header. */
|
|
205
184
|
interface ConversationAgent {
|
|
206
185
|
name: string;
|
|
@@ -262,13 +241,6 @@ interface StartConversationResponse {
|
|
|
262
241
|
/** Page-mode sidebar blocks (navigation, link cards). Backend ships it whenever the deployment renders the page-shell sidebar. */
|
|
263
242
|
blocks?: Record<string, unknown>;
|
|
264
243
|
messages?: WireMessage[];
|
|
265
|
-
/**
|
|
266
|
-
* Form submissions recorded for this conversation (via
|
|
267
|
-
* `/ai/agent/submit-form`), returned like `messages` so the widget can
|
|
268
|
-
* reconstruct the timeline on resume — each renders as a collapsed
|
|
269
|
-
* "form submitted" marker at its chronological position.
|
|
270
|
-
*/
|
|
271
|
-
formSubmissions?: FormSubmissionRecord[];
|
|
272
244
|
/**
|
|
273
245
|
* Server's authoritative copy of the visitor's preferences. When present,
|
|
274
246
|
* client adopts it (overrides local cache) and re-applies it to the UI
|
|
@@ -590,7 +562,7 @@ interface FormDef {
|
|
|
590
562
|
* answers. Default `true`. Set `false` to keep the marker plain — e.g.
|
|
591
563
|
* sensitive answers that shouldn't linger on screen (pairs with
|
|
592
564
|
* `mirrorToContext: false`; the backend should also omit `values` from the
|
|
593
|
-
* `
|
|
565
|
+
* `/activity` records for such forms).
|
|
594
566
|
*/
|
|
595
567
|
reviewable?: boolean;
|
|
596
568
|
}
|
|
@@ -698,7 +670,7 @@ interface LauncherOptions {
|
|
|
698
670
|
*
|
|
699
671
|
* `modules` is an ordered list of tabs; each picks a UI `layout`, optional
|
|
700
672
|
* content `contentTags` (the scope passed as the `tags` filter to
|
|
701
|
-
* `GET /
|
|
673
|
+
* `GET /content`), and a translatable `label`.
|
|
702
674
|
*/
|
|
703
675
|
/** The UI layouts a tab can render with. `chat` is the conversation; the rest render content. */
|
|
704
676
|
type ModuleLayout = "chat" | "home" | "help" | "news";
|
|
@@ -982,9 +954,11 @@ interface PageContext {
|
|
|
982
954
|
[key: string]: string | number | boolean | undefined;
|
|
983
955
|
}
|
|
984
956
|
/**
|
|
985
|
-
* Override the HTTP endpoints the widget calls.
|
|
986
|
-
*
|
|
987
|
-
*
|
|
957
|
+
* Override the HTTP endpoints the widget calls. `upload` / `transcribe`
|
|
958
|
+
* paths are appended to the agent base (`${baseUrl}/api/agent`);
|
|
959
|
+
* `submitForm` is appended to the data base (`dataBaseUrl`). Absolute URLs
|
|
960
|
+
* also work (useful when the upload endpoint lives on a different origin
|
|
961
|
+
* like a CDN presign service).
|
|
988
962
|
*/
|
|
989
963
|
interface Endpoints {
|
|
990
964
|
/** File upload endpoint. Default: `'/ai/agent/upload-file'`. */
|
|
@@ -992,7 +966,7 @@ interface Endpoints {
|
|
|
992
966
|
/** Voice transcription endpoint. Default: `'/ai/agent/transcribe-audio'`. */
|
|
993
967
|
transcribe?: string;
|
|
994
968
|
/**
|
|
995
|
-
* Form submission endpoint. Default: `'/
|
|
969
|
+
* Form submission endpoint. Default: `'/submit-form'`. When any form
|
|
996
970
|
* (a pre-chat lead capture, a CSAT survey, etc.) is completed, the widget POSTs
|
|
997
971
|
* the captured values here (fire-and-forget), keyed by the same `visitorId` +
|
|
998
972
|
* `conversationId` the conversation uses; the payload carries `formId` + `trigger`
|
|
@@ -1024,8 +998,8 @@ interface TrackingOptions {
|
|
|
1024
998
|
*/
|
|
1025
999
|
enabled?: boolean;
|
|
1026
1000
|
/**
|
|
1027
|
-
* Pixel collector URL. Default:
|
|
1028
|
-
* (e.g. `https://
|
|
1001
|
+
* Pixel collector URL. Default: `${dataBaseUrl}/elements/px.gif`
|
|
1002
|
+
* (e.g. `https://help.ai/api/data/elements/px.gif`).
|
|
1029
1003
|
*/
|
|
1030
1004
|
endpoint?: string;
|
|
1031
1005
|
/**
|
|
@@ -1090,8 +1064,21 @@ interface ChatWidgetOptions {
|
|
|
1090
1064
|
publicKey?: string;
|
|
1091
1065
|
/** Deployment ID (UUID). Sent as `x-ai-agent-deployment-id`. */
|
|
1092
1066
|
aiAgentDeploymentId?: string;
|
|
1093
|
-
/**
|
|
1067
|
+
/**
|
|
1068
|
+
* MAIN site origin (no trailing slash, e.g. `https://help.ai`). The API
|
|
1069
|
+
* modules derive from it: the agent API lives at `${baseUrl}/api/agent`,
|
|
1070
|
+
* the data API at `${baseUrl}/api/data`. Defaults to the brand's origin.
|
|
1071
|
+
*/
|
|
1094
1072
|
baseUrl?: string;
|
|
1073
|
+
/**
|
|
1074
|
+
* Data-API base (no trailing slash) — the `module-help-ai-data-api`
|
|
1075
|
+
* service serving content, form definitions, and form submissions at
|
|
1076
|
+
* root-level paths (`/content`, `/forms`, `/activity`, `/submit-form`).
|
|
1077
|
+
* Precedence: this option > server-pushed `config.dataBaseUrl` > derived
|
|
1078
|
+
* from the main origin (`${baseUrl}/api/data`, e.g.
|
|
1079
|
+
* `https://help.ai/api/data`).
|
|
1080
|
+
*/
|
|
1081
|
+
dataBaseUrl?: string;
|
|
1095
1082
|
/** Host-asserted, informational end-user context (untrusted; not auth). */
|
|
1096
1083
|
userContext?: UserContext;
|
|
1097
1084
|
/** Host-asserted, informational page/visit context (untrusted; `area`, url, …). */
|
|
@@ -1146,6 +1133,8 @@ interface ResolvedOptions {
|
|
|
1146
1133
|
publicKey?: string;
|
|
1147
1134
|
aiAgentDeploymentId?: string;
|
|
1148
1135
|
baseUrl: string;
|
|
1136
|
+
/** Resolved data-API base — see {@link ChatWidgetOptions.dataBaseUrl}. */
|
|
1137
|
+
dataBaseUrl: string;
|
|
1149
1138
|
userContext?: UserContext;
|
|
1150
1139
|
pageContext?: PageContext;
|
|
1151
1140
|
position: Position;
|
|
@@ -1237,8 +1226,14 @@ interface ResolvedOptions {
|
|
|
1237
1226
|
* Matches a subset of {@link ChatWidgetOptions} — anything the server can
|
|
1238
1227
|
* reasonably dictate (UI, behaviour, routing). Callbacks (`onMessage` etc.)
|
|
1239
1228
|
* and the public key are deliberately excluded — those are client concerns.
|
|
1229
|
+
*
|
|
1230
|
+
* Form definitions no longer ride this config: the widget fetches them from
|
|
1231
|
+
* the data-API (`GET /forms`). A stale backend still pushing
|
|
1232
|
+
* `config.forms` is ignored.
|
|
1240
1233
|
*/
|
|
1241
1234
|
interface ServerConfig {
|
|
1235
|
+
/** Data-API base override — adopted only when the host didn't set one. */
|
|
1236
|
+
dataBaseUrl?: string;
|
|
1242
1237
|
presentation?: PresentationOptions;
|
|
1243
1238
|
behavior?: BehaviorOptions;
|
|
1244
1239
|
theme?: ThemePreference | ThemeOverrides;
|
|
@@ -1249,7 +1244,6 @@ interface ServerConfig {
|
|
|
1249
1244
|
i18n?: I18nOptions;
|
|
1250
1245
|
footer?: FooterOptions;
|
|
1251
1246
|
features?: FeatureFlags;
|
|
1252
|
-
forms?: FormsOptions;
|
|
1253
1247
|
modules?: ModulesOptions;
|
|
1254
1248
|
tracking?: TrackingOptions;
|
|
1255
1249
|
endpoints?: Endpoints;
|
package/index.mjs
CHANGED
|
@@ -19,7 +19,12 @@ var BRAND = {
|
|
|
19
19
|
// and @goai builds.
|
|
20
20
|
tagName: true ? "web-ai-chat" : "web-ai-chat",
|
|
21
21
|
cssPrefix: true ? "helpai" : "helpai",
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* MAIN site origin (e.g. `https://help.ai`) — module bases derive from it:
|
|
24
|
+
* agent API at `${defaultBaseUrl}/api/agent`, data API at
|
|
25
|
+
* `${defaultBaseUrl}/api/data`.
|
|
26
|
+
*/
|
|
27
|
+
defaultBaseUrl: true ? "https://help.ai" : "",
|
|
23
28
|
standaloneUrl: true ? "https://help.ai/chat" : "",
|
|
24
29
|
/** Public-facing brand label, e.g. "Help AI" / "Go AI". */
|
|
25
30
|
displayName: true ? "Help AI" : "Help AI",
|
|
@@ -472,13 +477,12 @@ var DEFAULT_FEATURES = {
|
|
|
472
477
|
var DEFAULT_FORMS = { list: [], byTrigger: {} };
|
|
473
478
|
var DEFAULT_TRACKING = {
|
|
474
479
|
enabled: false,
|
|
475
|
-
endpoint: `https://t.${BRAND.domain}/api/ai-elements/px.gif`,
|
|
476
480
|
sampleRate: 1
|
|
477
481
|
};
|
|
478
482
|
var DEFAULT_ENDPOINTS = {
|
|
479
483
|
upload: "/ai/agent/upload-file",
|
|
480
484
|
transcribe: "/ai/agent/transcribe-audio",
|
|
481
|
-
submitForm: "/
|
|
485
|
+
submitForm: "/submit-form"
|
|
482
486
|
};
|
|
483
487
|
var DEFAULT_LAUNCHER = {
|
|
484
488
|
variant: "circle",
|
|
@@ -520,11 +524,14 @@ function resolveOptions(rawOpts) {
|
|
|
520
524
|
const mode = presentation.mode ?? "floating";
|
|
521
525
|
const locale = i18n.locale ?? resolveDefaultLocale(i18n.defaultLocale);
|
|
522
526
|
const availableLocales = i18n.availableLocales ?? [locale];
|
|
527
|
+
const baseUrl = (opts.baseUrl ?? BRAND.defaultBaseUrl).replace(/\/$/, "");
|
|
528
|
+
const dataBaseUrl = (opts.dataBaseUrl ?? `${baseUrl}/api/data`).replace(/\/$/, "");
|
|
523
529
|
return {
|
|
524
530
|
widgetId: opts.widgetId ?? "default",
|
|
525
531
|
publicKey: opts.publicKey,
|
|
526
532
|
aiAgentDeploymentId: opts.aiAgentDeploymentId,
|
|
527
|
-
baseUrl
|
|
533
|
+
baseUrl,
|
|
534
|
+
dataBaseUrl,
|
|
528
535
|
userContext: sanitizeUserContext(opts.userContext),
|
|
529
536
|
pageContext: sanitizePageContext(opts.pageContext),
|
|
530
537
|
position: presentation.position ?? "bottom-right",
|
|
@@ -569,7 +576,7 @@ function resolveOptions(rawOpts) {
|
|
|
569
576
|
poweredBy: resolvePoweredBy(footer.poweredBy),
|
|
570
577
|
composer: resolveComposer(opts.composer),
|
|
571
578
|
modules: resolveModules(opts.modules),
|
|
572
|
-
tracking: resolveTracking(opts.tracking),
|
|
579
|
+
tracking: resolveTracking(opts.tracking, dataBaseUrl),
|
|
573
580
|
storage: opts.storage ?? defaultStorage,
|
|
574
581
|
onMessage: opts.onMessage,
|
|
575
582
|
onOpen: opts.onOpen,
|
|
@@ -602,11 +609,13 @@ function resolveHaptics(overrides) {
|
|
|
602
609
|
events: overrides?.events
|
|
603
610
|
};
|
|
604
611
|
}
|
|
605
|
-
function resolveTracking(overrides) {
|
|
612
|
+
function resolveTracking(overrides, dataBaseUrl) {
|
|
606
613
|
const sampleRate = overrides?.sampleRate ?? DEFAULT_TRACKING.sampleRate;
|
|
607
614
|
return {
|
|
608
615
|
enabled: overrides?.enabled ?? DEFAULT_TRACKING.enabled,
|
|
609
|
-
|
|
616
|
+
// The collector rides the data module — `${dataBaseUrl}/elements/px.gif`
|
|
617
|
+
// (e.g. `https://help.ai/api/data/elements/px.gif`).
|
|
618
|
+
endpoint: overrides?.endpoint ?? `${dataBaseUrl}/elements/px.gif`,
|
|
610
619
|
// Clamp instead of reject — a malformed rate must never turn a
|
|
611
620
|
// disabled tracker on or crash resolve (no Zod on the IIFE path).
|
|
612
621
|
sampleRate: Math.min(1, Math.max(0, Number.isFinite(sampleRate) ? sampleRate : 1)),
|
|
@@ -775,7 +784,7 @@ function mergeServerConfig(user, server) {
|
|
|
775
784
|
out[key] = mergeLeaves(sv, uv);
|
|
776
785
|
}
|
|
777
786
|
if (server.modules !== void 0 && user.modules === void 0) out.modules = server.modules;
|
|
778
|
-
if (server.
|
|
787
|
+
if (server.dataBaseUrl !== void 0 && user.dataBaseUrl === void 0) out.dataBaseUrl = server.dataBaseUrl;
|
|
779
788
|
const userI18n = user.i18n;
|
|
780
789
|
const serverI18n = server.i18n;
|
|
781
790
|
if (userI18n || serverI18n) {
|
|
@@ -835,6 +844,7 @@ var EMBED_SCALAR_ATTRS = [
|
|
|
835
844
|
["public-key", "publicKey"],
|
|
836
845
|
["ai-agent-deployment-id", "aiAgentDeploymentId"],
|
|
837
846
|
["base-url", "baseUrl"],
|
|
847
|
+
["data-base-url", "dataBaseUrl"],
|
|
838
848
|
["theme", "theme", (v) => v]
|
|
839
849
|
];
|
|
840
850
|
var PRESENTATION_ATTRS = [
|
|
@@ -1270,16 +1280,33 @@ var DEFAULT_PATHS = {
|
|
|
1270
1280
|
* on the client; a failure just leaves the badge until the next sync.
|
|
1271
1281
|
*/
|
|
1272
1282
|
markRead: "/ai/agent/mark-read",
|
|
1273
|
-
// ──
|
|
1274
|
-
/** All widget content via one filtered endpoint. `GET /
|
|
1275
|
-
content: "/
|
|
1283
|
+
// ── Data API (module-help-ai-data-api, via `dataBaseUrl`) ─────────
|
|
1284
|
+
/** All widget content via one filtered endpoint. `GET /content?tags=…`. */
|
|
1285
|
+
content: "/content",
|
|
1286
|
+
/**
|
|
1287
|
+
* Resolved form definitions for the deployment. `GET /forms` →
|
|
1288
|
+
* `{ forms: FormDef[] }`. Fetched once at boot (in parallel with
|
|
1289
|
+
* start-conversation); form gating waits for it. A failure is treated as
|
|
1290
|
+
* "no forms" (non-fatal).
|
|
1291
|
+
*/
|
|
1292
|
+
forms: "/forms",
|
|
1276
1293
|
/**
|
|
1277
1294
|
* Form submission (the event-driven forms engine). POST `{ visitorId,
|
|
1278
1295
|
* conversationId, formId, trigger, values, skipped? }` → record the form's answers
|
|
1279
1296
|
* immediately, keyed by the same `visitorId` + `conversationId` the chat uses.
|
|
1280
1297
|
* Overridable via `endpoints.submitForm`. Fire-and-forget; 404 is ignored.
|
|
1281
1298
|
*/
|
|
1282
|
-
submitForm: "/
|
|
1299
|
+
submitForm: "/submit-form",
|
|
1300
|
+
/**
|
|
1301
|
+
* Visitor interaction records for a conversation. `GET
|
|
1302
|
+
* /activity?visitorId=…&conversationId=…` →
|
|
1303
|
+
* `{ formSubmissions: FormSubmissionRecord[] }` (chronological). Replaces
|
|
1304
|
+
* the former `formSubmissions` echo on start-conversation / list-messages;
|
|
1305
|
+
* deliberately generic — future visitor interaction records ride the same
|
|
1306
|
+
* endpoint. A failure (e.g. 404 `AI_AGENT_PUBLIC_ACCESS_NOT_FOUND` when the
|
|
1307
|
+
* deployment doesn't resolve) is treated as "no records" (non-fatal).
|
|
1308
|
+
*/
|
|
1309
|
+
activity: "/activity"
|
|
1283
1310
|
};
|
|
1284
1311
|
var CONTEXT_PARAM = "context";
|
|
1285
1312
|
function buildSendMessageRequest(params) {
|
|
@@ -1552,14 +1579,17 @@ var AgentTransport = class {
|
|
|
1552
1579
|
log4.debug("markRead failed (non-fatal)", { err });
|
|
1553
1580
|
}
|
|
1554
1581
|
}
|
|
1555
|
-
// ----
|
|
1582
|
+
// ---- Data API (content / forms — module-help-ai-data-api) --------------
|
|
1556
1583
|
//
|
|
1557
|
-
//
|
|
1558
|
-
//
|
|
1559
|
-
//
|
|
1560
|
-
|
|
1584
|
+
// The DATA surfaces live on `dataBaseUrl` (default `${baseUrl}/api/data`)
|
|
1585
|
+
// with ROOT-level paths — same auth headers + envelope, same retry
|
|
1586
|
+
// behavior. One endpoint for every content surface (Home / Help / News /
|
|
1587
|
+
// docs); callers pass filters; a thrown StreamError (e.g. 404 on an older
|
|
1588
|
+
// backend) is treated as "no content" by the module and the tab degrades
|
|
1589
|
+
// gracefully.
|
|
1590
|
+
/** Fetch content rows by filter. `GET /content` (data-API). */
|
|
1561
1591
|
async listContent(query = {}) {
|
|
1562
|
-
const url = new URL(this.
|
|
1592
|
+
const url = new URL(this.dataUrl(DEFAULT_PATHS.content));
|
|
1563
1593
|
for (const [key, value] of Object.entries(query)) {
|
|
1564
1594
|
if (value === void 0) continue;
|
|
1565
1595
|
url.searchParams.set(key, Array.isArray(value) ? value.join(",") : String(value));
|
|
@@ -1570,6 +1600,40 @@ var AgentTransport = class {
|
|
|
1570
1600
|
log4.debug("listContent \u2190", { count: items.length, total: res.pagination?.total });
|
|
1571
1601
|
return { ...res, items };
|
|
1572
1602
|
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Fetch the deployment's resolved form definitions. `GET /forms`
|
|
1605
|
+
* (data-API). Replaces the former `config.forms` push on the handshake —
|
|
1606
|
+
* called once at boot, in parallel with `startConversation`; form gating
|
|
1607
|
+
* (pre-chat etc.) waits for it. Callers treat a thrown StreamError as
|
|
1608
|
+
* "no forms" (non-fatal).
|
|
1609
|
+
*/
|
|
1610
|
+
async listForms() {
|
|
1611
|
+
log4.debug("listForms \u2192");
|
|
1612
|
+
const res = await this.getJson(this.dataUrl(DEFAULT_PATHS.forms), "listForms");
|
|
1613
|
+
const forms = res.forms ?? [];
|
|
1614
|
+
log4.debug("listForms \u2190", { count: forms.length });
|
|
1615
|
+
return { forms };
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Fetch the visitor's recorded activity for a conversation — currently the
|
|
1619
|
+
* form submissions that rebuild the collapsed "form submitted / skipped"
|
|
1620
|
+
* timeline markers on resume. `GET /activity` (data-API); the
|
|
1621
|
+
* envelope carries `visitorId` / `conversationId` as query params.
|
|
1622
|
+
* `conversationId` here is an optional resource selector (a thread that may
|
|
1623
|
+
* differ from the active conversation — the history-pane switch); when
|
|
1624
|
+
* omitted, the envelope's active conversation applies. Callers treat a
|
|
1625
|
+
* thrown StreamError (e.g. 404 on a deployment that doesn't resolve) as
|
|
1626
|
+
* "no activity" (non-fatal).
|
|
1627
|
+
*/
|
|
1628
|
+
async getActivity(conversationId) {
|
|
1629
|
+
const url = new URL(this.dataUrl(DEFAULT_PATHS.activity));
|
|
1630
|
+
if (conversationId) url.searchParams.set("conversationId", conversationId);
|
|
1631
|
+
log4.debug("getActivity \u2192", { conversationId: conversationId ?? this.conversationId });
|
|
1632
|
+
const res = await this.getJson(url.toString(), "getActivity");
|
|
1633
|
+
const formSubmissions = res.formSubmissions ?? [];
|
|
1634
|
+
log4.debug("getActivity \u2190", { count: formSubmissions.length });
|
|
1635
|
+
return { formSubmissions };
|
|
1636
|
+
}
|
|
1573
1637
|
async saveUserPrefs(userPrefs) {
|
|
1574
1638
|
log4.debug("saveUserPrefs \u2192", {
|
|
1575
1639
|
visitorId: this.visitorId,
|
|
@@ -1590,8 +1654,9 @@ var AgentTransport = class {
|
|
|
1590
1654
|
return this.opts.endpoints?.submitForm ?? DEFAULT_PATHS.submitForm;
|
|
1591
1655
|
}
|
|
1592
1656
|
/**
|
|
1593
|
-
* Record a completed form (intake, CSAT, claim, …).
|
|
1594
|
-
*
|
|
1657
|
+
* Record a completed form (intake, CSAT, claim, …). `POST
|
|
1658
|
+
* /submit-form` (data-API). Fire-and-forget — the record is
|
|
1659
|
+
* captured even if the visitor never sends a message. `conversationId`,
|
|
1595
1660
|
* `formId`, and `trigger` are explicit so the backend can tie + route;
|
|
1596
1661
|
* `visitorId` + context ride the envelope. A failure (e.g. 404 on a backend
|
|
1597
1662
|
* that doesn't implement the endpoint) is non-fatal, so callers don't await.
|
|
@@ -1599,7 +1664,7 @@ var AgentTransport = class {
|
|
|
1599
1664
|
async submitForm(body) {
|
|
1600
1665
|
log4.debug("submitForm \u2192", { formId: body.formId, trigger: body.trigger, fields: Object.keys(body.values).length });
|
|
1601
1666
|
try {
|
|
1602
|
-
await this.postJson(this.submitFormPath, body, "submitForm");
|
|
1667
|
+
await this.postJson(this.dataUrl(this.submitFormPath), body, "submitForm");
|
|
1603
1668
|
} catch (err) {
|
|
1604
1669
|
log4.debug("submitForm failed (non-fatal)", { err });
|
|
1605
1670
|
}
|
|
@@ -1835,9 +1900,24 @@ var AgentTransport = class {
|
|
|
1835
1900
|
if (!res.ok) throw new StreamError(`${label} failed: ${res.status}`, "server", res.status);
|
|
1836
1901
|
return await res.json();
|
|
1837
1902
|
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Agent-API URL — resolves a `/ai/agent/*` path against the agent module
|
|
1905
|
+
* base, `${baseUrl}/api/agent`. Absolute URLs pass through unchanged.
|
|
1906
|
+
*/
|
|
1838
1907
|
url(pathOrUrl) {
|
|
1839
1908
|
if (/^https?:\/\//i.test(pathOrUrl)) return pathOrUrl;
|
|
1840
|
-
return `${this.opts.baseUrl}${pathOrUrl}`;
|
|
1909
|
+
return `${this.opts.baseUrl}/api/agent${pathOrUrl}`;
|
|
1910
|
+
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Data-API variant of {@link url} — resolves a root-level path (content /
|
|
1913
|
+
* forms / submit-form / activity) against `dataBaseUrl`. Absolute URLs
|
|
1914
|
+
* (e.g. an `endpoints.submitForm` pointing straight at a CRM) pass through
|
|
1915
|
+
* unchanged; with no `dataBaseUrl` configured the data module derives from
|
|
1916
|
+
* the main origin: `${baseUrl}/api/data`.
|
|
1917
|
+
*/
|
|
1918
|
+
dataUrl(pathOrUrl) {
|
|
1919
|
+
if (/^https?:\/\//i.test(pathOrUrl)) return pathOrUrl;
|
|
1920
|
+
return `${this.opts.dataBaseUrl ?? `${this.opts.baseUrl}/api/data`}${pathOrUrl}`;
|
|
1841
1921
|
}
|
|
1842
1922
|
get fetchImpl() {
|
|
1843
1923
|
return this.opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
@@ -6362,6 +6442,8 @@ function App({ options, hostElement, bus }) {
|
|
|
6362
6442
|
const chatTabIdRef = useRef9(void 0);
|
|
6363
6443
|
chatTabIdRef.current = chatTabId();
|
|
6364
6444
|
const [conversationReady, setConversationReady] = useState13(false);
|
|
6445
|
+
const [remoteForms, setRemoteForms] = useState13(null);
|
|
6446
|
+
const [formsReady, setFormsReady] = useState13(false);
|
|
6365
6447
|
const isInlineLike = options.mode === "standalone" || options.mode === "inline";
|
|
6366
6448
|
const initialPanelRef = useRef9(
|
|
6367
6449
|
resolveInitialPanelState(options, currentViewportWidth(), persistence.loadPanelOpen(), persistence.loadPanelSize())
|
|
@@ -6394,6 +6476,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6394
6476
|
const [transport] = useState13(
|
|
6395
6477
|
() => new AgentTransport({
|
|
6396
6478
|
baseUrl: options.baseUrl,
|
|
6479
|
+
dataBaseUrl: options.dataBaseUrl,
|
|
6397
6480
|
auth: createAuth({
|
|
6398
6481
|
publicKey: options.publicKey,
|
|
6399
6482
|
aiAgentDeploymentId: options.aiAgentDeploymentId,
|
|
@@ -6474,6 +6557,26 @@ function App({ options, hostElement, bus }) {
|
|
|
6474
6557
|
},
|
|
6475
6558
|
[messagesSig]
|
|
6476
6559
|
);
|
|
6560
|
+
const fetchFormMarkers = useCallback6(
|
|
6561
|
+
async (conversationId) => {
|
|
6562
|
+
try {
|
|
6563
|
+
const { formSubmissions: subs } = await transport.getActivity(conversationId);
|
|
6564
|
+
const lastIndexByForm = /* @__PURE__ */ new Map();
|
|
6565
|
+
subs.forEach((rec, i) => lastIndexByForm.set(rec.formId, i));
|
|
6566
|
+
return subs.filter((rec, i) => !rec.skipped || lastIndexByForm.get(rec.formId) === i).map((rec, i) => ({
|
|
6567
|
+
key: `wire:${rec.formId}:${rec.createdAt ?? i}`,
|
|
6568
|
+
formId: rec.formId,
|
|
6569
|
+
outcome: rec.skipped ? "skipped" : "submitted",
|
|
6570
|
+
createdAt: parseWireDate(rec.createdAt),
|
|
6571
|
+
values: rec.values
|
|
6572
|
+
}));
|
|
6573
|
+
} catch (err) {
|
|
6574
|
+
log16.debug("getActivity failed \u2014 no form markers (non-fatal)", { err });
|
|
6575
|
+
return [];
|
|
6576
|
+
}
|
|
6577
|
+
},
|
|
6578
|
+
[transport]
|
|
6579
|
+
);
|
|
6477
6580
|
const connectGenRef = useRef9(0);
|
|
6478
6581
|
const runStartConversation = useCallback6(
|
|
6479
6582
|
async ({ newConversation }) => {
|
|
@@ -6542,24 +6645,14 @@ function App({ options, hostElement, bus }) {
|
|
|
6542
6645
|
if (isResume) {
|
|
6543
6646
|
setLoadingMessages(true);
|
|
6544
6647
|
try {
|
|
6545
|
-
const
|
|
6648
|
+
const chatPromise = res.messages?.length ? Promise.resolve({
|
|
6546
6649
|
conversationId: persistedChatId,
|
|
6547
6650
|
canContinue: res.canContinue ?? true,
|
|
6548
|
-
messages: res.messages
|
|
6549
|
-
|
|
6550
|
-
|
|
6651
|
+
messages: res.messages
|
|
6652
|
+
}) : transport.loadConversation(persistedChatId);
|
|
6653
|
+
const [chat, markers] = await Promise.all([chatPromise, fetchFormMarkers()]);
|
|
6551
6654
|
if (isStale()) return;
|
|
6552
6655
|
const loaded = (chat.messages ?? []).map(fromWireMessage);
|
|
6553
|
-
const subs = chat.formSubmissions ?? res.formSubmissions ?? [];
|
|
6554
|
-
const lastIndexByForm = /* @__PURE__ */ new Map();
|
|
6555
|
-
subs.forEach((rec, i) => lastIndexByForm.set(rec.formId, i));
|
|
6556
|
-
const markers = subs.filter((rec, i) => !rec.skipped || lastIndexByForm.get(rec.formId) === i).map((rec, i) => ({
|
|
6557
|
-
key: `wire:${rec.formId}:${rec.createdAt ?? i}`,
|
|
6558
|
-
formId: rec.formId,
|
|
6559
|
-
outcome: rec.skipped ? "skipped" : "submitted",
|
|
6560
|
-
createdAt: parseWireDate(rec.createdAt),
|
|
6561
|
-
values: rec.values
|
|
6562
|
-
}));
|
|
6563
6656
|
setFormMarkers(markers);
|
|
6564
6657
|
const timelineStamps = [...loaded.map((m) => m.createdAt), ...markers.map((m) => m.createdAt)].filter(
|
|
6565
6658
|
(n) => n !== void 0
|
|
@@ -6597,7 +6690,18 @@ function App({ options, hostElement, bus }) {
|
|
|
6597
6690
|
setConversationReady(true);
|
|
6598
6691
|
}
|
|
6599
6692
|
},
|
|
6600
|
-
[
|
|
6693
|
+
[
|
|
6694
|
+
transport,
|
|
6695
|
+
visitorId,
|
|
6696
|
+
persistence,
|
|
6697
|
+
options,
|
|
6698
|
+
bus,
|
|
6699
|
+
messagesSig,
|
|
6700
|
+
conversationIdSig,
|
|
6701
|
+
feedback,
|
|
6702
|
+
playWelcome,
|
|
6703
|
+
fetchFormMarkers
|
|
6704
|
+
]
|
|
6601
6705
|
);
|
|
6602
6706
|
const userSig = JSON.stringify(options.userContext ?? null);
|
|
6603
6707
|
const pageSig = JSON.stringify(options.pageContext ?? null);
|
|
@@ -6652,6 +6756,16 @@ function App({ options, hostElement, bus }) {
|
|
|
6652
6756
|
);
|
|
6653
6757
|
useEffect17(() => {
|
|
6654
6758
|
void runStartConversation({ newConversation: false });
|
|
6759
|
+
void (async () => {
|
|
6760
|
+
try {
|
|
6761
|
+
const res = await transport.listForms();
|
|
6762
|
+
setRemoteForms(resolveForms(res.forms));
|
|
6763
|
+
} catch (err) {
|
|
6764
|
+
log16.debug("listForms failed \u2014 treating as no forms", { err });
|
|
6765
|
+
} finally {
|
|
6766
|
+
setFormsReady(true);
|
|
6767
|
+
}
|
|
6768
|
+
})();
|
|
6655
6769
|
const persistedConversation = conversationIdSig.value;
|
|
6656
6770
|
let resume = null;
|
|
6657
6771
|
if (persistedConversation) {
|
|
@@ -6788,8 +6902,9 @@ function App({ options, hostElement, bus }) {
|
|
|
6788
6902
|
[options.features.humanInLoop, handleToolResult, handleToolDecision]
|
|
6789
6903
|
);
|
|
6790
6904
|
const userMessageCount = () => messagesSig.value.reduce((n, m) => n + (m.role === "user" ? 1 : 0), 0);
|
|
6905
|
+
const effectiveForms = options.forms.list.length > 0 ? options.forms : remoteForms ?? options.forms;
|
|
6791
6906
|
const forms = useForms({
|
|
6792
|
-
forms:
|
|
6907
|
+
forms: effectiveForms,
|
|
6793
6908
|
persistence,
|
|
6794
6909
|
userContext: () => options.userContext,
|
|
6795
6910
|
messageCount: userMessageCount,
|
|
@@ -6822,18 +6937,18 @@ function App({ options, hostElement, bus }) {
|
|
|
6822
6937
|
const pageArea = options.pageContext?.area ? String(options.pageContext.area) : void 0;
|
|
6823
6938
|
const msgCount = useComputed7(() => messagesSig.value.length);
|
|
6824
6939
|
useEffect17(() => {
|
|
6825
|
-
if (conversationReady) forms.fire("pre-chat");
|
|
6826
|
-
}, [conversationReady, forms]);
|
|
6940
|
+
if (conversationReady && formsReady) forms.fire("pre-chat");
|
|
6941
|
+
}, [conversationReady, formsReady, forms]);
|
|
6827
6942
|
useEffect17(() => {
|
|
6828
|
-
if (!canSend) forms.fire("conversation-closed");
|
|
6829
|
-
}, [canSend, forms]);
|
|
6943
|
+
if (formsReady && !canSend) forms.fire("conversation-closed");
|
|
6944
|
+
}, [formsReady, canSend, forms]);
|
|
6830
6945
|
useEffect17(() => {
|
|
6831
|
-
if (conversationReady && pageArea) forms.fire("page-area");
|
|
6832
|
-
}, [conversationReady, pageArea, forms]);
|
|
6946
|
+
if (conversationReady && formsReady && pageArea) forms.fire("page-area");
|
|
6947
|
+
}, [conversationReady, formsReady, pageArea, forms]);
|
|
6833
6948
|
const idleMs = useMemo3(() => {
|
|
6834
|
-
const secs =
|
|
6949
|
+
const secs = effectiveForms.list.flatMap((f) => f.triggers).filter((t) => t.kind === "idle").map((t) => Number(t.param));
|
|
6835
6950
|
return secs.length ? Math.min(...secs) * 1e3 : 0;
|
|
6836
|
-
}, [
|
|
6951
|
+
}, [effectiveForms]);
|
|
6837
6952
|
useEffect17(() => {
|
|
6838
6953
|
if (!idleMs || !isOpen) return;
|
|
6839
6954
|
const id = setTimeout(() => forms.fire("idle"), idleMs);
|
|
@@ -7010,9 +7125,13 @@ function App({ options, hostElement, bus }) {
|
|
|
7010
7125
|
log16.info("selectConversation", { conversationId: targetConversationId });
|
|
7011
7126
|
bus.emit("selectConversation", { conversationId: targetConversationId });
|
|
7012
7127
|
try {
|
|
7013
|
-
const res = await
|
|
7128
|
+
const [res, markers] = await Promise.all([
|
|
7129
|
+
transport.loadConversation(targetConversationId),
|
|
7130
|
+
fetchFormMarkers(targetConversationId)
|
|
7131
|
+
]);
|
|
7014
7132
|
const hydrated = res.messages.map(fromWireMessage);
|
|
7015
7133
|
messagesSig.value = hydrated;
|
|
7134
|
+
setFormMarkers(markers);
|
|
7016
7135
|
conversationIdSig.value = res.conversationId;
|
|
7017
7136
|
persistence.saveConversationId(res.conversationId);
|
|
7018
7137
|
if (res.agent) setAgent(res.agent);
|
|
@@ -7026,7 +7145,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7026
7145
|
options.onError?.(err);
|
|
7027
7146
|
}
|
|
7028
7147
|
},
|
|
7029
|
-
[transport, messagesSig, conversationIdSig, bus, options, persistence, refreshUnread]
|
|
7148
|
+
[transport, messagesSig, conversationIdSig, bus, options, persistence, refreshUnread, fetchFormMarkers]
|
|
7030
7149
|
);
|
|
7031
7150
|
useEffect17(() => {
|
|
7032
7151
|
const unsub = bindHostCommands(hostElement, {
|
|
@@ -7045,10 +7164,10 @@ function App({ options, hostElement, bus }) {
|
|
|
7045
7164
|
);
|
|
7046
7165
|
const enrichedFormMarkers = useMemo3(
|
|
7047
7166
|
() => formMarkers.map((m) => {
|
|
7048
|
-
const def =
|
|
7167
|
+
const def = effectiveForms.list.find((f) => f.id === m.formId);
|
|
7049
7168
|
return { ...m, title: def?.title, form: def };
|
|
7050
7169
|
}),
|
|
7051
|
-
[formMarkers,
|
|
7170
|
+
[formMarkers, effectiveForms]
|
|
7052
7171
|
);
|
|
7053
7172
|
const panelProps = {
|
|
7054
7173
|
options: effectiveOptions,
|
package/package.json
CHANGED
package/schema.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as Asset, B as BlocksConfig, C as ConnectionConfig, a as ConnectionConfigPartial, E as Endpoints, L as Link, P as PAGE_AREA_SUGGESTIONS, g as PageContext, S as ServerConfig, b as SiteConfig, c as StartConversationResponse, U as UserContext, W as WidgetConfig, d as WidgetConfigPartial, e as WidgetSettings, f as WidgetSettingsPartial, h as assetSchema, i as blocksConfigSchema, j as connectionConfigPartialSchema, k as connectionConfigSchema, l as cssColorSchema, m as cssLengthSchema, n as endpointsSchema, o as linkSchema, p as localeSchema, q as pageContextSchema, s as serverConfigSchema, r as siteConfigSchema, t as startConversationResponseSchema, u as userContextSchema, v as uuid7Schema, w as widgetConfigPartialSchema, x as widgetConfigSchema, y as widgetSettingsPartialSchema, z as widgetSettingsSchema } from './deployment-
|
|
1
|
+
export { A as Asset, B as BlocksConfig, C as ConnectionConfig, a as ConnectionConfigPartial, E as Endpoints, L as Link, P as PAGE_AREA_SUGGESTIONS, g as PageContext, S as ServerConfig, b as SiteConfig, c as StartConversationResponse, U as UserContext, W as WidgetConfig, d as WidgetConfigPartial, e as WidgetSettings, f as WidgetSettingsPartial, h as assetSchema, i as blocksConfigSchema, j as connectionConfigPartialSchema, k as connectionConfigSchema, l as cssColorSchema, m as cssLengthSchema, n as endpointsSchema, o as linkSchema, p as localeSchema, q as pageContextSchema, s as serverConfigSchema, r as siteConfigSchema, t as startConversationResponseSchema, u as userContextSchema, v as uuid7Schema, w as widgetConfigPartialSchema, x as widgetConfigSchema, y as widgetSettingsPartialSchema, z as widgetSettingsSchema } from './deployment-B0uvvJYK.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -776,7 +776,7 @@ type I18nOptions = z.infer<typeof i18nSchema>;
|
|
|
776
776
|
* meaningful in `floating` / `drawer` / `modal` modes.
|
|
777
777
|
*
|
|
778
778
|
* The list arrives via the start-conversation (`config.modules`); each non-chat
|
|
779
|
-
* tab lazy-fetches its content from `GET /
|
|
779
|
+
* tab lazy-fetches its content from `GET /content` scoped by its
|
|
780
780
|
* `category`. See `docs/api/messenger-modules.md` + `docs/api/content.md`.
|
|
781
781
|
*/
|
|
782
782
|
|