@webless/agent 0.9.3 → 0.10.1

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/dist/embed.cjs CHANGED
@@ -64,7 +64,7 @@ function submitAgentPanel(customerId, message, options) {
64
64
  }
65
65
 
66
66
  // src/react/components/AgentWidget/AgentWidget.tsx
67
- var import_react15 = require("react");
67
+ var import_react16 = require("react");
68
68
 
69
69
  // src/react/page-shift.ts
70
70
  var import_react = require("react");
@@ -1671,6 +1671,24 @@ function formatAgentError(error) {
1671
1671
  }
1672
1672
 
1673
1673
  // src/react/lib/composer-form.ts
1674
+ var COMPOSER_FORM_SKIP_DISMISSAL = "I'd like to keep chatting without sharing the requested details for now. Please don't ask for them again unless I choose to proceed with something that needs them.";
1675
+ function visitorMessageDisplayText(message) {
1676
+ if (message.role !== "visitor") return message.text;
1677
+ const text2 = message.text.trim();
1678
+ if (!text2) return message.text;
1679
+ if (text2.startsWith(COMPOSER_FORM_SKIP_DISMISSAL)) {
1680
+ const afterDismissal = text2.slice(COMPOSER_FORM_SKIP_DISMISSAL.length).trim().replace(/^\n+/, "").trim();
1681
+ if (afterDismissal) return afterDismissal;
1682
+ }
1683
+ const runtime = message.runtimeText?.trim();
1684
+ if (runtime && text2 === runtime && runtime.includes(COMPOSER_FORM_SKIP_DISMISSAL)) {
1685
+ const afterDismissal = runtime.slice(
1686
+ runtime.indexOf(COMPOSER_FORM_SKIP_DISMISSAL) + COMPOSER_FORM_SKIP_DISMISSAL.length
1687
+ ).trim().replace(/^\n+/, "").trim();
1688
+ if (afterDismissal) return afterDismissal;
1689
+ }
1690
+ return message.text;
1691
+ }
1674
1692
  var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1675
1693
  var MIN_FORM_FIELDS = 2;
1676
1694
  var MAX_FORM_FIELDS = 5;
@@ -2924,7 +2942,8 @@ function createInitialState(greeting = DEFAULT_GREETING) {
2924
2942
  pendingOffer: null,
2925
2943
  pendingInputs: [],
2926
2944
  toolResults: [],
2927
- error: null
2945
+ error: null,
2946
+ declinedComposerFormIds: []
2928
2947
  };
2929
2948
  }
2930
2949
  function stateFromConversation(conversation, initialState) {
@@ -3344,6 +3363,7 @@ function useAgentChat({
3344
3363
  const trimmed = visitorText.trim();
3345
3364
  const outgoing = options?.runtimeText ?? visitorText;
3346
3365
  if (!outgoing.trim()) return null;
3366
+ const declinedComposerFormId = options?.declinedComposerFormId?.trim();
3347
3367
  const chatResponse = chatInputResponseForText(
3348
3368
  state.pendingInputs ?? [],
3349
3369
  outgoing.trim()
@@ -3377,7 +3397,15 @@ function useAgentChat({
3377
3397
  state: "active"
3378
3398
  }
3379
3399
  ],
3380
- error: null
3400
+ error: null,
3401
+ ...declinedComposerFormId ? {
3402
+ declinedComposerFormIds: [
3403
+ .../* @__PURE__ */ new Set([
3404
+ ...prev.declinedComposerFormIds ?? [],
3405
+ declinedComposerFormId
3406
+ ])
3407
+ ]
3408
+ } : {}
3381
3409
  }));
3382
3410
  return await runTurn({
3383
3411
  controller: controller2,
@@ -3399,9 +3427,9 @@ ${outgoing}` : outgoing;
3399
3427
  const visitorMessage = {
3400
3428
  id: `visitor-${Date.now()}`,
3401
3429
  role: "visitor",
3402
- text: visitorText,
3430
+ text: trimmed,
3403
3431
  createdAt: Date.now(),
3404
- ...runtimeText !== visitorText ? { runtimeText } : {}
3432
+ ...runtimeText.trim() !== trimmed ? { runtimeText } : {}
3405
3433
  };
3406
3434
  setState((prev) => ({
3407
3435
  ...prev,
@@ -3421,7 +3449,15 @@ ${outgoing}` : outgoing;
3421
3449
  pendingOffer: options?.preservePendingOffer ? prev.pendingOffer : null,
3422
3450
  pendingInputs: [],
3423
3451
  toolResults: [],
3424
- error: null
3452
+ error: null,
3453
+ ...declinedComposerFormId ? {
3454
+ declinedComposerFormIds: [
3455
+ .../* @__PURE__ */ new Set([
3456
+ ...prev.declinedComposerFormIds ?? [],
3457
+ declinedComposerFormId
3458
+ ])
3459
+ ]
3460
+ } : {}
3425
3461
  }));
