@webless/agent 0.9.2 → 0.10.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/dist/embed.cjs CHANGED
@@ -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,
@@ -4032,6 +4068,7 @@ function AnswerReceiptDialog({
4032
4068
  // src/react/components/Composer/Composer.tsx
4033
4069
  var import_react8 = require("react");
4034
4070
  var import_jsx_runtime3 = require("react/jsx-runtime");
4071
+ var FORM_SUBMITTED_MESSAGE = "Shared my details";
4035
4072
  function SendIcon() {
4036
4073
  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
4074
  "path",
@@ -4044,6 +4081,30 @@ function SendIcon() {
4044
4081
  }
4045
4082
  ) });
4046
4083
  }
4084
+ function CheckIcon() {
4085
+ 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)(
4086
+ "path",
4087
+ {
4088
+ d: "M2.5 6.2l2.3 2.3 4.7-5",
4089
+ stroke: "currentColor",
4090
+ strokeWidth: "1.6",
4091
+ strokeLinecap: "round",
4092
+ strokeLinejoin: "round"
4093
+ }
4094
+ ) });
4095
+ }
4096
+ function CollapseIcon() {
4097
+ 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)(
4098
+ "path",
4099
+ {
4100
+ d: "M4 10l4-4 4 4",
4101
+ stroke: "currentColor",
4102
+ strokeWidth: "1.6",
4103
+ strokeLinecap: "round",
4104
+ strokeLinejoin: "round"
4105
+ }
4106
+ ) });
4107
+ }
4047
4108
  function emptyValues(form) {
4048
4109
  const values = {};
4049
4110
  for (const field of form?.fields ?? []) values[field.id] = "";
@@ -4077,26 +4138,31 @@ function Composer({
4077
4138
  const savedForm = allowFormResume && !draft.submitted ? draft.form : null;
4078
4139
  const activeForm = !disabled && draft.expanded ? savedForm : null;
4079
4140
  const hasDraft = Object.values(draft.values).some((value2) => value2.trim());
4141
+ const canSend = !disabled && Boolean(value.trim() || activeForm);
4080
4142
  (0, import_react8.useEffect)(() => {
4081
4143
  if (activeForm) firstFieldRef.current?.focus();
4082
4144
  else if ((savedForm || draft.submitted) && !disabled)
4083
4145
  inputRef.current?.focus();
4084
4146
  }, [activeForm?.id, disabled, savedForm?.id, draft.submitted]);
4147
+ function collapseForm() {
4148
+ setDraft((current) => ({ ...current, expanded: false }));
4149
+ }
4085
4150
  function submitChat() {
4086
4151
  const trimmed = value.trim();
4087
4152
  if (!trimmed || disabled) return;
4088
4153
  const shareDismissal = savedForm && !draft.dismissalSent;
4089
4154
  if (shareDismissal) {
4090
4155
  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}`
4156
+ declinedComposerFormId: savedForm.id
4094
4157
  });
4095
4158
  } else {
4096
4159
  onSubmit?.(trimmed);
4097
4160
  }
4098
- if (shareDismissal)
4099
- setDraft((current) => ({ ...current, dismissalSent: true }));
4161
+ setDraft((current) => ({
4162
+ ...current,
4163
+ expanded: false,
4164
+ dismissalSent: shareDismissal ? true : current.dismissalSent
4165
+ }));
4100
4166
  setValue("");
4101
4167
  inputRef.current?.focus();
4102
4168
  }
@@ -4111,7 +4177,7 @@ ${trimmed}`
4111
4177
  if (control instanceof HTMLElement) control.focus();
4112
4178
  return;
4113
4179
  }
4114
- onSubmit?.("", {
4180
+ onSubmit?.(FORM_SUBMITTED_MESSAGE, {
4115
4181
  runtimeText: formatComposerFormMessage(activeForm, draft.values)
4116
4182
  });
4117
4183
  setDraft((current) => ({
@@ -4122,10 +4188,16 @@ ${trimmed}`
4122
4188
  attempted: false
4123
4189
  }));
4124
4190
  }
4191
+ function handleSend() {
4192
+ if (value.trim()) {
4193
+ submitChat();
4194
+ return;
4195
+ }
4196
+ if (activeForm) submitForm();
4197
+ }
4125
4198
  function handleSubmit(event) {
4126
4199
  event.preventDefault();
4127
- if (activeForm) submitForm();
4128
- else submitChat();
4200
+ handleSend();
4129
4201
  }
4130
4202
  function handleChatKeyDown(event) {
4131
4203
  if (event.key === "Enter" && !event.shiftKey) {
@@ -4141,7 +4213,34 @@ ${trimmed}`
4141
4213
  submitForm();
4142
4214
  }
4143
4215
  }
4144
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4216
+ const chatField = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
4217
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4218
+ "textarea",
4219
+ {
4220
+ ref: inputRef,
4221
+ className: "composer__input",
4222
+ rows: 1,
4223
+ value,
4224
+ placeholder,
4225
+ disabled,
4226
+ spellCheck: false,
4227
+ "aria-label": "Message",
4228
+ onChange: (event) => setValue(event.target.value),
4229
+ onKeyDown: handleChatKeyDown
4230
+ }
4231
+ ),
4232
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4233
+ "button",
4234
+ {
4235
+ type: "submit",
4236
+ className: "composer__send",
4237
+ disabled: !canSend,
4238
+ "aria-label": "Send message",
4239
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SendIcon, {})
4240
+ }
4241
+ )
4242
+ ] });
4243
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
4145
4244
  "form",
