@helpai/elements 0.23.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 +21 -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 +50 -38
- package/index.mjs +175 -53
- package/package.json +1 -1
- package/schema.d.ts +54 -50
- package/schema.json +31 -385
- package/schema.mjs +21 -20
- package/web-component.mjs +175 -53
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
|
|
@@ -579,6 +551,20 @@ interface FormDef {
|
|
|
579
551
|
* submit endpoint, never onto the always-on context envelope.
|
|
580
552
|
*/
|
|
581
553
|
mirrorToContext?: boolean;
|
|
554
|
+
/**
|
|
555
|
+
* Skipped marker offers a "Fill out" button that reopens the form (skip is
|
|
556
|
+
* a deferral, not a refusal). Default `true`. Set `false` when a skip must
|
|
557
|
+
* be final — e.g. a consent ask that should not be re-surfaced.
|
|
558
|
+
*/
|
|
559
|
+
reopenable?: boolean;
|
|
560
|
+
/**
|
|
561
|
+
* Submitted marker expands in place to a read-only list of the visitor's
|
|
562
|
+
* answers. Default `true`. Set `false` to keep the marker plain — e.g.
|
|
563
|
+
* sensitive answers that shouldn't linger on screen (pairs with
|
|
564
|
+
* `mirrorToContext: false`; the backend should also omit `values` from the
|
|
565
|
+
* `/activity` records for such forms).
|
|
566
|
+
*/
|
|
567
|
+
reviewable?: boolean;
|
|
582
568
|
}
|
|
583
569
|
/** The `forms` block is an ordered list (max 20); order = priority. */
|
|
584
570
|
type FormsOptions = FormDef[];
|
|
@@ -595,6 +581,10 @@ interface ResolvedForm {
|
|
|
595
581
|
frequency: FormFrequency;
|
|
596
582
|
/** Mirror answers into `userContext` (false → PII, endpoint only). */
|
|
597
583
|
mirrorToContext: boolean;
|
|
584
|
+
/** Skipped marker offers "Fill out" (false → a skip is final). */
|
|
585
|
+
reopenable: boolean;
|
|
586
|
+
/** Submitted marker expands to a read-only answers list. */
|
|
587
|
+
reviewable: boolean;
|
|
598
588
|
}
|
|
599
589
|
/** Fully-resolved forms block: ordered list + an event→forms index. */
|
|
600
590
|
interface ResolvedForms {
|
|
@@ -680,7 +670,7 @@ interface LauncherOptions {
|
|
|
680
670
|
*
|
|
681
671
|
* `modules` is an ordered list of tabs; each picks a UI `layout`, optional
|
|
682
672
|
* content `contentTags` (the scope passed as the `tags` filter to
|
|
683
|
-
* `GET /
|
|
673
|
+
* `GET /content`), and a translatable `label`.
|
|
684
674
|
*/
|
|
685
675
|
/** The UI layouts a tab can render with. `chat` is the conversation; the rest render content. */
|
|
686
676
|
type ModuleLayout = "chat" | "home" | "help" | "news";
|
|
@@ -964,9 +954,11 @@ interface PageContext {
|
|
|
964
954
|
[key: string]: string | number | boolean | undefined;
|
|
965
955
|
}
|
|
966
956
|
/**
|
|
967
|
-
* Override the HTTP endpoints the widget calls.
|
|
968
|
-
*
|
|
969
|
-
*
|
|
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).
|
|
970
962
|
*/
|
|
971
963
|
interface Endpoints {
|
|
972
964
|
/** File upload endpoint. Default: `'/ai/agent/upload-file'`. */
|
|
@@ -974,7 +966,7 @@ interface Endpoints {
|
|
|
974
966
|
/** Voice transcription endpoint. Default: `'/ai/agent/transcribe-audio'`. */
|
|
975
967
|
transcribe?: string;
|
|
976
968
|
/**
|
|
977
|
-
* Form submission endpoint. Default: `'/
|
|
969
|
+
* Form submission endpoint. Default: `'/submit-form'`. When any form
|
|
978
970
|
* (a pre-chat lead capture, a CSAT survey, etc.) is completed, the widget POSTs
|
|
979
971
|
* the captured values here (fire-and-forget), keyed by the same `visitorId` +
|
|
980
972
|
* `conversationId` the conversation uses; the payload carries `formId` + `trigger`
|
|
@@ -1006,8 +998,8 @@ interface TrackingOptions {
|
|
|
1006
998
|
*/
|
|
1007
999
|
enabled?: boolean;
|
|
1008
1000
|
/**
|
|
1009
|
-
* Pixel collector URL. Default:
|
|
1010
|
-
* (e.g. `https://
|
|
1001
|
+
* Pixel collector URL. Default: `${dataBaseUrl}/elements/px.gif`
|
|
1002
|
+
* (e.g. `https://help.ai/api/data/elements/px.gif`).
|
|
1011
1003
|
*/
|
|
1012
1004
|
endpoint?: string;
|
|
1013
1005
|
/**
|
|
@@ -1072,8 +1064,21 @@ interface ChatWidgetOptions {
|
|
|
1072
1064
|
publicKey?: string;
|
|
1073
1065
|
/** Deployment ID (UUID). Sent as `x-ai-agent-deployment-id`. */
|
|
1074
1066
|
aiAgentDeploymentId?: string;
|
|
1075
|
-
/**
|
|
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
|
+
*/
|
|
1076
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;
|
|
1077
1082
|
/** Host-asserted, informational end-user context (untrusted; not auth). */
|
|
1078
1083
|
userContext?: UserContext;
|
|
1079
1084
|
/** Host-asserted, informational page/visit context (untrusted; `area`, url, …). */
|
|
@@ -1128,6 +1133,8 @@ interface ResolvedOptions {
|
|
|
1128
1133
|
publicKey?: string;
|
|
1129
1134
|
aiAgentDeploymentId?: string;
|
|
1130
1135
|
baseUrl: string;
|
|
1136
|
+
/** Resolved data-API base — see {@link ChatWidgetOptions.dataBaseUrl}. */
|
|
1137
|
+
dataBaseUrl: string;
|
|
1131
1138
|
userContext?: UserContext;
|
|
1132
1139
|
pageContext?: PageContext;
|
|
1133
1140
|
position: Position;
|
|
@@ -1219,8 +1226,14 @@ interface ResolvedOptions {
|
|
|
1219
1226
|
* Matches a subset of {@link ChatWidgetOptions} — anything the server can
|
|
1220
1227
|
* reasonably dictate (UI, behaviour, routing). Callbacks (`onMessage` etc.)
|
|
1221
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.
|
|
1222
1233
|
*/
|
|
1223
1234
|
interface ServerConfig {
|
|
1235
|
+
/** Data-API base override — adopted only when the host didn't set one. */
|
|
1236
|
+
dataBaseUrl?: string;
|
|
1224
1237
|
presentation?: PresentationOptions;
|
|
1225
1238
|
behavior?: BehaviorOptions;
|
|
1226
1239
|
theme?: ThemePreference | ThemeOverrides;
|
|
@@ -1231,7 +1244,6 @@ interface ServerConfig {
|
|
|
1231
1244
|
i18n?: I18nOptions;
|
|
1232
1245
|
footer?: FooterOptions;
|
|
1233
1246
|
features?: FeatureFlags;
|
|
1234
|
-
forms?: FormsOptions;
|
|
1235
1247
|
modules?: ModulesOptions;
|
|
1236
1248
|
tracking?: TrackingOptions;
|
|
1237
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)),
|
|
@@ -680,7 +689,9 @@ function resolveForms(overrides) {
|
|
|
680
689
|
submitLabel: def.submitLabel,
|
|
681
690
|
skippable: def.skippable ?? false,
|
|
682
691
|
frequency: def.frequency ?? "once",
|
|
683
|
-
mirrorToContext: def.mirrorToContext ?? true
|
|
692
|
+
mirrorToContext: def.mirrorToContext ?? true,
|
|
693
|
+
reopenable: def.reopenable ?? true,
|
|
694
|
+
reviewable: def.reviewable ?? true
|
|
684
695
|
});
|
|
685
696
|
}
|
|
686
697
|
const byTrigger = {};
|
|
@@ -773,7 +784,7 @@ function mergeServerConfig(user, server) {
|
|
|
773
784
|
out[key] = mergeLeaves(sv, uv);
|
|
774
785
|
}
|
|
775
786
|
if (server.modules !== void 0 && user.modules === void 0) out.modules = server.modules;
|
|
776
|
-
if (server.
|
|
787
|
+
if (server.dataBaseUrl !== void 0 && user.dataBaseUrl === void 0) out.dataBaseUrl = server.dataBaseUrl;
|
|
777
788
|
const userI18n = user.i18n;
|
|
778
789
|
const serverI18n = server.i18n;
|
|
779
790
|
if (userI18n || serverI18n) {
|
|
@@ -833,6 +844,7 @@ var EMBED_SCALAR_ATTRS = [
|
|
|
833
844
|
["public-key", "publicKey"],
|
|
834
845
|
["ai-agent-deployment-id", "aiAgentDeploymentId"],
|
|
835
846
|
["base-url", "baseUrl"],
|
|
847
|
+
["data-base-url", "dataBaseUrl"],
|
|
836
848
|
["theme", "theme", (v) => v]
|
|
837
849
|
];
|
|
838
850
|
var PRESENTATION_ATTRS = [
|
|
@@ -1268,16 +1280,33 @@ var DEFAULT_PATHS = {
|
|
|
1268
1280
|
* on the client; a failure just leaves the badge until the next sync.
|
|
1269
1281
|
*/
|
|
1270
1282
|
markRead: "/ai/agent/mark-read",
|
|
1271
|
-
// ──
|
|
1272
|
-
/** All widget content via one filtered endpoint. `GET /
|
|
1273
|
-
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",
|
|
1274
1293
|
/**
|
|
1275
1294
|
* Form submission (the event-driven forms engine). POST `{ visitorId,
|
|
1276
1295
|
* conversationId, formId, trigger, values, skipped? }` → record the form's answers
|
|
1277
1296
|
* immediately, keyed by the same `visitorId` + `conversationId` the chat uses.
|
|
1278
1297
|
* Overridable via `endpoints.submitForm`. Fire-and-forget; 404 is ignored.
|
|
1279
1298
|
*/
|
|
1280
|
-
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"
|
|
1281
1310
|
};
|
|
1282
1311
|
var CONTEXT_PARAM = "context";
|
|
1283
1312
|
function buildSendMessageRequest(params) {
|
|
@@ -1550,14 +1579,17 @@ var AgentTransport = class {
|
|
|
1550
1579
|
log4.debug("markRead failed (non-fatal)", { err });
|
|
1551
1580
|
}
|
|
1552
1581
|
}
|
|
1553
|
-
// ----
|
|
1582
|
+
// ---- Data API (content / forms — module-help-ai-data-api) --------------
|
|
1554
1583
|
//
|
|
1555
|
-
//
|
|
1556
|
-
//
|
|
1557
|
-
//
|
|
1558
|
-
|
|
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). */
|
|
1559
1591
|
async listContent(query = {}) {
|
|
1560
|
-
const url = new URL(this.
|
|
1592
|
+
const url = new URL(this.dataUrl(DEFAULT_PATHS.content));
|
|
1561
1593
|
for (const [key, value] of Object.entries(query)) {
|
|
1562
1594
|
if (value === void 0) continue;
|
|
1563
1595
|
url.searchParams.set(key, Array.isArray(value) ? value.join(",") : String(value));
|
|
@@ -1568,6 +1600,40 @@ var AgentTransport = class {
|
|
|
1568
1600
|
log4.debug("listContent \u2190", { count: items.length, total: res.pagination?.total });
|
|
1569
1601
|
return { ...res, items };
|
|
1570
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
|
+
}
|
|
1571
1637
|
async saveUserPrefs(userPrefs) {
|
|
1572
1638
|
log4.debug("saveUserPrefs \u2192", {
|
|
1573
1639
|
visitorId: this.visitorId,
|
|
@@ -1588,8 +1654,9 @@ var AgentTransport = class {
|
|
|
1588
1654
|
return this.opts.endpoints?.submitForm ?? DEFAULT_PATHS.submitForm;
|
|
1589
1655
|
}
|
|
1590
1656
|
/**
|
|
1591
|
-
* Record a completed form (intake, CSAT, claim, …).
|
|
1592
|
-
*
|
|
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`,
|
|
1593
1660
|
* `formId`, and `trigger` are explicit so the backend can tie + route;
|
|
1594
1661
|
* `visitorId` + context ride the envelope. A failure (e.g. 404 on a backend
|
|
1595
1662
|
* that doesn't implement the endpoint) is non-fatal, so callers don't await.
|
|
@@ -1597,7 +1664,7 @@ var AgentTransport = class {
|
|
|
1597
1664
|
async submitForm(body) {
|
|
1598
1665
|
log4.debug("submitForm \u2192", { formId: body.formId, trigger: body.trigger, fields: Object.keys(body.values).length });
|
|
1599
1666
|
try {
|
|
1600
|
-
await this.postJson(this.submitFormPath, body, "submitForm");
|
|
1667
|
+
await this.postJson(this.dataUrl(this.submitFormPath), body, "submitForm");
|
|
1601
1668
|
} catch (err) {
|
|
1602
1669
|
log4.debug("submitForm failed (non-fatal)", { err });
|
|
1603
1670
|
}
|
|
@@ -1833,9 +1900,24 @@ var AgentTransport = class {
|
|
|
1833
1900
|
if (!res.ok) throw new StreamError(`${label} failed: ${res.status}`, "server", res.status);
|
|
1834
1901
|
return await res.json();
|
|
1835
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
|
+
*/
|
|
1836
1907
|
url(pathOrUrl) {
|
|
1837
1908
|
if (/^https?:\/\//i.test(pathOrUrl)) return pathOrUrl;
|
|
1838
|
-
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}`;
|
|
1839
1921
|
}
|
|
1840
1922
|
get fetchImpl() {
|
|
1841
1923
|
return this.opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
@@ -4302,7 +4384,8 @@ function FormDoneMarker({
|
|
|
4302
4384
|
"\xB7 ",
|
|
4303
4385
|
marker.title
|
|
4304
4386
|
] }) : null;
|
|
4305
|
-
const
|
|
4387
|
+
const reviewable = marker.form?.reviewable ?? false;
|
|
4388
|
+
const values = !skipped && reviewable && marker.values && Object.keys(marker.values).length > 0 ? marker.values : null;
|
|
4306
4389
|
if (values) {
|
|
4307
4390
|
const labelFor = (name) => marker.form?.fields.find((f) => f.name === name)?.label ?? name;
|
|
4308
4391
|
return /* @__PURE__ */ jsxs9("details", { class: `${p11}-form-done`, "data-outcome": marker.outcome, "data-testid": TID.formDone, children: [
|
|
@@ -4322,7 +4405,7 @@ function FormDoneMarker({
|
|
|
4322
4405
|
skipped ? /* @__PURE__ */ jsx11(SkipIcon, {}) : /* @__PURE__ */ jsx11(CheckIcon, {}),
|
|
4323
4406
|
/* @__PURE__ */ jsx11("span", { children: skipped ? strings.formSkipped : strings.formSubmitted }),
|
|
4324
4407
|
title,
|
|
4325
|
-
skipped && marker.form && onFill ? /* @__PURE__ */ jsx11("button", { type: "button", class: `${p11}-form-done-fill`, "data-testid": TID.formFill, onClick: onFill, children: strings.formFillOut }) : null
|
|
4408
|
+
skipped && marker.form?.reopenable && onFill ? /* @__PURE__ */ jsx11("button", { type: "button", class: `${p11}-form-done-fill`, "data-testid": TID.formFill, onClick: onFill, children: strings.formFillOut }) : null
|
|
4326
4409
|
] });
|
|
4327
4410
|
}
|
|
4328
4411
|
|
|
@@ -6359,6 +6442,8 @@ function App({ options, hostElement, bus }) {
|
|
|
6359
6442
|
const chatTabIdRef = useRef9(void 0);
|
|
6360
6443
|
chatTabIdRef.current = chatTabId();
|
|
6361
6444
|
const [conversationReady, setConversationReady] = useState13(false);
|
|
6445
|
+
const [remoteForms, setRemoteForms] = useState13(null);
|
|
6446
|
+
const [formsReady, setFormsReady] = useState13(false);
|
|
6362
6447
|
const isInlineLike = options.mode === "standalone" || options.mode === "inline";
|
|
6363
6448
|
const initialPanelRef = useRef9(
|
|
6364
6449
|
resolveInitialPanelState(options, currentViewportWidth(), persistence.loadPanelOpen(), persistence.loadPanelSize())
|
|
@@ -6391,6 +6476,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6391
6476
|
const [transport] = useState13(
|
|
6392
6477
|
() => new AgentTransport({
|
|
6393
6478
|
baseUrl: options.baseUrl,
|
|
6479
|
+
dataBaseUrl: options.dataBaseUrl,
|
|
6394
6480
|
auth: createAuth({
|
|
6395
6481
|
publicKey: options.publicKey,
|
|
6396
6482
|
aiAgentDeploymentId: options.aiAgentDeploymentId,
|
|
@@ -6471,6 +6557,26 @@ function App({ options, hostElement, bus }) {
|
|
|
6471
6557
|
},
|
|
6472
6558
|
[messagesSig]
|
|
6473
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
|
+
);
|
|
6474
6580
|
const connectGenRef = useRef9(0);
|
|
6475
6581
|
const runStartConversation = useCallback6(
|
|
6476
6582
|
async ({ newConversation }) => {
|
|
@@ -6539,24 +6645,14 @@ function App({ options, hostElement, bus }) {
|
|
|
6539
6645
|
if (isResume) {
|
|
6540
6646
|
setLoadingMessages(true);
|
|
6541
6647
|
try {
|
|
6542
|
-
const
|
|
6648
|
+
const chatPromise = res.messages?.length ? Promise.resolve({
|
|
6543
6649
|
conversationId: persistedChatId,
|
|
6544
6650
|
canContinue: res.canContinue ?? true,
|
|
6545
|
-
messages: res.messages
|
|
6546
|
-
|
|
6547
|
-
|
|
6651
|
+
messages: res.messages
|
|
6652
|
+
}) : transport.loadConversation(persistedChatId);
|
|
6653
|
+
const [chat, markers] = await Promise.all([chatPromise, fetchFormMarkers()]);
|
|
6548
6654
|
if (isStale()) return;
|
|
6549
6655
|
const loaded = (chat.messages ?? []).map(fromWireMessage);
|
|
6550
|
-
const subs = chat.formSubmissions ?? res.formSubmissions ?? [];
|
|
6551
|
-
const lastIndexByForm = /* @__PURE__ */ new Map();
|
|
6552
|
-
subs.forEach((rec, i) => lastIndexByForm.set(rec.formId, i));
|
|
6553
|
-
const markers = subs.filter((rec, i) => !rec.skipped || lastIndexByForm.get(rec.formId) === i).map((rec, i) => ({
|
|
6554
|
-
key: `wire:${rec.formId}:${rec.createdAt ?? i}`,
|
|
6555
|
-
formId: rec.formId,
|
|
6556
|
-
outcome: rec.skipped ? "skipped" : "submitted",
|
|
6557
|
-
createdAt: parseWireDate(rec.createdAt),
|
|
6558
|
-
values: rec.values
|
|
6559
|
-
}));
|
|
6560
6656
|
setFormMarkers(markers);
|
|
6561
6657
|
const timelineStamps = [...loaded.map((m) => m.createdAt), ...markers.map((m) => m.createdAt)].filter(
|
|
6562
6658
|
(n) => n !== void 0
|
|
@@ -6594,7 +6690,18 @@ function App({ options, hostElement, bus }) {
|
|
|
6594
6690
|
setConversationReady(true);
|
|
6595
6691
|
}
|
|
6596
6692
|
},
|
|
6597
|
-
[
|
|
6693
|
+
[
|
|
6694
|
+
transport,
|
|
6695
|
+
visitorId,
|
|
6696
|
+
persistence,
|
|
6697
|
+
options,
|
|
6698
|
+
bus,
|
|
6699
|
+
messagesSig,
|
|
6700
|
+
conversationIdSig,
|
|
6701
|
+
feedback,
|
|
6702
|
+
playWelcome,
|
|
6703
|
+
fetchFormMarkers
|
|
6704
|
+
]
|
|
6598
6705
|
);
|
|
6599
6706
|
const userSig = JSON.stringify(options.userContext ?? null);
|
|
6600
6707
|
const pageSig = JSON.stringify(options.pageContext ?? null);
|
|
@@ -6649,6 +6756,16 @@ function App({ options, hostElement, bus }) {
|
|
|
6649
6756
|
);
|
|
6650
6757
|
useEffect17(() => {
|
|
6651
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
|
+
})();
|
|
6652
6769
|
const persistedConversation = conversationIdSig.value;
|
|
6653
6770
|
let resume = null;
|
|
6654
6771
|
if (persistedConversation) {
|
|
@@ -6785,8 +6902,9 @@ function App({ options, hostElement, bus }) {
|
|
|
6785
6902
|
[options.features.humanInLoop, handleToolResult, handleToolDecision]
|
|
6786
6903
|
);
|
|
6787
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;
|
|
6788
6906
|
const forms = useForms({
|
|
6789
|
-
forms:
|
|
6907
|
+
forms: effectiveForms,
|
|
6790
6908
|
persistence,
|
|
6791
6909
|
userContext: () => options.userContext,
|
|
6792
6910
|
messageCount: userMessageCount,
|
|
@@ -6819,18 +6937,18 @@ function App({ options, hostElement, bus }) {
|
|
|
6819
6937
|
const pageArea = options.pageContext?.area ? String(options.pageContext.area) : void 0;
|
|
6820
6938
|
const msgCount = useComputed7(() => messagesSig.value.length);
|
|
6821
6939
|
useEffect17(() => {
|
|
6822
|
-
if (conversationReady) forms.fire("pre-chat");
|
|
6823
|
-
}, [conversationReady, forms]);
|
|
6940
|
+
if (conversationReady && formsReady) forms.fire("pre-chat");
|
|
6941
|
+
}, [conversationReady, formsReady, forms]);
|
|
6824
6942
|
useEffect17(() => {
|
|
6825
|
-
if (!canSend) forms.fire("conversation-closed");
|
|
6826
|
-
}, [canSend, forms]);
|
|
6943
|
+
if (formsReady && !canSend) forms.fire("conversation-closed");
|
|
6944
|
+
}, [formsReady, canSend, forms]);
|
|
6827
6945
|
useEffect17(() => {
|
|
6828
|
-
if (conversationReady && pageArea) forms.fire("page-area");
|
|
6829
|
-
}, [conversationReady, pageArea, forms]);
|
|
6946
|
+
if (conversationReady && formsReady && pageArea) forms.fire("page-area");
|
|
6947
|
+
}, [conversationReady, formsReady, pageArea, forms]);
|
|
6830
6948
|
const idleMs = useMemo3(() => {
|
|
6831
|
-
const secs =
|
|
6949
|
+
const secs = effectiveForms.list.flatMap((f) => f.triggers).filter((t) => t.kind === "idle").map((t) => Number(t.param));
|
|
6832
6950
|
return secs.length ? Math.min(...secs) * 1e3 : 0;
|
|
6833
|
-
}, [
|
|
6951
|
+
}, [effectiveForms]);
|
|
6834
6952
|
useEffect17(() => {
|
|
6835
6953
|
if (!idleMs || !isOpen) return;
|
|
6836
6954
|
const id = setTimeout(() => forms.fire("idle"), idleMs);
|
|
@@ -7007,9 +7125,13 @@ function App({ options, hostElement, bus }) {
|
|
|
7007
7125
|
log16.info("selectConversation", { conversationId: targetConversationId });
|
|
7008
7126
|
bus.emit("selectConversation", { conversationId: targetConversationId });
|
|
7009
7127
|
try {
|
|
7010
|
-
const res = await
|
|
7128
|
+
const [res, markers] = await Promise.all([
|
|
7129
|
+
transport.loadConversation(targetConversationId),
|
|
7130
|
+
fetchFormMarkers(targetConversationId)
|
|
7131
|
+
]);
|
|
7011
7132
|
const hydrated = res.messages.map(fromWireMessage);
|
|
7012
7133
|
messagesSig.value = hydrated;
|
|
7134
|
+
setFormMarkers(markers);
|
|
7013
7135
|
conversationIdSig.value = res.conversationId;
|
|
7014
7136
|
persistence.saveConversationId(res.conversationId);
|
|
7015
7137
|
if (res.agent) setAgent(res.agent);
|
|
@@ -7023,7 +7145,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7023
7145
|
options.onError?.(err);
|
|
7024
7146
|
}
|
|
7025
7147
|
},
|
|
7026
|
-
[transport, messagesSig, conversationIdSig, bus, options, persistence, refreshUnread]
|
|
7148
|
+
[transport, messagesSig, conversationIdSig, bus, options, persistence, refreshUnread, fetchFormMarkers]
|
|
7027
7149
|
);
|
|
7028
7150
|
useEffect17(() => {
|
|
7029
7151
|
const unsub = bindHostCommands(hostElement, {
|
|
@@ -7042,10 +7164,10 @@ function App({ options, hostElement, bus }) {
|
|
|
7042
7164
|
);
|
|
7043
7165
|
const enrichedFormMarkers = useMemo3(
|
|
7044
7166
|
() => formMarkers.map((m) => {
|
|
7045
|
-
const def =
|
|
7167
|
+
const def = effectiveForms.list.find((f) => f.id === m.formId);
|
|
7046
7168
|
return { ...m, title: def?.title, form: def };
|
|
7047
7169
|
}),
|
|
7048
|
-
[formMarkers,
|
|
7170
|
+
[formMarkers, effectiveForms]
|
|
7049
7171
|
);
|
|
7050
7172
|
const panelProps = {
|
|
7051
7173
|
options: effectiveOptions,
|
package/package.json
CHANGED