@webless/agent 0.7.0 → 0.7.3

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
@@ -3804,42 +3804,72 @@ function emptyValues(form) {
3804
3804
  for (const field of form?.fields ?? []) values[field.id] = "";
3805
3805
  return values;
3806
3806
  }
3807
+ function createDraft(form) {
3808
+ return {
3809
+ form,
3810
+ values: emptyValues(form),
3811
+ expanded: Boolean(form),
3812
+ submitted: false,
3813
+ dismissalSent: false,
3814
+ attempted: false
3815
+ };
3816
+ }
3807
3817
  function Composer({
3808
3818
  disabled = false,
3809
3819
  placeholder = "Ask anything\u2026",
3810
3820
  variant = "default",
3811
3821
  form = null,
3822
+ allowFormResume = true,
3812
3823
  onSubmit
3813
3824
  }) {
3814
3825
  const [value, setValue] = (0, import_react8.useState)("");
3815
- const [values, setValues] = (0, import_react8.useState)(
3816
- () => emptyValues(form)
3817
- );
3818
- const [blurred, setBlurred] = (0, import_react8.useState)({});
3826
+ const [draft, setDraft] = (0, import_react8.useState)(() => createDraft(form));
3819
3827
  const inputRef = (0, import_react8.useRef)(null);
3820
3828
  const firstFieldRef = (0, import_react8.useRef)(null);
3829
+ const formRef = (0, import_react8.useRef)(null);
3821
3830
  const formId = (0, import_react8.useId)();
3822
- const activeForm = form;
3823
- const canSendForm = activeForm ? isComposerFormComplete(activeForm, values) : Boolean(value.trim());
3824
- (0, import_react8.useEffect)(() => {
3825
- setValues(emptyValues(form));
3826
- setBlurred({});
3827
- }, [form?.id]);
3831
+ if (form && form.id !== draft.form?.id) setDraft(createDraft(form));
3832
+ const savedForm = allowFormResume && !draft.submitted ? draft.form : null;
3833
+ const activeForm = !disabled && draft.expanded ? savedForm : null;
3834
+ const hasDraft = Object.values(draft.values).some((value2) => value2.trim());
3828
3835
  (0, import_react8.useEffect)(() => {
3829
3836
  if (activeForm) firstFieldRef.current?.focus();
3830
- }, [activeForm?.id]);
3837
+ else if ((savedForm || draft.submitted) && !disabled)
3838
+ inputRef.current?.focus();
3839
+ }, [activeForm?.id, disabled, savedForm?.id, draft.submitted]);
3831
3840
  function submitChat() {
3832
3841
  const trimmed = value.trim();
3833
3842
  if (!trimmed || disabled) return;
3834
- onSubmit?.(trimmed);
3843
+ const shareDismissal = savedForm && !draft.dismissalSent;
3844
+ onSubmit?.(
3845
+ shareDismissal ? `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.
3846
+
3847
+ ${trimmed}` : trimmed
3848
+ );
3849
+ if (shareDismissal)
3850
+ setDraft((current) => ({ ...current, dismissalSent: true }));
3835
3851
  setValue("");
3836
3852
  inputRef.current?.focus();
3837
3853
  }
3838
3854
  function submitForm() {
3839
- if (!activeForm || disabled || !canSendForm) return;
3840
- onSubmit?.(formatComposerFormMessage(activeForm, values));
3841
- setValues(emptyValues(activeForm));
3842
- setBlurred({});
3855
+ if (!activeForm || disabled) return;
3856
+ if (!isComposerFormComplete(activeForm, draft.values)) {
3857
+ setDraft((current) => ({ ...current, attempted: true }));
3858
+ const invalidField = activeForm.fields.find(
3859
+ (field) => !isValidComposerFieldValue(field, draft.values[field.id] ?? "")
3860
+ );
3861
+ const control = invalidField && formRef.current?.elements.namedItem(invalidField.id);
3862
+ if (control instanceof HTMLElement) control.focus();
3863
+ return;
3864
+ }
3865
+ onSubmit?.(formatComposerFormMessage(activeForm, draft.values));
3866
+ setDraft((current) => ({
3867
+ ...current,
3868
+ values: emptyValues(activeForm),
3869
+ expanded: false,
3870
+ submitted: true,
3871
+ attempted: false
3872
+ }));
3843
3873
  }