3426
3462
  return await runTurn({
3427
3463
  controller,
@@ -3768,8 +3804,7 @@ function workSummary(steps, failed, brandLabel) {
3768
3804
  )}`;
3769
3805
  const active = [...steps].reverse().find((step) => step.state === "active");
3770
3806
  if (active?.kind === "search") return "Searching this site";
3771
- if (active)
3772
- return brandLabel ? `${brandLabel} is choosing the best way to help` : "Choosing the best way to help";
3807
+ if (active) return "Reviewing your details to find the best approach";
3773
3808
  if (failed) return "Couldn\u2019t complete this request";
3774
3809
  const hasError = steps.some((step) => step.state === "error");
3775
3810
  const specialists = steps.filter(
@@ -3878,7 +3913,7 @@ function AgentActivityBubble({
3878
3913
  }
3879
3914
  ),
3880
3915
  workSummary(steps, failed, brandLabel),
3881
- active ? "\u2026" : ""
3916
+ active ? "..." : ""
3882
3917
  ] }),
3883
3918
  !active ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "agent-activity-bubble__details", children: [
3884
3919
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -4032,6 +4067,7 @@ function AnswerReceiptDialog({
4032
4067
  // src/react/components/Composer/Composer.tsx
4033
4068
  var import_react8 = require("react");
4034
4069
  var import_jsx_runtime3 = require("react/jsx-runtime");
4070
+ var FORM_SUBMITTED_MESSAGE = "Shared my details";
4035
4071
  function SendIcon() {
4036
4072
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4037
4073
  "path",
@@ -4044,6 +4080,30 @@ function SendIcon() {
4044
4080
  }
4045
4081
  ) });
4046
4082
  }
4083
+ function CheckIcon() {
4084
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { viewBox: "0 0 12 12", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4085
+ "path",
4086
+ {
4087
+ d: "M2.5 6.2l2.3 2.3 4.7-5",
4088
+ stroke: "currentColor",
4089
+ strokeWidth: "1.6",
4090
+ strokeLinecap: "round",
4091
+ strokeLinejoin: "round"
4092
+ }
4093
+ ) });
4094
+ }
4095
+ function CollapseIcon() {
4096
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4097
+ "path",
4098
+ {
4099
+ d: "M4 10l4-4 4 4",
4100
+ stroke: "currentColor",
4101
+ strokeWidth: "1.6",
4102
+ strokeLinecap: "round",
4103
+ strokeLinejoin: "round"
4104
+ }
4105
+ ) });
4106
+ }
4047
4107
  function emptyValues(form) {
4048
4108
  const values = {};
4049
4109
  for (const field of form?.fields ?? []) values[field.id] = "";
@@ -4077,26 +4137,31 @@ function Composer({
4077
4137
  const savedForm = allowFormResume && !draft.submitted ? draft.form : null;
4078
4138
  const activeForm = !disabled && draft.expanded ? savedForm : null;
4079
4139
  const hasDraft = Object.values(draft.values).some((value2) => value2.trim());
4140
+ const canSend = !disabled && Boolean(value.trim() || activeForm);
4080
4141
  (0, import_react8.useEffect)(() => {
4081
4142
  if (activeForm) firstFieldRef.current?.focus();
4082
4143
  else if ((savedForm || draft.submitted) && !disabled)
4083
4144
  inputRef.current?.focus();
4084
4145
  }, [activeForm?.id, disabled, savedForm?.id, draft.submitted]);
4146
+ function collapseForm() {
4147
+ setDraft((current) => ({ ...current, expanded: false }));
4148
+ }
4085
4149
  function submitChat() {
4086
4150
  const trimmed = value.trim();
4087
4151
  if (!trimmed || disabled) return;
4088
4152
  const shareDismissal = savedForm && !draft.dismissalSent;
4089
4153
  if (shareDismissal) {
4090
4154
  onSubmit?.(trimmed, {
4091
- runtimeText: `I'd like to keep chatting without sharing the requested details for now. Please don't ask for them again unless I choose to proceed with something that needs them.
4092
-
4093
- ${trimmed}`
4155
+ declinedComposerFormId: savedForm.id
4094
4156
  });
4095
4157
  } else {
4096
4158
  onSubmit?.(trimmed);
4097
4159
  }
4098
- if (shareDismissal)
4099
- setDraft((current) => ({ ...current, dismissalSent: true }));
4160
+ setDraft((current) => ({
4161
+ ...current,
4162
+ expanded: false,
4163
+ dismissalSent: shareDismissal ? true : current.dismissalSent
4164
+ }));
4100
4165
  setValue("");
4101
4166
  inputRef.current?.focus();
4102
4167
  }
@@ -4111,7 +4176,7 @@ ${trimmed}`
4111
4176
  if (control instanceof HTMLElement) control.focus();
4112
4177
  return;
4113
4178
  }
