@eshal-bot/chat-widget 0.2.54 → 0.2.56

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.
@@ -1 +1 @@
1
- export{S as default}from"./chat-widget-index-C0VY-3ea.esm.js";import"react";import"react-dom/client";import"react/jsx-runtime";import"react-dom";
1
+ export{S as default}from"./chat-widget-index-dlp0htSy.esm.js";import"react";import"react-dom/client";import"react/jsx-runtime";import"react-dom";
@@ -13640,6 +13640,32 @@
13640
13640
  }
13641
13641
  const VERIFY_CARD = /"type"\s*:\s*"verification"/;
13642
13642
 
13643
+ /**
13644
+ * What the caller said after the answer that carried the card on screen, or
13645
+ * undefined while nothing has followed it. The call screen redraws its card
13646
+ * on every new line, so a card cannot keep its own "picked" state: an option
13647
+ * list tapped once came back live, ready for a second pick, until the agent's
13648
+ * next answer replaced it. Read from the lines, as the chat reads it from
13649
+ * history (MessageList), it locks the card on the caller's answer, tapped or
13650
+ * said. A verify card is left out: it stays live while the caller talks, until
13651
+ * its code is confirmed (waitsForCode).
13652
+ */
13653
+ function voiceCardAnswer(lines) {
13654
+ let at = -1;
13655
+ for (let i = lines.length - 1; i >= 0; i--) {
13656
+ if (lines[i].role === "assistant" && lines[i].a2ui) {
13657
+ at = i;
13658
+ break;
13659
+ }
13660
+ }
13661
+ if (at === -1 || VERIFY_CARD.test(lines[at].a2ui)) return undefined;
13662
+ for (let i = at + 1; i < lines.length; i++) {
13663
+ const line = lines[i];
13664
+ if (line.role === "user" && !line.sendFailed && String(line.content || "").trim()) return String(line.content);
13665
+ }
13666
+ return undefined;
13667
+ }
13668
+
13643
13669
  /** A verification code card (VerifyCard) whose code is not confirmed yet. */
13644
13670
  function waitsForCode(line) {
13645
13671
  return Boolean(line?.a2ui && VERIFY_CARD.test(line.a2ui) && !line.cardDone);
@@ -13743,7 +13769,8 @@
13743
13769
  * Inbox (contentType human_reply, handoff_ack), the away and holding lines
13744
13770
  * sent while the visitor waits for a person (handoff_wait, EV-4803; backend
13745
13771
  * services/widgetHandoffLines.ts), the code card an agent sends with Request
13746
- * verification (verification_request; backend services/verificationRequest.ts)
13772
+ * verification (verification_request; backend services/verificationRequest.ts),
13773
+ * a payment's outcome (payment_status; backend services/paymentEvents.ts)
13747
13774
  * and a Skill's reply to a staff
13748
13775
  * turn (staffTurn: Instruct Skill, or a hand-back that resumes the Skill;
13749
13776
  * backend services/staffTurn.ts). Dev, 15 Oct 2026: a Skill reply sent while
@@ -13751,7 +13778,7 @@
13751
13778
  * replies were merged. Mirrors packages/frontend/src/lib/messages/repliedOutsideStream.ts.
13752
13779
  */
13753
13780
  function isRepliedOutsideStream(m) {
13754
- return m?.contentType === 'human_reply' || m?.contentType === 'handoff_ack' || m?.contentType === 'handoff_wait' || m?.contentType === 'verification_request' || m?.staffTurn === true;
13781
+ return m?.contentType === 'human_reply' || m?.contentType === 'handoff_ack' || m?.contentType === 'handoff_wait' || m?.contentType === 'verification_request' || m?.contentType === 'payment_status' || m?.staffTurn === true;
13755
13782
  }
13756
13783
 
13757
13784
  /**
@@ -13763,6 +13790,84 @@
13763
13790
  return m?.responder === 'ai' || m?.responder === 'human' ? m.responder : null;
13764
13791
  }
13765
13792
 
13793
+ /**
13794
+ * Whether an agent reply put a payment link on the screen.
13795
+ *
13796
+ * Dev, 13 Nov 2026, Aurelia Airways: the agent held a booking and put the
13797
+ * secure payment link on the screen; the customer paid, and heard nothing until
13798
+ * they asked. A payment's outcome now reaches the conversation's event stream
13799
+ * (backend services/paymentEvents.ts, contentType payment_status). The widget
13800
+ * listens to that stream only once a person has the chat, so while a reply's
13801
+ * payment link is open it listens too, and the outcome shows the moment it
13802
+ * lands.
13803
+ *
13804
+ * The link rides an `eshal-ui` card the Skill writes: an action whose value is
13805
+ * the address ("Go to checkout", a Shopify cart; "Pay securely", a checkout
13806
+ * page). Mirrors services/voice-runtime/voice_runtime/payment_link.py `find`.
13807
+ */
13808
+
13809
+ const FENCE = /```eshal-ui\s*([\s\S]*?)```/g;
13810
+ const PAY_WORDS = /\b(pay|pay now|payment|checkout|check out|purchase|complete (?:the )?(?:booking|order|purchase)|secure (?:payment|checkout)|place (?:the )?order)\b/i;
13811
+ const PAY_PATH = /(?:^|[/._?=&-])(pay|payment|payments|checkout|checkouts|cart|invoice)(?:$|[/._?=&-])/i;
13812
+ function cardsIn(content) {
13813
+ const cards = [];
13814
+ if (typeof content !== 'string' || !content.includes('eshal-ui')) return cards;
13815
+ for (const match of content.matchAll(FENCE)) {
13816
+ try {
13817
+ const parsed = JSON.parse(match[1].trim());
13818
+ if (Array.isArray(parsed)) cards.push(...parsed.filter(c => c && typeof c === 'object'));else if (parsed && typeof parsed === 'object') cards.push(parsed);
13819
+ } catch {
13820
+ /* not a card */
13821
+ }
13822
+ }
13823
+ return cards;
13824
+ }
13825
+ function actionsOf(card) {
13826
+ const own = Array.isArray(card.actions) ? card.actions.filter(a => a && typeof a === 'object') : [];
13827
+ const children = Array.isArray(card.children) ? card.children : Array.isArray(card.items) ? card.items : [];
13828
+ return own.concat(...children.filter(c => c && typeof c === 'object').map(actionsOf));
13829
+ }
13830
+ function addressOf(action) {
13831
+ for (const key of ['value', 'url', 'href', 'link']) {
13832
+ const v = action[key];
13833
+ if (typeof v === 'string' && /^https?:\/\//.test(v)) return v;
13834
+ }
13835
+ return null;
13836
+ }
13837
+
13838
+ /** True when the reply's text carries a card with a payment link. */
13839
+ function hasPaymentLink(content) {
13840
+ for (const card of cardsIn(content)) {
13841
+ const title = String(card.title || '');
13842
+ for (const action of actionsOf(card)) {
13843
+ const url = addressOf(action);
13844
+ if (!url) continue;
13845
+ let path = '';
13846
+ try {
13847
+ const u = new URL(url);
13848
+ path = `${u.pathname}?${u.search.replace(/^\?/, '')}`;
13849
+ } catch {
13850
+ continue;
13851
+ }
13852
+ if (PAY_WORDS.test(String(action.label || '')) || PAY_WORDS.test(title) || PAY_PATH.test(path)) return true;
13853
+ }
13854
+ }
13855
+ return false;
13856
+ }
13857
+
13858
+ /** True when the newest agent reply in the transcript put a payment link on the screen. */
13859
+ function awaitsPayment(messages) {
13860
+ if (!Array.isArray(messages)) return false;
13861
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
13862
+ const m = messages[i];
13863
+ if (m?.role !== 'assistant') continue;
13864
+ if (m?.metadata?.contentType === 'payment_status') return false; // the outcome is in
13865
+ if (m?.isProcessing) continue;
13866
+ return hasPaymentLink(m.content);
13867
+ }
13868
+ return false;
13869
+ }
13870
+
13766
13871
  // EV-4048: human-agent identification in the widget transcript.
13767
13872
  //
13768
13873
  // A message pushed by a human agent (inbox reply / takeover) carries
@@ -15058,6 +15163,14 @@
15058
15163
  }