3844
3874
  function handleSubmit(event) {
3845
3875
  event.preventDefault();
@@ -3863,112 +3893,151 @@ function Composer({
3863
3893
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3864
3894
  "form",
3865
3895
  {
3896
+ ref: formRef,
3897
+ noValidate: true,
3866
3898
  className: [
3867
3899
  "composer",
3868
3900
  variant === "dock" ? "composer--dock" : "",
3869
3901
  activeForm ? "composer--form" : ""
3870
3902
  ].filter(Boolean).join(" "),
3871
3903
  onSubmit: handleSubmit,
3872
- children: activeForm ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "composer__sheet", role: "group", "aria-label": "Required details", children: [
3873
- activeForm.fields.map((field, index) => {
3874
- const fieldId = `${formId}-${field.id}`;
3875
- const invalid = Boolean(blurred[field.id]) && !isValidComposerFieldValue(field, values[field.id] ?? "");
3876
- const controlProps = {
3877
- id: fieldId,
3878
- name: field.id,
3879
- disabled,
3880
- required: field.required,
3881
- autoComplete: field.autocomplete,
3882
- placeholder: field.placeholder,
3883
- spellCheck: false,
3884
- value: values[field.id] ?? "",
3885
- "aria-invalid": invalid || void 0,
3886
- "aria-describedby": invalid ? `${fieldId}-error` : void 0,
3887
- onBlur: () => setBlurred((current) => ({ ...current, [field.id]: true })),
3888
- onChange: (event) => {
3889
- const next = readComposerControlValue(event);
3890
- setValues((current) => ({
3891
- ...current,
3892
- [field.id]: next
3893
- }));
3894
- },
3895
- onKeyDown: handleFormKeyDown
3896
- };
3897
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3898
- "div",
3904
+ children: activeForm ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3905
+ "div",
3906
+ {
3907
+ className: "composer__sheet",
3908
+ role: "group",
3909
+ "aria-label": "Contact details",
3910
+ children: [
3911
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "composer__form-heading", children: [
3912
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Share your details" }),
3913
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Optional" }),
3914
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: "You can also keep chatting." })
3915
+ ] }),
3916
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "composer__fields", children: activeForm.fields.map((field, index) => {
3917
+ const fieldId = `${formId}-${field.id}`;
3918
+ const invalid = draft.attempted && !isValidComposerFieldValue(field, draft.values[field.id] ?? "");
3919
+ const controlProps = {
3920
+ id: fieldId,
3921
+ name: field.id,
3922
+ disabled,
3923
+ required: field.required,
3924
+ autoComplete: field.autocomplete,
3925
+ placeholder: field.placeholder,
3926
+ spellCheck: false,
3927
+ value: draft.values[field.id] ?? "",
3928
+ "aria-invalid": invalid || void 0,
3929
+ "aria-describedby": invalid ? `${fieldId}-error` : void 0,
3930
+ onChange: (event) => {
3931
+ const next = readComposerControlValue(event);
3932
+ setDraft((current) => ({
3933
+ ...current,
3934
+ values: { ...current.values, [field.id]: next }
3935
+ }));
3936
+ },
3937
+ onKeyDown: handleFormKeyDown
3938
+ };
3939
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3940
+ "div",
3941
+ {
3942
+ className: [
3943
+ "composer__row",
3944
+ field.kind === "textarea" ? "composer__row--grow" : ""
3945
+ ].filter(Boolean).join(" "),
3946
+ children: [
3947
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "composer__label", htmlFor: fieldId, children: [
3948
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "composer__field-label", children: field.label }),
3949
+ field.kind === "textarea" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3950
+ "textarea",
3951
+ {
3952
+ ...controlProps,
3953
+ ref: index === 0 ? (node) => {
3954
+ firstFieldRef.current = node;
3955
+ } : void 0,
3956
+ className: "composer__control composer__control--area",
3957
+ rows: 3
3958
+ }
3959
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3960
+ "input",
3961
+ {
3962
+ ...controlProps,
3963
+ ref: index === 0 ? (node) => {
3964
+ firstFieldRef.current = node;
3965
+ } : void 0,
3966
+ className: "composer__control",
3967
+ type: field.kind,
3968
+ inputMode: field.kind === "tel" ? "tel" : void 0
3969
+ }
3970
+ )
3971
+ ] }),
3972
+ 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
3973
+ ]
3974
+ },
3975
+ field.id
3976
+ );
3977
+ }) }),
3978
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "composer__toolbar", children: [
3979
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3980
+ "button",
3981
+ {
3982
+ type: "submit",
3983
+ className: "composer__action composer__action--primary",
3984
+ disabled,
3985
+ children: "Share details"
3986
+ }
3987
+ ),
3988
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3989
+ "button",
3990
+ {
3991
+ type: "button",
3992
+ className: "composer__action",
3993
+ disabled,
3994
+ onClick: () => setDraft((current) => ({ ...current, expanded: false })),
3995
+ children: "Keep chatting"
3996
+ }
3997
+ )
3998
+ ] })
3999
+ ]
4000
+ }
4001
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
4002
+ savedForm ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "composer__resume", children: [
4003
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: hasDraft ? "Your details aren\u2019t shared yet" : "Contact details are optional" }),
4004
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4005
+ "button",
3899
4006
  {
3900
- className: [
3901
- "composer__row",
3902
- field.kind === "textarea" ? "composer__row--grow" : ""
3903
- ].filter(Boolean).join(" "),
3904
- children: [
3905
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "composer__label", htmlFor: fieldId, children: [
3906
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "composer__sr-only", children: field.label }),
3907
- field.kind === "textarea" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3908
- "textarea",
3909
- {
3910
- ...controlProps,
3911
- ref: index === 0 ? (node) => {
3912
- firstFieldRef.current = node;
3913
- } : void 0,
3914
- className: "composer__control composer__control--area",
3915
- rows: 3
3916
- }
3917
- ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3918
- "input",
3919
- {
3920
- ...controlProps,
3921
- ref: index === 0 ? (node) => {
3922
- firstFieldRef.current = node;
3923
- } : void 0,
3924
- className: "composer__control",
3925
- type: field.kind,
3926
- inputMode: field.kind === "tel" ? "tel" : void 0
3927
- }
3928
- )
3929
- ] }),
3930
- invalid ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "composer__error", id: `${fieldId}-error`, children: field.kind === "email" ? "Enter a valid email to continue." : `Add your ${field.label.toLowerCase()} to continue.` }) : null
3931
- ]
3932
- },
3933
- field.id
3934
- );
3935
- }),
3936
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "composer__toolbar", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3937
- "button",
3938
- {
3939
- type: "submit",
3940
- className: "composer__send",
3941
- disabled: disabled || !canSendForm,
3942
- "aria-label": "Send details",
3943
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SendIcon, {})
3944
- }
3945
- ) })
3946
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "composer__field", children: [
3947
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3948
- "textarea",
3949
- {
3950
- ref: inputRef,
3951
- className: "composer__input",
3952
- rows: 1,
3953
- value,
3954
- placeholder,
3955
- disabled,
3956
- spellCheck: false,
3957
- "aria-label": "Message",
3958
- onChange: (event) => setValue(event.target.value),
3959
- onKeyDown: handleChatKeyDown
3960
- }
3961
- ),
3962
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3963
- "button",
3964
- {
3965
- type: "submit",
3966
- className: "composer__send",
3967
- disabled: disabled || !value.trim(),
3968
- "aria-label": "Send message",
3969
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SendIcon, {})
3970
- }
3971
- )
4007
+ type: "button",
4008
+ disabled,
4009
+ onClick: () => setDraft((current) => ({ ...current, expanded: true })),
4010
+ children: hasDraft ? "Resume details" : "Add details"
4011
+ }
4012
+ )
4013
+ ] }) : null,
4014
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "composer__field", children: [
4015
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4016
+ "textarea",
4017
+ {
4018
+ ref: inputRef,
4019
+ className: "composer__input",
4020
+ rows: 1,
4021
+ value,
4022
+ placeholder,
4023
+ disabled,
4024
+ spellCheck: false,
4025
+ "aria-label": "Message",
4026
+ onChange: (event) => setValue(event.target.value),
4027
+ onKeyDown: handleChatKeyDown
4028
+ }
4029
+ ),
4030
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4031
+ "button",
4032
+ {
4033
+ type: "submit",
4034
+ className: "composer__send",
4035
+ disabled: disabled || !value.trim(),
4036
+ "aria-label": "Send message",
4037
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SendIcon, {})
4038
+ }
4039
+ )
4040
+ ] })
3972
4041
  ] })
