@sensiblestats/widget-react 0.19.0 → 0.20.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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @sensiblestats/widget-react
2
2
 
3
+ ## 0.20.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Club-dashboard taps (FixtureHero CTA, MatchPreview/TeamDna facts) now send the backend's opaque `actionRef` instead of a prose command, and `useChatStream` gains a client-side fallback timeout so the panel can no longer hang forever waiting for a `turn_complete`/`error` that never arrives.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @sensiblestats/widget-sdk@0.17.0
13
+
3
14
  ## 0.19.0
4
15
 
5
16
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -625,21 +625,42 @@ function reducer(state, action) {
625
625
  }
626
626
 
627
627
  // src/useChatStream.ts
628
+ var STREAM_FALLBACK_TIMEOUT_MS = 3e4;
628
629
  function useChatStream(conversation, starterTexts, greetingMessage) {
629
630
  const seed = (0, import_react.useMemo)(() => initialState(starterTexts.map(chipFromText)), []);
630
631
  const [state, dispatch] = (0, import_react.useReducer)(reducer, seed);
631
632
  const idRef = (0, import_react.useRef)(0);
632
633
  const handleRef = (0, import_react.useRef)(null);
634
+ const fallbackTimerRef = (0, import_react.useRef)(null);
633
635
  const stateRef = (0, import_react.useRef)(state);
634
636
  stateRef.current = state;
635
637
  const greetedRef = (0, import_react.useRef)(false);
636
638
  const nextId = () => `m${idRef.current++}`;
639
+ const clearFallbackTimer = (0, import_react.useCallback)(() => {
640
+ if (fallbackTimerRef.current != null) {
641
+ clearTimeout(fallbackTimerRef.current);
642
+ fallbackTimerRef.current = null;
643
+ }
644
+ }, []);
645
+ const armFallbackTimer = (0, import_react.useCallback)((onFire) => {
646
+ clearFallbackTimer();
647
+ fallbackTimerRef.current = setTimeout(() => {
648
+ fallbackTimerRef.current = null;
649
+ handleRef.current?.cancel();
650
+ handleRef.current = null;
651
+ onFire();
652
+ }, STREAM_FALLBACK_TIMEOUT_MS);
653
+ }, [clearFallbackTimer]);
637
654
  const consume = (0, import_react.useCallback)((handle, onError) => {
638
655
  ;
639
656
  (async () => {
640
657
  try {
641
- for await (const event of handle) dispatch({ kind: "EVENT", event });
658
+ for await (const event of handle) {
659
+ dispatch({ kind: "EVENT", event });
660
+ if (event.type === "turn_complete" || event.type === "error") clearFallbackTimer();
661
+ }
642
662
  } catch (err) {
663
+ if (handleRef.current === handle) clearFallbackTimer();
643
664
  if (err instanceof import_widget_sdk3.WidgetSdkError) onError(err);
644
665
  else if (err?.name === "AbortError") {
645
666
  } else throw err;
@@ -647,18 +668,23 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
647
668
  if (handleRef.current === handle) handleRef.current = null;
648
669
  }
649
670
  })();
650
- }, []);
671
+ }, [clearFallbackTimer]);
651
672
  const run = (0, import_react.useCallback)((input, displayText) => {
652
673
  handleRef.current?.cancel();
674
+ clearFallbackTimer();
653
675
  dispatch({ kind: "USER_SENT", input, userId: nextId(), assistantId: nextId(), displayText });
654
676
  const handle = conversation.send(input);
655
677
  handleRef.current = handle;
678
+ armFallbackTimer(() => {
679
+ dispatch({ kind: "STOP" });
680
+ dispatch({ kind: "STREAM_ERROR", error: new import_widget_sdk3.WidgetSdkError("No response received in time", "timeout") });
681
+ });
656
682
  consume(handle, (err) => dispatch({ kind: "STREAM_ERROR", error: err }));
657
- }, [conversation, consume]);
683
+ }, [conversation, consume, clearFallbackTimer, armFallbackTimer]);
658
684
  const send = (0, import_react.useCallback)((text, opts) => {
659
685
  const trimmed = text.trim();
660
686
  if (!trimmed) return;
661
- run({ message: trimmed, actionId: opts?.actionId, grounding: opts?.grounding }, opts?.displayText);
687
+ run({ message: trimmed, actionId: opts?.actionId, actionRef: opts?.actionRef }, opts?.displayText);
662
688
  }, [run]);