4114
- onSubmit?.("", {
4179
+ onSubmit?.(FORM_SUBMITTED_MESSAGE, {
4115
4180
  runtimeText: formatComposerFormMessage(activeForm, draft.values)
4116
4181
  });
4117
4182
  setDraft((current) => ({
@@ -4122,10 +4187,16 @@ ${trimmed}`
4122
4187
  attempted: false
4123
4188
  }));
4124
4189
  }
4190
+ function handleSend() {
4191
+ if (value.trim()) {
4192
+ submitChat();
4193
+ return;
4194
+ }
4195
+ if (activeForm) submitForm();
4196
+ }
4125
4197
  function handleSubmit(event) {
4126
4198
  event.preventDefault();
4127
- if (activeForm) submitForm();
4128
- else submitChat();
4199
+ handleSend();
4129
4200
  }
4130
4201
  function handleChatKeyDown(event) {
4131
4202
  if (event.key === "Enter" && !event.shiftKey) {
@@ -4141,7 +4212,34 @@ ${trimmed}`
4141
4212
  submitForm();
4142
4213
  }
4143
4214
  }
4144
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4215
+ const chatField = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
4216
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4217
+ "textarea",
4218
+ {
4219
+ ref: inputRef,
4220
+ className: "composer__input",
4221
+ rows: 1,
4222
+ value,
4223
+ placeholder,
4224
+ disabled,
4225
+ spellCheck: false,
4226
+ "aria-label": "Message",
4227
+ onChange: (event) => setValue(event.target.value),
4228
+ onKeyDown: handleChatKeyDown
4229
+ }
4230
+ ),
4231
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4232
+ "button",
4233
+ {
4234
+ type: "submit",
4235
+ className: "composer__send",
4236
+ disabled: !canSend,
4237
+ "aria-label": "Send message",
4238
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SendIcon, {})
4239
+ }
4240
+ )
4241
+ ] });
4242
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
4145
4243
  "form",
4146
4244
  {
4147
4245
  ref: formRef,
@@ -4152,107 +4250,10 @@ ${trimmed}`
4152
4250
  activeForm ? "composer--form" : ""
4153
4251
  ].filter(Boolean).join(" "),
4154
4252
  onSubmit: handleSubmit,
4155
- children: activeForm ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
4156
- "div",
4157
- {
4158
- className: "composer__sheet",
4159
- role: "group",
4160
- "aria-label": "Contact details",
4161
- children: [
4162
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "composer__form-heading", children: [
4163
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Share your details" }),
4164
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Optional" }),
4165
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: "You can also keep chatting." })
4166
- ] }),
4167
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "composer__fields", children: activeForm.fields.map((field, index) => {
4168
- const fieldId = `${formId}-${field.id}`;
4169
- const invalid = draft.attempted && !isValidComposerFieldValue(field, draft.values[field.id] ?? "");
4170
- const controlProps = {
4171
- id: fieldId,
4172
- name: field.id,
4173
- disabled,
4174
- required: field.required,
4175
- autoComplete: field.autocomplete,
4176
- placeholder: field.placeholder,
4177
- spellCheck: false,
4178
- value: draft.values[field.id] ?? "",
4179
- "aria-invalid": invalid || void 0,
4180
- "aria-describedby": invalid ? `${fieldId}-error` : void 0,
4181
- onChange: (event) => {
4182
- const next = readComposerControlValue(event);
4183
- setDraft((current) => ({
4184
- ...current,
4185
- values: { ...current.values, [field.id]: next }
4186
- }));
4187
- },
4188
- onKeyDown: handleFormKeyDown
4189
- };
4190
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
4191
- "div",
4192
- {
4193
- className: [
4194
- "composer__row",
4195
- field.kind === "textarea" ? "composer__row--grow" : ""
4196
- ].filter(Boolean).join(" "),
4197
- children: [
4198
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "composer__label", htmlFor: fieldId, children: [
4199
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "composer__field-label", children: field.label }),
4200
- field.kind === "textarea" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4201
- "textarea",
4202
- {
4203
- ...controlProps,
4204
- ref: index === 0 ? (node) => {
4205
- firstFieldRef.current = node;
4206
- } : void 0,
4207
- className: "composer__control composer__control--area",
4208
- rows: 3
4209
- }
4210
- ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4211
- "input",
4212
- {
4213
- ...controlProps,
4214
- ref: index === 0 ? (node) => {
4215
- firstFieldRef.current = node;
4216
- } : void 0,
4217
- className: "composer__control",
4218
- type: field.kind,
4219
- inputMode: field.kind === "tel" ? "tel" : void 0
4220
- }
4221
- )
4222
- ] }),
4223
- invalid ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "composer__error", id: `${fieldId}-error`, children: field.kind === "email" ? "Enter a valid email to share your details." : `Add your ${field.label.toLowerCase()} to share your details.` }) : null
4224
- ]
4225
- },
4226
- field.id
4227
- );
4228
- }) }),
4229
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "composer__toolbar", children: [
4230
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4231
- "button",
4232
- {
4233
- type: "submit",
4234
- className: "composer__action composer__action--primary",
4235
- disabled,
4236
- children: "Share details"
4237
- }
4238
- ),
4239
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4240
- "button",
4241
- {
4242
- type: "button",
4243
- className: "composer__action",
4244
- disabled,
4245
- onClick: () => setDraft((current) => ({ ...current, expanded: false })),
4246
- children: "Keep chatting"
4247
- }
4248
- )
4249
- ] })
4250
- ]
4251
- }
4252
- ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
4253
+ children: [
4253
4254
  savedForm ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "composer__resume", children: [
4254
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: hasDraft ? "Your details aren\u2019t shared yet" : "Contact details are optional" }),
4255
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4255
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: hasDraft ? "Your details aren\u2019t shared yet" : "Share your details so we can follow up" }),
4256
+ !activeForm ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4256
4257
  "button",
4257
4258
  {
4258
4259
  type: "button",
@@ -4260,36 +4261,100 @@ ${trimmed}`
4260
4261
  onClick: () => setDraft((current) => ({ ...current, expanded: true })),
4261
4262
  children: hasDraft ? "Resume details" : "Add details"
4262
4263
  }
