@sensiblestats/widget-react 0.25.0 → 0.26.1
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 +17 -0
- package/dist/index.cjs +254 -116
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +216 -78
- package/dist/index.js.map +1 -1
- package/dist/styles.css +26 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/StatsWidget.tsx
|
|
2
|
-
import { useEffect as
|
|
2
|
+
import { useEffect as useEffect7, useMemo as useMemo2, useRef as useRef4 } from "react";
|
|
3
3
|
import { StatsWidgetClient } from "@sensiblestats/widget-sdk";
|
|
4
4
|
|
|
5
5
|
// src/kickoff.ts
|
|
@@ -225,7 +225,7 @@ function themeAttrs(ui) {
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
// src/useChatStream.ts
|
|
228
|
-
import { useCallback, useEffect, useMemo, useReducer, useRef } from "react";
|
|
228
|
+
import { useCallback, useEffect as useEffect2, useMemo, useReducer, useRef as useRef2 } from "react";
|
|
229
229
|
import { WidgetSdkError as WidgetSdkError2 } from "@sensiblestats/widget-sdk";
|
|
230
230
|
|
|
231
231
|
// src/reducer.ts
|
|
@@ -567,7 +567,50 @@ function reducer(state, action) {
|
|
|
567
567
|
return { ...state, error: action.error, isStreaming: false, status: null, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, done: true })) };
|
|
568
568
|
case "STOP":
|
|
569
569
|
return { ...state, isStreaming: false, status: null, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, done: true })) };
|
|
570
|
+
case "LIVE_REFRESH":
|
|
571
|
+
return {
|
|
572
|
+
...state,
|
|
573
|
+
messages: state.messages.map(
|
|
574
|
+
(m) => m.role === "assistant" && m.id === action.messageId && m.clubFanDashboard ? { ...m, clubFanDashboard: { ...m.clubFanDashboard, cards: action.cards } } : m
|
|
575
|
+
)
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// src/useLiveDashboardPoll.ts
|
|
581
|
+
import { useEffect, useRef } from "react";
|
|
582
|
+
var POLL_INTERVAL_MS = 6e4;
|
|
583
|
+
function findLiveMessageId(messages) {
|
|
584
|
+
for (const m of messages.slice().reverse()) {
|
|
585
|
+
if (m.role === "assistant" && m.clubFanDashboard?.phase === "live") return m.id;
|
|
570
586
|
}
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
function useLiveDashboardPoll({ messages, dispatch, getLiveDashboard }) {
|
|
590
|
+
const messageId = findLiveMessageId(messages);
|
|
591
|
+
const inFlightRef = useRef(false);
|
|
592
|
+
useEffect(() => {
|
|
593
|
+
if (!messageId) return;
|
|
594
|
+
const tick = async () => {
|
|
595
|
+
if (inFlightRef.current) return;
|
|
596
|
+
inFlightRef.current = true;
|
|
597
|
+
try {
|
|
598
|
+
const payload = await getLiveDashboard();
|
|
599
|
+
if (payload && payload.phase === "live") {
|
|
600
|
+
dispatch({ kind: "LIVE_REFRESH", messageId, cards: payload.cards });
|
|
601
|
+
} else {
|
|
602
|
+
window.clearInterval(id);
|
|
603
|
+
}
|
|
604
|
+
} catch {
|
|
605
|
+
} finally {
|
|
606
|
+
inFlightRef.current = false;
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
const id = window.setInterval(() => {
|
|
610
|
+
void tick();
|
|
611
|
+
}, POLL_INTERVAL_MS);
|
|
612
|
+
return () => window.clearInterval(id);
|
|
613
|
+
}, [messageId, dispatch, getLiveDashboard]);
|
|
571
614
|
}
|
|
572
615
|
|
|
573
616
|
// src/useChatStream.ts
|
|
@@ -575,12 +618,12 @@ var STREAM_FALLBACK_TIMEOUT_MS = 3e4;
|
|
|
575
618
|
function useChatStream(conversation, starterTexts, greetingMessage) {
|
|
576
619
|
const seed = useMemo(() => initialState(starterTexts.map(chipFromText)), []);
|
|
577
620
|
const [state, dispatch] = useReducer(reducer, seed);
|
|
578
|
-
const idRef =
|
|
579
|
-
const handleRef =
|
|
580
|
-
const fallbackTimerRef =
|
|
581
|
-
const stateRef =
|
|
621
|
+
const idRef = useRef2(0);
|
|
622
|
+
const handleRef = useRef2(null);
|
|
623
|
+
const fallbackTimerRef = useRef2(null);
|
|
624
|
+
const stateRef = useRef2(state);
|
|
582
625
|
stateRef.current = state;
|
|
583
|
-
const greetedRef =
|
|
626
|
+
const greetedRef = useRef2(false);
|
|
584
627
|
const nextId = () => `m${idRef.current++}`;
|
|
585
628
|
const clearFallbackTimer = useCallback(() => {
|
|
586
629
|
if (fallbackTimerRef.current != null) {
|
|
@@ -636,7 +679,7 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
|
|
|
636
679
|
const last = stateRef.current.lastUserInput;
|
|
637
680
|
if (last) run(last, stateRef.current.lastDisplayText ?? void 0);
|
|
638
681
|
}, [run]);
|
|
639
|
-
|
|
682
|
+
useEffect2(() => {
|
|
640
683
|
const message = greetingMessage?.trim();
|
|
641
684
|
if (!message || greetedRef.current) return;
|
|
642
685
|
greetedRef.current = true;
|
|
@@ -670,6 +713,8 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
|
|
|
670
713
|
else open();
|
|
671
714
|
}, [close, open]);
|
|
672
715
|
const dismissPopup = useCallback(() => dispatch({ kind: "DISMISS_POPUP" }), []);
|
|
716
|
+
const getLiveDashboard = useCallback(() => conversation.getLiveDashboard(), [conversation]);
|
|
717
|
+
useLiveDashboardPoll({ messages: state.messages, dispatch, getLiveDashboard });
|
|
673
718
|
return { state, open, close, stop, toggle, send, retry, dismissPopup };
|
|
674
719
|
}
|
|
675
720
|
|
|
@@ -730,7 +775,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
|
|
|
730
775
|
}
|
|
731
776
|
|
|
732
777
|
// src/components/ChatPanel.tsx
|
|
733
|
-
import { useEffect as
|
|
778
|
+
import { useEffect as useEffect6, useState as useState8 } from "react";
|
|
734
779
|
|
|
735
780
|
// src/components/ChatHeader.tsx
|
|
736
781
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -743,7 +788,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat" }) {
|
|
|
743
788
|
}
|
|
744
789
|
|
|
745
790
|
// src/components/MessageList.tsx
|
|
746
|
-
import { useEffect as
|
|
791
|
+
import { useEffect as useEffect5, useRef as useRef3 } from "react";
|
|
747
792
|
|
|
748
793
|
// src/components/Markdown.tsx
|
|
749
794
|
import ReactMarkdown from "react-markdown";
|
|
@@ -898,11 +943,11 @@ function resultToToast(result, shownOdds, ui) {
|
|
|
898
943
|
}
|
|
899
944
|
|
|
900
945
|
// src/components/Toast.tsx
|
|
901
|
-
import { useEffect as
|
|
946
|
+
import { useEffect as useEffect3 } from "react";
|
|
902
947
|
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
903
948
|
var DISMISS_MS = 4e3;
|
|
904
949
|
function Toast({ toast, onDismiss }) {
|
|
905
|
-
|
|
950
|
+
useEffect3(() => {
|
|
906
951
|
if (!toast) return;
|
|
907
952
|
const timer = setTimeout(onDismiss, DISMISS_MS);
|
|
908
953
|
return () => clearTimeout(timer);
|
|
@@ -1370,7 +1415,7 @@ function FixtureCard({ card, onTap }) {
|
|
|
1370
1415
|
}
|
|
1371
1416
|
|
|
1372
1417
|
// src/components/dashboard/CountdownCard.tsx
|
|
1373
|
-
import { Fragment as Fragment6, useEffect as
|
|
1418
|
+
import { Fragment as Fragment6, useEffect as useEffect4, useState as useState6 } from "react";
|
|
1374
1419
|
import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1375
1420
|
var DAY_MS = 864e5;
|
|
1376
1421
|
function remainingSegments(kickoffUnix, nowMs, withSeconds) {
|
|
@@ -1394,7 +1439,7 @@ function CountdownCard({ card }) {
|
|
|
1394
1439
|
() => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
1395
1440
|
);
|
|
1396
1441
|
const withinDay = data.kickoffUnix * 1e3 - nowMs < DAY_MS;
|
|
1397
|
-
|
|
1442
|
+
useEffect4(() => {
|
|
1398
1443
|
if (reduced) return;
|
|
1399
1444
|
const id = window.setInterval(() => setNowMs(Date.now()), withinDay ? 1e3 : 6e4);
|
|
1400
1445
|
return () => window.clearInterval(id);
|
|
@@ -1650,85 +1695,139 @@ function RestingFormCard({ card }) {
|
|
|
1650
1695
|
] });
|
|
1651
1696
|
}
|
|
1652
1697
|
|
|
1653
|
-
// src/components/dashboard/
|
|
1698
|
+
// src/components/dashboard/LiveScoreCard.tsx
|
|
1654
1699
|
import { jsx as jsx34, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
1700
|
+
function LiveScoreCard({ card }) {
|
|
1701
|
+
const { data } = card;
|
|
1702
|
+
return /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-card ss-w-fandash-live-score", children: [
|
|
1703
|
+
card.title ? /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
|
|
1704
|
+
/* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-postmatch-scoreboard", children: [
|
|
1705
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-team", children: data.homeName }),
|
|
1706
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-score", children: data.homeScore }),
|
|
1707
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-sep", children: "\u2013" }),
|
|
1708
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-score", children: data.awayScore }),
|
|
1709
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-team ss-w-fandash-postmatch-team--away", children: data.awayName })
|
|
1710
|
+
] }),
|
|
1711
|
+
/* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-meta", children: [
|
|
1712
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-minute", children: data.minuteLabel }),
|
|
1713
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-status", children: data.statusLabel }),
|
|
1714
|
+
data.competition ? /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-competition", children: data.competition }) : null
|
|
1715
|
+
] })
|
|
1716
|
+
] });
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
// src/components/dashboard/LiveTimelineCard.tsx
|
|
1720
|
+
import { jsx as jsx35, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
1721
|
+
function LiveTimelineCard({ card }) {
|
|
1722
|
+
return /* @__PURE__ */ jsxs31("div", { className: "ss-w-fandash-card ss-w-fandash-live-timeline", children: [
|
|
1723
|
+
card.title ? /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
|
|
1724
|
+
/* @__PURE__ */ jsx35("ul", { className: "ss-w-fandash-live-event-list", children: card.data.events.map((e, i) => /* @__PURE__ */ jsxs31("li", { className: e.scoredByClub ? "ss-w-fandash-live-event ss-w-fandash-live-event--club" : "ss-w-fandash-live-event", children: [
|
|
1725
|
+
/* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-event-minute", children: e.minuteLabel }),
|
|
1726
|
+
/* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-event-text", children: e.text })
|
|
1727
|
+
] }, `${e.minuteLabel}-${i}`)) })
|
|
1728
|
+
] });
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// src/components/dashboard/LiveStatsCard.tsx
|
|
1732
|
+
import { jsx as jsx36, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
1733
|
+
function LiveStatsCard({ card }) {
|
|
1734
|
+
const { data } = card;
|
|
1735
|
+
return /* @__PURE__ */ jsxs32("div", { className: "ss-w-fandash-card ss-w-fandash-live-stats", children: [
|
|
1736
|
+
card.title ? /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
|
|
1737
|
+
/* @__PURE__ */ jsx36("div", { className: "ss-w-fandash-live-stat-rows", children: data.rows.map((r, i) => /* @__PURE__ */ jsxs32("div", { className: "ss-w-fandash-live-stat-row", children: [
|
|
1738
|
+
/* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-stat-value", children: r.clubValue }),
|
|
1739
|
+
/* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-stat-label", children: r.label }),
|
|
1740
|
+
/* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-stat-value", children: r.oppValue })
|
|
1741
|
+
] }, `${r.label}-${i}`)) }),
|
|
1742
|
+
data.momentumLabel ? /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-momentum", children: data.momentumLabel }) : null
|
|
1743
|
+
] });
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// src/components/dashboard/ClubFanDashboard.tsx
|
|
1747
|
+
import { jsx as jsx37, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
1655
1748
|
function ClubFanDashboard({ dashboard, onTap, ui }) {
|
|
1656
1749
|
const orderedCards = [
|
|
1657
1750
|
...dashboard.cards.filter((c) => c.cardType === "countdown"),
|
|
1658
1751
|
...dashboard.cards.filter((c) => c.cardType !== "countdown")
|
|
1659
1752
|
];
|
|
1660
|
-
return /* @__PURE__ */
|
|
1661
|
-
dashboard.greeting ? /* @__PURE__ */
|
|
1662
|
-
/* @__PURE__ */
|
|
1753
|
+
return /* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash", children: [
|
|
1754
|
+
dashboard.greeting ? /* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-greeting", children: dashboard.greeting }) : null,
|
|
1755
|
+
/* @__PURE__ */ jsx37("div", { className: "ss-w-fandash-cards", children: orderedCards.map((card, i) => /* @__PURE__ */ jsx37(FanDashboardCard, { card, onTap, ui }, `${card.cardType}-${i}`)) })
|
|
1663
1756
|
] });
|
|
1664
1757
|
}
|
|
1665
1758
|
function FanDashboardCard({ card, onTap, ui }) {
|
|
1666
1759
|
switch (card.cardType) {
|
|
1667
1760
|
case "fixture":
|
|
1668
|
-
return /* @__PURE__ */
|
|
1761
|
+
return /* @__PURE__ */ jsx37(FixtureCard, { card, onTap });
|
|
1669
1762
|
case "countdown":
|
|
1670
|
-
return /* @__PURE__ */
|
|
1763
|
+
return /* @__PURE__ */ jsx37(CountdownCard, { card });
|
|
1671
1764
|
case "injuries":
|
|
1672
|
-
return /* @__PURE__ */
|
|
1765
|
+
return /* @__PURE__ */ jsx37(InjuriesCard, { card });
|
|
1673
1766
|
case "reasonToBet":
|
|
1674
|
-
return /* @__PURE__ */
|
|
1767
|
+
return /* @__PURE__ */ jsx37(ReasonToBetCard, { card });
|
|
1675
1768
|
case "whereToWatch":
|
|
1676
|
-
return /* @__PURE__ */
|
|
1769
|
+
return /* @__PURE__ */ jsx37(WhereToWatchCard, { card });
|
|
1677
1770
|
case "congestedWeek":
|
|
1678
|
-
return /* @__PURE__ */
|
|
1771
|
+
return /* @__PURE__ */ jsx37(CongestedWeekCard, { card, onTap });
|
|
1679
1772
|
case "transfers":
|
|
1680
|
-
return /* @__PURE__ */
|
|
1773
|
+
return /* @__PURE__ */ jsx37(TransfersCard, { card });
|
|
1681
1774
|
case "links":
|
|
1682
|
-
return /* @__PURE__ */
|
|
1775
|
+
return /* @__PURE__ */ jsx37(LinksCard, { card });
|
|
1683
1776
|
case "postMatchResult":
|
|
1684
|
-
return /* @__PURE__ */
|
|
1777
|
+
return /* @__PURE__ */ jsx37(PostMatchResultCard, { card });
|
|
1685
1778
|
case "postMatchGoals":
|
|
1686
|
-
return /* @__PURE__ */
|
|
1779
|
+
return /* @__PURE__ */ jsx37(PostMatchGoalsCard, { card, onTap });
|
|
1687
1780
|
case "postMatchStats":
|
|
1688
|
-
return /* @__PURE__ */
|
|
1781
|
+
return /* @__PURE__ */ jsx37(PostMatchStatsCard, { card, onTap });
|
|
1689
1782
|
case "postMatchTopPerformer":
|
|
1690
|
-
return /* @__PURE__ */
|
|
1783
|
+
return /* @__PURE__ */ jsx37(PostMatchTopPerformerCard, { card, onTap });
|
|
1691
1784
|
case "postMatchNext":
|
|
1692
|
-
return /* @__PURE__ */
|
|
1785
|
+
return /* @__PURE__ */ jsx37(PostMatchNextCard, { card });
|
|
1693
1786
|
case "restingNext":
|
|
1694
|
-
return /* @__PURE__ */
|
|
1787
|
+
return /* @__PURE__ */ jsx37(RestingNextCard, { card, onTap });
|
|
1695
1788
|
case "restingStandings":
|
|
1696
|
-
return /* @__PURE__ */
|
|
1789
|
+
return /* @__PURE__ */ jsx37(RestingStandingsCard, { card, onTap });
|
|
1697
1790
|
case "restingSeason":
|
|
1698
|
-
return /* @__PURE__ */
|
|
1791
|
+
return /* @__PURE__ */ jsx37(RestingSeasonCard, { card, onTap });
|
|
1699
1792
|
case "restingForm":
|
|
1700
|
-
return /* @__PURE__ */
|
|
1793
|
+
return /* @__PURE__ */ jsx37(RestingFormCard, { card });
|
|
1794
|
+
case "liveScore":
|
|
1795
|
+
return /* @__PURE__ */ jsx37(LiveScoreCard, { card });
|
|
1796
|
+
case "liveTimeline":
|
|
1797
|
+
return /* @__PURE__ */ jsx37(LiveTimelineCard, { card });
|
|
1798
|
+
case "liveStats":
|
|
1799
|
+
return /* @__PURE__ */ jsx37(LiveStatsCard, { card });
|
|
1701
1800
|
default:
|
|
1702
1801
|
return null;
|
|
1703
1802
|
}
|
|
1704
1803
|
}
|
|
1705
1804
|
|
|
1706
1805
|
// src/components/ChatMessage.tsx
|
|
1707
|
-
import { jsx as
|
|
1806
|
+
import { jsx as jsx38, jsxs as jsxs34 } from "react/jsx-runtime";
|
|
1708
1807
|
function ChatMessage({ message, disabled, onAct, onTap, ui, slip }) {
|
|
1709
1808
|
if (message.role === "user") {
|
|
1710
|
-
return /* @__PURE__ */
|
|
1809
|
+
return /* @__PURE__ */ jsx38("div", { className: "ss-w-msg ss-w-user", "data-msg-id": message.id, children: /* @__PURE__ */ jsx38("div", { className: "ss-w-bubble", children: message.text }) });
|
|
1711
1810
|
}
|
|
1712
1811
|
const onFanDashboardTap = (card) => {
|
|
1713
1812
|
onAct({ message: card.actionLabel, actionRef: card.actionRef, label: card.actionLabel || card.title || "" });
|
|
1714
1813
|
};
|
|
1715
|
-
return /* @__PURE__ */
|
|
1716
|
-
message.text && !message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */
|
|
1717
|
-
message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */
|
|
1718
|
-
message.clubFanDashboard ? /* @__PURE__ */
|
|
1719
|
-
/* @__PURE__ */
|
|
1720
|
-
/* @__PURE__ */
|
|
1721
|
-
/* @__PURE__ */
|
|
1722
|
-
/* @__PURE__ */
|
|
1723
|
-
/* @__PURE__ */
|
|
1814
|
+
return /* @__PURE__ */ jsxs34("div", { className: "ss-w-msg ss-w-bot", "data-msg-id": message.id, children: [
|
|
1815
|
+
message.text && !message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx38(Markdown, { text: message.text }) : null,
|
|
1816
|
+
message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx38(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
|
|
1817
|
+
message.clubFanDashboard ? /* @__PURE__ */ jsx38(ClubFanDashboard, { dashboard: message.clubFanDashboard, onTap: onFanDashboardTap, ui }) : null,
|
|
1818
|
+
/* @__PURE__ */ jsx38(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
|
|
1819
|
+
/* @__PURE__ */ jsx38(BettingInsightList, { insights: message.insights, ui, slip }),
|
|
1820
|
+
/* @__PURE__ */ jsx38(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
|
|
1821
|
+
/* @__PURE__ */ jsx38(OddsMovementCardList, { cards: message.oddsMovement, ui }),
|
|
1822
|
+
/* @__PURE__ */ jsx38(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
|
|
1724
1823
|
] });
|
|
1725
1824
|
}
|
|
1726
1825
|
|
|
1727
1826
|
// src/components/IntentChips.tsx
|
|
1728
|
-
import { jsx as
|
|
1827
|
+
import { jsx as jsx39 } from "react/jsx-runtime";
|
|
1729
1828
|
function IntentChips({ chips, onPick }) {
|
|
1730
1829
|
if (chips.length === 0) return null;
|
|
1731
|
-
return /* @__PURE__ */
|
|
1830
|
+
return /* @__PURE__ */ jsx39("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ jsx39("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
|
|
1732
1831
|
}
|
|
1733
1832
|
|
|
1734
1833
|
// src/scroll.ts
|
|
@@ -1738,18 +1837,32 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
|
|
|
1738
1837
|
}
|
|
1739
1838
|
|
|
1740
1839
|
// src/components/MessageList.tsx
|
|
1741
|
-
import { jsx as
|
|
1840
|
+
import { jsx as jsx40, jsxs as jsxs35 } from "react/jsx-runtime";
|
|
1841
|
+
function turnAnchor(messages) {
|
|
1842
|
+
const last = messages[messages.length - 1];
|
|
1843
|
+
if (!last || last.role !== "assistant") return null;
|
|
1844
|
+
const prev = messages[messages.length - 2];
|
|
1845
|
+
return { turnId: last.id, anchorId: prev && prev.role === "user" ? prev.id : last.id };
|
|
1846
|
+
}
|
|
1742
1847
|
function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
1743
1848
|
const { state, send } = controller;
|
|
1744
|
-
const listRef =
|
|
1745
|
-
const endRef =
|
|
1746
|
-
const pinnedRef =
|
|
1747
|
-
|
|
1849
|
+
const listRef = useRef3(null);
|
|
1850
|
+
const endRef = useRef3(null);
|
|
1851
|
+
const pinnedRef = useRef3(true);
|
|
1852
|
+
const anchoredTurnRef = useRef3(null);
|
|
1853
|
+
useEffect5(() => {
|
|
1854
|
+
const anchor = turnAnchor(state.messages);
|
|
1855
|
+
if (anchor && anchor.turnId !== anchoredTurnRef.current) {
|
|
1856
|
+
anchoredTurnRef.current = anchor.turnId;
|
|
1857
|
+
pinnedRef.current = false;
|
|
1858
|
+
listRef.current?.querySelector(`[data-msg-id="${anchor.anchorId}"]`)?.scrollIntoView({ block: "start" });
|
|
1859
|
+
return;
|
|
1860
|
+
}
|
|
1748
1861
|
if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
|
|
1749
1862
|
}, [state.messages, state.status]);
|
|
1750
1863
|
const hasUserMessage = state.messages.some((m) => m.role === "user");
|
|
1751
1864
|
const hasDashboard = state.messages.some((m) => m.role === "assistant" && (m.greetingDashboard || m.clubFanDashboard));
|
|
1752
|
-
return /* @__PURE__ */
|
|
1865
|
+
return /* @__PURE__ */ jsxs35(
|
|
1753
1866
|
"div",
|
|
1754
1867
|
{
|
|
1755
1868
|
className: "ss-w-list",
|
|
@@ -1759,7 +1872,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
|
1759
1872
|
if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
|
|
1760
1873
|
},
|
|
1761
1874
|
children: [
|
|
1762
|
-
state.messages.map((m) => /* @__PURE__ */
|
|
1875
|
+
state.messages.map((m) => /* @__PURE__ */ jsx40(
|
|
1763
1876
|
ChatMessage,
|
|
1764
1877
|
{
|
|
1765
1878
|
message: m,
|
|
@@ -1774,15 +1887,15 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
|
1774
1887
|
},
|
|
1775
1888
|
m.id
|
|
1776
1889
|
)),
|
|
1777
|
-
state.isStreaming && state.actionsLoading ? /* @__PURE__ */
|
|
1890
|
+
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx40(ActionButtons, { actions: [], loading: true, onAct: () => {
|
|
1778
1891
|
} }) : null,
|
|
1779
|
-
state.status ? /* @__PURE__ */
|
|
1780
|
-
state.error ? /* @__PURE__ */
|
|
1781
|
-
/* @__PURE__ */
|
|
1782
|
-
/* @__PURE__ */
|
|
1892
|
+
state.status ? /* @__PURE__ */ jsx40("div", { className: "ss-w-status", children: state.status }) : null,
|
|
1893
|
+
state.error ? /* @__PURE__ */ jsxs35("div", { className: "ss-w-error", role: "alert", children: [
|
|
1894
|
+
/* @__PURE__ */ jsx40("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
|
|
1895
|
+
/* @__PURE__ */ jsx40("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
|
|
1783
1896
|
] }) : null,
|
|
1784
|
-
!hasUserMessage && !hasDashboard ? /* @__PURE__ */
|
|
1785
|
-
/* @__PURE__ */
|
|
1897
|
+
!hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx40(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
|
|
1898
|
+
/* @__PURE__ */ jsx40("div", { ref: endRef })
|
|
1786
1899
|
]
|
|
1787
1900
|
}
|
|
1788
1901
|
);
|
|
@@ -1790,7 +1903,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
|
1790
1903
|
|
|
1791
1904
|
// src/components/ChatInput.tsx
|
|
1792
1905
|
import { useState as useState7 } from "react";
|
|
1793
|
-
import { jsx as
|
|
1906
|
+
import { jsx as jsx41, jsxs as jsxs36 } from "react/jsx-runtime";
|
|
1794
1907
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
1795
1908
|
const [value, setValue] = useState7("");
|
|
1796
1909
|
const submit = () => {
|
|
@@ -1805,18 +1918,18 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
|
|
|
1805
1918
|
submit();
|
|
1806
1919
|
}
|
|
1807
1920
|
};
|
|
1808
|
-
return /* @__PURE__ */
|
|
1809
|
-
/* @__PURE__ */
|
|
1810
|
-
streaming ? /* @__PURE__ */
|
|
1921
|
+
return /* @__PURE__ */ jsxs36("div", { className: "ss-w-input", children: [
|
|
1922
|
+
/* @__PURE__ */ jsx41("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
|
|
1923
|
+
streaming ? /* @__PURE__ */ jsx41("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ jsx41("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx41("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ jsx41(SendIcon, {}) })
|
|
1811
1924
|
] });
|
|
1812
1925
|
}
|
|
1813
1926
|
|
|
1814
1927
|
// src/components/ChatPanel.tsx
|
|
1815
|
-
import { jsx as
|
|
1928
|
+
import { jsx as jsx42, jsxs as jsxs37 } from "react/jsx-runtime";
|
|
1816
1929
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
1817
1930
|
function useIsModal() {
|
|
1818
1931
|
const [modal, setModal] = useState8(false);
|
|
1819
|
-
|
|
1932
|
+
useEffect6(() => {
|
|
1820
1933
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
1821
1934
|
const mql = window.matchMedia(FULLSCREEN_QUERY);
|
|
1822
1935
|
const sync = () => setModal(mql.matches);
|
|
@@ -1829,17 +1942,17 @@ function useIsModal() {
|
|
|
1829
1942
|
function ChatPanel({ controller, ui, slip, onDashboardTap }) {
|
|
1830
1943
|
const { state, close, stop, send } = controller;
|
|
1831
1944
|
const isModal = useIsModal();
|
|
1832
|
-
|
|
1945
|
+
useEffect6(() => {
|
|
1833
1946
|
const onKey = (e) => {
|
|
1834
1947
|
if (e.key === "Escape") close();
|
|
1835
1948
|
};
|
|
1836
1949
|
window.addEventListener("keydown", onKey);
|
|
1837
1950
|
return () => window.removeEventListener("keydown", onKey);
|
|
1838
1951
|
}, [close]);
|
|
1839
|
-
return /* @__PURE__ */
|
|
1840
|
-
/* @__PURE__ */
|
|
1841
|
-
/* @__PURE__ */
|
|
1842
|
-
/* @__PURE__ */
|
|
1952
|
+
return /* @__PURE__ */ jsxs37("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
1953
|
+
/* @__PURE__ */ jsx42(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
|
|
1954
|
+
/* @__PURE__ */ jsx42(MessageList, { controller, ui, slip, onDashboardTap }),
|
|
1955
|
+
/* @__PURE__ */ jsx42(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
|
|
1843
1956
|
] });
|
|
1844
1957
|
}
|
|
1845
1958
|
|
|
@@ -1903,7 +2016,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
1903
2016
|
.ss-w-close { margin-left: auto; background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 16px; display: grid; place-items: center; }
|
|
1904
2017
|
|
|
1905
2018
|
/* messages */
|
|
1906
|
-
.ss-w-list { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 12px; }
|
|
2019
|
+
.ss-w-list { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 12px; scroll-padding-top: 14px; }
|
|
1907
2020
|
.ss-w-msg { display: flex; }
|
|
1908
2021
|
.ss-w-user { justify-content: flex-end; }
|
|
1909
2022
|
.ss-w-user .ss-w-bubble { background: color-mix(in srgb, var(--ss-w-accent) 14%, var(--ss-w-panel)); border: 1px solid color-mix(in srgb, var(--ss-w-accent) 30%, var(--ss-w-line)); border-radius: 14px; padding: 8px 12px; max-width: 80%; }
|
|
@@ -2351,10 +2464,35 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
2351
2464
|
|
|
2352
2465
|
/* resting form: reuses the fixture last-five pill classes (ss-w-fandash-form / -form-pill) */
|
|
2353
2466
|
.ss-w-fandash-resting-form .ss-w-fandash-form { margin-top: 2px; }
|
|
2467
|
+
|
|
2468
|
+
/* live score: reuses the post-match scoreboard classes for the team/score row, with its own
|
|
2469
|
+
meta row -- minute called out in the accent color, status and competition beside it */
|
|
2470
|
+
.ss-w-fandash-live-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 11.5px; color: var(--ss-w-faint); }
|
|
2471
|
+
.ss-w-fandash-live-minute { flex: none; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ss-w-accent); }
|
|
2472
|
+
.ss-w-fandash-live-status { font-weight: 650; color: var(--ss-w-ink); }
|
|
2473
|
+
.ss-w-fandash-live-competition { color: var(--ss-w-faint); }
|
|
2474
|
+
|
|
2475
|
+
/* live timeline: a plain event list, the club's own events picked out in the accent color --
|
|
2476
|
+
same mechanism as the post-match goal list, plus a minute column */
|
|
2477
|
+
.ss-w-fandash-live-event-list { list-style: none; display: flex; flex-direction: column; gap: 4px; }
|
|
2478
|
+
.ss-w-fandash-live-event { display: flex; align-items: baseline; gap: 8px; font-size: 12.5px; color: var(--ss-w-faint); }
|
|
2479
|
+
.ss-w-fandash-live-event--club { color: var(--ss-w-ink); font-weight: 650; }
|
|
2480
|
+
.ss-w-fandash-live-event-minute { flex: none; font-variant-numeric: tabular-nums; color: var(--ss-w-faint); }
|
|
2481
|
+
.ss-w-fandash-live-event--club .ss-w-fandash-live-event-minute { color: var(--ss-w-accent); }
|
|
2482
|
+
.ss-w-fandash-live-event-text { min-width: 0; }
|
|
2483
|
+
|
|
2484
|
+
/* live stats: club value / stat label / opponent value, same 3-column layout as post-match
|
|
2485
|
+
stats, plus an optional momentum line underneath */
|
|
2486
|
+
.ss-w-fandash-live-stat-rows { display: flex; flex-direction: column; gap: 5px; }
|
|
2487
|
+
.ss-w-fandash-live-stat-row { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 8px; }
|
|
2488
|
+
.ss-w-fandash-live-stat-value { font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ss-w-ink); }
|
|
2489
|
+
.ss-w-fandash-live-stat-row .ss-w-fandash-live-stat-value:last-child { text-align: right; }
|
|
2490
|
+
.ss-w-fandash-live-stat-label { font-size: 10.5px; text-align: center; color: var(--ss-w-faint); text-transform: uppercase; letter-spacing: .03em; }
|
|
2491
|
+
.ss-w-fandash-live-momentum { font-size: 11.5px; font-weight: 650; color: var(--ss-w-accent); }
|
|
2354
2492
|
`;
|
|
2355
2493
|
|
|
2356
2494
|
// src/StatsWidget.tsx
|
|
2357
|
-
import { jsx as
|
|
2495
|
+
import { jsx as jsx43 } from "react/jsx-runtime";
|
|
2358
2496
|
function buildClient(props) {
|
|
2359
2497
|
try {
|
|
2360
2498
|
if ("client" in props && props.client) {
|
|
@@ -2381,7 +2519,7 @@ function StatsWidget(props) {
|
|
|
2381
2519
|
const cfg = props.config;
|
|
2382
2520
|
const built = useMemo2(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
|
|
2383
2521
|
const ui = useMemo2(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
|
|
2384
|
-
const rootRef =
|
|
2522
|
+
const rootRef = useRef4(null);
|
|
2385
2523
|
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
|
|
2386
2524
|
const slip = useMemo2(
|
|
2387
2525
|
() => ({
|
|
@@ -2400,14 +2538,14 @@ function StatsWidget(props) {
|
|
|
2400
2538
|
() => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
|
|
2401
2539
|
[built]
|
|
2402
2540
|
);
|
|
2403
|
-
|
|
2541
|
+
useEffect7(() => {
|
|
2404
2542
|
if (!built || !ui.injectStyles) return;
|
|
2405
2543
|
const node = rootRef.current?.getRootNode();
|
|
2406
2544
|
if (node) injectStyles(node);
|
|
2407
2545
|
}, [built, ui.injectStyles]);
|
|
2408
2546
|
if (!built) return null;
|
|
2409
2547
|
const attrs = themeAttrs(ui);
|
|
2410
|
-
return /* @__PURE__ */
|
|
2548
|
+
return /* @__PURE__ */ jsx43("div", { ref: rootRef, className: "ss-w-root", "data-ss-w-theme": attrs["data-ss-w-theme"], "data-ss-w-pos": attrs["data-ss-w-pos"], style: attrs.style, children: controller.state.isOpen ? /* @__PURE__ */ jsx43(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ jsx43(
|
|
2411
2549
|
ChatFab,
|
|
2412
2550
|
{
|
|
2413
2551
|
label: ui.launcherLabel,
|