663
689
  const retry = (0, import_react.useCallback)(() => {
664
690
  const last = stateRef.current.lastUserInput;
@@ -670,25 +696,29 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
670
696
  greetedRef.current = true;
671
697
  try {
672
698
  handleRef.current?.cancel();
699
+ clearFallbackTimer();
673
700
  dispatch({ kind: "GREETING_SENT", assistantId: nextId() });
674
701
  const handle = conversation.send({ message });
675
702
  handleRef.current = handle;
703
+ armFallbackTimer(() => dispatch({ kind: "STOP" }));
676
704
  consume(handle, () => dispatch({ kind: "STOP" }));
677
705
  } catch {
678
706
  dispatch({ kind: "STOP" });
679
707
  }
680
- }, [greetingMessage, conversation, consume]);
708
+ }, [greetingMessage, conversation, consume, clearFallbackTimer, armFallbackTimer]);
681
709
  const open = (0, import_react.useCallback)(() => dispatch({ kind: "OPEN" }), []);
682
710
  const close = (0, import_react.useCallback)(() => {
683
711
  handleRef.current?.cancel();
684
712
  handleRef.current = null;
713
+ clearFallbackTimer();
685
714
  dispatch({ kind: "CLOSE" });
686
- }, []);
715
+ }, [clearFallbackTimer]);
687
716
  const stop = (0, import_react.useCallback)(() => {
688
717
  handleRef.current?.cancel();
689
718
  handleRef.current = null;
719
+ clearFallbackTimer();
690
720
  dispatch({ kind: "STOP" });
691
- }, []);
721
+ }, [clearFallbackTimer]);
692
722
  const toggle = (0, import_react.useCallback)(() => {
693
723
  if (stateRef.current.isOpen) close();
694
724
  else open();
@@ -1353,7 +1383,7 @@ function FixtureHeroBlock({
1353
1383
  block.venue ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "ss-w-cdash-venue", children: block.venue }) : null,
1354
1384
  block.competition ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "ss-w-cdash-comp", children: block.competition }) : null
1355
1385
  ] }),
1356
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: "ss-w-cdash-cta", onClick: () => onAction(block.ctaActionValue, void 0, block.ctaLabel), children: block.ctaLabel })
1386
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: "ss-w-cdash-cta", onClick: () => onAction(block.ctaLabel, { actionRef: block.ctaActionRef }), children: block.ctaLabel })
1357
1387
  ] });
1358
1388
  }
1359
1389
 
@@ -1382,12 +1412,12 @@ function MatchPreviewBlock({
1382
1412
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "ss-w-cdash-lines", children: [
1383
1413
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1384
1414
  block.facts.map(
1385
- (f, i) => f.entityRef ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
1415
+ (f, i) => f.actionRef ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
1386
1416
  "button",
1387
1417
  {
1388
1418
  type: "button",
1389
1419
  className: "ss-w-cdash-fact",
1390
- onClick: () => onAction(f.question ?? f.sentence, { ...f.entityRef, subject: f.sentence, personaHint: "club" }),
1420
+ onClick: () => onAction(f.label ?? f.sentence, { actionRef: f.actionRef }),
1391
1421
  children: [
1392
1422
  f.sentence,
1393
1423
  f.label ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "ss-w-cdash-fact-label", children: [
@@ -1414,12 +1444,12 @@ function TeamDnaBlock({
1414
1444
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "ss-w-cdash-lines", children: [
1415
1445
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1416
1446
  block.facts.map(
1417
- (f, i) => f.entityRef ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
1447
+ (f, i) => f.actionRef ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
1418
1448
  "button",
1419
1449
  {
1420
1450
  type: "button",
1421
1451
  className: "ss-w-cdash-fact",
1422
- onClick: () => onAction(f.question ?? f.sentence, { ...f.entityRef, subject: f.sentence, personaHint: "club" }),
1452
+ onClick: () => onAction(f.label ?? f.sentence, { actionRef: f.actionRef }),
1423
1453
  children: [
1424
1454
  f.sentence,
1425
1455
  f.label ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "ss-w-cdash-fact-label", children: [
@@ -1565,7 +1595,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1565
1595
  send(tile.actionValue, { displayText: tile.title });
1566
1596
  if (m.role === "assistant" && m.greetingDashboard) onDashboardTap?.(tile, index, m.greetingDashboard.layout);
1567
1597
  },
1568
- onClubAction: (message, grounding, displayText) => send(message, { grounding, displayText: displayText ?? message }),
1598
+ onClubAction: (label, opts) => send(label, { actionRef: opts.actionRef }),
1569
1599
  ui,
1570
1600
  slip
1571
1601
  },