4263
- )
4264
+ ) : null
4264
4265
  ] }) : null,
4265
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "composer__field", children: [
4266
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4267
- "textarea",
4268
- {
4269
- ref: inputRef,
4270
- className: "composer__input",
4271
- rows: 1,
4272
- value,
4273
- placeholder,
4274
- disabled,
4275
- spellCheck: false,
4276
- "aria-label": "Message",
4277
- onChange: (event) => setValue(event.target.value),
4278
- onKeyDown: handleChatKeyDown
4279
- }
4280
- ),
4281
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4282
- "button",
4283
- {
4284
- type: "submit",
4285
- className: "composer__send",
4286
- disabled: disabled || !value.trim(),
4287
- "aria-label": "Send message",
4288
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SendIcon, {})
4289
- }
4290
- )
4291
- ] })
4292
- ] })
4266
+ activeForm ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
4267
+ "div",
4268
+ {
4269
+ className: "composer__sheet",
4270
+ role: "group",
4271
+ "aria-label": "Contact details",
4272
+ children: [
4273
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "composer__form-heading", children: [
4274
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Share your details" }),
4275
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Optional" }),
4276
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4277
+ "button",
4278
+ {
4279
+ type: "button",
4280
+ className: "composer__collapse",
4281
+ "aria-label": "Collapse form",
4282
+ disabled,
4283
+ onClick: collapseForm,
4284
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CollapseIcon, {})
4285
+ }
4286
+ )
4287
+ ] }),
4288
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "composer__fields", children: activeForm.fields.map((field, index) => {
4289
+ const fieldId = `${formId}-${field.id}`;
4290
+ const fieldValue = draft.values[field.id] ?? "";
4291
+ const invalid = draft.attempted && !isValidComposerFieldValue(field, fieldValue);
4292
+ const complete = fieldValue.trim().length > 0 && isValidComposerFieldValue(field, fieldValue);
4293
+ const controlProps = {
4294
+ id: fieldId,
4295
+ name: field.id,
4296
+ disabled,
4297
+ required: field.required,
4298
+ autoComplete: field.autocomplete,
4299
+ placeholder: field.placeholder,
4300
+ spellCheck: false,
4301
+ value: fieldValue,
4302
+ "aria-invalid": invalid || void 0,
4303
+ "aria-describedby": invalid ? `${fieldId}-error` : void 0,
4304
+ onChange: (event) => {
4305
+ const next = readComposerControlValue(event);
4306
+ setDraft((current) => ({
4307
+ ...current,
4308
+ values: { ...current.values, [field.id]: next }
4309
+ }));
4310
+ },
4311
+ onKeyDown: handleFormKeyDown
4312
+ };
4313
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
4314
+ "div",
4315
+ {
4316
+ className: [
4317
+ "composer__row",
4318
+ field.kind === "textarea" ? "composer__row--area" : ""
4319
+ ].filter(Boolean).join(" "),
4320
+ children: [
4321
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "composer__line", children: [
4322
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "composer__label", htmlFor: fieldId, children: field.label }),
4323
+ field.kind === "textarea" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4324
+ "textarea",
4325
+ {
4326
+ ...controlProps,
4327
+ ref: index === 0 ? (node) => {
4328
+ firstFieldRef.current = node;
4329
+ } : void 0,
4330
+ className: "composer__control composer__control--area",
4331
+ rows: 2
4332
+ }
4333
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4334
+ "input",
4335
+ {
4336
+ ...controlProps,
4337
+ ref: index === 0 ? (node) => {
4338
+ firstFieldRef.current = node;
4339
+ } : void 0,
4340
+ className: "composer__control",
4341
+ type: field.kind,
4342
+ inputMode: field.kind === "tel" ? "tel" : void 0
4343
+ }
4344
+ ),
4345
+ complete && field.kind !== "textarea" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "composer__check", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CheckIcon, {}) }) : null
4346
+ ] }),
4347
+ invalid ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "composer__error", id: `${fieldId}-error`, children: field.kind === "email" ? "Enter a valid email to share your details." : `Add your ${field.label.toLowerCase()} to share your details.` }) : null
4348
+ ]
4349
+ },
4350
+ field.id
4351
+ );
4352
+ }) }),
4353
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "composer__field composer__field--inset", children: chatField })
4354
+ ]
4355
+ }
4356
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "composer__field", children: chatField })
4357
+ ]
4293
4358
  }