4146
4245
  {
4147
4246
  ref: formRef,
@@ -4152,107 +4251,10 @@ ${trimmed}`
4152
4251
  activeForm ? "composer--form" : ""
4153
4252
  ].filter(Boolean).join(" "),
4154
4253
  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: [
4254
+ children: [
4253
4255
  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)(
4256
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: hasDraft ? "Your details aren\u2019t shared yet" : "Share your details so we can follow up" }),
4257
+ !activeForm ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4256
4258
  "button",
4257
4259
  {
4258
4260
  type: "button",
@@ -4260,36 +4262,100 @@ ${trimmed}`
4260
4262
  onClick: () => setDraft((current) => ({ ...current, expanded: true })),
4261
4263
  children: hasDraft ? "Resume details" : "Add details"
4262
4264
  }
4263
- )
4265
+ ) : null
4264
4266
  ] }) : 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
- ] })
4267
+ activeForm ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
4268
+ "div",
4269
+ {
4270
+ className: "composer__sheet",
4271
+ role: "group",
4272
+ "aria-label": "Contact details",
4273
+ children: [
4274
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "composer__form-heading", children: [
4275
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Share your details" }),
4276
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Optional" }),
4277
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4278
+ "button",
4279
+ {
4280
+ type: "button",
4281
+ className: "composer__collapse",
4282
+ "aria-label": "Collapse form",
4283
+ disabled,
4284
+ onClick: collapseForm,
4285
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CollapseIcon, {})
4286
+ }
4287
+ )
4288
+ ] }),
4289
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "composer__fields", children: activeForm.fields.map((field, index) => {
4290
+ const fieldId = `${formId}-${field.id}`;
4291
+ const fieldValue = draft.values[field.id] ?? "";
4292
+ const invalid = draft.attempted && !isValidComposerFieldValue(field, fieldValue);
4293
+ const complete = fieldValue.trim().length > 0 && isValidComposerFieldValue(field, fieldValue);
4294
+ const controlProps = {
4295
+ id: fieldId,
4296
+ name: field.id,
4297
+ disabled,
4298
+ required: field.required,
4299
+ autoComplete: field.autocomplete,
4300
+ placeholder: field.placeholder,
4301
+ spellCheck: false,
4302
+ value: fieldValue,
4303
+ "aria-invalid": invalid || void 0,
4304
+ "aria-describedby": invalid ? `${fieldId}-error` : void 0,
4305
+ onChange: (event) => {
4306
+ const next = readComposerControlValue(event);
4307
+ setDraft((current) => ({
4308
+ ...current,
4309
+ values: { ...current.values, [field.id]: next }
4310
+ }));
4311
+ },
4312
+ onKeyDown: handleFormKeyDown
4313
+ };
4314
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
4315
+ "div",
4316
+ {
4317
+ className: [
4318
+ "composer__row",
4319
+ field.kind === "textarea" ? "composer__row--area" : ""
4320
+ ].filter(Boolean).join(" "),
4321
+ children: [
4322
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "composer__line", children: [
4323
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "composer__label", htmlFor: fieldId, children: field.label }),
4324
+ field.kind === "textarea" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4325
+ "textarea",
4326
+ {
4327
+ ...controlProps,
4328
+ ref: index === 0 ? (node) => {
4329
+ firstFieldRef.current = node;
4330
+ } : void 0,
4331
+ className: "composer__control composer__control--area",
4332
+ rows: 2
4333
+ }
4334
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4335
+ "input",
4336
+ {
4337
+ ...controlProps,
4338
+ ref: index === 0 ? (node) => {
4339
+ firstFieldRef.current = node;
4340
+ } : void 0,
4341
+ className: "composer__control",
4342
+ type: field.kind,
4343
+ inputMode: field.kind === "tel" ? "tel" : void 0
4344
+ }
4345
+ ),
4346
+ 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
4347
+ ] }),
4348
+ 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
4349
+ ]
4350
+ },
4351
+ field.id
4352
+ );
4353
+ }) }),
4354
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "composer__field composer__field--inset", children: chatField })
4355
+ ]
4356
+ }
4357
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "composer__field", children: chatField })
4358
+ ]
4293
4359
  }
