@helpai/elements 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/configurator.mjs CHANGED
@@ -377,108 +377,144 @@ var footerSchema = z12.object({
377
377
  ]
378
378
  });
379
379
 
380
- // src/schema/widget/i18n.ts
380
+ // src/schema/widget/forms.ts
381
381
  import { z as z13 } from "zod";
382
- var localeStringsSchema = z13.record(z13.string(), z13.string());
383
- var stringsOverrideSchema = z13.union([localeStringsSchema, z13.record(localeSchema, localeStringsSchema)]).describe(
384
- "Per-instance UI string overrides. Flat = active-locale only; nested = multi-locale map. Both forms are partial \u2014 missing keys fall through to built-in defaults."
385
- ).meta({
386
- examples: [
387
- { send: "Click", stop: "Halt", composerPlaceholder: "Type your question\u2026" },
388
- {
389
- en: { send: "Send", composerPlaceholder: "Type a message\u2026" },
390
- fr: { send: "Envoyer", composerPlaceholder: "Tapez un message\u2026" }
391
- }
392
- ]
393
- });
394
- var i18nSchema = z13.object({
395
- locale: localeSchema.optional().describe("BCP-47 locale tag forced for this widget instance (e.g. `fr-CA`). Page-owner explicit override."),
396
- defaultLocale: z13.union([z13.literal("auto"), localeSchema]).default("auto").describe("Initial / fallback locale for users who haven't picked one. `auto` sniffs `navigator.language`."),
397
- availableLocales: z13.array(localeSchema).max(200).optional().describe("BCP-47 locale tags shown in the runtime language switcher. Hidden when fewer than 2 entries."),
398
- strings: stringsOverrideSchema.optional()
399
- }).loose().describe("Locale + i18n overrides.").meta({
400
- examples: [
401
- { defaultLocale: "auto" },
402
- { defaultLocale: "en", availableLocales: ["en", "fr", "de"] },
403
- { locale: "fr-CA", strings: { send: "Envoyer" } }
404
- ]
405
- });
406
-
407
- // src/schema/widget/intake.ts
408
- import { z as z14 } from "zod";
409
- var fieldTypeSchema = z14.enum([
382
+ var fieldTypeSchema = z13.enum([
410
383
  "text",
411
384
  "email",
412
385
  "tel",
413
386
  "url",
414
387
  "number",
388
+ "date",
389
+ "time",
415
390
  "textarea",
416
391
  "select",
417
392
  "radio",
418
393
  "checkbox",
419
394
  "multiselect"
420
395
  ]);
421
- var fieldOptionSchema = z14.object({
422
- value: z14.string().min(1).max(200).describe("The value recorded when this choice is picked."),
423
- label: z14.string().min(1).max(200).optional().describe("Display text; falls back to `value`."),
424
- description: z14.string().max(280).optional().describe("Optional sub-label under the choice.")
396
+ var fieldOptionSchema = z13.object({
397
+ value: z13.string().min(1).max(200).describe("The value recorded when this choice is picked."),
398
+ label: z13.string().min(1).max(200).optional().describe("Display text; falls back to `value`."),
399
+ description: z13.string().max(280).optional().describe("Optional sub-label under the choice.")
425
400
  }).loose();
426
- var fieldValidationSchema = z14.object({
427
- pattern: z14.string().max(500).optional().describe("Regex source string \u2014 text-like fields (e.g. `^\\d{5}$`)."),
428
- minLength: z14.number().int().min(0).max(1e4).optional(),
429
- maxLength: z14.number().int().min(1).max(1e4).optional(),
430
- min: z14.number().optional().describe("Minimum \u2014 `number` fields."),
431
- max: z14.number().optional().describe("Maximum \u2014 `number` fields."),
432
- minSelections: z14.number().int().min(0).max(50).optional().describe("Minimum picks \u2014 `multiselect`."),
433
- maxSelections: z14.number().int().min(1).max(50).optional().describe("Maximum picks \u2014 `multiselect`.")
401
+ var fieldValidationSchema = z13.object({
402
+ pattern: z13.string().max(500).optional().describe("Regex source string \u2014 text-like fields (e.g. `^\\d{5}$`)."),
403
+ minLength: z13.number().int().min(0).max(1e4).optional(),
404
+ maxLength: z13.number().int().min(1).max(1e4).optional(),
405
+ min: z13.number().optional().describe("Minimum \u2014 `number` fields."),
406
+ max: z13.number().optional().describe("Maximum \u2014 `number` fields."),
407
+ minSelections: z13.number().int().min(0).max(50).optional().describe("Minimum picks \u2014 `multiselect`."),
408
+ maxSelections: z13.number().int().min(1).max(50).optional().describe("Maximum picks \u2014 `multiselect`.")
434
409
  }).loose();
435
- var formFieldSchema = z14.object({
436
- name: z14.string().min(1).max(80).describe("Stable key the answer is recorded under (e.g. `email`). Sent to the agent as captured context."),
437
- label: z14.string().min(1).max(200).describe("Visible field label."),
438
- type: fieldTypeSchema.describe("Field renderer + built-in validation (`email`/`tel`/`url`/`number` are checked)."),
439
- placeholder: z14.string().max(200).optional(),
440
- required: z14.boolean().optional().default(true).describe("Must be answered to submit. Set `false` to allow skipping."),
441
- defaultValue: z14.string().max(2e3).optional().describe("Pre-filled value (comma-join for `multiselect`)."),
442
- options: z14.array(fieldOptionSchema).max(50).optional().describe("Choices for `select` / `radio` / `multiselect`."),
410
+ var formFieldSchema = z13.object({
411
+ name: z13.string().min(1).max(80).describe("Stable key the answer is recorded under (e.g. `email`)."),
412
+ label: z13.string().min(1).max(200).describe("Visible field label."),
413
+ type: fieldTypeSchema.describe("Field renderer + built-in validation (email/tel/url/number/date/time checked)."),
414
+ placeholder: z13.string().max(200).optional(),
415
+ required: z13.boolean().optional().default(true).describe("Must be answered to submit. `false` allows skipping."),
416
+ defaultValue: z13.string().max(2e3).optional().describe("Pre-filled value (comma-join for `multiselect`)."),
417
+ options: z13.array(fieldOptionSchema).max(50).optional().describe("Choices for `select` / `radio` / `multiselect`."),
443
418
  validation: fieldValidationSchema.optional(),
444
- allowOther: z14.boolean().optional().describe("`select`/`radio`/`multiselect` \u2014 allow a free-text 'Other' answer."),
445
- validationHint: z14.string().max(280).optional().describe("Small helper line under the field.")
419
+ allowOther: z13.boolean().optional().describe("`select`/`radio`/`multiselect` \u2014 allow a free-text 'Other' answer."),
420
+ validationHint: z13.string().max(280).optional().describe("Small helper line under the field.")
446
421
  }).loose();
447
- var intakeSchema = z14.object({
448
- enabled: z14.boolean().default(false).describe("Turn the pre-chat intake gate on. Off by default."),
449
- title: z14.string().max(120).optional().describe("Form heading (e.g. 'Before we start')."),
450
- description: z14.string().max(500).optional().describe("Sub-heading under the title."),
451
- fields: z14.array(formFieldSchema).max(20).default([]).describe("The questions to ask, in order. The gate is only active when there is \u22651 field."),
452
- submitLabel: z14.string().max(60).optional().describe("Submit button label (default 'Start chat')."),
453
- skippable: z14.boolean().default(false).describe("Show a Skip control to dismiss the whole form.")
454
- }).loose().describe(
455
- "Pre-chat intake form \u2014 a blocking lead/sales-capture gate of typed, validated fields. The same field shape powers the AI ask-input tool. Off by default."
422
+ var formTriggerSchema = z13.string().regex(
423
+ /^(pre-chat|conversation-closed|panel-close|manual|after-messages:\d+|idle:\d+|page-area:.+)$/,
424
+ "Unrecognised form trigger"
425
+ ).describe(
426
+ "When the form fires: `pre-chat`, `after-messages:N`, `conversation-closed`, `panel-close`, `idle:Nseconds`, `page-area:<area>`, or `manual`."
427
+ );
428
+ var formConditionSchema = z13.object({
429
+ missingContext: z13.array(z13.string().max(80)).max(20).optional().describe("Only show if `userContext` lacks ALL of these keys (progressive profiling)."),
430
+ pageArea: z13.union([z13.string().max(64), z13.array(z13.string().max(64)).max(20)]).optional().describe("Only on these `pageContext.area` values."),
431
+ minMessages: z13.number().int().min(0).max(1e3).optional(),
432
+ maxMessages: z13.number().int().min(0).max(1e3).optional()
433
+ }).loose();
434
+ var formDefSchema = z13.object({
435
+ id: z13.string().min(1).max(80).describe("Stable id \u2014 persistence dedupe + `manual` openForm(id)."),
436
+ on: z13.union([formTriggerSchema, z13.array(formTriggerSchema).min(1).max(8)]).describe("Trigger(s) that surface this form."),
437
+ when: formConditionSchema.optional().describe("Extra eligibility predicate evaluated when a trigger fires."),
438
+ fields: z13.array(formFieldSchema).max(20).default([]).describe("The questions, in order. \u22651 required to show."),
439
+ title: z13.string().max(120).optional(),
440
+ description: z13.string().max(500).optional(),
441
+ submitLabel: z13.string().max(60).optional(),
442
+ skippable: z13.boolean().default(false).describe("Show a Skip control."),
443
+ blocking: z13.boolean().default(false).describe("Gate the composer (honored only for `pre-chat` / `after-messages`); otherwise renders inline."),
444
+ frequency: z13.enum(["once", "session", "always"]).default("once").describe("`once` (persisted per-device), `session`, or `always`."),
445
+ mirrorToContext: z13.boolean().default(true).describe(
446
+ "Mirror the answers into `userContext` so they ride every request. Set `false` for PII (DOB, policy #, symptoms) \u2014 those then go only to the submit endpoint."
447
+ )
448
+ }).loose();
449
+ var formsSchema = z13.array(formDefSchema).max(20).describe(
450
+ "Event-driven forms \u2014 an ordered list (order = priority). Each binds to trigger event(s), shows blocking or inline, dedupes by frequency, and records via `endpoints.forms`. Generic across verticals."
456
451
  ).meta({
457
452
  examples: [
458
- { enabled: false },
453
+ [{ id: "intake", on: "pre-chat", blocking: true, fields: [{ name: "email", label: "Email", type: "email" }] }],
454
+ [
455
+ {
456
+ id: "lead",
457
+ on: "pre-chat",
458
+ blocking: true,
459
+ title: "Before we start",
460
+ fields: [
461
+ { name: "name", label: "Your name", type: "text" },
462
+ { name: "email", label: "Work email", type: "email" }
463
+ ]
464
+ },
465
+ {
466
+ id: "csat",
467
+ on: "conversation-closed",
468
+ frequency: "always",
469
+ fields: [
470
+ { name: "rating", label: "How did we do?", type: "radio", options: [{ value: "good" }, { value: "bad" }] }
471
+ ]
472
+ },
473
+ {
474
+ id: "appointment",
475
+ on: ["page-area:booking", "manual"],
476
+ mirrorToContext: false,
477
+ fields: [
478
+ {
479
+ name: "department",
480
+ label: "Department",
481
+ type: "select",
482
+ options: [{ value: "dermatology" }, { value: "cardiology" }]
483
+ },
484
+ { name: "date", label: "Preferred date", type: "date" },
485
+ { name: "consent", label: "I consent to be contacted", type: "checkbox" }
486
+ ]
487
+ }
488
+ ]
489
+ ]
490
+ });
491
+
492
+ // src/schema/widget/i18n.ts
493
+ import { z as z14 } from "zod";
494
+ var localeStringsSchema = z14.record(z14.string(), z14.string());
495
+ var stringsOverrideSchema = z14.union([localeStringsSchema, z14.record(localeSchema, localeStringsSchema)]).describe(
496
+ "Per-instance UI string overrides. Flat = active-locale only; nested = multi-locale map. Both forms are partial \u2014 missing keys fall through to built-in defaults."
497
+ ).meta({
498
+ examples: [
499
+ { send: "Click", stop: "Halt", composerPlaceholder: "Type your question\u2026" },
459
500
  {
460
- enabled: true,
461
- title: "Before we start",
462
- description: "Tell us who you are so we can help faster.",
463
- submitLabel: "Start chat",
464
- fields: [
465
- { name: "name", label: "Your name", type: "text", required: true },
466
- { name: "email", label: "Work email", type: "email", required: true },
467
- { name: "phone", label: "Phone", type: "tel", required: false },
468
- {
469
- name: "topic",
470
- label: "What can we help with?",
471
- type: "select",
472
- required: true,
473
- options: [
474
- { value: "sales", label: "Talk to sales" },
475
- { value: "support", label: "Get support" }
476
- ]
477
- }
478
- ]
501
+ en: { send: "Send", composerPlaceholder: "Type a message\u2026" },
502
+ fr: { send: "Envoyer", composerPlaceholder: "Tapez un message\u2026" }
479
503
  }
480
504
  ]
481
505
  });
506
+ var i18nSchema = z14.object({
507
+ locale: localeSchema.optional().describe("BCP-47 locale tag forced for this widget instance (e.g. `fr-CA`). Page-owner explicit override."),
508
+ defaultLocale: z14.union([z14.literal("auto"), localeSchema]).default("auto").describe("Initial / fallback locale for users who haven't picked one. `auto` sniffs `navigator.language`."),
509
+ availableLocales: z14.array(localeSchema).max(200).optional().describe("BCP-47 locale tags shown in the runtime language switcher. Hidden when fewer than 2 entries."),
510
+ strings: stringsOverrideSchema.optional()
511
+ }).loose().describe("Locale + i18n overrides.").meta({
512
+ examples: [
513
+ { defaultLocale: "auto" },
514
+ { defaultLocale: "en", availableLocales: ["en", "fr", "de"] },
515
+ { locale: "fr-CA", strings: { send: "Envoyer" } }
516
+ ]
517
+ });
482
518
 
483
519
  // src/schema/widget/modules.ts
484
520
  import { z as z15 } from "zod";
@@ -544,14 +580,14 @@ var modulesSchema = z15.array(moduleSchema).max(4).describe(
544
580
  var endpointsSchema = z16.object({
545
581
  upload: z16.string().min(1).max(2048).default("/ai/agent/upload-file"),
546
582
  transcribe: z16.string().min(1).max(2048).default("/ai/agent/transcribe-audio"),
547
- intake: z16.string().min(1).max(2048).default("/ai/agent/intake").describe(
548
- "Pre-chat intake submission endpoint. The widget POSTs the captured lead values here on completion (fire-and-forget), keyed by the same visitorId + sessionId as the conversation. Optional \u2014 a 404 is ignored."
583
+ submitForm: z16.string().min(1).max(2048).default("/ai/agent/submit-form").describe(
584
+ "Form submission endpoint. The widget POSTs each completed form's values here (fire-and-forget), keyed by the same visitorId + sessionId as the conversation; the payload carries `formId` + `trigger` so the backend can route. Optional \u2014 a 404 is ignored."
549
585
  )
550
586
  }).loose().describe("HTTP endpoint overrides. Paths are appended to baseUrl; absolute URLs are accepted too.").meta({
551
587
  examples: [
552
588
  { upload: "/ai/agent/upload-file", transcribe: "/ai/agent/transcribe-audio" },
553
589
  { upload: "https://help.ai/uploads/presign" },
554
- { intake: "https://crm.acme.com/leads/intake" }
590
+ { submitForm: "https://crm.acme.com/leads" }
555
591
  ]
556
592
  });
557
593
  var userContextSchema = z16.object({
@@ -633,7 +669,7 @@ var widgetSettingsSchema = z16.object({
633
669
  i18n: i18nSchema.optional(),
634
670
  footer: footerSchema.optional(),
635
671
  features: featureFlagsSchema.optional(),
636
- intake: intakeSchema.optional(),
672
+ forms: formsSchema.optional(),
637
673
  modules: modulesSchema.optional()
638
674
  }).loose().describe(
639
675
  "Portal-configurable widget settings \u2014 thirteen sections, one per dashboard tab. The backend pushes this same shape on `/ai/agent/start-session` under `config`."