4294
4359
  );
4295
4360
  }
@@ -4874,8 +4939,9 @@ function MessageBubble({
4874
4939
  offers.length > 0 ? sanitizeBookingOfferCopy(visibleText) : looksLikeBookingAvailabilityDump(visibleText) ? sanitizeBookingOfferCopy(visibleText) : visibleText || (isStreaming ? "" : message.text)
4875
4940
  );
4876
4941
  if (message.role === "visitor") {
4877
- if (!message.text.trim()) return null;
4878
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("article", { className: "message-bubble message-bubble--visitor", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "message-bubble__text", children: message.text }) });
4942
+ const visitorText = visitorMessageDisplayText(message).trim();
4943
+ if (!visitorText) return null;
4944
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("article", { className: "message-bubble message-bubble--visitor", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "message-bubble__text", children: visitorText }) });
4879
4945
  }
4880
4946
  const citations = message.citations ?? [];
4881
4947
  const agentText = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "message-bubble__text", children: [
@@ -5113,7 +5179,7 @@ function CopyIcon() {
5113
5179
  )
5114
5180
  ] });
5115
5181
  }
5116
- function CheckIcon() {
5182
+ function CheckIcon2() {
5117
5183
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
5118
5184
  "path",
5119
5185
  {
@@ -5488,7 +5554,7 @@ function MessageActions({
5488
5554
  title: copied ? "Copied" : "Copy answer",
5489
5555
  disabled,
5490
5556
  onClick: handleCopy,
5491
- children: copied ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CheckIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CopyIcon, {})
5557
+ children: copied ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CheckIcon2, {}) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CopyIcon, {})
5492
5558
  }
5493
5559
  ),
