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