4294
4360
  );
4295
4361
  }
@@ -4800,6 +4866,20 @@ function InteractiveBookingCard({
4800
4866
 
4801
4867
  // src/react/components/MessageBubble/MessageBubble.tsx
4802
4868
  var import_streamdown = require("streamdown");
4869
+
4870
+ // src/react/lib/resolve-message-url.ts
4871
+ function resolveMessageUrl(safeUrl, baseUrl) {
4872
+ if (!safeUrl || !baseUrl) return safeUrl;
4873
+ try {
4874
+ const base = new URL(baseUrl);
4875
+ if (base.protocol !== "https:" && base.protocol !== "http:") return "";
4876
+ return new URL(safeUrl, base).href;
4877
+ } catch {
4878
+ return "";
4879
+ }
4880
+ }
4881
+
4882
+ // src/react/components/MessageBubble/MessageBubble.tsx
4803
4883
  var import_styles = require("streamdown/styles.css");
4804
4884
  var import_jsx_runtime7 = require("react/jsx-runtime");
4805
4885
  function normalizeDedupeText(text2) {
@@ -4842,6 +4922,7 @@ function MessageBubble({
4842
4922
  brandLogoUrl,
4843
4923
  bookingDisabled = false,
4844
4924
  bookingReadOnly = false,
4925
+ linkBaseUrl,
4845
4926
  offer,
4846
4927
  onBook
4847
4928
  }) {
@@ -4859,8 +4940,9 @@ function MessageBubble({
4859
4940
  offers.length > 0 ? sanitizeBookingOfferCopy(visibleText) : looksLikeBookingAvailabilityDump(visibleText) ? sanitizeBookingOfferCopy(visibleText) : visibleText || (isStreaming ? "" : message.text)
4860
4941
  );
4861
4942
  if (message.role === "visitor") {
4862
- if (!message.text.trim()) return null;
4863
- 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 }) });
4943
+ const visitorText = visitorMessageDisplayText(message).trim();
4944
+ if (!visitorText) return null;
4945
+ 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 }) });
4864
4946
  }
4865
4947
  const citations = message.citations ?? [];
4866
4948
  const agentText = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "message-bubble__text", children: [
@@ -4874,6 +4956,10 @@ function MessageBubble({
4874
4956
  isAnimating: isStreaming,
4875
4957
  linkSafety: { enabled: false },
4876
4958
  mode: isStreaming ? "streaming" : "static",
4959
+ urlTransform: (url, key, node) => resolveMessageUrl(
4960
+ (0, import_streamdown.defaultUrlTransform)(url, key, node) ?? "",
4961
+ key === "href" ? linkBaseUrl : void 0
4962
+ ),
4877
4963
  skipHtml: true,
4878
4964
  children: displayText
4879
4965
  }
@@ -4891,7 +4977,8 @@ function MessageBubble({
4891
4977
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: citation.label })
4892
4978
  ] }) }, citation.id)) }) : null
4893
4979
  ] });