5494
5560
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
@@ -6442,13 +6508,17 @@ function AgentRail({
6442
6508
  const hasPendingConfirmation = pendingInputRequests.some(
6443
6509
  (request) => request.kind === "tool-approval" || request.kind === "question" && (request.options?.length ?? 0) > 0
6444
6510
  );
6445
- const composerForm = resolveComposerForm({
6511
+ const inferredComposerForm = resolveComposerForm({
6446
6512
  agentText: lastAgentText,
6447
6513
  cards: extractToolCards(lastAgentText),
6448
6514
  hasBookingOffer: Boolean(state.pendingOffer) || waitingForBooking,
6449
6515
  hasPendingConfirmation,
6450
6516
  enabled: lastIsAgent && !isBusy
6451
6517
  });
6518
+ const declinedComposerFormIds = state.declinedComposerFormIds ?? [];
6519
+ const composerForm = inferredComposerForm && lastMessage?.role === "agent" && declinedComposerFormIds.includes(
6520
+ `${lastMessage.id}:${inferredComposerForm.id}`
6521
+ ) ? null : inferredComposerForm;
6452
6522
  const latestResultId = [
6453
6523
  ...visibleMessages.flatMap(
6454
6524
  (message) => message.role === "agent" ? message.toolResults ?? [] : []
@@ -6818,55 +6888,114 @@ function AgentRail({
6818
6888
  }
6819
6889
 
6820
6890
  // src/react/components/AssistEdgeTab/AssistEdgeTab.tsx
6891
+ var import_react15 = require("react");
6821
6892
  var import_jsx_runtime18 = require("react/jsx-runtime");
6822
- function SparklesIcon() {
6893
+ function ChatSparkIcon() {
6823
6894
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
6824
6895
  "svg",
6825
6896
  {
6826
- className: "assist-edge-tab__sparkles",
6827
- viewBox: "0 0 18 16",
6897
+ className: "assist-edge-tab__chat-spark",
6898
+ viewBox: "0 0 24 24",
6828
6899
  fill: "none",
6829
6900
  "aria-hidden": "true",
6830
6901
  children: [
6831
6902
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6832
6903
  "path",
6833
6904
  {
6834
- d: "M8 1.2l.95 2.7 2.85.05-2.25 1.75.8 2.75L8 6.7 5.65 8.45l.8-2.75L4.2 3.95l2.85-.05L8 1.2z",
6905
+ className: "assist-edge-tab__spark assist-edge-tab__spark--a",
6906
+ d: "M19.55 1.55l.78 1.82 1.82.78-1.82.78-.78 1.82-.78-1.82-1.82-.78 1.82-.78.78-1.82z",
6835
6907
  fill: "currentColor"
6836
6908
  }
6837
6909
  ),
6838
6910
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6839
6911
  "path",
6840
6912
  {
6841
- d: "M14.2 6.4l.55 1.55 1.65.03-1.3 1 .46 1.58-1.36-1-1.36 1 .46-1.58-1.3-1 1.65-.03.55-1.55z",
6913
+ className: "assist-edge-tab__spark assist-edge-tab__spark--b",
6914
+ d: "M4.15 17.35l.48 1.12 1.12.48-1.12.48-.48 1.12-.48-1.12-1.12-.48 1.12-.48.48-1.12z",
6842
6915
  fill: "currentColor"
6843
6916
  }
6844
6917
  ),
6845
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6846
- "path",
6847
- {
6848
- d: "M3.1 9.1l.4 1.15 1.22.02-.96.74.34 1.17-1-.74-1 .74.34-1.17-.96-.74 1.22-.02.4-1.15z",
6849
- fill: "currentColor"
6850
- }
6851
- )
6918
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("g", { className: "assist-edge-tab__bot", children: [
6919
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6920
+ "path",
6921
+ {
6922
+ d: "M11.15 5.2V3.05",
6923
+ stroke: "currentColor",
6924
+ strokeWidth: "1.9",
6925
+ strokeLinecap: "round"
6926
+ }
6927
+ ),
6928
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("circle", { cx: "11.15", cy: "2.45", r: "0.85", fill: "currentColor" }),
6929
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6930
+ "rect",
6931
+ {
6932
+ x: "3.55",
6933
+ y: "5.35",
6934
+ width: "15.2",
6935
+ height: "11.15",
6936
+ rx: "3.9",
6937
+ stroke: "currentColor",
6938
+ strokeWidth: "1.9"
6939
+ }
6940
+ ),
6941
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6942
+ "circle",
6943
+ {
6944
+ className: "assist-edge-tab__eye",
6945
+ cx: "8.7",
6946
+ cy: "10.85",
6947
+ r: "1.28",
6948
+ fill: "currentColor"
6949
+ }
6950
+ ),
6951
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6952
+ "circle",
6953
+ {
6954
+ className: "assist-edge-tab__eye assist-edge-tab__eye--r",
6955
+ cx: "14.35",
6956
+ cy: "10.85",
6957
+ r: "1.28",
6958
+ fill: "currentColor"
6959
+ }
6960
+ )
6961
+ ] })
6852
6962
  ]
6853
6963
  }
6854
6964
  );
6855
6965
  }
6856
- function TabMarkIcon({ customIconUrl }) {
6857
- const url = customIconUrl?.trim();
6858
- if (url) {
6966
+ function TabMarkIcon({
6967
+ customIconUrl,
6968
+ logoUrl
6969
+ }) {
6970
+ const custom = customIconUrl?.trim();
6971
+ const logo = logoUrl?.trim();
6972
+ const [customFailed, setCustomFailed] = (0, import_react15.useState)(false);
6973
+ const [logoFailed, setLogoFailed] = (0, import_react15.useState)(false);
6974
+ if (custom && !customFailed) {
6859
6975
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6860
6976
  "img",
6861
6977
  {
6862
6978
  alt: "",
6863
6979
  "aria-hidden": true,
6864
6980
  className: "assist-edge-tab__custom-icon",
6865
- src: url
6981
+ src: custom,
6982
+ onError: () => setCustomFailed(true)
6983
+ }
6984
+ );
6985
+ }
6986
+ if (logo && !logoFailed) {
6987
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6988
+ "img",
6989
+ {
6990
+ alt: "",
6991
+ "aria-hidden": true,
6992
+ className: "assist-edge-tab__logo",
6993
+ src: logo,
6994
+ onError: () => setLogoFailed(true)
6866
6995
  }
6867
6996
  );
6868
6997
  }
6869
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SparklesIcon, {});
6998
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChatSparkIcon, {});
6870
6999
  }
