@webless/agent 0.7.0 → 0.7.2

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
@@ -4517,6 +4517,126 @@ function toSpeechText(text) {
4517
4517
  function isSpeechSupported() {
4518
4518
  return typeof window !== "undefined" && "speechSynthesis" in window && typeof window.SpeechSynthesisUtterance === "function";
4519
4519
  }
4520
+ function stopSpeech() {
4521
+ if (!isSpeechSupported()) return;
4522
+ window.speechSynthesis.cancel();
4523
+ }
4524
+ var speechInterruptListeners = /* @__PURE__ */ new Set();
4525
+ var pageListenersAttached = false;
4526
+ var lastPathname = "";
4527
+ var originalPushState = null;
4528
+ var originalReplaceState = null;
4529
+ var patchedPushState = null;
4530
+ var patchedReplaceState = null;
4531
+ function currentPathname() {
4532
+ return window.location.pathname;
4533
+ }
4534
+ function notifySpeechInterrupts() {
4535
+ if (speechInterruptListeners.size === 0) return;
4536
+ stopSpeech();
4537
+ for (const listener of speechInterruptListeners) {
4538
+ listener();
4539
+ }
4540
+ }
4541
+ function interruptIfPathChanged(nextPath) {
4542
+ if (nextPath === lastPathname) return;
4543
+ lastPathname = nextPath;
4544
+ notifySpeechInterrupts();
4545
+ }
4546
+ function patchHistoryMethod(original) {
4547
+ return function patched(data, unused, url) {
4548
+ const result = original.call(this, data, unused, url);
4549
+ interruptIfPathChanged(currentPathname());
4550
+ return result;
4551
+ };
4552
+ }
4553
+ function historyMethodState(name) {
4554
+ return name === "pushState" ? {
4555
+ original: originalPushState,
4556
+ patched: patchedPushState,
4557
+ set(original, patched) {
4558
+ originalPushState = original;
4559
+ patchedPushState = patched;
4560
+ }
4561
+ } : {
4562
+ original: originalReplaceState,
4563
+ patched: patchedReplaceState,
4564
+ set(original, patched) {
4565
+ originalReplaceState = original;
4566
+ patchedReplaceState = patched;
4567
+ }
4568
+ };
4569
+ }
4570
+ function patchHistoryNamed(name) {
4571
+ const state = historyMethodState(name);
4572
+ const current = history[name];
4573
+ if (state.patched && current === state.patched) return;
4574
+ const patched = patchHistoryMethod(current);
4575
+ state.set(current, patched);
4576
+ history[name] = patched;
4577
+ }
4578
+ function restoreHistoryMethod(name) {
4579
+ const state = historyMethodState(name);
4580
+ if (!state.patched || !state.original || typeof history === "undefined") {
4581
+ return false;
4582
+ }
4583
+ if (history[name] !== state.patched) return false;
4584
+ history[name] = state.original;
4585
+ state.set(null, null);
4586
+ return true;
4587
+ }
4588
+ function handlePageHide() {
4589
+ notifySpeechInterrupts();
4590
+ }
4591
+ function handlePopState() {
4592
+ interruptIfPathChanged(currentPathname());
4593
+ }
4594
+ function handleVisibilityChange() {
4595
+ if (document.visibilityState === "hidden") {
4596
+ notifySpeechInterrupts();
4597
+ }
4598
+ }
4599
+ function attachPageListeners() {
4600
+ if (pageListenersAttached || typeof window === "undefined") return;
4601
+ pageListenersAttached = true;
4602
+ window.addEventListener("pagehide", handlePageHide);
4603
+ window.addEventListener("popstate", handlePopState);
4604
+ document.addEventListener("visibilitychange", handleVisibilityChange);
4605
+ }
4606
+ function detachPageListeners() {
4607
+ if (!pageListenersAttached || typeof window === "undefined") return;
4608
+ window.removeEventListener("pagehide", handlePageHide);
4609
+ window.removeEventListener("popstate", handlePopState);
4610
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
4611
+ pageListenersAttached = false;
4612
+ }
4613
+ function ensureSpeechInterruptsPatched() {
4614
+ if (typeof window === "undefined") return;
4615
+ lastPathname = currentPathname();
4616
+ patchHistoryNamed("pushState");
4617
+ patchHistoryNamed("replaceState");
4618
+ attachPageListeners();
4619
+ }
4620
+ function teardownSpeechInterrupts() {
4621
+ detachPageListeners();
4622
+ restoreHistoryMethod("pushState");
4623
+ restoreHistoryMethod("replaceState");
4624
+ if (!patchedPushState && !patchedReplaceState) {
4625
+ lastPathname = "";
4626
+ }
4627
+ }
4628
+ function subscribeSpeechInterrupts(onInterrupt) {
4629
+ if (typeof window === "undefined") return () => {
4630
+ };
4631
+ ensureSpeechInterruptsPatched();
4632
+ speechInterruptListeners.add(onInterrupt);
4633
+ return () => {
4634
+ speechInterruptListeners.delete(onInterrupt);
4635
+ if (speechInterruptListeners.size === 0) {
4636
+ teardownSpeechInterrupts();
4637
+ }
4638
+ };
4639
+ }
4520
4640
 
4521
4641
  // src/react/components/MessageActions/MessageActions.tsx
4522
4642
  var import_jsx_runtime7 = require("react/jsx-runtime");
@@ -4772,15 +4892,20 @@ function MessageActions({
4772
4892
  const [menuOpen, setMenuOpen] = (0, import_react11.useState)(false);
4773
4893
  const [menuPosition, setMenuPosition] = (0, import_react11.useState)(null);
4774
4894
  const [speaking, setSpeaking] = (0, import_react11.useState)(false);
4895
+ const [speechSupported, setSpeechSupported] = (0, import_react11.useState)(null);
4775
4896
  const copyTimerRef = (0, import_react11.useRef)(null);
4776
4897
  const menuRef = (0, import_react11.useRef)(null);
4777
4898
  const portaledMenuRef = (0, import_react11.useRef)(null);
4778
4899
  const moreButtonRef = (0, import_react11.useRef)(null);
4779
4900
  const answeredLabel = formatAnsweredAt(answeredAt ?? 0);
4780
4901
  const resolvedSpeechText = speechText ?? toSpeechText(copyText);
4781
- const canReadAloud = readAloud && isSpeechSupported() && resolvedSpeechText;
4782
- const showMenu = Boolean(answeredLabel) || Boolean(receiptSteps) || canReadAloud;
4783
4902
  const canOpenReceipt = Boolean(receiptSteps) && Boolean(onOpenReceipt);
4903
+ const readAloudEligible = Boolean(readAloud && resolvedSpeechText);
4904
+ const canReadAloud = readAloudEligible && speechSupported === true;
4905
+ const showMenu = canOpenReceipt || (speechSupported === null ? readAloudEligible : canReadAloud);
4906
+ (0, import_react11.useLayoutEffect)(() => {
4907
+ setSpeechSupported(isSpeechSupported());
4908
+ }, []);
4784
4909
  (0, import_react11.useLayoutEffect)(() => {
4785
4910
  if (!menuOpen || !moreButtonRef.current || !portaledMenuRef.current) {
4786
4911
  setMenuPosition(null);
@@ -4798,17 +4923,16 @@ function MessageActions({
4798
4923
  })
4799
4924
  );
4800
4925
  }, [menuOpen, menuPortalRoot]);
4801
- (0, import_react11.useEffect)(
4802
- () => () => {
4926
+ (0, import_react11.useEffect)(() => {
4927
+ const unsubscribe = subscribeSpeechInterrupts(() => setSpeaking(false));
4928
+ return () => {
4929
+ unsubscribe();
4803
4930
  if (copyTimerRef.current !== null) {
4804
4931
  window.clearTimeout(copyTimerRef.current);
4805
4932
  }
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
- );
4933
+ stopSpeech();
4934
+ };
4935
+ }, []);
4812
4936
  (0, import_react11.useEffect)(() => {
4813
4937
  if (!menuOpen) return;
4814
4938
  const handlePointerDown = (event) => {
@@ -4847,14 +4971,14 @@ function MessageActions({
4847
4971
  if (!canReadAloud) return;
4848
4972
  setMenuOpen(false);
4849
4973
  if (speaking) {
4850
- window.speechSynthesis.cancel();
4974
+ stopSpeech();
4851
4975
  setSpeaking(false);
4852
4976
  return;
4853
4977
  }
4854
4978
  const utterance = new SpeechSynthesisUtterance(resolvedSpeechText);
4855
4979
  utterance.onend = () => setSpeaking(false);
4856
4980
  utterance.onerror = () => setSpeaking(false);
4857
- window.speechSynthesis.cancel();
4981
+ stopSpeech();
4858
4982
  window.speechSynthesis.speak(utterance);
4859
4983
  setSpeaking(true);
4860
4984
  }
@@ -5752,8 +5876,9 @@ function AgentRail({
5752
5876
  colorScheme = "auto",
5753
5877
  brandLabel = "",
5754
5878
  brandLogoUrl,
5755
- poweredByLabel = "Powered by Webless",
5879
+ poweredByLabel,
5756
5880
  disclaimerLabel,
5881
+ disclaimerLink,
5757
5882
  answerReceipt = false,
5758
5883
  readAloud = false,
5759
5884
  composerPlaceholder = "Ask anything\u2026",
@@ -6188,7 +6313,15 @@ function AgentRail({
6188
6313
  onRetry ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
6189
6314
  ] }) : null
6190
6315
  ] }) }),
6191
- showDisclaimerLabel ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "agent-rail__disclaimer", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { children: resolvedDisclaimerLabel }) }) : null,
6316
+ 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)(
6317
+ "a",
6318
+ {
6319
+ href: disclaimerLink,
6320
+ rel: "noopener noreferrer",
6321
+ target: "_blank",
6322
+ children: resolvedDisclaimerLabel
6323
+ }
6324
+ ) : resolvedDisclaimerLabel }) }) : null,
6192
6325
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "agent-rail__composer-wrap", children: [
6193
6326
  showJumpToLatest ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6194
6327
  "button",
@@ -6653,8 +6786,9 @@ function AgentWidget({
6653
6786
  brandLabel: agentName,
6654
6787
  brandLogoUrl: branding?.logoUrl,
6655
6788
  composerPlaceholder: branding?.composerPlaceholder ?? "Ask a question\u2026",
6656
- poweredByLabel: branding?.poweredByLabel ?? "Powered by Webless",
6789
+ poweredByLabel: branding?.poweredByLabel,
6657
6790
  disclaimerLabel: branding?.disclaimer,
6791
+ disclaimerLink: branding?.disclaimerLink,
6658
6792
  answerReceipt: branding?.answerReceipt,
6659
6793
  readAloud: branding?.readAloud,
6660
6794
  state,
@@ -6808,6 +6942,20 @@ function normalizeOptionalValue(value) {
6808
6942
  const normalized = value?.trim();
6809
6943
  return normalized || void 0;
6810
6944
  }
6945
+ function normalizeOptionalHttpUrl(value) {
6946
+ if (value === null) return null;
6947
+ const normalized = value?.trim();
6948
+ if (!normalized) return void 0;
6949
+ try {
6950
+ const url = new URL(normalized);
6951
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
6952
+ return void 0;
6953
+ }
6954
+ return normalized;
6955
+ } catch {
6956
+ return void 0;
6957
+ }
6958
+ }
6811
6959
  function normalizeAgentBranding(branding) {
6812
6960
  if (!branding) return void 0;
6813
6961
  const colors = branding.colors ? Object.fromEntries(
@@ -6825,6 +6973,7 @@ function normalizeAgentBranding(branding) {
6825
6973
  composerPlaceholder: normalizeOptionalValue(branding.composerPlaceholder),
6826
6974
  poweredByLabel: normalizeOptionalValue(branding.poweredByLabel),
6827
6975
  disclaimer: branding.disclaimer === null ? null : normalizeOptionalValue(branding.disclaimer),
6976
+ disclaimerLink: normalizeOptionalHttpUrl(branding.disclaimerLink),
6828
6977
  answerReceipt: branding.answerReceipt === true ? true : void 0,
6829
6978
  readAloud: branding.readAloud === true ? true : void 0,
6830
6979
  fontFamily: normalizeOptionalValue(branding.fontFamily),