4894
- if (!displayText && !message.searchResults?.length && offers.length === 0) return null;
4980
+ if (!displayText && !message.searchResults?.length && offers.length === 0)
4981
+ return null;
4895
4982
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("article", { className: "message-bubble message-bubble--agent", children: [
4896
4983
  displayText || message.searchResults?.length ? showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "message-bubble__agent-row", children: [
4897
4984
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "message-bubble__agent-avatar", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
@@ -5093,7 +5180,7 @@ function CopyIcon() {
5093
5180
  )
5094
5181
  ] });
5095
5182
  }
5096
- function CheckIcon() {
5183
+ function CheckIcon2() {
5097
5184
  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)(
5098
5185
  "path",
5099
5186
  {
@@ -5468,7 +5555,7 @@ function MessageActions({
5468
5555
  title: copied ? "Copied" : "Copy answer",
5469
5556
  disabled,
5470
5557
  onClick: handleCopy,
5471
- children: copied ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CheckIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CopyIcon, {})
5558
+ children: copied ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CheckIcon2, {}) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CopyIcon, {})
5472
5559
  }
5473
5560
  ),
5474
5561
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
@@ -6422,13 +6509,17 @@ function AgentRail({
6422
6509
  const hasPendingConfirmation = pendingInputRequests.some(
6423
6510
  (request) => request.kind === "tool-approval" || request.kind === "question" && (request.options?.length ?? 0) > 0
6424
6511
  );
6425
- const composerForm = resolveComposerForm({
6512
+ const inferredComposerForm = resolveComposerForm({
6426
6513
  agentText: lastAgentText,
6427
6514
  cards: extractToolCards(lastAgentText),
6428
6515
  hasBookingOffer: Boolean(state.pendingOffer) || waitingForBooking,
6429
6516
  hasPendingConfirmation,
6430
6517
  enabled: lastIsAgent && !isBusy
6431
6518
  });
6519
+ const declinedComposerFormIds = state.declinedComposerFormIds ?? [];
6520
+ const composerForm = inferredComposerForm && lastMessage?.role === "agent" && declinedComposerFormIds.includes(
6521
+ `${lastMessage.id}:${inferredComposerForm.id}`
6522
+ ) ? null : inferredComposerForm;
6432
6523
  const latestResultId = [
6433
6524
  ...visibleMessages.flatMap(
6434
6525
  (message) => message.role === "agent" ? message.toolResults ?? [] : []
@@ -6799,36 +6890,75 @@ function AgentRail({
6799
6890
 
6800
6891
  // src/react/components/AssistEdgeTab/AssistEdgeTab.tsx
6801
6892
  var import_jsx_runtime18 = require("react/jsx-runtime");
6802
- function SparklesIcon() {
6893
+ function ChatSparkIcon() {
6803
6894
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
6804
6895
  "svg",
6805
6896
  {
6806
- className: "assist-edge-tab__sparkles",
6807
- viewBox: "0 0 18 16",
6897
+ className: "assist-edge-tab__chat-spark",
6898
+ viewBox: "0 0 24 24",
6808
6899
  fill: "none",
6809
6900
  "aria-hidden": "true",
6810
6901
  children: [
6811
6902
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6812
6903
  "path",
6813
6904
  {
6814
- 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",
6815
6907
  fill: "currentColor"
6816
6908
  }
6817
6909
  ),
6818
6910
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6819
6911
  "path",
6820
6912
  {
6821
- 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",
6822
6915
  fill: "currentColor"
6823
6916
  }
6824
6917
  ),
6825
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6826
- "path",
6827
- {
6828
- 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",
6829
- fill: "currentColor"
6830
- }
6831
- )
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
+ ] })
6832
6962
  ]
6833
6963
  }
6834
6964
  );
@@ -6846,7 +6976,7 @@ function TabMarkIcon({ customIconUrl }) {
6846
6976
  }
6847
6977
  );
6848
6978
  }
6849
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SparklesIcon, {});
6979
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChatSparkIcon, {});
6850
6980
  }