6871
7000
  function ChevronLeftIcon() {
6872
7001
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
@@ -6907,6 +7036,7 @@ function AssistEdgeTab({
6907
7036
  inset,
6908
7037
  visible,
6909
7038
  label,
7039
+ subLabel,
6910
7040
  customIconUrl,
6911
7041
  logoUrl,
6912
7042
  brandColor,
@@ -6922,12 +7052,16 @@ function AssistEdgeTab({
6922
7052
  const resolvedColorScheme = useAgentColorScheme(colorScheme);
6923
7053
  const copy = VARIANT_COPY[variant];
6924
7054
  const visibleLabel = label?.trim() || copy.label;
7055
+ const visibleSubLabel = subLabel?.trim() || "";
7056
+ const pill = mobile || side === "bottom";
7057
+ const chromeScheme = pill ? "light" : resolvedColorScheme;
7058
+ const ariaLabel = visibleSubLabel ? `Open ${visibleLabel} \u2014 ${visibleSubLabel}` : `Open ${visibleLabel}`;
6925
7059
  const alignment = along < 50 ? "start" : along > 50 ? "end" : "center";
6926
- const showLogo = Boolean(logoUrl?.trim()) && !customIconUrl?.trim();
6927
- const resolvedBrandColor = brandColor ?? (resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.brand : void 0);
6928
- const resolvedBorderColor = resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.border : borderColor;
6929
- const resolvedSurfaceColor = resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.surface : surfaceColor;
6930
- const resolvedTextColor = resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.text : textColor;
7060
+ const resolvedBrandColor = brandColor ?? (chromeScheme === "dark" ? defaultDarkAgentRailTheme.brand : void 0);
7061
+ const resolvedBorderColor = chromeScheme === "dark" ? defaultDarkAgentRailTheme.border : borderColor;
7062
+ const resolvedSurfaceColor = chromeScheme === "dark" ? defaultDarkAgentRailTheme.surface : surfaceColor;
7063
+ const resolvedTextColor = chromeScheme === "dark" ? defaultDarkAgentRailTheme.text : textColor;
7064
+ const markSourceKey = `${customIconUrl?.trim() ?? ""}|${logoUrl?.trim() ?? ""}`;
6931
7065
  const style = {
6932
7066
  "--tab-along": `${along}%`,
6933
7067
  "--tab-inset": `${inset}px`,
@@ -6935,83 +7069,72 @@ function AssistEdgeTab({
6935
7069
  ...brandForeground ? { "--as-visitor-text": brandForeground } : {},
6936
7070
  ...resolvedBorderColor ? { "--as-border": resolvedBorderColor } : {},
6937
7071
  ...fontFamily ? { "--as-font-display": fontFamily } : {},
6938
- ...resolvedSurfaceColor ? { "--as-surface": resolvedSurfaceColor } : {},
6939
- ...resolvedTextColor ? { "--as-text": resolvedTextColor } : {},
6940
- colorScheme: resolvedColorScheme
7072
+ ...pill ? { "--as-surface": "#ffffff", "--as-text": "#171717" } : resolvedSurfaceColor ? { "--as-surface": resolvedSurfaceColor } : {},
7073
+ ...!pill && resolvedTextColor ? { "--as-text": resolvedTextColor } : {},
7074
+ colorScheme: chromeScheme
6941
7075
  };
6942
7076
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
6943
7077
  "button",
6944
7078
  {
6945
7079
  type: "button",
6946
7080
  className: `assist-edge-tab assist-edge-tab--${variant} assist-edge-tab--${side} assist-edge-tab--align-${alignment}${mobile ? " assist-edge-tab--mobile" : ""}${visible ? " is-visible" : ""}`,
6947
- "data-color-scheme": resolvedColorScheme,
7081
+ "data-color-scheme": chromeScheme,
6948
7082
  style,
6949
- "aria-label": `Open ${visibleLabel}`,
7083
+ "aria-label": ariaLabel,
6950
7084
  "aria-hidden": !visible,
6951
7085
  tabIndex: visible ? 0 : -1,
6952
7086
  onClick: onOpen,
6953
7087
  children: [
6954
- mobile ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
7088
+ pill ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
6955
7089
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
6956
7090
  "span",
6957
7091
  {
6958
- className: "assist-edge-tab__mark assist-edge-tab__mark--mobile",
7092
+ className: `assist-edge-tab__mark assist-edge-tab__mark--chip${mobile ? " assist-edge-tab__mark--mobile" : ""}`,
6959
7093
  "aria-hidden": "true",
6960
7094
  children: [
6961
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TabMarkIcon, { customIconUrl }),
6962
- showLogo ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6963
- "img",
7095
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "assist-edge-tab__wash assist-edge-tab__wash--light" }),
7096
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "assist-edge-tab__wash assist-edge-tab__wash--cool" }),
7097
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7098
+ TabMarkIcon,
6964
7099
  {
6965
- className: "assist-edge-tab__logo",
6966
- src: logoUrl,
6967
- alt: "",
6968
- onError: (event) => {
6969
- event.currentTarget.hidden = true;
6970
- }
6971
- }
6972
- ) : null
7100
+ customIconUrl,
7101
+ logoUrl
7102
+ },
7103
+ markSourceKey
7104
+ )
6973
7105
  ]
6974
7106
  }
6975
7107
  ),