3973
4042
  }
3974
4043
  );
@@ -4517,6 +4586,126 @@ function toSpeechText(text) {
4517
4586
  function isSpeechSupported() {
4518
4587
  return typeof window !== "undefined" && "speechSynthesis" in window && typeof window.SpeechSynthesisUtterance === "function";
4519
4588
  }
4589
+ function stopSpeech() {
4590
+ if (!isSpeechSupported()) return;
4591
+ window.speechSynthesis.cancel();
4592
+ }
4593
+ var speechInterruptListeners = /* @__PURE__ */ new Set();
4594
+ var pageListenersAttached = false;
4595
+ var lastPathname = "";
4596
+ var originalPushState = null;
4597
+ var originalReplaceState = null;
4598
+ var patchedPushState = null;
4599
+ var patchedReplaceState = null;
4600
+ function currentPathname() {
4601
+ return window.location.pathname;
4602
+ }
4603
+ function notifySpeechInterrupts() {
4604
+ if (speechInterruptListeners.size === 0) return;
4605
+ stopSpeech();
4606
+ for (const listener of speechInterruptListeners) {
4607
+ listener();
4608
+ }
4609
+ }
4610
+ function interruptIfPathChanged(nextPath) {
4611
+ if (nextPath === lastPathname) return;
4612
+ lastPathname = nextPath;
4613
+ notifySpeechInterrupts();
4614
+ }
4615
+ function patchHistoryMethod(original) {
4616
+ return function patched(data, unused, url) {
4617
+ const result = original.call(this, data, unused, url);
4618
+ interruptIfPathChanged(currentPathname());
4619
+ return result;
4620
+ };
4621
+ }
4622
+ function historyMethodState(name) {
4623
+ return name === "pushState" ? {
4624
+ original: originalPushState,
4625
+ patched: patchedPushState,
4626
+ set(original, patched) {
4627
+ originalPushState = original;
4628
+ patchedPushState = patched;
4629
+ }
4630
+ } : {
4631
+ original: originalReplaceState,
4632
+ patched: patchedReplaceState,
4633
+ set(original, patched) {
4634
+ originalReplaceState = original;
4635
+ patchedReplaceState = patched;
4636
+ }
4637
+ };
4638
+ }
4639
+ function patchHistoryNamed(name) {
4640
+ const state = historyMethodState(name);
4641
+ const current = history[name];
4642
+ if (state.patched && current === state.patched) return;
4643
+ const patched = patchHistoryMethod(current);
4644
+ state.set(current, patched);
4645
+ history[name] = patched;
4646
+ }
4647
+ function restoreHistoryMethod(name) {
4648
+ const state = historyMethodState(name);
4649
+ if (!state.patched || !state.original || typeof history === "undefined") {
4650
+ return false;
4651
+ }
4652
+ if (history[name] !== state.patched) return false;
4653
+ history[name] = state.original;
4654
+ state.set(null, null);
4655
+ return true;
4656
+ }
4657
+ function handlePageHide() {
4658
+ notifySpeechInterrupts();
4659
+ }
4660
+ function handlePopState() {
4661
+ interruptIfPathChanged(currentPathname());
4662
+ }
4663
+ function handleVisibilityChange() {
4664
+ if (document.visibilityState === "hidden") {
4665
+ notifySpeechInterrupts();
4666
+ }
4667
+ }
4668
+ function attachPageListeners() {
4669
+ if (pageListenersAttached || typeof window === "undefined") return;
4670
+ pageListenersAttached = true;
4671
+ window.addEventListener("pagehide", handlePageHide);
4672
+ window.addEventListener("popstate", handlePopState);
4673
+ document.addEventListener("visibilitychange", handleVisibilityChange);
4674
+ }
4675
+ function detachPageListeners() {
4676
+ if (!pageListenersAttached || typeof window === "undefined") return;
4677
+ window.removeEventListener("pagehide", handlePageHide);
4678
+ window.removeEventListener("popstate", handlePopState);
4679
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
4680
+ pageListenersAttached = false;
4681
+ }
4682
+ function ensureSpeechInterruptsPatched() {
4683
+ if (typeof window === "undefined") return;
4684
+ lastPathname = currentPathname();
4685
+ patchHistoryNamed("pushState");
4686
+ patchHistoryNamed("replaceState");
4687
+ attachPageListeners();
4688
+ }
4689
+ function teardownSpeechInterrupts() {
4690
+ detachPageListeners();
4691
+ restoreHistoryMethod("pushState");
4692
+ restoreHistoryMethod("replaceState");
4693
+ if (!patchedPushState && !patchedReplaceState) {
4694
+ lastPathname = "";
4695
+ }
4696
+ }
4697
+ function subscribeSpeechInterrupts(onInterrupt) {
4698
+ if (typeof window === "undefined") return () => {
4699
+ };
4700
+ ensureSpeechInterruptsPatched();
4701
+ speechInterruptListeners.add(onInterrupt);
4702
+ return () => {
4703
+ speechInterruptListeners.delete(onInterrupt);
4704
+ if (speechInterruptListeners.size === 0) {
4705
+ teardownSpeechInterrupts();
4706
+ }
4707
+ };
4708
+ }
4520
4709
 
4521
4710
  // src/react/components/MessageActions/MessageActions.tsx
4522
4711
  var import_jsx_runtime7 = require("react/jsx-runtime");
@@ -4772,15 +4961,20 @@ function MessageActions({
4772
4961
  const [menuOpen, setMenuOpen] = (0, import_react11.useState)(false);
4773
4962
  const [menuPosition, setMenuPosition] = (0, import_react11.useState)(null);
4774
4963
  const [speaking, setSpeaking] = (0, import_react11.useState)(false);
4964
+ const [speechSupported, setSpeechSupported] = (0, import_react11.useState)(null);
4775
4965
  const copyTimerRef = (0, import_react11.useRef)(null);
4776
4966
  const menuRef = (0, import_react11.useRef)(null);
4777
4967
  const portaledMenuRef = (0, import_react11.useRef)(null);
4778
4968
  const moreButtonRef = (0, import_react11.useRef)(null);
4779
4969
  const answeredLabel = formatAnsweredAt(answeredAt ?? 0);
4780
4970
  const resolvedSpeechText = speechText ?? toSpeechText(copyText);
4781
- const canReadAloud = readAloud && isSpeechSupported() && resolvedSpeechText;
4782
- const showMenu = Boolean(answeredLabel) || Boolean(receiptSteps) || canReadAloud;
4783
4971
  const canOpenReceipt = Boolean(receiptSteps) && Boolean(onOpenReceipt);
4972
+ const readAloudEligible = Boolean(readAloud && resolvedSpeechText);
4973
+ const canReadAloud = readAloudEligible && speechSupported === true;
4974
+ const showMenu = canOpenReceipt || (speechSupported === null ? readAloudEligible : canReadAloud);
4975
+ (0, import_react11.useLayoutEffect)(() => {
4976
+ setSpeechSupported(isSpeechSupported());
4977
+ }, []);
4784
4978
  (0, import_react11.useLayoutEffect)(() => {
4785
4979
  if (!menuOpen || !moreButtonRef.current || !portaledMenuRef.current) {
4786
4980
  setMenuPosition(null);
@@ -4798,17 +4992,16 @@ function MessageActions({
4798
4992
  })
4799
4993
  );
4800
4994
  }, [menuOpen, menuPortalRoot]);
4801
- (0, import_react11.useEffect)(
4802
- () => () => {
4995
+ (0, import_react11.useEffect)(() => {
4996
+ const unsubscribe = subscribeSpeechInterrupts(() => setSpeaking(false));
4997
+ return () => {
4998
+ unsubscribe();
4803
4999
  if (copyTimerRef.current !== null) {
4804
5000
  window.clearTimeout(copyTimerRef.current);
4805
5001
  }
4806
- if (speaking) window.speechSynthesis.cancel();
4807
- },
4808
- // Cancel speech only when this answer's actions unmount.
4809
- // eslint-disable-next-line react-hooks/exhaustive-deps
4810
- []
4811
- );
5002
+ stopSpeech();
5003
+ };
5004
+ }, []);
4812
5005
  (0, import_react11.useEffect)(() => {
4813
5006
  if (!menuOpen) return;
4814
5007
  const handlePointerDown = (event) => {
@@ -4847,14 +5040,14 @@ function MessageActions({
4847
5040
  if (!canReadAloud) return;
4848
5041
  setMenuOpen(false);
4849
5042
  if (speaking) {
4850
- window.speechSynthesis.cancel();
5043
+ stopSpeech();
4851
5044
  setSpeaking(false);
4852
5045
  return;
4853
5046
  }
4854
5047
  const utterance = new SpeechSynthesisUtterance(resolvedSpeechText);
4855
5048
  utterance.onend = () => setSpeaking(false);
4856
5049
  utterance.onerror = () => setSpeaking(false);
4857
- window.speechSynthesis.cancel();
5050
+ stopSpeech();
4858
5051
  window.speechSynthesis.speak(utterance);
4859
5052
  setSpeaking(true);
4860
5053
  }
@@ -5752,8 +5945,9 @@ function AgentRail({
5752
5945
  colorScheme = "auto",
5753
5946
  brandLabel = "",
5754
5947
  brandLogoUrl,
5755
- poweredByLabel = "Powered by Webless",
5948
+ poweredByLabel,
5756
5949
  disclaimerLabel,
5950
+ disclaimerLink,
5757
5951
  answerReceipt = false,
5758
5952
  readAloud = false,
5759
5953
  composerPlaceholder = "Ask anything\u2026",
@@ -6188,7 +6382,15 @@ function AgentRail({
6188
6382
  onRetry ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
6189
6383
  ] }) : null
6190
6384
  ] }) }),
6191
- showDisclaimerLabel ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "agent-rail__disclaimer", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { children: resolvedDisclaimerLabel }) }) : null,
6385
+ showDisclaimerLabel ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "agent-rail__disclaimer", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { children: disclaimerLink ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6386
+ "a",
6387
+ {
6388
+ href: disclaimerLink,
6389
+ rel: "noopener noreferrer",
6390
+ target: "_blank",
6391
+ children: resolvedDisclaimerLabel
6392
+ }
6393
+ ) : resolvedDisclaimerLabel }) }) : null,
6192
6394
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "agent-rail__composer-wrap", children: [
6193
6395
  showJumpToLatest ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6194
6396
  "button",
@@ -6206,10 +6408,12 @@ function AgentRail({
6206
6408
  {
6207
6409
  variant: expanded || mobileFullscreen ? "dock" : "default",
6208
6410
  disabled: isBusy,
6209
- form: composerForm,
6411
+ form: composerForm && lastMessage ? { ...composerForm, id: `${lastMessage.id}:${composerForm.id}` } : null,
6412
+ allowFormResume: !state.pendingOffer && !waitingForBooking && !hasPendingConfirmation,
6210
6413
  placeholder: composerPlaceholder,
6211
6414
  onSubmit: handleSubmit
6212
- }
6415
+ },
6416
+ state.messages.find((message) => message.role === "visitor")?.id ?? "empty"
6213
6417
  ),
6214
6418
  poweredByLabel ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "agent-rail__footer", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: poweredByLabel }) }) }) : null