15059
15164
  };
15060
15165
  }, []);
15166
+
15167
+ // A reply's payment link is open: the conversation's stream is read for the
15168
+ // payment's outcome, so it shows the moment it lands (lib/paymentLink.js;
15169
+ // backend services/paymentEvents.ts). Dev, 13 Nov 2026, Aurelia: the customer
15170
+ // paid and was told only once they asked.
15171
+ reactExports.useEffect(() => {
15172
+ if (awaitsPayment(messages)) void startHandoffEventSource();
15173
+ }, [messages, startHandoffEventSource]);
15061
15174
  const websocketUrl = reactExports.useMemo(() => {
15062
15175
  if (!organizationId) return null;
15063
15176
  const resolvedWsBaseUrl = getWebSocketUrl(apiBaseUrl, wsBaseUrl);
@@ -23780,6 +23893,7 @@
23780
23893
  const lastAgent = latestLine(lines, "agent");
23781
23894
  const lastCaller = latestLine(lines, "user");
23782
23895
  const card = latestVoiceCard(lines);
23896
+ const cardAnswer = voiceCardAnswer(lines);
23783
23897
  reactExports.useEffect(() => {
23784
23898
  const el = scrollRef.current;
23785
23899
  if (el) el.scrollTop = el.scrollHeight;
@@ -23851,13 +23965,16 @@
23851
23965
  children: /*#__PURE__*/jsxRuntimeExports.jsx(CardBoundary, {
23852
23966
  children: /*#__PURE__*/jsxRuntimeExports.jsx(reactExports.Suspense, {
23853
23967
  fallback: null,
23854
- children: /*#__PURE__*/jsxRuntimeExports.jsx(MarkdownRenderer, {
23855
- content: card,
23856
- isDark: isDark,
23857
- isRtl: isRtl,
23858
- fontFamily: fontFamily,
23859
- provideCitations: false,
23860
- onButtonClick: (value, _label, extra) => onCardAction?.(String(value ?? ""), extra)
23968
+ children: /*#__PURE__*/jsxRuntimeExports.jsx(A2uiAnsweredContext.Provider, {
23969
+ value: cardAnswer,
23970
+ children: /*#__PURE__*/jsxRuntimeExports.jsx(MarkdownRenderer, {
23971
+ content: card,
23972
+ isDark: isDark,
23973
+ isRtl: isRtl,
23974
+ fontFamily: fontFamily,
23975
+ provideCitations: false,
23976
+ onButtonClick: (value, _label, extra) => onCardAction?.(String(value ?? ""), extra)
23977
+ })
23861
23978
  })
23862
23979
  })
23863
23980
  }, card)
@@ -25674,7 +25791,7 @@
25674
25791
  * value by its own test (that constant sat at 0.1.113 while this package was
25675
25792
  * at 0.2.2 precisely because nothing enforced it - EV-2431).
25676
25793
  */
25677
- const WIDGET_VERSION = '0.2.54';
25794
+ const WIDGET_VERSION = '0.2.56';
25678
25795
 
25679
25796
  /**
25680
25797
  * API utility functions