6851
6981
  function ChevronLeftIcon() {
6852
6982
  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)(
@@ -6887,6 +7017,7 @@ function AssistEdgeTab({
6887
7017
  inset,
6888
7018
  visible,
6889
7019
  label,
7020
+ subLabel,
6890
7021
  customIconUrl,
6891
7022
  logoUrl,
6892
7023
  brandColor,
@@ -6902,6 +7033,9 @@ function AssistEdgeTab({
6902
7033
  const resolvedColorScheme = useAgentColorScheme(colorScheme);
6903
7034
  const copy = VARIANT_COPY[variant];
6904
7035
  const visibleLabel = label?.trim() || copy.label;
7036
+ const visibleSubLabel = subLabel?.trim() || "";
7037
+ const pill = mobile || side === "bottom";
7038
+ const ariaLabel = visibleSubLabel ? `Open ${visibleLabel} \u2014 ${visibleSubLabel}` : `Open ${visibleLabel}`;
6905
7039
  const alignment = along < 50 ? "start" : along > 50 ? "end" : "center";
6906
7040
  const showLogo = Boolean(logoUrl?.trim()) && !customIconUrl?.trim();
6907
7041
  const resolvedBrandColor = brandColor ?? (resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.brand : void 0);
@@ -6926,18 +7060,20 @@ function AssistEdgeTab({
6926
7060
  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" : ""}`,
6927
7061
  "data-color-scheme": resolvedColorScheme,
6928
7062
  style,
6929
- "aria-label": `Open ${visibleLabel}`,
7063
+ "aria-label": ariaLabel,
6930
7064
  "aria-hidden": !visible,
6931
7065
  tabIndex: visible ? 0 : -1,
6932
7066
  onClick: onOpen,
6933
7067
  children: [
6934
- mobile ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
7068
+ pill ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
6935
7069
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
6936
7070
  "span",
6937
7071
  {
6938
- className: "assist-edge-tab__mark assist-edge-tab__mark--mobile",
7072
+ className: `assist-edge-tab__mark assist-edge-tab__mark--chip${mobile ? " assist-edge-tab__mark--mobile" : ""}`,
6939
7073
  "aria-hidden": "true",
6940
7074
  children: [
7075
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "assist-edge-tab__wash assist-edge-tab__wash--light" }),
7076
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "assist-edge-tab__wash assist-edge-tab__wash--cool" }),
6941
7077
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TabMarkIcon, { customIconUrl }),
6942
7078
  showLogo ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6943
7079
  "img",
@@ -6953,7 +7089,10 @@ function AssistEdgeTab({
6953
7089
  ]
6954
7090
  }
6955
7091
  ),
6956
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel })
7092
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "assist-edge-tab__text", children: [
7093
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
7094
+ visibleSubLabel ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "assist-edge-tab__sublabel", children: visibleSubLabel }) : null
7095
+ ] })
6957
7096
  ] }) : variant === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
6958
7097
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
6959
7098
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TabMarkIcon, { customIconUrl }),
@@ -6972,12 +7111,12 @@ function AssistEdgeTab({
6972
7111
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
6973
7112
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChevronDownIcon2, {})
6974
7113
  ] }) : null,
6975
- variant === "ask" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
7114
+ !pill && variant === "ask" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
6976
7115
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChevronLeftIcon, {}),
6977
7116
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
6978
7117
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DragDots, {})
6979
7118
  ] }) : null,
6980
- variant === "fill" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
7119
+ !pill && variant === "fill" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
6981
7120
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
6982
7121
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TabMarkIcon, { customIconUrl }),
6983
7122
  showLogo ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
@@ -7258,6 +7397,7 @@ function AgentWidget({
7258
7397
  inset: placement.inset,
7259
7398
  visible: true,
7260
7399
  label: tabLabel,
7400
+ subLabel: branding?.tabSubLabel,
7261
7401
  customIconUrl: branding?.tabIconUrl,
7262
7402
  logoUrl: branding?.logoUrl,
7263
7403
  brandColor: branding?.colors?.primary,
@@ -7401,6 +7541,7 @@ function normalizeAgentBranding(branding) {
7401
7541
  const normalized = {
7402
7542
  agentName: normalizeOptionalValue(branding.agentName),
7403
7543
  tabLabel: normalizeOptionalValue(branding.tabLabel),
7544
+ tabSubLabel: normalizeOptionalValue(branding.tabSubLabel),
7404
7545
  tabIconUrl: normalizeOptionalValue(branding.tabIconUrl),
7405
7546
  logoUrl: normalizeOptionalValue(branding.logoUrl),
7406
7547
  greeting: normalizeOptionalValue(branding.greeting),