6215
6419
  ] })
@@ -6653,8 +6857,9 @@ function AgentWidget({
6653
6857
  brandLabel: agentName,
6654
6858
  brandLogoUrl: branding?.logoUrl,
6655
6859
  composerPlaceholder: branding?.composerPlaceholder ?? "Ask a question\u2026",
6656
- poweredByLabel: branding?.poweredByLabel ?? "Powered by Webless",
6860
+ poweredByLabel: branding?.poweredByLabel,
6657
6861
  disclaimerLabel: branding?.disclaimer,
6862
+ disclaimerLink: branding?.disclaimerLink,
6658
6863
  answerReceipt: branding?.answerReceipt,
6659
6864
  readAloud: branding?.readAloud,
6660
6865
  state,
@@ -6808,6 +7013,20 @@ function normalizeOptionalValue(value) {
6808
7013
  const normalized = value?.trim();
6809
7014
  return normalized || void 0;
6810
7015
  }
7016
+ function normalizeOptionalHttpUrl(value) {
7017
+ if (value === null) return null;
7018
+ const normalized = value?.trim();
7019
+ if (!normalized) return void 0;
7020
+ try {
7021
+ const url = new URL(normalized);
7022
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
7023
+ return void 0;
7024
+ }
7025
+ return normalized;
7026
+ } catch {
7027
+ return void 0;
7028
+ }
7029
+ }
6811
7030
  function normalizeAgentBranding(branding) {
6812
7031
  if (!branding) return void 0;
6813
7032
  const colors = branding.colors ? Object.fromEntries(
@@ -6825,6 +7044,7 @@ function normalizeAgentBranding(branding) {
6825
7044
  composerPlaceholder: normalizeOptionalValue(branding.composerPlaceholder),
6826
7045
  poweredByLabel: normalizeOptionalValue(branding.poweredByLabel),
6827
7046
  disclaimer: branding.disclaimer === null ? null : normalizeOptionalValue(branding.disclaimer),
7047
+ disclaimerLink: normalizeOptionalHttpUrl(branding.disclaimerLink),
6828
7048
  answerReceipt: branding.answerReceipt === true ? true : void 0,
6829
7049
  readAloud: branding.readAloud === true ? true : void 0,
6830
7050
  fontFamily: normalizeOptionalValue(branding.fontFamily),