@helpai/elements 1.1.13 → 1.1.15
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 +4 -3
- package/configurator.mjs +1 -1
- package/elements-web-component.esm.js +22 -22
- package/elements-web-component.esm.js.map +3 -3
- package/elements.cjs.js +22 -22
- package/elements.cjs.js.map +3 -3
- package/elements.esm.js +22 -22
- package/elements.esm.js.map +3 -3
- package/elements.js +26 -26
- package/elements.js.map +3 -3
- package/index.d.ts +222 -418
- package/index.mjs +5 -5
- package/loader.js +1 -1
- package/package.json +1 -1
- package/schema.d.ts +32 -24
- package/schema.json +25 -20
- package/schema.mjs +1 -1
- package/web-component.mjs +5 -5
package/loader.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
//
|
|
6
6
|
// <script>
|
|
7
7
|
// !function(w,d){w.HelpAI=w.HelpAI||function(){(w.HelpAI.q=w.HelpAI.q||[]).push(arguments)};var s=d.createElement("script");s.async=1;s.src="https://cdn.help.ai/elements/latest/elements.js";d.head.appendChild(s)}(window,document);
|
|
8
|
-
// HelpAI('init', { publicKey: 'agent_pk_xxxx' });
|
|
8
|
+
// HelpAI('init', { publicKey: 'agent_pk_xxxx', aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000' });
|
|
9
9
|
// </script>
|
|
10
10
|
|
|
11
11
|
(function (w, d) {
|
package/package.json
CHANGED
package/schema.d.ts
CHANGED
|
@@ -111,6 +111,7 @@ declare const behaviorSchema: z.ZodObject<{
|
|
|
111
111
|
showSources: z.ZodDefault<z.ZodBoolean>;
|
|
112
112
|
disableMessageFeedback: z.ZodDefault<z.ZodBoolean>;
|
|
113
113
|
autoSendSuggestions: z.ZodDefault<z.ZodBoolean>;
|
|
114
|
+
popOutUrl: z.ZodOptional<z.ZodString>;
|
|
114
115
|
}, z.core.$loose>;
|
|
115
116
|
type Behavior = z.infer<typeof behaviorSchema>;
|
|
116
117
|
//#endregion
|
|
@@ -286,6 +287,7 @@ declare const actionNameSchema: z.ZodEnum<{
|
|
|
286
287
|
theme: "theme";
|
|
287
288
|
clear: "clear";
|
|
288
289
|
expand: "expand";
|
|
290
|
+
popOut: "popOut";
|
|
289
291
|
sound: "sound";
|
|
290
292
|
history: "history";
|
|
291
293
|
language: "language";
|
|
@@ -298,6 +300,7 @@ declare const headerActionsSchema: z.ZodArray<z.ZodEnum<{
|
|
|
298
300
|
theme: "theme";
|
|
299
301
|
clear: "clear";
|
|
300
302
|
expand: "expand";
|
|
303
|
+
popOut: "popOut";
|
|
301
304
|
sound: "sound";
|
|
302
305
|
history: "history";
|
|
303
306
|
language: "language";
|
|
@@ -312,6 +315,7 @@ declare const headerSchema: z.ZodObject<{
|
|
|
312
315
|
theme: "theme";
|
|
313
316
|
clear: "clear";
|
|
314
317
|
expand: "expand";
|
|
318
|
+
popOut: "popOut";
|
|
315
319
|
sound: "sound";
|
|
316
320
|
history: "history";
|
|
317
321
|
language: "language";
|
|
@@ -467,9 +471,9 @@ type FooterOptions = z.infer<typeof footerSchema>;
|
|
|
467
471
|
//#region src/schema/widget/forms.d.ts
|
|
468
472
|
declare const fieldTypeSchema: z.ZodEnum<{
|
|
469
473
|
number: "number";
|
|
470
|
-
url: "url";
|
|
471
474
|
email: "email";
|
|
472
475
|
date: "date";
|
|
476
|
+
url: "url";
|
|
473
477
|
text: "text";
|
|
474
478
|
tel: "tel";
|
|
475
479
|
time: "time";
|
|
@@ -501,9 +505,9 @@ declare const formFieldSchema: z.ZodObject<{
|
|
|
501
505
|
label: z.ZodString;
|
|
502
506
|
type: z.ZodEnum<{
|
|
503
507
|
number: "number";
|
|
504
|
-
url: "url";
|
|
505
508
|
email: "email";
|
|
506
509
|
date: "date";
|
|
510
|
+
url: "url";
|
|
507
511
|
text: "text";
|
|
508
512
|
tel: "tel";
|
|
509
513
|
time: "time";
|
|
@@ -603,9 +607,9 @@ declare const formDefSchema: z.ZodObject<{
|
|
|
603
607
|
label: z.ZodString;
|
|
604
608
|
type: z.ZodEnum<{
|
|
605
609
|
number: "number";
|
|
606
|
-
url: "url";
|
|
607
610
|
email: "email";
|
|
608
611
|
date: "date";
|
|
612
|
+
url: "url";
|
|
609
613
|
text: "text";
|
|
610
614
|
tel: "tel";
|
|
611
615
|
time: "time";
|
|
@@ -681,9 +685,9 @@ declare const formsSchema: z.ZodArray<z.ZodObject<{
|
|
|
681
685
|
label: z.ZodString;
|
|
682
686
|
type: z.ZodEnum<{
|
|
683
687
|
number: "number";
|
|
684
|
-
url: "url";
|
|
685
688
|
email: "email";
|
|
686
689
|
date: "date";
|
|
690
|
+
url: "url";
|
|
687
691
|
text: "text";
|
|
688
692
|
tel: "tel";
|
|
689
693
|
time: "time";
|
|
@@ -840,7 +844,8 @@ declare const endpointsSchema: z.ZodObject<{
|
|
|
840
844
|
type Endpoints = z.infer<typeof endpointsSchema>;
|
|
841
845
|
/**
|
|
842
846
|
* Host-asserted, **informational** context about the end-user — never an auth
|
|
843
|
-
* credential (
|
|
847
|
+
* credential (browser access uses the matching deployment public-key/id pair,
|
|
848
|
+
* or the deployment id alone in guest mode). The widget runs on
|
|
844
849
|
* third-party sites; some have their own login, some are guest-only, and we
|
|
845
850
|
* don't register or authenticate these users. `id` / `email` / `name` are
|
|
846
851
|
* recognised fields; any extra scalar fields are forwarded to the agent as
|
|
@@ -880,7 +885,7 @@ type PageContext = z.infer<typeof pageContextSchema>;
|
|
|
880
885
|
declare const connectionConfigSchema: z.ZodObject<{
|
|
881
886
|
widgetId: z.ZodDefault<z.ZodString>;
|
|
882
887
|
publicKey: z.ZodOptional<z.ZodString>;
|
|
883
|
-
aiAgentDeploymentId: z.
|
|
888
|
+
aiAgentDeploymentId: z.ZodString;
|
|
884
889
|
baseUrl: z.ZodOptional<z.ZodString>;
|
|
885
890
|
agentApiBaseUrl: z.ZodOptional<z.ZodString>;
|
|
886
891
|
dataApiBaseUrl: z.ZodOptional<z.ZodString>;
|
|
@@ -975,6 +980,7 @@ declare const widgetSettingsSchema: z.ZodObject<{
|
|
|
975
980
|
showSources: z.ZodDefault<z.ZodBoolean>;
|
|
976
981
|
disableMessageFeedback: z.ZodDefault<z.ZodBoolean>;
|
|
977
982
|
autoSendSuggestions: z.ZodDefault<z.ZodBoolean>;
|
|
983
|
+
popOutUrl: z.ZodOptional<z.ZodString>;
|
|
978
984
|
}, z.core.$loose>>;
|
|
979
985
|
theme: z.ZodDefault<z.ZodUnion<readonly [z.ZodEnum<{
|
|
980
986
|
auto: "auto";
|
|
@@ -1026,6 +1032,7 @@ declare const widgetSettingsSchema: z.ZodObject<{
|
|
|
1026
1032
|
theme: "theme";
|
|
1027
1033
|
clear: "clear";
|
|
1028
1034
|
expand: "expand";
|
|
1035
|
+
popOut: "popOut";
|
|
1029
1036
|
sound: "sound";
|
|
1030
1037
|
history: "history";
|
|
1031
1038
|
language: "language";
|
|
@@ -1119,9 +1126,9 @@ declare const widgetSettingsSchema: z.ZodObject<{
|
|
|
1119
1126
|
label: z.ZodString;
|
|
1120
1127
|
type: z.ZodEnum<{
|
|
1121
1128
|
number: "number";
|
|
1122
|
-
url: "url";
|
|
1123
1129
|
email: "email";
|
|
1124
1130
|
date: "date";
|
|
1131
|
+
url: "url";
|
|
1125
1132
|
text: "text";
|
|
1126
1133
|
tel: "tel";
|
|
1127
1134
|
time: "time";
|
|
@@ -1226,7 +1233,7 @@ type WidgetSettings = z.infer<typeof widgetSettingsSchema>;
|
|
|
1226
1233
|
declare const widgetConfigSchema: z.ZodObject<{
|
|
1227
1234
|
widgetId: z.ZodDefault<z.ZodString>;
|
|
1228
1235
|
publicKey: z.ZodOptional<z.ZodString>;
|
|
1229
|
-
aiAgentDeploymentId: z.
|
|
1236
|
+
aiAgentDeploymentId: z.ZodString;
|
|
1230
1237
|
baseUrl: z.ZodOptional<z.ZodString>;
|
|
1231
1238
|
agentApiBaseUrl: z.ZodOptional<z.ZodString>;
|
|
1232
1239
|
dataApiBaseUrl: z.ZodOptional<z.ZodString>;
|
|
@@ -1310,6 +1317,7 @@ declare const widgetConfigSchema: z.ZodObject<{
|
|
|
1310
1317
|
showSources: z.ZodDefault<z.ZodBoolean>;
|
|
1311
1318
|
disableMessageFeedback: z.ZodDefault<z.ZodBoolean>;
|
|
1312
1319
|
autoSendSuggestions: z.ZodDefault<z.ZodBoolean>;
|
|
1320
|
+
popOutUrl: z.ZodOptional<z.ZodString>;
|
|
1313
1321
|
}, z.core.$loose>>;
|
|
1314
1322
|
theme: z.ZodDefault<z.ZodUnion<readonly [z.ZodEnum<{
|
|
1315
1323
|
auto: "auto";
|
|
@@ -1361,6 +1369,7 @@ declare const widgetConfigSchema: z.ZodObject<{
|
|
|
1361
1369
|
theme: "theme";
|
|
1362
1370
|
clear: "clear";
|
|
1363
1371
|
expand: "expand";
|
|
1372
|
+
popOut: "popOut";
|
|
1364
1373
|
sound: "sound";
|
|
1365
1374
|
history: "history";
|
|
1366
1375
|
language: "language";
|
|
@@ -1454,9 +1463,9 @@ declare const widgetConfigSchema: z.ZodObject<{
|
|
|
1454
1463
|
label: z.ZodString;
|
|
1455
1464
|
type: z.ZodEnum<{
|
|
1456
1465
|
number: "number";
|
|
1457
|
-
url: "url";
|
|
1458
1466
|
email: "email";
|
|
1459
1467
|
date: "date";
|
|
1468
|
+
url: "url";
|
|
1460
1469
|
text: "text";
|
|
1461
1470
|
tel: "tel";
|
|
1462
1471
|
time: "time";
|
|
@@ -1560,7 +1569,7 @@ type WidgetConfig = z.infer<typeof widgetConfigSchema>;
|
|
|
1560
1569
|
declare const widgetConfigPartialSchema: z.ZodObject<{
|
|
1561
1570
|
widgetId: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
1562
1571
|
publicKey: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
1563
|
-
aiAgentDeploymentId: z.ZodOptional<z.
|
|
1572
|
+
aiAgentDeploymentId: z.ZodOptional<z.ZodString>;
|
|
1564
1573
|
baseUrl: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
1565
1574
|
agentApiBaseUrl: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
1566
1575
|
dataApiBaseUrl: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
@@ -1644,6 +1653,7 @@ declare const widgetConfigPartialSchema: z.ZodObject<{
|
|
|
1644
1653
|
showSources: z.ZodDefault<z.ZodBoolean>;
|
|
1645
1654
|
disableMessageFeedback: z.ZodDefault<z.ZodBoolean>;
|
|
1646
1655
|
autoSendSuggestions: z.ZodDefault<z.ZodBoolean>;
|
|
1656
|
+
popOutUrl: z.ZodOptional<z.ZodString>;
|
|
1647
1657
|
}, z.core.$loose>>>;
|
|
1648
1658
|
theme: z.ZodOptional<z.ZodDefault<z.ZodUnion<readonly [z.ZodEnum<{
|
|
1649
1659
|
auto: "auto";
|
|
@@ -1695,6 +1705,7 @@ declare const widgetConfigPartialSchema: z.ZodObject<{
|
|
|
1695
1705
|
theme: "theme";
|
|
1696
1706
|
clear: "clear";
|
|
1697
1707
|
expand: "expand";
|
|
1708
|
+
popOut: "popOut";
|
|
1698
1709
|
sound: "sound";
|
|
1699
1710
|
history: "history";
|
|
1700
1711
|
language: "language";
|
|
@@ -1788,9 +1799,9 @@ declare const widgetConfigPartialSchema: z.ZodObject<{
|
|
|
1788
1799
|
label: z.ZodString;
|
|
1789
1800
|
type: z.ZodEnum<{
|
|
1790
1801
|
number: "number";
|
|
1791
|
-
url: "url";
|
|
1792
1802
|
email: "email";
|
|
1793
1803
|
date: "date";
|
|
1804
|
+
url: "url";
|
|
1794
1805
|
text: "text";
|
|
1795
1806
|
tel: "tel";
|
|
1796
1807
|
time: "time";
|
|
@@ -1938,6 +1949,7 @@ declare const widgetSettingsPartialSchema: z.ZodObject<{
|
|
|
1938
1949
|
showSources: z.ZodDefault<z.ZodBoolean>;
|
|
1939
1950
|
disableMessageFeedback: z.ZodDefault<z.ZodBoolean>;
|
|
1940
1951
|
autoSendSuggestions: z.ZodDefault<z.ZodBoolean>;
|
|
1952
|
+
popOutUrl: z.ZodOptional<z.ZodString>;
|
|
1941
1953
|
}, z.core.$loose>>>;
|
|
1942
1954
|
theme: z.ZodOptional<z.ZodDefault<z.ZodUnion<readonly [z.ZodEnum<{
|
|
1943
1955
|
auto: "auto";
|
|
@@ -1989,6 +2001,7 @@ declare const widgetSettingsPartialSchema: z.ZodObject<{
|
|
|
1989
2001
|
theme: "theme";
|
|
1990
2002
|
clear: "clear";
|
|
1991
2003
|
expand: "expand";
|
|
2004
|
+
popOut: "popOut";
|
|
1992
2005
|
sound: "sound";
|
|
1993
2006
|
history: "history";
|
|
1994
2007
|
language: "language";
|
|
@@ -2082,9 +2095,9 @@ declare const widgetSettingsPartialSchema: z.ZodObject<{
|
|
|
2082
2095
|
label: z.ZodString;
|
|
2083
2096
|
type: z.ZodEnum<{
|
|
2084
2097
|
number: "number";
|
|
2085
|
-
url: "url";
|
|
2086
2098
|
email: "email";
|
|
2087
2099
|
date: "date";
|
|
2100
|
+
url: "url";
|
|
2088
2101
|
text: "text";
|
|
2089
2102
|
tel: "tel";
|
|
2090
2103
|
time: "time";
|
|
@@ -2183,7 +2196,7 @@ type WidgetSettingsPartial = z.infer<typeof widgetSettingsPartialSchema>;
|
|
|
2183
2196
|
declare const connectionConfigPartialSchema: z.ZodObject<{
|
|
2184
2197
|
widgetId: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
2185
2198
|
publicKey: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
2186
|
-
aiAgentDeploymentId: z.ZodOptional<z.
|
|
2199
|
+
aiAgentDeploymentId: z.ZodOptional<z.ZodString>;
|
|
2187
2200
|
baseUrl: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
2188
2201
|
agentApiBaseUrl: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
2189
2202
|
dataApiBaseUrl: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
@@ -2290,6 +2303,7 @@ declare const serverConfigSchema: z.ZodObject<{
|
|
|
2290
2303
|
showSources: z.ZodDefault<z.ZodBoolean>;
|
|
2291
2304
|
disableMessageFeedback: z.ZodDefault<z.ZodBoolean>;
|
|
2292
2305
|
autoSendSuggestions: z.ZodDefault<z.ZodBoolean>;
|
|
2306
|
+
popOutUrl: z.ZodOptional<z.ZodString>;
|
|
2293
2307
|
}, z.core.$loose>>>;
|
|
2294
2308
|
theme: z.ZodOptional<z.ZodDefault<z.ZodUnion<readonly [z.ZodEnum<{
|
|
2295
2309
|
auto: "auto";
|
|
@@ -2341,6 +2355,7 @@ declare const serverConfigSchema: z.ZodObject<{
|
|
|
2341
2355
|
theme: "theme";
|
|
2342
2356
|
clear: "clear";
|
|
2343
2357
|
expand: "expand";
|
|
2358
|
+
popOut: "popOut";
|
|
2344
2359
|
sound: "sound";
|
|
2345
2360
|
history: "history";
|
|
2346
2361
|
language: "language";
|
|
@@ -2576,6 +2591,7 @@ declare const handshakeResponseSchema: z.ZodObject<{
|
|
|
2576
2591
|
showSources: z.ZodDefault<z.ZodBoolean>;
|
|
2577
2592
|
disableMessageFeedback: z.ZodDefault<z.ZodBoolean>;
|
|
2578
2593
|
autoSendSuggestions: z.ZodDefault<z.ZodBoolean>;
|
|
2594
|
+
popOutUrl: z.ZodOptional<z.ZodString>;
|
|
2579
2595
|
}, z.core.$loose>>>;
|
|
2580
2596
|
theme: z.ZodOptional<z.ZodDefault<z.ZodUnion<readonly [z.ZodEnum<{
|
|
2581
2597
|
auto: "auto";
|
|
@@ -2627,6 +2643,7 @@ declare const handshakeResponseSchema: z.ZodObject<{
|
|
|
2627
2643
|
theme: "theme";
|
|
2628
2644
|
clear: "clear";
|
|
2629
2645
|
expand: "expand";
|
|
2646
|
+
popOut: "popOut";
|
|
2630
2647
|
sound: "sound";
|
|
2631
2648
|
history: "history";
|
|
2632
2649
|
language: "language";
|
|
@@ -2788,17 +2805,8 @@ declare const handshakeResponseSchema: z.ZodObject<{
|
|
|
2788
2805
|
conversationId: z.ZodString;
|
|
2789
2806
|
userPrefs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
2790
2807
|
rebind: z.ZodOptional<z.ZodObject<{
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
expired: "expired";
|
|
2794
|
-
invalid: "invalid";
|
|
2795
|
-
}>>;
|
|
2796
|
-
conversationId: z.ZodOptional<z.ZodEnum<{
|
|
2797
|
-
conflict: "conflict";
|
|
2798
|
-
expired: "expired";
|
|
2799
|
-
invalid: "invalid";
|
|
2800
|
-
}>>;
|
|
2801
|
-
}, z.core.$loose>>;
|
|
2808
|
+
conversationId: z.ZodString;
|
|
2809
|
+
}, z.core.$strict>>;
|
|
2802
2810
|
}, z.core.$loose>;
|
|
2803
2811
|
type HandshakeResponse = z.infer<typeof handshakeResponseSchema>;
|
|
2804
2812
|
//#endregion
|
package/schema.json
CHANGED
|
@@ -15,16 +15,16 @@
|
|
|
15
15
|
"maxLength": 80
|
|
16
16
|
},
|
|
17
17
|
"publicKey": {
|
|
18
|
-
"description": "
|
|
18
|
+
"description": "Deployment public key (`agent_pk_…`) issued from the dashboard. Sent on every API call as the `x-public-key` header when present.",
|
|
19
19
|
"type": "string",
|
|
20
20
|
"minLength": 1,
|
|
21
21
|
"maxLength": 200
|
|
22
22
|
},
|
|
23
23
|
"aiAgentDeploymentId": {
|
|
24
|
-
"description": "Deployment UUID from the dashboard. Picks which agent / settings bundle answers this mount. Sent as `x-ai-agent-deployment-id`.",
|
|
25
24
|
"type": "string",
|
|
26
25
|
"minLength": 1,
|
|
27
|
-
"maxLength": 200
|
|
26
|
+
"maxLength": 200,
|
|
27
|
+
"description": "Required deployment UUID from the dashboard. Picks which agent / settings bundle answers this mount. Sent as `x-ai-agent-deployment-id`."
|
|
28
28
|
},
|
|
29
29
|
"baseUrl": {
|
|
30
30
|
"description": "MAIN site origin (e.g. `https://help.ai`) — the API modules derive from it: agent API at `${baseUrl}/api/agent`, data API at `${baseUrl}/api/data`. Set this to point at your staging / self-hosted / custom backend. Default: the brand's `defaultBaseUrl` baked at build time.",
|
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
"maxLength": 2048
|
|
34
34
|
},
|
|
35
35
|
"agentApiBaseUrl": {
|
|
36
|
-
"description": "Full agent-API base — set it to reach a STANDALONE `module-help-ai-agent-api` directly (local module on its own port, e.g. `http://localhost:
|
|
36
|
+
"description": "Full agent-API base — set it to reach a STANDALONE `module-help-ai-agent-api` directly (local module on its own port, e.g. `http://localhost:3111`; routes attach as-is: `/pai/handshake`). Precedence: this option > derived from the main origin (`${baseUrl}/api/agent`). Not server-pushable.",
|
|
37
37
|
"type": "string",
|
|
38
38
|
"minLength": 1,
|
|
39
39
|
"maxLength": 2048
|
|
40
40
|
},
|
|
41
41
|
"dataApiBaseUrl": {
|
|
42
|
-
"description": "Full data-API base (e.g. `https://help.ai/api/data`, or a standalone local module like `http://localhost:
|
|
42
|
+
"description": "Full data-API base (e.g. `https://help.ai/api/data`, or a standalone local module like `http://localhost:3105`) — the service serving content, form definitions (`GET /pai/bootstrap`), and form submissions at root-level paths. Precedence: this option > server-pushed `config.dataApiBaseUrl` > derived from the main origin (`${baseUrl}/api/data`).",
|
|
43
43
|
"type": "string",
|
|
44
44
|
"minLength": 1,
|
|
45
45
|
"maxLength": 2048
|
|
@@ -494,6 +494,12 @@
|
|
|
494
494
|
"default": false,
|
|
495
495
|
"description": "Auto-send a suggestion chip's text the moment it's tapped. `false` (default) fills the composer with the text so the visitor can edit before sending; `true` sends it immediately.",
|
|
496
496
|
"type": "boolean"
|
|
497
|
+
},
|
|
498
|
+
"popOutUrl": {
|
|
499
|
+
"description": "URL opened by the `popOut` header action. The widget adds the active conversation, widget, and deployment identity as query parameters.",
|
|
500
|
+
"type": "string",
|
|
501
|
+
"minLength": 1,
|
|
502
|
+
"maxLength": 2048
|
|
497
503
|
}
|
|
498
504
|
},
|
|
499
505
|
"required": [
|
|
@@ -759,6 +765,7 @@
|
|
|
759
765
|
"clear",
|
|
760
766
|
"expand",
|
|
761
767
|
"fullscreen",
|
|
768
|
+
"popOut",
|
|
762
769
|
"sound",
|
|
763
770
|
"history",
|
|
764
771
|
"language",
|
|
@@ -1988,6 +1995,7 @@
|
|
|
1988
1995
|
},
|
|
1989
1996
|
"required": [
|
|
1990
1997
|
"widgetId",
|
|
1998
|
+
"aiAgentDeploymentId",
|
|
1991
1999
|
"theme"
|
|
1992
2000
|
],
|
|
1993
2001
|
"additionalProperties": {},
|
|
@@ -2340,6 +2348,12 @@
|
|
|
2340
2348
|
"default": false,
|
|
2341
2349
|
"description": "Auto-send a suggestion chip's text the moment it's tapped. `false` (default) fills the composer with the text so the visitor can edit before sending; `true` sends it immediately.",
|
|
2342
2350
|
"type": "boolean"
|
|
2351
|
+
},
|
|
2352
|
+
"popOutUrl": {
|
|
2353
|
+
"description": "URL opened by the `popOut` header action. The widget adds the active conversation, widget, and deployment identity as query parameters.",
|
|
2354
|
+
"type": "string",
|
|
2355
|
+
"minLength": 1,
|
|
2356
|
+
"maxLength": 2048
|
|
2343
2357
|
}
|
|
2344
2358
|
},
|
|
2345
2359
|
"required": [
|
|
@@ -2605,6 +2619,7 @@
|
|
|
2605
2619
|
"clear",
|
|
2606
2620
|
"expand",
|
|
2607
2621
|
"fullscreen",
|
|
2622
|
+
"popOut",
|
|
2608
2623
|
"sound",
|
|
2609
2624
|
"history",
|
|
2610
2625
|
"language",
|
|
@@ -3601,24 +3616,14 @@
|
|
|
3601
3616
|
"rebind": {
|
|
3602
3617
|
"type": "object",
|
|
3603
3618
|
"properties": {
|
|
3604
|
-
"visitorId": {
|
|
3605
|
-
"type": "string",
|
|
3606
|
-
"enum": [
|
|
3607
|
-
"conflict",
|
|
3608
|
-
"expired",
|
|
3609
|
-
"invalid"
|
|
3610
|
-
]
|
|
3611
|
-
},
|
|
3612
3619
|
"conversationId": {
|
|
3613
|
-
"type": "string"
|
|
3614
|
-
"enum": [
|
|
3615
|
-
"conflict",
|
|
3616
|
-
"expired",
|
|
3617
|
-
"invalid"
|
|
3618
|
-
]
|
|
3620
|
+
"type": "string"
|
|
3619
3621
|
}
|
|
3620
3622
|
},
|
|
3621
|
-
"
|
|
3623
|
+
"required": [
|
|
3624
|
+
"conversationId"
|
|
3625
|
+
],
|
|
3626
|
+
"additionalProperties": false
|
|
3622
3627
|
}
|
|
3623
3628
|
},
|
|
3624
3629
|
"required": [
|
package/schema.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{z as e}from"zod";const t=e.object({url:e.string().min(1).max(2048).describe(`Public URL of the image. Must be a full URL or root-relative path.`),alt:e.string().min(1).max(200).optional().describe(`Alt text for screen readers. Omit only for purely decorative images.`)}).strict().describe(`A single image asset (logo, hero, icon).`),n=e.object({id:e.string().min(1).max(100).optional().describe(`Stable id used as Preact key + analytics target.`),label:e.string().min(1).max(120).describe(`Visible link text.`),href:e.string().min(1).max(2048).optional().describe(`Click target. Optional — when omitted the link renders as a non-clickable heading.`),icon:e.string().min(1).max(100).optional().describe(`Built-in icon preset name (chat / spark / message / help / wand) or a custom string.`),description:e.string().min(1).max(300).optional().describe(`Subtitle shown below the label. Used by link cards in page mode.`)}).loose().describe(`A navigation link / link card.`),r=e.string().min(1).max(120).describe("Any CSS length — `640px`, `80vh`, `calc(100vh - 64px)`, etc. Validated only for size, not syntax."),i=e.string().min(1).max(120).describe("Any CSS colour — hex, `rgb()`, `hsl()`, `oklch()`, a named colour, or a `var(--token)`."),a=e.string().min(1).max(35).describe("BCP-47 locale tag (e.g. `en`, `fr-CA`, `zh-Hant-HK`)."),ee=e.uuidv7().describe(`UUID v7 (RFC 9562) — the single id format for visitor / conversation / message / file / content ids.`),o=e.enum([`normal`,`expanded`,`fullscreen`,`auto`]),te=e.object({enabled:e.boolean().default(!1).describe(`Show a corner-grip handle the user can drag to resize the panel.`),minWidth:r.optional().describe(`Smallest width the user can drag the panel to.`),maxWidth:r.optional().describe(`Largest width the user can drag the panel to.`),minHeight:r.optional().describe(`Smallest height the user can drag the panel to.`),maxHeight:r.optional().describe(`Largest height the user can drag the panel to.`)}).loose().describe("Drag-resize controls. When `enabled`, a corner grip lets users resize the panel within the optional min/max bounds.").meta({examples:[{enabled:!1},{enabled:!0,minWidth:`320px`,maxWidth:`720px`,minHeight:`400px`,maxHeight:`90vh`}]}),ne=e.object({width:r.optional().describe("Panel width (CSS length). Mode-specific defaults — floating `380px`, modal `min(960px, 92vw)`, drawer `min(440px, calc(100vw - 32px))`. Set this to override; inline/standalone/page modes ignore it."),height:r.optional().describe("Panel height (CSS length). Mode-specific defaults — floating `600px`, modal `min(720px, 88vh)`, drawer `100vh` (clamped to `100vh − inset-y × 2`). Set this to override."),expanded:e.object({width:r.optional().describe(`Width when the user clicks the expand button.`),height:r.optional().describe(`Height when the user clicks the expand button.`)}).loose().optional().describe("Dimensions when the panel is in its `expanded` tier (between normal and fullscreen). Applies to floating / modal / drawer; ignored in inline / standalone / page where the panel always fills its parent."),resize:te.optional().describe(`Drag-resize controls — adds a corner grip the user can drag.`),inset:r.optional().describe("Drawer-mode edge inset — gap from each viewport edge (top/bottom/left/right). Single value applied to all four sides. Defaults to `3vh` vertical / `12px` horizontal. Use `0` for a full-edge drawer (no gap), or `5vh` for a centered-looking flyout with extra breathing room. Ignored by every other mode."),initialSize:o.default(`auto`).describe("Which size tier to open with on the first mount: `normal` / `expanded` / `fullscreen` / `auto`. Default `auto` is responsive by viewport: phones (< 640 px) open `fullscreen` edge-to-edge, tablets (640 px … `autoSizeBreakpoint`) open `normal`, and desktop/laptop (≥ `autoSizeBreakpoint`) open `expanded` — so the panel is right for the device out of the box. Set a literal to pin one tier everywhere; the user's own resize is remembered per-device and wins on reload."),autoSizeBreakpoint:e.number().int().min(0).max(1e4).default(900).describe("Viewport width (px) at/above which `initialSize: auto` opens `expanded` instead of `normal`. (Phones below 640 px always open `fullscreen`.)")}).loose().describe("Panel dimensions and drawer inset. Width/height override the mode-specific defaults (floating compact popover, modal centered dialog, drawer flyout). `inset` only affects drawer mode.").meta({examples:[{width:`420px`,height:`640px`},{width:`min(560px, 92vw)`,height:`min(640px, 90vh)`},{width:`420px`,expanded:{width:`640px`,height:`780px`},initialSize:`auto`,autoSizeBreakpoint:900},{width:`min(1100px, 95vw)`,height:`min(820px, 92vh)`},{width:`360px`,inset:`8vh`},{width:`420px`,height:`100vh`,inset:`0`},{width:`420px`,height:`60vh`,inset:`20vh`}]}),re=e.enum([`floating`,`inline`,`standalone`,`page`,`modal`,`drawer`]),ie=e.enum([`bottom-right`,`bottom-left`,`top-right`,`top-left`]),s=e.object({mode:re.default(`floating`).describe("Rendering shape. `floating` (FAB + popover), `inline` (in-flow block), `standalone` (full viewport), `page` (sidebar + chat), `modal` (centered overlay), `drawer` (side dock)."),position:ie.default(`bottom-right`).describe("Anchor corner. `floating` uses it for the FAB; `drawer` uses the `*-left` / `*-right` suffix to pick the dock side. Ignored in other modes."),iframe:e.boolean().default(!1).describe(`Wrap the widget in a same-origin iframe for stricter JS + CSS isolation from the host page. Composes with every mode.`),size:ne.optional().describe("Panel dimensions. Mode-aware defaults (floating compact popover, modal centered dialog, drawer flyout) — fields here override them. Includes `inset` (drawer flyout gap), `expanded` tier, drag-resize grip.")}).loose().describe(`Layout fundamentals — mode, anchor corner, iframe isolation, panel size + drawer inset.`).meta({examples:[{mode:`floating`,position:`bottom-right`},{mode:`modal`,size:{width:`min(1100px, 95vw)`,height:`min(820px, 92vh)`}},{mode:`drawer`,position:`bottom-right`},{mode:`drawer`,position:`top-right`,size:{width:`360px`,inset:`8vh`}},{mode:`drawer`,position:`bottom-right`,size:{width:`420px`,height:`100vh`,inset:`0`}}]}),c=e.enum([`streaming`,`buffered`]),l=e.object({startMinimized:e.boolean().default(!0).describe("Don't auto-open the floating panel on first visit. `true` (default) shows the FAB collapsed; `false` opens the panel on mount."),responseMode:c.default(`streaming`).describe("How the assistant's reply arrives. `streaming` (default) types tokens as they generate; `buffered` waits + shows the full reply at once."),showReasoning:e.boolean().default(!1).describe(`Show the model's reasoning ("thoughts") as a collapsible block above each answer. \`false\` (default) hides it — a loading indicator still shows while the model works. Reasoning is absent from durable history, but the current turn's short-lived resume backlog may replay it after an immediate reconnect or reload.`),showToolCalls:e.boolean().default(!1).describe("Show automatic tool/function calls the model makes (e.g. search) as collapsible detail+result cards in the transcript. `false` (default) hides them. Interactive tools (ask-questions / approval) always show."),showSources:e.boolean().default(!1).describe("Show the citation sources behind an answer (web links + document/file references) in the transcript. `false` (default) hides them."),disableMessageFeedback:e.boolean().default(!1).describe("Turn OFF the 👍/👎 feedback controls under each rateable reply (AI or operator). Unset/`false` = shown (default), `true` = hidden. A choice fires the `messageFeedback` event and is POSTed best-effort to `/pai/message-feedback`."),autoSendSuggestions:e.boolean().default(!1).describe("Auto-send a suggestion chip's text the moment it's tapped. `false` (default) fills the composer with the text so the visitor can edit before sending; `true` sends it immediately.")}).loose().describe(`Conversation behaviour — lifecycle, streaming, and the transcript surface (scroll, feedback).`).meta({examples:[{startMinimized:!0,responseMode:`streaming`},{startMinimized:!1,responseMode:`buffered`,disableMessageFeedback:!0}]}),u=e.enum([`auto`,`light`,`dark`]),ae=e.object({accent:i.optional().describe(`Brand accent colour. Drives the FAB, send button, links, and active states. CSS colour.`),accentText:i.optional().describe(`Foreground colour rendered ON the accent (FAB icon, send-button glyph). CSS colour.`),radius:r.optional().describe("Corner radius for the panel + buttons. CSS length (`8px`, `0.75rem`, `12px`)."),fontFamily:e.string().min(1).max(200).optional().describe("Font stack used inside the widget. Standard CSS `font-family` value."),contentMaxWidth:r.optional().describe("Max width of the readable content column (messages, history, home, composer) on a wide surface — content centres, full-bleed bars keep their edges. CSS length; default `768px`. Set `100%` to fill the surface (no cap). Modal + drawer already fill their card by default.")}).loose().describe(`Theme token overrides. Any field left unset falls through to the brand's default theme.`).meta({examples:[{accent:`#10b981`,radius:`12px`},{accent:`#7c3aed`,accentText:`#ffffff`,radius:`16px`,fontFamily:`'Inter', system-ui`},{accent:`oklch(0.7 0.2 145)`,radius:`0.5rem`},{accent:`#5b5bd6`,contentMaxWidth:`960px`}]}),d=e.union([u,ae]).describe(`'auto' | 'light' | 'dark', or a tokens override object.`).meta({examples:[`auto`,`dark`,{accent:`#10b981`,radius:`12px`},{accent:`#7c3aed`,accentText:`#fff`,radius:`16px`,fontFamily:`'Inter', system-ui`}]}),f=e.enum([`circle`,`pill`,`bar`,`minimal`]),p=e.enum([`sm`,`md`,`lg`]),m=e.enum([`pill`,`bubble`,`callout`]),h=e.enum([`auto`,`horizontal`,`vertical`]),g=e.object({text:e.string().min(1).max(500).default(`👋 Hi! Need any help?`).describe("The message to show in the bubble. Defaults to a friendly greeting; replace with your own copy (e.g. 'Click here →'). May also be an i18n key (e.g. `launcherCallout`) resolved against `i18n.strings` for per-locale copy; a non-key literal renders as-is."),shape:m.default(`pill`).describe("Visual shape — `pill` (slim label + arrow), `bubble` (speech-bubble tail), `callout` (loud variant)."),position:h.default(`auto`).describe("Where the bubble sits relative to the FAB. `auto` (default) picks the side facing into the page. `horizontal` anchors beside the FAB (left/right depending on the FAB's corner). `vertical` anchors above/below the FAB. Concrete left/right/top/bottom is derived from the FAB's corner so the bubble always stays on-screen."),delayMs:e.number().int().min(0).max(6e4).default(1200).describe(`How long to wait after page load before the bubble appears, in milliseconds.`),autoDismissMs:e.number().int().min(0).max(6e5).default(0).describe("Auto-hide the bubble after this many milliseconds. `0` (default) keeps it until the user dismisses."),animated:e.boolean().default(!0).describe("Subtle horizontal nudge to draw the eye. Disabled automatically by `prefers-reduced-motion`."),persistent:e.boolean().default(!0).describe("How dismissals stick. `true` (default) — re-show on every visit regardless of prior dismissal (the 'recurring greeting' behaviour). `false` — clicking X stores the message text and that exact message stays hidden across visits (the 'one-time hint' behaviour).")}).loose().describe(`Pre-engagement message bubble next to the FAB. Use it for a greeting ('Hi, need help?'), an attention-grabbing nudge ('Click here →'), or any one-time onboarding tip — same component, configured for the use case.`),_=e.object({variant:f.default(`circle`).describe("FAB visual style. `circle` — round bubble (icon only). `pill` — icon + label. `bar` — wide rectangular button. `minimal` — text-only outline."),size:p.default(`md`).describe(`FAB size preset — small / medium / large.`),label:e.string().max(200).optional().describe("Text rendered on the FAB (only shown for `pill` / `bar` / `minimal` variants)."),icon:e.string().max(8e3).optional().describe("Icon shown on the FAB / drawer edge tab. Built-in presets: `chat` / `message` / `bubbles` / `spark` / `wand` / `bot` / `help` / `lifebuoy` / `headset`. Anything else is treated as raw SVG."),hidden:e.boolean().default(!1).describe("Hide the FAB entirely (page brings its own trigger that calls `instance.open()`)."),callout:g.optional()}).loose().describe(`Floating-launcher (FAB) appearance and behaviour. The FAB is the round button in the corner that opens the chat panel. Floating mode only.`).meta({examples:[{variant:`circle`,size:`md`},{variant:`pill`,label:`Ask AI`,icon:`spark`},{variant:`circle`,callout:{text:`👋 Need help?`,shape:`bubble`,persistent:!0}},{variant:`circle`,callout:{text:`Click to start`,shape:`pill`,delayMs:1200}}]}),v=e.enum([`local`,`server`,`disabled`]),y=e.object({disableFileUpload:e.boolean().default(!1).describe("Turn OFF file uploads — hide the paperclip button + reject drag/drop/paste. Unset/`false` = enabled (default), `true` = disabled."),voice:v.default(`local`).describe("Voice input mode. `local` — Web Speech API in the browser (free, runs on-device). `server` — record audio + upload to the transcribe endpoint. `disabled` — hide the mic button."),disableHumanInLoop:e.boolean().default(!1).describe("Turn OFF the human-in-the-loop tool UI — the inline ask-questions form + approve/reject controls for gated tools. Unset/`false` = shown (default), `true` = suppressed (the wire still carries the signals)."),enableOperatorRequest:e.boolean().default(!1).describe("Opt IN operator control — expose the 'Talk to an operator' affordance + connected-to-operator states for this deployment. Unset/`false` = off (default), `true` = on. Unlike the `disable*` flags this is a positive opt-in for a new capability.")}).loose().describe(`Capability flags — file upload, voice mode, HITL, and operator requests.`).meta({examples:[{voice:`local`},{disableFileUpload:!0,voice:`disabled`},{voice:`server`,enableOperatorRequest:!0}]}),b=e.enum([`clear`,`expand`,`fullscreen`,`sound`,`history`,`language`,`theme`,`textSize`,`close`]),x=e.array(b).max(10).describe("Which actions the header exposes. `history` + `close` are dedicated buttons; the rest appear in the ⋯ menu. Unlisted actions are hidden; order is irrelevant. Pick from: `clear`, `expand`, `fullscreen`, `sound`, `history`, `language`, `theme`, `textSize`, `close`. Per-mode defaults live in `DEFAULT_ACTIONS_BY_MODE`.").meta({examples:[[`theme`,`history`,`close`,`clear`,`fullscreen`,`sound`,`language`],[`theme`,`history`,`clear`,`language`],[`close`]]}),S=e.object({actions:x.optional().describe(`Which header actions are enabled (history/✕ buttons + ⋯ menu).`)}).loose().describe(`Panel-header configuration.`).meta({examples:[{actions:[`theme`,`history`,`close`,`clear`,`fullscreen`]}]}),C=e.enum([`messageReceived`,`messageSent`,`error`,`voiceStart`,`voiceStop`]),w=e.object({enabled:e.boolean().default(!0).describe(`Master on/off for audible notifications. On by default — browser autoplay policy still requires a prior user gesture before any sound plays.`),volume:e.number().min(0).max(1).default(.3).describe("Global volume 0..1. `0.3` (default) is intentionally subtle."),events:e.partialRecord(C,e.union([e.boolean(),e.string().min(1).max(2048)])).optional().describe("Per-event override map. Value: `true` = built-in tone, `false` = silent, `string` = URL to a custom audio file (mp3/wav/ogg).")}).loose().describe("Audible notifications. `events` accepts true (default tone), false (off), or a URL to an audio file.").meta({examples:[{enabled:!0,volume:.3},{enabled:!0,volume:.5,events:{messageReceived:!0,error:!1}},{enabled:!0,events:{messageReceived:`https://cdn.help.ai/ping.mp3`}}]}),T=e.object({enabled:e.boolean().default(!0).describe(`Master on/off. Default: on for touch devices, silently ignored elsewhere.`),events:e.partialRecord(C,e.union([e.boolean(),e.number().int(),e.array(e.number().int())])).optional().describe("Per-event vibration pattern. Value: `true` = default pattern, `false` = silent, number = single vibrate ms, array = `[vibrate, pause, vibrate, …]`.")}).loose().describe("Touch-device vibration. `events` accepts true (default), number (single vibrate ms), or [vibrate,pause,vibrate,…].").meta({examples:[{enabled:!0},{enabled:!0,events:{messageReceived:30,error:[40,60,40]}}]}),E=e.object({sound:w.optional(),haptics:T.optional()}).loose().describe(`Audible + tactile feedback.`).meta({examples:[{sound:{enabled:!0,volume:.3},haptics:{enabled:!0}},{sound:{enabled:!1},haptics:{enabled:!1}}]}),D=e.enum([`circle`,`square`,`pill`]),O=e.enum([`arrowUp`,`paperPlane`,`send`,`chevron`]),k=e.enum([`filled`,`outline`,`ghost`]),A=e.object({shape:D.default(`circle`).describe("Send button outline shape — `circle` / `square` / `pill`."),icon:O.default(`arrowUp`).describe("Icon inside the send button — `arrowUp` / `paperPlane` / `send` / `chevron`."),variant:k.default(`filled`).describe("Fill treatment — `filled` (solid accent), `outline` (border only), `ghost` (no border, no fill).")}).loose().describe(`Send-button styling — shape, icon, and fill treatment.`).meta({examples:[{shape:`circle`,icon:`arrowUp`,variant:`filled`},{shape:`pill`,icon:`send`,variant:`outline`},{shape:`square`,icon:`paperPlane`,variant:`ghost`}]}),j=e.object({maxBytes:e.number().int().min(0).max(1024*1024*1024).default(10*1024*1024).describe("Maximum size per uploaded file, in bytes. `10 MiB` (10485760) by default."),maxCount:e.number().int().min(0).max(100).default(10).describe(`Maximum number of attachments per message.`),accept:e.string().max(2e3).default(`*/*`).describe("Allowed MIME types — comma-separated `accept` attribute string, e.g. `image/*,application/pdf`.")}).loose().describe(`Client-side attachment limits. Server enforcement is still required.`).meta({examples:[{maxBytes:10485760,maxCount:10,accept:`*/*`},{maxBytes:5242880,maxCount:3,accept:`image/*,application/pdf`}]}),M=e.object({sendButton:A.optional(),attachments:j.optional()}).loose().describe(`Composer chrome — send-button styling + attachment limits.`).meta({examples:[{sendButton:{shape:`circle`,icon:`arrowUp`,variant:`filled`}},{sendButton:{shape:`pill`,icon:`send`,variant:`outline`}},{attachments:{maxBytes:5242880,maxCount:3,accept:`image/*,application/pdf`}}]}),N=e.object({text:e.string().max(200).optional().describe("Visible attribution text (e.g. `Powered by Help AI`)."),href:e.url().max(2048).optional().describe("Click target — the attribution becomes a link. Must be a full URL (`https://…`)."),logoUrl:e.url().max(2048).optional().describe(`Small logo rendered before the text. Must be a full URL.`)}).loose().describe(`"Powered by …" attribution shown below the composer.`).meta({examples:[{text:`Powered by Help AI`},{text:`Powered by Help AI`,href:`https://help.ai`,logoUrl:`https://help.ai/logo.svg`}]}),P=e.object({poweredBy:N.optional(),composerDisclaimer:e.string().max(500).optional().describe(`Small muted text under the composer (like ChatGPT's 'ChatGPT can make mistakes.'). Empty = hidden.`)}).loose().describe(`Footer — attribution + composer disclaimer.`).meta({examples:[{poweredBy:{text:`Powered by Help AI`,href:`https://help.ai`}},{composerDisclaimer:`AI responses may be inaccurate.`}]}),F=e.enum([`text`,`email`,`tel`,`url`,`number`,`date`,`time`,`textarea`,`select`,`radio`,`checkbox`,`multiselect`]),I=e.object({value:e.string().min(1).max(200).describe(`The value recorded when this choice is picked.`),label:e.string().min(1).max(200).optional().describe("Display text; falls back to `value`."),description:e.string().max(280).optional().describe(`Optional sub-label under the choice.`)}).loose(),L=e.object({pattern:e.string().max(500).optional().describe("Regex source string — text-like fields (e.g. `^\\d{5}$`)."),minLength:e.number().int().min(0).max(1e4).optional(),maxLength:e.number().int().min(1).max(1e4).optional(),min:e.number().optional().describe("Minimum — `number` fields."),max:e.number().optional().describe("Maximum — `number` fields."),minSelections:e.number().int().min(0).max(50).optional().describe("Minimum picks — `multiselect`."),maxSelections:e.number().int().min(1).max(50).optional().describe("Maximum picks — `multiselect`.")}).loose(),R=e.object({name:e.string().min(1).max(80).describe("Stable key the answer is recorded under (e.g. `email`)."),label:e.string().min(1).max(200).describe(`Visible field label.`),type:F.describe(`Field renderer + built-in validation (email/tel/url/number/date/time checked).`),placeholder:e.string().max(200).optional(),required:e.boolean().optional().default(!0).describe("Must be answered to submit. `false` allows skipping."),defaultValue:e.string().max(2e3).optional().describe("Pre-filled value (comma-join for `multiselect`)."),options:e.array(I).max(50).optional().describe("Choices for `select` / `radio` / `multiselect`."),validation:L.optional(),allowOther:e.boolean().optional().describe("`select`/`radio`/`multiselect` — allow a free-text 'Other' answer."),validationHint:e.string().max(280).optional().describe(`Small helper line under the field.`)}).loose(),z=e.string().regex(/^(pre-chat|conversation-closed|panel-close|manual|after-messages:\d+|idle:\d+|page-area:.+)$/,`Unrecognised form trigger`).describe("When the form fires: `pre-chat`, `after-messages:N`, `conversation-closed`, `panel-close`, `idle:Nseconds`, `page-area:<area>`, or `manual`."),B=e.object({missingContext:e.array(e.string().max(80)).max(20).optional().describe("Only show if `userContext` lacks ALL of these keys (progressive profiling)."),pageArea:e.union([e.string().max(64),e.array(e.string().max(64)).max(20)]).optional().describe("Only on these `pageContext.area` values."),minMessages:e.number().int().min(0).max(1e3).optional(),maxMessages:e.number().int().min(0).max(1e3).optional()}).loose(),V=e.object({value:e.string().min(1).max(200).describe("The canonical option `value` this localizes."),label:e.string().min(1).max(200).optional(),description:e.string().max(280).optional()}).loose(),H=e.object({name:e.string().min(1).max(80).describe("The canonical field `name` this localizes."),label:e.string().min(1).max(200).optional(),placeholder:e.string().max(200).optional(),validationHint:e.string().max(280).optional(),options:e.array(V).max(50).optional()}).loose(),U=e.object({locale:a,title:e.string().max(120).optional(),description:e.string().max(500).optional(),submitLabel:e.string().max(60).optional(),fields:e.array(H).max(50).optional()}).loose(),W=e.object({id:e.string().min(1).max(80).describe("Stable id — persistence dedupe + `manual` openForm(id)."),on:e.union([z,e.array(z).min(1).max(8)]).describe(`Trigger(s) that surface this form.`),when:B.optional().describe(`Extra eligibility predicate evaluated when a trigger fires.`),fields:e.array(R).max(20).default([]).describe(`The questions, in order. ≥1 required to show.`),title:e.string().max(120).optional(),description:e.string().max(500).optional(),submitLabel:e.string().max(60).optional(),skippable:e.boolean().default(!1).describe(`Show a Skip control so the visitor can dismiss the form.`),frequency:e.enum([`once`,`conversation`,`always`]).default(`once`).describe("`once` (per device, persisted), `conversation` (re-eligible each new conversation), or `always`."),mirrorToContext:e.boolean().default(!0).describe("Mirror the answers into `userContext` so they ride every request. Set `false` for PII (DOB, policy #, symptoms) — those then go only to the submit endpoint."),reopenable:e.boolean().default(!0).describe('Skipped marker offers a "Fill out" button that reopens the form. Set `false` when a skip must be final (e.g. a consent ask that should not be re-surfaced).'),reviewable:e.boolean().default(!0).describe("Submitted marker expands in place to a read-only list of the visitor's answers. Set `false` for sensitive answers that shouldn't linger on screen."),defaultLocale:a.optional().describe(`BCP-47 locale the top-level strings (title / fields copy) are authored in.`),translations:e.array(U).max(50).optional().describe("Per-locale string overrides — the data module trims to the bootstrap `?locale=` (≤1); the widget overlays it onto the default copy.")}).loose(),G=e.array(W).max(20).describe("Event-driven forms — an ordered list (order = priority). Each binds to trigger event(s), gates the chat until submit/skip, dedupes by frequency, and records via `endpoints.submitForm`. Generic across verticals.").meta({examples:[[{id:`intake`,on:`pre-chat`,fields:[{name:`email`,label:`Email`,type:`email`}]}],[{id:`signal`,on:`pre-chat`,title:`Before we start`,fields:[{name:`name`,label:`Your name`,type:`text`},{name:`email`,label:`Work email`,type:`email`}]},{id:`csat`,on:`conversation-closed`,frequency:`always`,fields:[{name:`rating`,label:`How did we do?`,type:`radio`,options:[{value:`good`},{value:`bad`}]}]},{id:`appointment`,on:[`page-area:booking`,`manual`],mirrorToContext:!1,fields:[{name:`department`,label:`Department`,type:`select`,options:[{value:`dermatology`},{value:`cardiology`}]},{name:`date`,label:`Preferred date`,type:`date`},{name:`consent`,label:`I consent to be contacted`,type:`checkbox`}]}]]}),K=e.record(e.string(),e.string()),q=e.union([K,e.record(a,K)]).describe(`Per-instance UI string overrides. Flat = active-locale only; nested = multi-locale map. Both forms are partial — missing keys fall through to built-in defaults.`).meta({examples:[{send:`Click`,stop:`Halt`,composerPlaceholder:`Type your question…`},{en:{send:`Send`,composerPlaceholder:`Type a message…`},fr:{send:`Envoyer`,composerPlaceholder:`Tapez un message…`}}]}),J=e.object({locale:a.optional().describe("BCP-47 locale tag forced for this widget instance (e.g. `fr-CA`). Page-owner explicit override."),defaultLocale:e.union([e.literal(`auto`),a]).default(`auto`).describe("Initial / fallback locale for users who haven't picked one. `auto` sniffs `navigator.language`."),availableLocales:e.array(a).max(200).optional().describe(`BCP-47 locale tags shown in the runtime language switcher. Hidden when fewer than 2 entries.`),strings:q.optional()}).loose().describe(`Locale + i18n overrides.`).meta({examples:[{defaultLocale:`auto`},{defaultLocale:`en`,availableLocales:[`en`,`fr`,`de`]},{locale:`fr-CA`,strings:{send:`Envoyer`}}]}),Y=e.enum([`chat`,`home`,`help`,`news`]),oe=e.object({label:e.string().min(1).max(40).describe("Tab label — an i18n key (resolved against the i18n strings, e.g. `tabHome`) or a literal. Translations ride `i18n.strings`."),layout:Y.describe("UI layout that renders this tab: `chat` (the conversation), `home`, `help`, or `news`."),contentTags:e.array(e.string().max(60)).max(20).optional().describe("Content tags this tab scopes to — the `tags` filter on `GET /content` (matches content tagged with ANY of them). Omit for the `chat` layout."),id:e.string().max(60).optional().describe("Stable id for navigation + last-tab persistence. Defaults to the first `contentTags` entry, else `layout`."),brandName:e.string().max(80).optional().describe("`home` — hero wordmark (e.g. your company name)."),hideGreeting:e.boolean().optional().describe("`home` — hide the greeting hero. Unset/`false` = shown (default), `true` = hidden."),greetingText:e.string().max(280).optional().describe("`home` — override the greeting headline (default 'How can we help?'); supports a `{name}` token."),userAvatars:e.array(e.object({name:e.string().min(1).max(80),avatar:e.string().max(2048).optional().describe("Image URL; falls back to initials of `name`."),role:e.string().max(80).optional()}).loose()).max(10).optional().describe("`home` — team/agent avatars shown in the hero."),hideSearchBar:e.boolean().optional().describe("`home` — hide the 'Search for help' bar. Unset/`false` = shown (default), `true` = hidden."),hideRecentConversations:e.boolean().optional().describe("`home` — hide the visitor's most recent conversation card. Unset/`false` = shown (default), `true` = hidden."),status:e.object({text:e.string().min(1).max(120).optional().describe("`home` — status line; an i18n key or a literal (resolved via `localizeText`). Omit to use the built-in generic `homeStatus` string (localized, no custom key needed)."),level:e.string().max(40).optional().describe("`home` — status severity, drives the status icon colour. Free text; `operational` (green, default), `degraded` (amber), and `down` (red) are styled, any other value falls back to the operational style.").meta({examples:[`operational`,`degraded`,`down`]}),url:e.string().max(2048).optional().describe(`Opens in an in-widget iframe when tapped.`)}).optional().describe("`home` — system-status card. Omit to hide."),contentBlockTitle:e.string().max(80).optional().describe("`home` — heading for the content list built from `contentTags` (default 'Popular articles').")}).loose(),se=e.array(oe).max(4).describe("Messenger tabs — an ordered list of up to 4 tabs, each picking a `layout` + optional content `contentTags` (the content scope) + a translatable `label`. One tab → no tab bar (just that content); zero → an empty state. Floating / drawer / modal only.").meta({examples:[[{label:`tabConversations`,layout:`chat`}],[{label:`tabHome`,layout:`home`,brandName:`Acme`,contentBlockTitle:`Popular articles`,contentTags:[`popular`]},{label:`tabConversations`,layout:`chat`},{label:`tabHelp`,layout:`help`,contentTags:[`help`,`guides`]},{label:`tabNews`,layout:`news`,contentTags:[`news`]}]]}),ce=e.object({enabled:e.boolean().optional().describe("Master switch. Default `true` — the widget always fires; the data module gates ingestion per deployment server-side. Set `false` to silence the client entirely."),endpoint:e.url().max(2048).optional().describe("Pixel collector URL. Default: `${dataApiBaseUrl}/pai/elements-px.gif` (e.g. `https://help.ai/api/data/pai/elements-px.gif`). Must be a full URL."),sampleRate:e.number().min(0).max(1).optional().describe("Per-widget-runtime sampling 0..1 (one decision per mounted runtime, keeping that runtime's funnel intact). Default `1`."),token:e.string().max(512).optional().describe("Anti-forgery token, echoed as `tk=` on every hit. Server-issued in the `/handshake` response — never set by hand (see `docs/api/tracking-collector.md`).")}).loose().describe(`Anonymous usage tracking for the widget owner — GA-style pixel events (open, send, form submit, …) with page / device / locale dimensions. Never includes message or form content; the only visitor identifier is the widget's anonymous visitorId.`).meta({examples:[{enabled:!0},{enabled:!0,sampleRate:.5}]}),le=e.object({upload:e.string().min(1).max(2048).default(`/pai/upload-file`),transcribe:e.string().min(1).max(2048).default(`/pai/transcribe-audio`),submitForm:e.string().min(1).max(2048).default(`/pai/submit-form`).describe("Form submission endpoint. The widget POSTs each completed form's values here (fire-and-forget), keyed by the same visitorId + conversationId as the conversation; the payload carries `formId` + `trigger` so the backend can route. Optional — a 404 is ignored.")}).loose().describe("HTTP endpoint overrides. `upload` / `transcribe` paths are appended to the agent base (`${baseUrl}/api/agent`), `submitForm` to the data base (`dataApiBaseUrl`); absolute URLs are accepted too.").meta({examples:[{upload:`/pai/upload-file`,transcribe:`/pai/transcribe-audio`},{upload:`https://help.ai/uploads/presign`},{submitForm:`https://crm.acme.com/contacts`}]}),ue=e.object({id:e.string().min(1).max(200).optional().describe(`Host's stable id for the user — informational context the agent/backend can key on (not auth).`),email:e.string().min(1).max(320).optional().describe(`User email — surfaced to the agent for context (optional).`),name:e.string().min(1).max(200).optional().describe("Display name — shown in the Home greeting (`{name}`) and conversation transcripts (optional)."),plan:e.string().min(1).max(120).optional().describe("Subscription / pricing tier, e.g. `pro` (optional)."),role:e.string().min(1).max(120).optional().describe("The user's role on the host site, e.g. `admin` (optional)."),phone:e.string().min(1).max(40).optional().describe(`Phone number (optional).`)}).catchall(e.union([e.string(),e.number(),e.boolean()])).describe("Host-asserted end-user context (informational, untrusted — not authentication). Recognised `id` / `email` / `name` / `plan` / `role` / `phone` plus any extra custom scalar fields forwarded to the agent.").meta({examples:[{id:`u_123`},{id:`u_123`,email:`alex@acme.com`,name:`Alex Lee`,plan:`pro`}]}),de=[`home`,`docs`,`help-desk`,`support`,`pricing`,`checkout`,`orders`,`account`,`blog`],fe=e.object({area:e.string().min(1).max(64).optional().describe(`Host-defined surface the visitor is on. Free-form; suggested: ${de.join(` | `)}.`),url:e.string().min(1).max(2048).optional().describe(`Full page URL. Auto-filled from the host page when omitted.`),path:e.string().min(1).max(2048).optional().describe("Page path, e.g. `/pricing/enterprise`."),title:e.string().min(1).max(300).optional().describe(`Human-readable page title.`)}).catchall(e.union([e.string(),e.number(),e.boolean()])).describe("Host-asserted page/visit context (informational, untrusted). Where the visitor is + the surface (`area`) they're using, plus any custom scalar fields, forwarded to the agent.").meta({examples:[{area:`pricing`,path:`/pricing`},{area:`help-desk`}]}),X=e.object({widgetId:e.string().min(1).max(80).default(`default`).describe(`Stable per-instance id used to namespace localStorage keys. Set this when mounting more than one widget on the same page so their conversation state doesn't collide.`),publicKey:e.string().min(1).max(200).optional().describe("Workspace-scoped public key (`agent_pk_…`) issued from the dashboard. Sent on every API call as the `x-public-key` header."),aiAgentDeploymentId:e.string().min(1).max(200).optional().describe("Deployment UUID from the dashboard. Picks which agent / settings bundle answers this mount. Sent as `x-ai-agent-deployment-id`."),baseUrl:e.string().min(1).max(2048).optional().describe("MAIN site origin (e.g. `https://help.ai`) — the API modules derive from it: agent API at `${baseUrl}/api/agent`, data API at `${baseUrl}/api/data`. Set this to point at your staging / self-hosted / custom backend. Default: the brand's `defaultBaseUrl` baked at build time."),agentApiBaseUrl:e.string().min(1).max(2048).optional().describe("Full agent-API base — set it to reach a STANDALONE `module-help-ai-agent-api` directly (local module on its own port, e.g. `http://localhost:3087`; routes attach as-is: `/pai/handshake`). Precedence: this option > derived from the main origin (`${baseUrl}/api/agent`). Not server-pushable."),dataApiBaseUrl:e.string().min(1).max(2048).optional().describe("Full data-API base (e.g. `https://help.ai/api/data`, or a standalone local module like `http://localhost:3106`) — the service serving content, form definitions (`GET /pai/bootstrap`), and form submissions at root-level paths. Precedence: this option > server-pushed `config.dataApiBaseUrl` > derived from the main origin (`${baseUrl}/api/data`)."),userContext:ue.optional(),pageContext:fe.optional(),endpoints:le.optional(),debug:e.union([e.boolean(),e.enum([`off`,`error`,`warn`,`info`,`debug`,`trace`])]).optional().describe('Console log verbosity. `false` / `"off"` silences everything (default in production). `true` → `"debug"`. Levels `error` / `warn` / `info` / `debug` / `trace` gate output additively. Use `"trace"` to capture per-chunk SSE frames + every state transition. Also toggleable at runtime via `chat.setDebug(level)` or `?<brand>-debug=trace` in the page URL — the URL beats this option, this option beats the persisted `localStorage` value.')}).loose().describe("Where the widget talks to and how it authenticates. Owned by the embedding developer (typically baked into the page template / env var) — distinct from `userPrefs`, which is the dashboard-pushable UX surface.").meta({examples:[{publicKey:`agent_pk_demo`,aiAgentDeploymentId:`01900000-0000-7000-8000-000000000001`},{publicKey:`agent_pk_demo`,aiAgentDeploymentId:`01900000-0000-7000-8000-000000000001`,widgetId:`support`},{publicKey:`agent_pk_demo`,aiAgentDeploymentId:`01900000-0000-7000-8000-000000000001`,baseUrl:`https://api.acme.com`}]}),Z=e.object({presentation:s.optional(),behavior:l.optional(),theme:d.default(`auto`).describe("Theme preference (`auto` follows OS) or a tokens override object (`accent`, `radius`, `fontFamily`)."),launcher:_.optional(),header:S.optional(),composer:M.optional(),feedback:E.optional(),i18n:J.optional(),footer:P.optional(),features:y.optional(),forms:G.optional(),modules:se.optional(),tracking:ce.optional()}).loose().describe("Portal-configurable widget settings — thirteen sections, one per dashboard tab. The backend pushes this shape on `/pai/handshake` under `config`, except `forms`, which the data API serves separately.").meta({examples:[{presentation:{mode:`floating`,position:`bottom-right`},theme:`auto`},{presentation:{mode:`floating`},theme:{accent:`#7c3aed`,radius:`12px`},launcher:{variant:`pill`,label:`Ask AI`,callout:{text:`👋 Need help?`}}},{presentation:{mode:`inline`},behavior:{responseMode:`streaming`},features:{voice:`local`}}]}),Q=X.extend({...Z.shape,site:e.unknown().optional().describe('Site config — applies in `mode: "page"`. Comes from the handshake response, not user-edited.'),blocks:e.unknown().optional().describe('Blocks (nav + link cards) — applies in `mode: "page"`. Comes from the handshake response.')}).describe("Full tooling config — connection (where + auth) + widget settings (look + behaviour) + page-mode handshake extras. The production widget does not import this Zod schema. Mode-specific constraints (e.g. `position` only meaningful in `floating`) are NOT enforced here so the same object can flow across mode changes.").meta({examples:[{publicKey:`agent_pk_demo`,aiAgentDeploymentId:`01900000-0000-7000-8000-000000000001`,presentation:{mode:`floating`},theme:`auto`},{publicKey:`agent_pk_demo`,aiAgentDeploymentId:`01900000-0000-7000-8000-000000000001`,presentation:{mode:`modal`,size:{width:`720px`,height:`640px`}}}]}),pe=Q.partial(),me=Z.partial(),he=X.partial(),ge=Z.partial().omit({forms:!0}).extend({dataApiBaseUrl:e.string().min(1).max(2048).optional(),storageVersion:e.string().min(1).max(64).optional()}),$=e.object({title:e.string().min(1).max(120).optional().describe(`Brand / site name. Used as the logo's alt-text fallback.`),logo:t.optional().describe(`Brand logo shown at the top of the page-mode sidebar.`),logoDark:t.optional().describe("Optional dark-theme logo variant. Falls back to `logo` when the active theme is light or `logoDark` is unset.")}).loose(),_e=e.object({navigation:e.array(n).max(100).optional().describe(`Sidebar navigation links (vertical list, primary nav).`),linkCards:e.array(n).max(100).optional().describe("Sidebar link cards (richer entries with optional `description`).")}).loose(),ve=e.object({id:e.string(),role:e.enum([`assistant`,`system`]),text:e.string()}).loose(),ye=e.object({id:e.string(),label:e.string(),text:e.string().optional()}).loose(),be=e.enum([`conflict`,`expired`,`invalid`]),xe=e.object({deployment:e.object({id:e.string(),name:e.string(),slug:e.string().optional(),publicKey:e.string().optional()}).loose(),agent:e.object({id:e.string(),name:e.string(),title:e.string().optional(),status:e.enum([`online`,`away`,`offline`]).optional()}).loose(),config:ge.optional(),site:$.optional(),blocks:_e.optional(),welcome:e.object({messages:e.array(ve).optional(),suggestions:e.array(ye).optional()}).loose().optional(),visitorId:e.string(),conversationId:e.string(),userPrefs:e.record(e.string(),e.unknown()).optional(),rebind:e.object({visitorId:be.optional(),conversationId:be.optional()}).loose().optional()}).loose(),Se=[`theme`,`header`,`composer`,`footer`,`i18n`,`features`,`feedback`,`tracking`],Ce={floating:[`presentation`,`behavior`,`modules`,`launcher`],inline:[`presentation`,`behavior`],modal:[`presentation`,`behavior`,`modules`],drawer:[`presentation`,`behavior`,`modules`,`launcher`],page:[`presentation`],standalone:[`presentation`]};function we(e){let t=[...Ce[e],...Se],n={};for(let e of t)n[e]=!0;return Z.pick(n)}const Te=[`floating`,`inline`,`standalone`,`page`,`modal`,`drawer`];function Ee(){return{$schema:`https://json-schema.org/draft/2020-12/schema`,title:`@helpai/elements config`,description:`Canonical config schemas for the embeddable AI agent elements widget.`,definitions:{WidgetConfig:e.toJSONSchema(Q),HandshakeResponse:e.toJSONSchema(xe)}}}export{Te as ALL_MODES,de as PAGE_AREA_SUGGESTIONS,b as actionNameSchema,t as assetSchema,j as attachmentLimitsSchema,l as behaviorSchema,_e as blocksConfigSchema,h as calloutPositionSchema,m as calloutShapeSchema,M as composerOptionsSchema,he as connectionConfigPartialSchema,X as connectionConfigSchema,i as cssColorSchema,r as cssLengthSchema,Ee as emitJsonSchema,le as endpointsSchema,y as featureFlagsSchema,C as feedbackEventSchema,E as feedbackSchema,I as fieldOptionSchema,V as fieldOptionTranslationSchema,F as fieldTypeSchema,L as fieldValidationSchema,P as footerSchema,B as formConditionSchema,W as formDefSchema,R as formFieldSchema,H as formFieldTranslationSchema,U as formTranslationSchema,z as formTriggerSchema,G as formsSchema,xe as handshakeResponseSchema,T as hapticsOptionsSchema,x as headerActionsSchema,S as headerSchema,J as i18nSchema,o as initialSizeSchema,g as launcherCalloutSchema,_ as launcherOptionsSchema,p as launcherSizeSchema,f as launcherVariantSchema,n as linkSchema,a as localeSchema,re as modeSchema,Y as moduleLayoutSchema,oe as moduleSchema,se as modulesSchema,fe as pageContextSchema,ie as positionSchema,N as poweredBySchema,s as presentationSchema,te as resizeOptionsSchema,c as responseModeSchema,O as sendButtonIconSchema,A as sendButtonOptionsSchema,D as sendButtonShapeSchema,k as sendButtonVariantSchema,ge as serverConfigSchema,$ as siteConfigSchema,ne as sizeOptionsSchema,w as soundOptionsSchema,q as stringsOverrideSchema,d as themeFieldSchema,ae as themeOverridesSchema,u as themePreferenceSchema,ce as trackingSchema,ue as userContextSchema,ee as uuid7Schema,v as voiceModeSchema,pe as widgetConfigPartialSchema,Q as widgetConfigSchema,me as widgetSettingsPartialSchema,Z as widgetSettingsSchema,we as widgetSettingsSchemaForMode};
|
|
1
|
+
import{z as e}from"zod";const t=e.object({url:e.string().min(1).max(2048).describe(`Public URL of the image. Must be a full URL or root-relative path.`),alt:e.string().min(1).max(200).optional().describe(`Alt text for screen readers. Omit only for purely decorative images.`)}).strict().describe(`A single image asset (logo, hero, icon).`),n=e.object({id:e.string().min(1).max(100).optional().describe(`Stable id used as Preact key + analytics target.`),label:e.string().min(1).max(120).describe(`Visible link text.`),href:e.string().min(1).max(2048).optional().describe(`Click target. Optional — when omitted the link renders as a non-clickable heading.`),icon:e.string().min(1).max(100).optional().describe(`Built-in icon preset name (chat / spark / message / help / wand) or a custom string.`),description:e.string().min(1).max(300).optional().describe(`Subtitle shown below the label. Used by link cards in page mode.`)}).loose().describe(`A navigation link / link card.`),r=e.string().min(1).max(120).describe("Any CSS length — `640px`, `80vh`, `calc(100vh - 64px)`, etc. Validated only for size, not syntax."),i=e.string().min(1).max(120).describe("Any CSS colour — hex, `rgb()`, `hsl()`, `oklch()`, a named colour, or a `var(--token)`."),a=e.string().min(1).max(35).describe("BCP-47 locale tag (e.g. `en`, `fr-CA`, `zh-Hant-HK`)."),ee=e.uuidv7().describe(`UUID v7 (RFC 9562) — the single id format for visitor / conversation / message / file / content ids.`),o=e.enum([`normal`,`expanded`,`fullscreen`,`auto`]),te=e.object({enabled:e.boolean().default(!1).describe(`Show a corner-grip handle the user can drag to resize the panel.`),minWidth:r.optional().describe(`Smallest width the user can drag the panel to.`),maxWidth:r.optional().describe(`Largest width the user can drag the panel to.`),minHeight:r.optional().describe(`Smallest height the user can drag the panel to.`),maxHeight:r.optional().describe(`Largest height the user can drag the panel to.`)}).loose().describe("Drag-resize controls. When `enabled`, a corner grip lets users resize the panel within the optional min/max bounds.").meta({examples:[{enabled:!1},{enabled:!0,minWidth:`320px`,maxWidth:`720px`,minHeight:`400px`,maxHeight:`90vh`}]}),ne=e.object({width:r.optional().describe("Panel width (CSS length). Mode-specific defaults — floating `380px`, modal `min(960px, 92vw)`, drawer `min(440px, calc(100vw - 32px))`. Set this to override; inline/standalone/page modes ignore it."),height:r.optional().describe("Panel height (CSS length). Mode-specific defaults — floating `600px`, modal `min(720px, 88vh)`, drawer `100vh` (clamped to `100vh − inset-y × 2`). Set this to override."),expanded:e.object({width:r.optional().describe(`Width when the user clicks the expand button.`),height:r.optional().describe(`Height when the user clicks the expand button.`)}).loose().optional().describe("Dimensions when the panel is in its `expanded` tier (between normal and fullscreen). Applies to floating / modal / drawer; ignored in inline / standalone / page where the panel always fills its parent."),resize:te.optional().describe(`Drag-resize controls — adds a corner grip the user can drag.`),inset:r.optional().describe("Drawer-mode edge inset — gap from each viewport edge (top/bottom/left/right). Single value applied to all four sides. Defaults to `3vh` vertical / `12px` horizontal. Use `0` for a full-edge drawer (no gap), or `5vh` for a centered-looking flyout with extra breathing room. Ignored by every other mode."),initialSize:o.default(`auto`).describe("Which size tier to open with on the first mount: `normal` / `expanded` / `fullscreen` / `auto`. Default `auto` is responsive by viewport: phones (< 640 px) open `fullscreen` edge-to-edge, tablets (640 px … `autoSizeBreakpoint`) open `normal`, and desktop/laptop (≥ `autoSizeBreakpoint`) open `expanded` — so the panel is right for the device out of the box. Set a literal to pin one tier everywhere; the user's own resize is remembered per-device and wins on reload."),autoSizeBreakpoint:e.number().int().min(0).max(1e4).default(900).describe("Viewport width (px) at/above which `initialSize: auto` opens `expanded` instead of `normal`. (Phones below 640 px always open `fullscreen`.)")}).loose().describe("Panel dimensions and drawer inset. Width/height override the mode-specific defaults (floating compact popover, modal centered dialog, drawer flyout). `inset` only affects drawer mode.").meta({examples:[{width:`420px`,height:`640px`},{width:`min(560px, 92vw)`,height:`min(640px, 90vh)`},{width:`420px`,expanded:{width:`640px`,height:`780px`},initialSize:`auto`,autoSizeBreakpoint:900},{width:`min(1100px, 95vw)`,height:`min(820px, 92vh)`},{width:`360px`,inset:`8vh`},{width:`420px`,height:`100vh`,inset:`0`},{width:`420px`,height:`60vh`,inset:`20vh`}]}),re=e.enum([`floating`,`inline`,`standalone`,`page`,`modal`,`drawer`]),ie=e.enum([`bottom-right`,`bottom-left`,`top-right`,`top-left`]),s=e.object({mode:re.default(`floating`).describe("Rendering shape. `floating` (FAB + popover), `inline` (in-flow block), `standalone` (full viewport), `page` (sidebar + chat), `modal` (centered overlay), `drawer` (side dock)."),position:ie.default(`bottom-right`).describe("Anchor corner. `floating` uses it for the FAB; `drawer` uses the `*-left` / `*-right` suffix to pick the dock side. Ignored in other modes."),iframe:e.boolean().default(!1).describe(`Wrap the widget in a same-origin iframe for stricter JS + CSS isolation from the host page. Composes with every mode.`),size:ne.optional().describe("Panel dimensions. Mode-aware defaults (floating compact popover, modal centered dialog, drawer flyout) — fields here override them. Includes `inset` (drawer flyout gap), `expanded` tier, drag-resize grip.")}).loose().describe(`Layout fundamentals — mode, anchor corner, iframe isolation, panel size + drawer inset.`).meta({examples:[{mode:`floating`,position:`bottom-right`},{mode:`modal`,size:{width:`min(1100px, 95vw)`,height:`min(820px, 92vh)`}},{mode:`drawer`,position:`bottom-right`},{mode:`drawer`,position:`top-right`,size:{width:`360px`,inset:`8vh`}},{mode:`drawer`,position:`bottom-right`,size:{width:`420px`,height:`100vh`,inset:`0`}}]}),c=e.enum([`streaming`,`buffered`]),l=e.object({startMinimized:e.boolean().default(!0).describe("Don't auto-open the floating panel on first visit. `true` (default) shows the FAB collapsed; `false` opens the panel on mount."),responseMode:c.default(`streaming`).describe("How the assistant's reply arrives. `streaming` (default) types tokens as they generate; `buffered` waits + shows the full reply at once."),showReasoning:e.boolean().default(!1).describe(`Show the model's reasoning ("thoughts") as a collapsible block above each answer. \`false\` (default) hides it — a loading indicator still shows while the model works. Reasoning is absent from durable history, but the current turn's short-lived resume backlog may replay it after an immediate reconnect or reload.`),showToolCalls:e.boolean().default(!1).describe("Show automatic tool/function calls the model makes (e.g. search) as collapsible detail+result cards in the transcript. `false` (default) hides them. Interactive tools (ask-questions / approval) always show."),showSources:e.boolean().default(!1).describe("Show the citation sources behind an answer (web links + document/file references) in the transcript. `false` (default) hides them."),disableMessageFeedback:e.boolean().default(!1).describe("Turn OFF the 👍/👎 feedback controls under each rateable reply (AI or operator). Unset/`false` = shown (default), `true` = hidden. A choice fires the `messageFeedback` event and is POSTed best-effort to `/pai/message-feedback`."),autoSendSuggestions:e.boolean().default(!1).describe("Auto-send a suggestion chip's text the moment it's tapped. `false` (default) fills the composer with the text so the visitor can edit before sending; `true` sends it immediately."),popOutUrl:e.string().trim().min(1).max(2048).optional().describe("URL opened by the `popOut` header action. The widget adds the active conversation, widget, and deployment identity as query parameters.")}).loose().describe(`Conversation behaviour — lifecycle, streaming, and the transcript surface (scroll, feedback).`).meta({examples:[{startMinimized:!0,responseMode:`streaming`},{startMinimized:!1,responseMode:`buffered`,disableMessageFeedback:!0}]}),u=e.enum([`auto`,`light`,`dark`]),ae=e.object({accent:i.optional().describe(`Brand accent colour. Drives the FAB, send button, links, and active states. CSS colour.`),accentText:i.optional().describe(`Foreground colour rendered ON the accent (FAB icon, send-button glyph). CSS colour.`),radius:r.optional().describe("Corner radius for the panel + buttons. CSS length (`8px`, `0.75rem`, `12px`)."),fontFamily:e.string().min(1).max(200).optional().describe("Font stack used inside the widget. Standard CSS `font-family` value."),contentMaxWidth:r.optional().describe("Max width of the readable content column (messages, history, home, composer) on a wide surface — content centres, full-bleed bars keep their edges. CSS length; default `768px`. Set `100%` to fill the surface (no cap). Modal + drawer already fill their card by default.")}).loose().describe(`Theme token overrides. Any field left unset falls through to the brand's default theme.`).meta({examples:[{accent:`#10b981`,radius:`12px`},{accent:`#7c3aed`,accentText:`#ffffff`,radius:`16px`,fontFamily:`'Inter', system-ui`},{accent:`oklch(0.7 0.2 145)`,radius:`0.5rem`},{accent:`#5b5bd6`,contentMaxWidth:`960px`}]}),d=e.union([u,ae]).describe(`'auto' | 'light' | 'dark', or a tokens override object.`).meta({examples:[`auto`,`dark`,{accent:`#10b981`,radius:`12px`},{accent:`#7c3aed`,accentText:`#fff`,radius:`16px`,fontFamily:`'Inter', system-ui`}]}),f=e.enum([`circle`,`pill`,`bar`,`minimal`]),p=e.enum([`sm`,`md`,`lg`]),m=e.enum([`pill`,`bubble`,`callout`]),h=e.enum([`auto`,`horizontal`,`vertical`]),g=e.object({text:e.string().min(1).max(500).default(`👋 Hi! Need any help?`).describe("The message to show in the bubble. Defaults to a friendly greeting; replace with your own copy (e.g. 'Click here →'). May also be an i18n key (e.g. `launcherCallout`) resolved against `i18n.strings` for per-locale copy; a non-key literal renders as-is."),shape:m.default(`pill`).describe("Visual shape — `pill` (slim label + arrow), `bubble` (speech-bubble tail), `callout` (loud variant)."),position:h.default(`auto`).describe("Where the bubble sits relative to the FAB. `auto` (default) picks the side facing into the page. `horizontal` anchors beside the FAB (left/right depending on the FAB's corner). `vertical` anchors above/below the FAB. Concrete left/right/top/bottom is derived from the FAB's corner so the bubble always stays on-screen."),delayMs:e.number().int().min(0).max(6e4).default(1200).describe(`How long to wait after page load before the bubble appears, in milliseconds.`),autoDismissMs:e.number().int().min(0).max(6e5).default(0).describe("Auto-hide the bubble after this many milliseconds. `0` (default) keeps it until the user dismisses."),animated:e.boolean().default(!0).describe("Subtle horizontal nudge to draw the eye. Disabled automatically by `prefers-reduced-motion`."),persistent:e.boolean().default(!0).describe("How dismissals stick. `true` (default) — re-show on every visit regardless of prior dismissal (the 'recurring greeting' behaviour). `false` — clicking X stores the message text and that exact message stays hidden across visits (the 'one-time hint' behaviour).")}).loose().describe(`Pre-engagement message bubble next to the FAB. Use it for a greeting ('Hi, need help?'), an attention-grabbing nudge ('Click here →'), or any one-time onboarding tip — same component, configured for the use case.`),_=e.object({variant:f.default(`circle`).describe("FAB visual style. `circle` — round bubble (icon only). `pill` — icon + label. `bar` — wide rectangular button. `minimal` — text-only outline."),size:p.default(`md`).describe(`FAB size preset — small / medium / large.`),label:e.string().max(200).optional().describe("Text rendered on the FAB (only shown for `pill` / `bar` / `minimal` variants)."),icon:e.string().max(8e3).optional().describe("Icon shown on the FAB / drawer edge tab. Built-in presets: `chat` / `message` / `bubbles` / `spark` / `wand` / `bot` / `help` / `lifebuoy` / `headset`. Anything else is treated as raw SVG."),hidden:e.boolean().default(!1).describe("Hide the FAB entirely (page brings its own trigger that calls `instance.open()`)."),callout:g.optional()}).loose().describe(`Floating-launcher (FAB) appearance and behaviour. The FAB is the round button in the corner that opens the chat panel. Floating mode only.`).meta({examples:[{variant:`circle`,size:`md`},{variant:`pill`,label:`Ask AI`,icon:`spark`},{variant:`circle`,callout:{text:`👋 Need help?`,shape:`bubble`,persistent:!0}},{variant:`circle`,callout:{text:`Click to start`,shape:`pill`,delayMs:1200}}]}),v=e.enum([`local`,`server`,`disabled`]),y=e.object({disableFileUpload:e.boolean().default(!1).describe("Turn OFF file uploads — hide the paperclip button + reject drag/drop/paste. Unset/`false` = enabled (default), `true` = disabled."),voice:v.default(`local`).describe("Voice input mode. `local` — Web Speech API in the browser (free, runs on-device). `server` — record audio + upload to the transcribe endpoint. `disabled` — hide the mic button."),disableHumanInLoop:e.boolean().default(!1).describe("Turn OFF the human-in-the-loop tool UI — the inline ask-questions form + approve/reject controls for gated tools. Unset/`false` = shown (default), `true` = suppressed (the wire still carries the signals)."),enableOperatorRequest:e.boolean().default(!1).describe("Opt IN operator control — expose the 'Talk to an operator' affordance + connected-to-operator states for this deployment. Unset/`false` = off (default), `true` = on. Unlike the `disable*` flags this is a positive opt-in for a new capability.")}).loose().describe(`Capability flags — file upload, voice mode, HITL, and operator requests.`).meta({examples:[{voice:`local`},{disableFileUpload:!0,voice:`disabled`},{voice:`server`,enableOperatorRequest:!0}]}),b=e.enum([`clear`,`expand`,`fullscreen`,`popOut`,`sound`,`history`,`language`,`theme`,`textSize`,`close`]),x=e.array(b).max(10).describe("Which actions the header exposes. `history` + `close` are dedicated buttons; the rest appear in the ⋯ menu. Unlisted actions are hidden; order is irrelevant. Pick from: `clear`, `expand`, `fullscreen`, `popOut`, `sound`, `history`, `language`, `theme`, `textSize`, `close`. Per-mode defaults live in `DEFAULT_ACTIONS_BY_MODE`.").meta({examples:[[`theme`,`history`,`close`,`clear`,`fullscreen`,`sound`,`language`],[`theme`,`history`,`clear`,`language`],[`close`]]}),S=e.object({actions:x.optional().describe(`Which header actions are enabled (history/✕ buttons + ⋯ menu).`)}).loose().describe(`Panel-header configuration.`).meta({examples:[{actions:[`theme`,`history`,`close`,`clear`,`fullscreen`]}]}),C=e.enum([`messageReceived`,`messageSent`,`error`,`voiceStart`,`voiceStop`]),w=e.object({enabled:e.boolean().default(!0).describe(`Master on/off for audible notifications. On by default — browser autoplay policy still requires a prior user gesture before any sound plays.`),volume:e.number().min(0).max(1).default(.3).describe("Global volume 0..1. `0.3` (default) is intentionally subtle."),events:e.partialRecord(C,e.union([e.boolean(),e.string().min(1).max(2048)])).optional().describe("Per-event override map. Value: `true` = built-in tone, `false` = silent, `string` = URL to a custom audio file (mp3/wav/ogg).")}).loose().describe("Audible notifications. `events` accepts true (default tone), false (off), or a URL to an audio file.").meta({examples:[{enabled:!0,volume:.3},{enabled:!0,volume:.5,events:{messageReceived:!0,error:!1}},{enabled:!0,events:{messageReceived:`https://cdn.help.ai/ping.mp3`}}]}),T=e.object({enabled:e.boolean().default(!0).describe(`Master on/off. Default: on for touch devices, silently ignored elsewhere.`),events:e.partialRecord(C,e.union([e.boolean(),e.number().int(),e.array(e.number().int())])).optional().describe("Per-event vibration pattern. Value: `true` = default pattern, `false` = silent, number = single vibrate ms, array = `[vibrate, pause, vibrate, …]`.")}).loose().describe("Touch-device vibration. `events` accepts true (default), number (single vibrate ms), or [vibrate,pause,vibrate,…].").meta({examples:[{enabled:!0},{enabled:!0,events:{messageReceived:30,error:[40,60,40]}}]}),E=e.object({sound:w.optional(),haptics:T.optional()}).loose().describe(`Audible + tactile feedback.`).meta({examples:[{sound:{enabled:!0,volume:.3},haptics:{enabled:!0}},{sound:{enabled:!1},haptics:{enabled:!1}}]}),D=e.enum([`circle`,`square`,`pill`]),O=e.enum([`arrowUp`,`paperPlane`,`send`,`chevron`]),k=e.enum([`filled`,`outline`,`ghost`]),A=e.object({shape:D.default(`circle`).describe("Send button outline shape — `circle` / `square` / `pill`."),icon:O.default(`arrowUp`).describe("Icon inside the send button — `arrowUp` / `paperPlane` / `send` / `chevron`."),variant:k.default(`filled`).describe("Fill treatment — `filled` (solid accent), `outline` (border only), `ghost` (no border, no fill).")}).loose().describe(`Send-button styling — shape, icon, and fill treatment.`).meta({examples:[{shape:`circle`,icon:`arrowUp`,variant:`filled`},{shape:`pill`,icon:`send`,variant:`outline`},{shape:`square`,icon:`paperPlane`,variant:`ghost`}]}),j=e.object({maxBytes:e.number().int().min(0).max(1024*1024*1024).default(10*1024*1024).describe("Maximum size per uploaded file, in bytes. `10 MiB` (10485760) by default."),maxCount:e.number().int().min(0).max(100).default(10).describe(`Maximum number of attachments per message.`),accept:e.string().max(2e3).default(`*/*`).describe("Allowed MIME types — comma-separated `accept` attribute string, e.g. `image/*,application/pdf`.")}).loose().describe(`Client-side attachment limits. Server enforcement is still required.`).meta({examples:[{maxBytes:10485760,maxCount:10,accept:`*/*`},{maxBytes:5242880,maxCount:3,accept:`image/*,application/pdf`}]}),M=e.object({sendButton:A.optional(),attachments:j.optional()}).loose().describe(`Composer chrome — send-button styling + attachment limits.`).meta({examples:[{sendButton:{shape:`circle`,icon:`arrowUp`,variant:`filled`}},{sendButton:{shape:`pill`,icon:`send`,variant:`outline`}},{attachments:{maxBytes:5242880,maxCount:3,accept:`image/*,application/pdf`}}]}),N=e.object({text:e.string().max(200).optional().describe("Visible attribution text (e.g. `Powered by Help AI`)."),href:e.url().max(2048).optional().describe("Click target — the attribution becomes a link. Must be a full URL (`https://…`)."),logoUrl:e.url().max(2048).optional().describe(`Small logo rendered before the text. Must be a full URL.`)}).loose().describe(`"Powered by …" attribution shown below the composer.`).meta({examples:[{text:`Powered by Help AI`},{text:`Powered by Help AI`,href:`https://help.ai`,logoUrl:`https://help.ai/logo.svg`}]}),P=e.object({poweredBy:N.optional(),composerDisclaimer:e.string().max(500).optional().describe(`Small muted text under the composer (like ChatGPT's 'ChatGPT can make mistakes.'). Empty = hidden.`)}).loose().describe(`Footer — attribution + composer disclaimer.`).meta({examples:[{poweredBy:{text:`Powered by Help AI`,href:`https://help.ai`}},{composerDisclaimer:`AI responses may be inaccurate.`}]}),F=e.enum([`text`,`email`,`tel`,`url`,`number`,`date`,`time`,`textarea`,`select`,`radio`,`checkbox`,`multiselect`]),I=e.object({value:e.string().min(1).max(200).describe(`The value recorded when this choice is picked.`),label:e.string().min(1).max(200).optional().describe("Display text; falls back to `value`."),description:e.string().max(280).optional().describe(`Optional sub-label under the choice.`)}).loose(),L=e.object({pattern:e.string().max(500).optional().describe("Regex source string — text-like fields (e.g. `^\\d{5}$`)."),minLength:e.number().int().min(0).max(1e4).optional(),maxLength:e.number().int().min(1).max(1e4).optional(),min:e.number().optional().describe("Minimum — `number` fields."),max:e.number().optional().describe("Maximum — `number` fields."),minSelections:e.number().int().min(0).max(50).optional().describe("Minimum picks — `multiselect`."),maxSelections:e.number().int().min(1).max(50).optional().describe("Maximum picks — `multiselect`.")}).loose(),R=e.object({name:e.string().min(1).max(80).describe("Stable key the answer is recorded under (e.g. `email`)."),label:e.string().min(1).max(200).describe(`Visible field label.`),type:F.describe(`Field renderer + built-in validation (email/tel/url/number/date/time checked).`),placeholder:e.string().max(200).optional(),required:e.boolean().optional().default(!0).describe("Must be answered to submit. `false` allows skipping."),defaultValue:e.string().max(2e3).optional().describe("Pre-filled value (comma-join for `multiselect`)."),options:e.array(I).max(50).optional().describe("Choices for `select` / `radio` / `multiselect`."),validation:L.optional(),allowOther:e.boolean().optional().describe("`select`/`radio`/`multiselect` — allow a free-text 'Other' answer."),validationHint:e.string().max(280).optional().describe(`Small helper line under the field.`)}).loose(),z=e.string().regex(/^(pre-chat|conversation-closed|panel-close|manual|after-messages:\d+|idle:\d+|page-area:.+)$/,`Unrecognised form trigger`).describe("When the form fires: `pre-chat`, `after-messages:N`, `conversation-closed`, `panel-close`, `idle:Nseconds`, `page-area:<area>`, or `manual`."),B=e.object({missingContext:e.array(e.string().max(80)).max(20).optional().describe("Only show if `userContext` lacks ALL of these keys (progressive profiling)."),pageArea:e.union([e.string().max(64),e.array(e.string().max(64)).max(20)]).optional().describe("Only on these `pageContext.area` values."),minMessages:e.number().int().min(0).max(1e3).optional(),maxMessages:e.number().int().min(0).max(1e3).optional()}).loose(),V=e.object({value:e.string().min(1).max(200).describe("The canonical option `value` this localizes."),label:e.string().min(1).max(200).optional(),description:e.string().max(280).optional()}).loose(),H=e.object({name:e.string().min(1).max(80).describe("The canonical field `name` this localizes."),label:e.string().min(1).max(200).optional(),placeholder:e.string().max(200).optional(),validationHint:e.string().max(280).optional(),options:e.array(V).max(50).optional()}).loose(),U=e.object({locale:a,title:e.string().max(120).optional(),description:e.string().max(500).optional(),submitLabel:e.string().max(60).optional(),fields:e.array(H).max(50).optional()}).loose(),W=e.object({id:e.string().min(1).max(80).describe("Stable id — persistence dedupe + `manual` openForm(id)."),on:e.union([z,e.array(z).min(1).max(8)]).describe(`Trigger(s) that surface this form.`),when:B.optional().describe(`Extra eligibility predicate evaluated when a trigger fires.`),fields:e.array(R).max(20).default([]).describe(`The questions, in order. ≥1 required to show.`),title:e.string().max(120).optional(),description:e.string().max(500).optional(),submitLabel:e.string().max(60).optional(),skippable:e.boolean().default(!1).describe(`Show a Skip control so the visitor can dismiss the form.`),frequency:e.enum([`once`,`conversation`,`always`]).default(`once`).describe("`once` (per device, persisted), `conversation` (re-eligible each new conversation), or `always`."),mirrorToContext:e.boolean().default(!0).describe("Mirror the answers into `userContext` so they ride every request. Set `false` for PII (DOB, policy #, symptoms) — those then go only to the submit endpoint."),reopenable:e.boolean().default(!0).describe('Skipped marker offers a "Fill out" button that reopens the form. Set `false` when a skip must be final (e.g. a consent ask that should not be re-surfaced).'),reviewable:e.boolean().default(!0).describe("Submitted marker expands in place to a read-only list of the visitor's answers. Set `false` for sensitive answers that shouldn't linger on screen."),defaultLocale:a.optional().describe(`BCP-47 locale the top-level strings (title / fields copy) are authored in.`),translations:e.array(U).max(50).optional().describe("Per-locale string overrides — the data module trims to the bootstrap `?locale=` (≤1); the widget overlays it onto the default copy.")}).loose(),G=e.array(W).max(20).describe("Event-driven forms — an ordered list (order = priority). Each binds to trigger event(s), gates the chat until submit/skip, dedupes by frequency, and records via `endpoints.submitForm`. Generic across verticals.").meta({examples:[[{id:`intake`,on:`pre-chat`,fields:[{name:`email`,label:`Email`,type:`email`}]}],[{id:`signal`,on:`pre-chat`,title:`Before we start`,fields:[{name:`name`,label:`Your name`,type:`text`},{name:`email`,label:`Work email`,type:`email`}]},{id:`csat`,on:`conversation-closed`,frequency:`always`,fields:[{name:`rating`,label:`How did we do?`,type:`radio`,options:[{value:`good`},{value:`bad`}]}]},{id:`appointment`,on:[`page-area:booking`,`manual`],mirrorToContext:!1,fields:[{name:`department`,label:`Department`,type:`select`,options:[{value:`dermatology`},{value:`cardiology`}]},{name:`date`,label:`Preferred date`,type:`date`},{name:`consent`,label:`I consent to be contacted`,type:`checkbox`}]}]]}),K=e.record(e.string(),e.string()),q=e.union([K,e.record(a,K)]).describe(`Per-instance UI string overrides. Flat = active-locale only; nested = multi-locale map. Both forms are partial — missing keys fall through to built-in defaults.`).meta({examples:[{send:`Click`,stop:`Halt`,composerPlaceholder:`Type your question…`},{en:{send:`Send`,composerPlaceholder:`Type a message…`},fr:{send:`Envoyer`,composerPlaceholder:`Tapez un message…`}}]}),J=e.object({locale:a.optional().describe("BCP-47 locale tag forced for this widget instance (e.g. `fr-CA`). Page-owner explicit override."),defaultLocale:e.union([e.literal(`auto`),a]).default(`auto`).describe("Initial / fallback locale for users who haven't picked one. `auto` sniffs `navigator.language`."),availableLocales:e.array(a).max(200).optional().describe(`BCP-47 locale tags shown in the runtime language switcher. Hidden when fewer than 2 entries.`),strings:q.optional()}).loose().describe(`Locale + i18n overrides.`).meta({examples:[{defaultLocale:`auto`},{defaultLocale:`en`,availableLocales:[`en`,`fr`,`de`]},{locale:`fr-CA`,strings:{send:`Envoyer`}}]}),Y=e.enum([`chat`,`home`,`help`,`news`]),oe=e.object({label:e.string().min(1).max(40).describe("Tab label — an i18n key (resolved against the i18n strings, e.g. `tabHome`) or a literal. Translations ride `i18n.strings`."),layout:Y.describe("UI layout that renders this tab: `chat` (the conversation), `home`, `help`, or `news`."),contentTags:e.array(e.string().max(60)).max(20).optional().describe("Content tags this tab scopes to — the `tags` filter on `GET /content` (matches content tagged with ANY of them). Omit for the `chat` layout."),id:e.string().max(60).optional().describe("Stable id for navigation + last-tab persistence. Defaults to the first `contentTags` entry, else `layout`."),brandName:e.string().max(80).optional().describe("`home` — hero wordmark (e.g. your company name)."),hideGreeting:e.boolean().optional().describe("`home` — hide the greeting hero. Unset/`false` = shown (default), `true` = hidden."),greetingText:e.string().max(280).optional().describe("`home` — override the greeting headline (default 'How can we help?'); supports a `{name}` token."),userAvatars:e.array(e.object({name:e.string().min(1).max(80),avatar:e.string().max(2048).optional().describe("Image URL; falls back to initials of `name`."),role:e.string().max(80).optional()}).loose()).max(10).optional().describe("`home` — team/agent avatars shown in the hero."),hideSearchBar:e.boolean().optional().describe("`home` — hide the 'Search for help' bar. Unset/`false` = shown (default), `true` = hidden."),hideRecentConversations:e.boolean().optional().describe("`home` — hide the visitor's most recent conversation card. Unset/`false` = shown (default), `true` = hidden."),status:e.object({text:e.string().min(1).max(120).optional().describe("`home` — status line; an i18n key or a literal (resolved via `localizeText`). Omit to use the built-in generic `homeStatus` string (localized, no custom key needed)."),level:e.string().max(40).optional().describe("`home` — status severity, drives the status icon colour. Free text; `operational` (green, default), `degraded` (amber), and `down` (red) are styled, any other value falls back to the operational style.").meta({examples:[`operational`,`degraded`,`down`]}),url:e.string().max(2048).optional().describe(`Opens in an in-widget iframe when tapped.`)}).optional().describe("`home` — system-status card. Omit to hide."),contentBlockTitle:e.string().max(80).optional().describe("`home` — heading for the content list built from `contentTags` (default 'Popular articles').")}).loose(),se=e.array(oe).max(4).describe("Messenger tabs — an ordered list of up to 4 tabs, each picking a `layout` + optional content `contentTags` (the content scope) + a translatable `label`. One tab → no tab bar (just that content); zero → an empty state. Floating / drawer / modal only.").meta({examples:[[{label:`tabConversations`,layout:`chat`}],[{label:`tabHome`,layout:`home`,brandName:`Acme`,contentBlockTitle:`Popular articles`,contentTags:[`popular`]},{label:`tabConversations`,layout:`chat`},{label:`tabHelp`,layout:`help`,contentTags:[`help`,`guides`]},{label:`tabNews`,layout:`news`,contentTags:[`news`]}]]}),ce=e.object({enabled:e.boolean().optional().describe("Master switch. Default `true` — the widget always fires; the data module gates ingestion per deployment server-side. Set `false` to silence the client entirely."),endpoint:e.url().max(2048).optional().describe("Pixel collector URL. Default: `${dataApiBaseUrl}/pai/elements-px.gif` (e.g. `https://help.ai/api/data/pai/elements-px.gif`). Must be a full URL."),sampleRate:e.number().min(0).max(1).optional().describe("Per-widget-runtime sampling 0..1 (one decision per mounted runtime, keeping that runtime's funnel intact). Default `1`."),token:e.string().max(512).optional().describe("Anti-forgery token, echoed as `tk=` on every hit. Server-issued in the `/handshake` response — never set by hand (see `docs/api/tracking-collector.md`).")}).loose().describe(`Anonymous usage tracking for the widget owner — GA-style pixel events (open, send, form submit, …) with page / device / locale dimensions. Never includes message or form content; the only visitor identifier is the widget's anonymous visitorId.`).meta({examples:[{enabled:!0},{enabled:!0,sampleRate:.5}]}),le=e.object({upload:e.string().min(1).max(2048).default(`/pai/upload-file`),transcribe:e.string().min(1).max(2048).default(`/pai/transcribe-audio`),submitForm:e.string().min(1).max(2048).default(`/pai/submit-form`).describe("Form submission endpoint. The widget POSTs each completed form's values here (fire-and-forget), keyed by the same visitorId + conversationId as the conversation; the payload carries `formId` + `trigger` so the backend can route. Optional — a 404 is ignored.")}).loose().describe("HTTP endpoint overrides. `upload` / `transcribe` paths are appended to the agent base (`${baseUrl}/api/agent`), `submitForm` to the data base (`dataApiBaseUrl`); absolute URLs are accepted too.").meta({examples:[{upload:`/pai/upload-file`,transcribe:`/pai/transcribe-audio`},{upload:`https://help.ai/uploads/presign`},{submitForm:`https://crm.acme.com/contacts`}]}),ue=e.object({id:e.string().min(1).max(200).optional().describe(`Host's stable id for the user — informational context the agent/backend can key on (not auth).`),email:e.string().min(1).max(320).optional().describe(`User email — surfaced to the agent for context (optional).`),name:e.string().min(1).max(200).optional().describe("Display name — shown in the Home greeting (`{name}`) and conversation transcripts (optional)."),plan:e.string().min(1).max(120).optional().describe("Subscription / pricing tier, e.g. `pro` (optional)."),role:e.string().min(1).max(120).optional().describe("The user's role on the host site, e.g. `admin` (optional)."),phone:e.string().min(1).max(40).optional().describe(`Phone number (optional).`)}).catchall(e.union([e.string(),e.number(),e.boolean()])).describe("Host-asserted end-user context (informational, untrusted — not authentication). Recognised `id` / `email` / `name` / `plan` / `role` / `phone` plus any extra custom scalar fields forwarded to the agent.").meta({examples:[{id:`u_123`},{id:`u_123`,email:`alex@acme.com`,name:`Alex Lee`,plan:`pro`}]}),de=[`home`,`docs`,`help-desk`,`support`,`pricing`,`checkout`,`orders`,`account`,`blog`],fe=e.object({area:e.string().min(1).max(64).optional().describe(`Host-defined surface the visitor is on. Free-form; suggested: ${de.join(` | `)}.`),url:e.string().min(1).max(2048).optional().describe(`Full page URL. Auto-filled from the host page when omitted.`),path:e.string().min(1).max(2048).optional().describe("Page path, e.g. `/pricing/enterprise`."),title:e.string().min(1).max(300).optional().describe(`Human-readable page title.`)}).catchall(e.union([e.string(),e.number(),e.boolean()])).describe("Host-asserted page/visit context (informational, untrusted). Where the visitor is + the surface (`area`) they're using, plus any custom scalar fields, forwarded to the agent.").meta({examples:[{area:`pricing`,path:`/pricing`},{area:`help-desk`}]}),X=e.object({widgetId:e.string().min(1).max(80).default(`default`).describe(`Stable per-instance id used to namespace localStorage keys. Set this when mounting more than one widget on the same page so their conversation state doesn't collide.`),publicKey:e.string().min(1).max(200).optional().describe("Deployment public key (`agent_pk_…`) issued from the dashboard. Sent on every API call as the `x-public-key` header when present."),aiAgentDeploymentId:e.string().min(1).max(200).describe("Required deployment UUID from the dashboard. Picks which agent / settings bundle answers this mount. Sent as `x-ai-agent-deployment-id`."),baseUrl:e.string().min(1).max(2048).optional().describe("MAIN site origin (e.g. `https://help.ai`) — the API modules derive from it: agent API at `${baseUrl}/api/agent`, data API at `${baseUrl}/api/data`. Set this to point at your staging / self-hosted / custom backend. Default: the brand's `defaultBaseUrl` baked at build time."),agentApiBaseUrl:e.string().min(1).max(2048).optional().describe("Full agent-API base — set it to reach a STANDALONE `module-help-ai-agent-api` directly (local module on its own port, e.g. `http://localhost:3111`; routes attach as-is: `/pai/handshake`). Precedence: this option > derived from the main origin (`${baseUrl}/api/agent`). Not server-pushable."),dataApiBaseUrl:e.string().min(1).max(2048).optional().describe("Full data-API base (e.g. `https://help.ai/api/data`, or a standalone local module like `http://localhost:3105`) — the service serving content, form definitions (`GET /pai/bootstrap`), and form submissions at root-level paths. Precedence: this option > server-pushed `config.dataApiBaseUrl` > derived from the main origin (`${baseUrl}/api/data`)."),userContext:ue.optional(),pageContext:fe.optional(),endpoints:le.optional(),debug:e.union([e.boolean(),e.enum([`off`,`error`,`warn`,`info`,`debug`,`trace`])]).optional().describe('Console log verbosity. `false` / `"off"` silences everything (default in production). `true` → `"debug"`. Levels `error` / `warn` / `info` / `debug` / `trace` gate output additively. Use `"trace"` to capture per-chunk SSE frames + every state transition. Also toggleable at runtime via `chat.setDebug(level)` or `?<brand>-debug=trace` in the page URL — the URL beats this option, this option beats the persisted `localStorage` value.')}).loose().describe("Where the widget talks to and how it authenticates. Owned by the embedding developer (typically baked into the page template / env var) — distinct from `userPrefs`, which is the dashboard-pushable UX surface.").meta({examples:[{publicKey:`agent_pk_demo`,aiAgentDeploymentId:`01900000-0000-7000-8000-000000000001`},{publicKey:`agent_pk_demo`,aiAgentDeploymentId:`01900000-0000-7000-8000-000000000001`,widgetId:`support`},{publicKey:`agent_pk_demo`,aiAgentDeploymentId:`01900000-0000-7000-8000-000000000001`,baseUrl:`https://api.acme.com`}]}),Z=e.object({presentation:s.optional(),behavior:l.optional(),theme:d.default(`auto`).describe("Theme preference (`auto` follows OS) or a tokens override object (`accent`, `radius`, `fontFamily`)."),launcher:_.optional(),header:S.optional(),composer:M.optional(),feedback:E.optional(),i18n:J.optional(),footer:P.optional(),features:y.optional(),forms:G.optional(),modules:se.optional(),tracking:ce.optional()}).loose().describe("Portal-configurable widget settings — thirteen sections, one per dashboard tab. The backend pushes this shape on `/pai/handshake` under `config`, except `forms`, which the data API serves separately.").meta({examples:[{presentation:{mode:`floating`,position:`bottom-right`},theme:`auto`},{presentation:{mode:`floating`},theme:{accent:`#7c3aed`,radius:`12px`},launcher:{variant:`pill`,label:`Ask AI`,callout:{text:`👋 Need help?`}}},{presentation:{mode:`inline`},behavior:{responseMode:`streaming`},features:{voice:`local`}}]}),Q=X.extend({...Z.shape,site:e.unknown().optional().describe('Site config — applies in `mode: "page"`. Comes from the handshake response, not user-edited.'),blocks:e.unknown().optional().describe('Blocks (nav + link cards) — applies in `mode: "page"`. Comes from the handshake response.')}).describe("Full tooling config — connection (where + auth) + widget settings (look + behaviour) + page-mode handshake extras. The production widget does not import this Zod schema. Mode-specific constraints (e.g. `position` only meaningful in `floating`) are NOT enforced here so the same object can flow across mode changes.").meta({examples:[{publicKey:`agent_pk_demo`,aiAgentDeploymentId:`01900000-0000-7000-8000-000000000001`,presentation:{mode:`floating`},theme:`auto`},{publicKey:`agent_pk_demo`,aiAgentDeploymentId:`01900000-0000-7000-8000-000000000001`,presentation:{mode:`modal`,size:{width:`720px`,height:`640px`}}}]}),pe=Q.partial(),me=Z.partial(),he=X.partial(),ge=Z.partial().omit({forms:!0}).extend({dataApiBaseUrl:e.string().min(1).max(2048).optional(),storageVersion:e.string().min(1).max(64).optional()}),$=e.object({title:e.string().min(1).max(120).optional().describe(`Brand / site name. Used as the logo's alt-text fallback.`),logo:t.optional().describe(`Brand logo shown at the top of the page-mode sidebar.`),logoDark:t.optional().describe("Optional dark-theme logo variant. Falls back to `logo` when the active theme is light or `logoDark` is unset.")}).loose(),_e=e.object({navigation:e.array(n).max(100).optional().describe(`Sidebar navigation links (vertical list, primary nav).`),linkCards:e.array(n).max(100).optional().describe("Sidebar link cards (richer entries with optional `description`).")}).loose(),ve=e.object({id:e.string(),role:e.enum([`assistant`,`system`]),text:e.string()}).loose(),ye=e.object({id:e.string(),label:e.string(),text:e.string().optional()}).loose(),be=e.object({deployment:e.object({id:e.string(),name:e.string(),slug:e.string().optional(),publicKey:e.string().optional()}).loose(),agent:e.object({id:e.string(),name:e.string(),title:e.string().optional(),status:e.enum([`online`,`away`,`offline`]).optional()}).loose(),config:ge.optional(),site:$.optional(),blocks:_e.optional(),welcome:e.object({messages:e.array(ve).optional(),suggestions:e.array(ye).optional()}).loose().optional(),visitorId:e.string(),conversationId:e.string(),userPrefs:e.record(e.string(),e.unknown()).optional(),rebind:e.object({conversationId:e.string()}).strict().optional()}).loose(),xe=[`theme`,`header`,`composer`,`footer`,`i18n`,`features`,`feedback`,`tracking`],Se={floating:[`presentation`,`behavior`,`modules`,`launcher`],inline:[`presentation`,`behavior`],modal:[`presentation`,`behavior`,`modules`],drawer:[`presentation`,`behavior`,`modules`,`launcher`],page:[`presentation`],standalone:[`presentation`]};function Ce(e){let t=[...Se[e],...xe],n={};for(let e of t)n[e]=!0;return Z.pick(n)}const we=[`floating`,`inline`,`standalone`,`page`,`modal`,`drawer`];function Te(){return{$schema:`https://json-schema.org/draft/2020-12/schema`,title:`@helpai/elements config`,description:`Canonical config schemas for the embeddable AI agent elements widget.`,definitions:{WidgetConfig:e.toJSONSchema(Q),HandshakeResponse:e.toJSONSchema(be)}}}export{we as ALL_MODES,de as PAGE_AREA_SUGGESTIONS,b as actionNameSchema,t as assetSchema,j as attachmentLimitsSchema,l as behaviorSchema,_e as blocksConfigSchema,h as calloutPositionSchema,m as calloutShapeSchema,M as composerOptionsSchema,he as connectionConfigPartialSchema,X as connectionConfigSchema,i as cssColorSchema,r as cssLengthSchema,Te as emitJsonSchema,le as endpointsSchema,y as featureFlagsSchema,C as feedbackEventSchema,E as feedbackSchema,I as fieldOptionSchema,V as fieldOptionTranslationSchema,F as fieldTypeSchema,L as fieldValidationSchema,P as footerSchema,B as formConditionSchema,W as formDefSchema,R as formFieldSchema,H as formFieldTranslationSchema,U as formTranslationSchema,z as formTriggerSchema,G as formsSchema,be as handshakeResponseSchema,T as hapticsOptionsSchema,x as headerActionsSchema,S as headerSchema,J as i18nSchema,o as initialSizeSchema,g as launcherCalloutSchema,_ as launcherOptionsSchema,p as launcherSizeSchema,f as launcherVariantSchema,n as linkSchema,a as localeSchema,re as modeSchema,Y as moduleLayoutSchema,oe as moduleSchema,se as modulesSchema,fe as pageContextSchema,ie as positionSchema,N as poweredBySchema,s as presentationSchema,te as resizeOptionsSchema,c as responseModeSchema,O as sendButtonIconSchema,A as sendButtonOptionsSchema,D as sendButtonShapeSchema,k as sendButtonVariantSchema,ge as serverConfigSchema,$ as siteConfigSchema,ne as sizeOptionsSchema,w as soundOptionsSchema,q as stringsOverrideSchema,d as themeFieldSchema,ae as themeOverridesSchema,u as themePreferenceSchema,ce as trackingSchema,ue as userContextSchema,ee as uuid7Schema,v as voiceModeSchema,pe as widgetConfigPartialSchema,Q as widgetConfigSchema,me as widgetSettingsPartialSchema,Z as widgetSettingsSchema,Ce as widgetSettingsSchemaForMode};
|
|
2
2
|
//# sourceMappingURL=schema.mjs.map
|