6976
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel })
7108
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "assist-edge-tab__text", children: [
7109
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
7110
+ visibleSubLabel ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "assist-edge-tab__sublabel", children: visibleSubLabel }) : null
7111
+ ] })
6977
7112
  ] }) : variant === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
6978
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
6979
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TabMarkIcon, { customIconUrl }),
6980
- showLogo ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6981
- "img",
6982
- {
6983
- className: "assist-edge-tab__logo",
6984
- src: logoUrl,
6985
- alt: "",
6986
- onError: (event) => {
6987
- event.currentTarget.hidden = true;
6988
- }
6989
- }
6990
- ) : null
6991
- ] }),
7113
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7114
+ TabMarkIcon,
7115
+ {
7116
+ customIconUrl,
7117
+ logoUrl
7118
+ },
7119
+ markSourceKey
7120
+ ) }),
6992
7121
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
6993
7122
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChevronDownIcon2, {})
6994
7123
  ] }) : null,
6995
- variant === "ask" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
7124
+ !pill && variant === "ask" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
6996
7125
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChevronLeftIcon, {}),
6997
7126
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
6998
7127
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DragDots, {})
6999
7128
  ] }) : null,
7000
- variant === "fill" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
7001
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
7002
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TabMarkIcon, { customIconUrl }),
7003
- showLogo ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7004
- "img",
7005
- {
7006
- className: "assist-edge-tab__logo",
7007
- src: logoUrl,
7008
- alt: "",
7009
- onError: (event) => {
7010
- event.currentTarget.hidden = true;
7011
- }
7012
- }
7013
- ) : null
7014
- ] }),
7129
+ !pill && variant === "fill" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
7130
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7131
+ TabMarkIcon,
7132
+ {
7133
+ customIconUrl,
7134
+ logoUrl
7135
+ },
7136
+ markSourceKey
7137
+ ) }),
7015
7138
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
7016
7139
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChevronLeftIcon, {})
7017
7140
  ] }) : null
@@ -7096,9 +7219,9 @@ function AgentWidget({
7096
7219
  }) {
7097
7220
  const isMobile = useIsMobile();
7098
7221
  const placement = normalizeAgentPlacement(placementInput);
7099
- const railSlotRef = (0, import_react15.useRef)(null);
7100
- const [railCollapsed, setRailCollapsed] = (0, import_react15.useState)(defaultCollapsed);
7101
- const [railExpanded, setRailExpanded] = (0, import_react15.useState)(false);
7222
+ const railSlotRef = (0, import_react16.useRef)(null);
7223
+ const [railCollapsed, setRailCollapsed] = (0, import_react16.useState)(defaultCollapsed);
7224
+ const [railExpanded, setRailExpanded] = (0, import_react16.useState)(false);
7102
7225
  const pageShiftActive = shouldApplyPageShift({
7103
7226
  pageShift,
7104
7227
  isMobile,
@@ -7151,7 +7274,7 @@ function AgentWidget({
7151
7274
  } : {},
7152
7275
  ...branding?.colors?.border ? { border: branding.colors.border } : {}
7153
7276
  };
7154
- (0, import_react15.useEffect)(() => {
7277
+ (0, import_react16.useEffect)(() => {
7155
7278
  if (!registerPanelController) return;
7156
7279
  registerAgentPanelController(customerId, {
7157
7280
  open: () => setRailCollapsed(false),
@@ -7186,7 +7309,7 @@ function AgentWidget({
7186
7309
  })
7187
7310
  );
7188
7311
  }
7189
- (0, import_react15.useEffect)(() => {
7312
+ (0, import_react16.useEffect)(() => {
7190
7313
  if (railCollapsed) return;
7191
7314
  const handleKeyDown = (event) => {
7192
7315
  if (event.key === "Tab" && (isMobile || railExpanded)) {
@@ -7278,6 +7401,7 @@ function AgentWidget({
7278
7401
  inset: placement.inset,
7279
7402
  visible: true,
7280
7403
  label: tabLabel,
7404
+ subLabel: branding?.tabSubLabel,
7281
7405
  customIconUrl: branding?.tabIconUrl,
7282
7406
  logoUrl: branding?.logoUrl,
7283
7407
  brandColor: branding?.colors?.primary,
@@ -7421,6 +7545,7 @@ function normalizeAgentBranding(branding) {
7421
7545
  const normalized = {
7422
7546
  agentName: normalizeOptionalValue(branding.agentName),
7423
7547
  tabLabel: normalizeOptionalValue(branding.tabLabel),
7548
+ tabSubLabel: normalizeOptionalValue(branding.tabSubLabel),
7424
7549
  tabIconUrl: normalizeOptionalValue(branding.tabIconUrl),
7425
7550
  logoUrl: normalizeOptionalValue(branding.logoUrl),
7426
7551
  greeting: normalizeOptionalValue(branding.greeting),