@sensiblestats/widget-react 0.28.0 → 0.29.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 +56 -0
- package/dist/index.cjs +473 -136
- 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 +472 -135
- package/dist/index.js.map +1 -1
- package/dist/styles.css +83 -23
- 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 useEffect8, useMemo as useMemo2, useRef as useRef5 } 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 as useEffect2, useMemo, useReducer, useRef as useRef2 } from "react";
|
|
228
|
+
import { useCallback, useEffect as useEffect2, useMemo, useReducer as useReducer2, useRef as useRef2 } from "react";
|
|
229
229
|
import { WidgetSdkError as WidgetSdkError2 } from "@sensiblestats/widget-sdk";
|
|
230
230
|
|
|
231
231
|
// src/reducer.ts
|
|
@@ -571,35 +571,90 @@ function reducer(state, action) {
|
|
|
571
571
|
return {
|
|
572
572
|
...state,
|
|
573
573
|
messages: state.messages.map(
|
|
574
|
-
(m) => m.role === "assistant" && m.id === action.messageId && m.clubFanDashboard ? { ...m, clubFanDashboard: { ...m.clubFanDashboard, cards: action.cards } } : m
|
|
574
|
+
(m) => m.role === "assistant" && m.id === action.messageId && m.clubFanDashboard ? { ...m, clubFanDashboard: { ...m.clubFanDashboard, cards: action.cards, greeting: action.greeting ?? m.clubFanDashboard.greeting } } : m
|
|
575
|
+
)
|
|
576
|
+
};
|
|
577
|
+
case "LIVE_TRANSITION":
|
|
578
|
+
return {
|
|
579
|
+
...state,
|
|
580
|
+
messages: state.messages.map(
|
|
581
|
+
(m) => m.role === "assistant" && m.id === action.messageId && m.clubFanDashboard ? { ...m, clubFanDashboard: action.dashboard } : m
|
|
575
582
|
)
|
|
576
583
|
};
|
|
577
584
|
}
|
|
578
585
|
}
|
|
579
586
|
|
|
580
587
|
// src/useLiveDashboardPoll.ts
|
|
581
|
-
import { useEffect, useRef } from "react";
|
|
588
|
+
import { useEffect, useReducer, useRef } from "react";
|
|
582
589
|
var POLL_INTERVAL_MS = 6e4;
|
|
583
|
-
|
|
590
|
+
var LEAD_MS = 12e4;
|
|
591
|
+
var GIVE_UP_MS = 30 * 6e4;
|
|
592
|
+
function extractKickoffMs(dashboard) {
|
|
593
|
+
const countdown = dashboard.cards.find((c) => c.cardType === "countdown");
|
|
594
|
+
if (countdown) return countdown.data.kickoffUnix * 1e3;
|
|
595
|
+
for (const c of dashboard.cards) {
|
|
596
|
+
const d = c.data;
|
|
597
|
+
if (typeof d?.kickoffUnix === "number") return d.kickoffUnix * 1e3;
|
|
598
|
+
}
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
function findAnchor(messages) {
|
|
602
|
+
const now = Date.now();
|
|
584
603
|
for (const m of messages.slice().reverse()) {
|
|
585
|
-
if (m.role
|
|
604
|
+
if (m.role !== "assistant" || !m.clubFanDashboard) continue;
|
|
605
|
+
const { phase } = m.clubFanDashboard;
|
|
606
|
+
if (phase === "live") return { messageId: m.id, kind: "live" };
|
|
607
|
+
if (phase === "preMatch") {
|
|
608
|
+
const kickoffMs = extractKickoffMs(m.clubFanDashboard);
|
|
609
|
+
if (kickoffMs === null) continue;
|
|
610
|
+
if (now >= kickoffMs - LEAD_MS && now <= kickoffMs + GIVE_UP_MS) {
|
|
611
|
+
return { messageId: m.id, kind: "preMatch", kickoffMs };
|
|
612
|
+
}
|
|
613
|
+
}
|
|
586
614
|
}
|
|
587
615
|
return null;
|
|
588
616
|
}
|
|
617
|
+
function findEarliestWakeupMs(messages) {
|
|
618
|
+
const now = Date.now();
|
|
619
|
+
let earliest = null;
|
|
620
|
+
for (const m of messages) {
|
|
621
|
+
if (m.role !== "assistant" || m.clubFanDashboard?.phase !== "preMatch") continue;
|
|
622
|
+
const kickoffMs = extractKickoffMs(m.clubFanDashboard);
|
|
623
|
+
if (kickoffMs === null) continue;
|
|
624
|
+
const wakeAt = kickoffMs - LEAD_MS;
|
|
625
|
+
if (wakeAt > now && (earliest === null || wakeAt < earliest)) earliest = wakeAt;
|
|
626
|
+
}
|
|
627
|
+
return earliest;
|
|
628
|
+
}
|
|
589
629
|
function useLiveDashboardPoll({ messages, dispatch, getLiveDashboard }) {
|
|
590
|
-
const
|
|
630
|
+
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
|
631
|
+
const anchor = findAnchor(messages);
|
|
632
|
+
const anchorRef = useRef(anchor);
|
|
633
|
+
anchorRef.current = anchor;
|
|
591
634
|
const inFlightRef = useRef(false);
|
|
635
|
+
const anchorKey = anchor ? `${anchor.messageId}:${anchor.kind}` : null;
|
|
592
636
|
useEffect(() => {
|
|
593
|
-
if (!
|
|
637
|
+
if (!anchorKey || !anchorRef.current) return;
|
|
594
638
|
const tick = async () => {
|
|
595
639
|
if (inFlightRef.current) return;
|
|
596
640
|
inFlightRef.current = true;
|
|
597
641
|
try {
|
|
598
642
|
const payload = await getLiveDashboard();
|
|
599
|
-
|
|
600
|
-
|
|
643
|
+
const current = anchorRef.current;
|
|
644
|
+
if (!current) return;
|
|
645
|
+
if (payload?.phase === "live") {
|
|
646
|
+
if (current.kind === "preMatch") {
|
|
647
|
+
dispatch({ kind: "LIVE_TRANSITION", messageId: current.messageId, dashboard: payload });
|
|
648
|
+
window.clearInterval(id);
|
|
649
|
+
} else {
|
|
650
|
+
dispatch({ kind: "LIVE_REFRESH", messageId: current.messageId, cards: payload.cards, greeting: payload.greeting });
|
|
651
|
+
}
|
|
601
652
|
} else {
|
|
602
|
-
|
|
653
|
+
if (current.kind === "live") {
|
|
654
|
+
window.clearInterval(id);
|
|
655
|
+
} else if (Date.now() > current.kickoffMs + GIVE_UP_MS) {
|
|
656
|
+
window.clearInterval(id);
|
|
657
|
+
}
|
|
603
658
|
}
|
|
604
659
|
} catch {
|
|
605
660
|
} finally {
|
|
@@ -610,14 +665,23 @@ function useLiveDashboardPoll({ messages, dispatch, getLiveDashboard }) {
|
|
|
610
665
|
void tick();
|
|
611
666
|
}, POLL_INTERVAL_MS);
|
|
612
667
|
return () => window.clearInterval(id);
|
|
613
|
-
}, [
|
|
668
|
+
}, [anchorKey, dispatch, getLiveDashboard]);
|
|
669
|
+
useEffect(() => {
|
|
670
|
+
if (anchorKey !== null) return;
|
|
671
|
+
const wakeAt = findEarliestWakeupMs(messages);
|
|
672
|
+
if (wakeAt === null) return;
|
|
673
|
+
const delay = wakeAt - Date.now();
|
|
674
|
+
if (delay <= 0) return;
|
|
675
|
+
const id = window.setTimeout(forceUpdate, delay);
|
|
676
|
+
return () => window.clearTimeout(id);
|
|
677
|
+
}, [anchorKey, messages, forceUpdate]);
|
|
614
678
|
}
|
|
615
679
|
|
|
616
680
|
// src/useChatStream.ts
|
|
617
681
|
var STREAM_FALLBACK_TIMEOUT_MS = 3e4;
|
|
618
682
|
function useChatStream(conversation, starterTexts, greetingMessage) {
|
|
619
683
|
const seed = useMemo(() => initialState(starterTexts.map(chipFromText)), []);
|
|
620
|
-
const [state, dispatch] =
|
|
684
|
+
const [state, dispatch] = useReducer2(reducer, seed);
|
|
621
685
|
const idRef = useRef2(0);
|
|
622
686
|
const handleRef = useRef2(null);
|
|
623
687
|
const fallbackTimerRef = useRef2(null);
|
|
@@ -775,7 +839,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
|
|
|
775
839
|
}
|
|
776
840
|
|
|
777
841
|
// src/components/ChatPanel.tsx
|
|
778
|
-
import { useEffect as
|
|
842
|
+
import { useEffect as useEffect7, useState as useState11 } from "react";
|
|
779
843
|
|
|
780
844
|
// src/components/ChatHeader.tsx
|
|
781
845
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -788,7 +852,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat" }) {
|
|
|
788
852
|
}
|
|
789
853
|
|
|
790
854
|
// src/components/MessageList.tsx
|
|
791
|
-
import { useEffect as
|
|
855
|
+
import { useEffect as useEffect6, useRef as useRef4 } from "react";
|
|
792
856
|
|
|
793
857
|
// src/components/Markdown.tsx
|
|
794
858
|
import ReactMarkdown from "react-markdown";
|
|
@@ -1718,139 +1782,352 @@ function RestingFormCard({ card }) {
|
|
|
1718
1782
|
] });
|
|
1719
1783
|
}
|
|
1720
1784
|
|
|
1785
|
+
// src/components/dashboard/LiveScoreCard.tsx
|
|
1786
|
+
import { useRef as useRef3, useState as useState8 } from "react";
|
|
1787
|
+
|
|
1788
|
+
// src/useLiveClock.ts
|
|
1789
|
+
import { useEffect as useEffect5, useState as useState7 } from "react";
|
|
1790
|
+
var TICK_MS = 6e4;
|
|
1791
|
+
var MAX_LIVE_TICK_MINUTES = 15;
|
|
1792
|
+
function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
1793
|
+
if (!clockRunning) return minute;
|
|
1794
|
+
const elapsedMinutes = Math.floor((Date.now() - receivedAtMs) / TICK_MS);
|
|
1795
|
+
return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
|
|
1796
|
+
}
|
|
1797
|
+
function useLiveClock(minute, clockRunning, receivedAtMs) {
|
|
1798
|
+
const [displayMinute, setDisplayMinute] = useState7(
|
|
1799
|
+
() => computeDisplayMinute(minute, clockRunning, receivedAtMs)
|
|
1800
|
+
);
|
|
1801
|
+
useEffect5(() => {
|
|
1802
|
+
setDisplayMinute(computeDisplayMinute(minute, clockRunning, receivedAtMs));
|
|
1803
|
+
if (!clockRunning) return;
|
|
1804
|
+
const id = window.setInterval(() => {
|
|
1805
|
+
setDisplayMinute(computeDisplayMinute(minute, clockRunning, receivedAtMs));
|
|
1806
|
+
}, TICK_MS);
|
|
1807
|
+
return () => window.clearInterval(id);
|
|
1808
|
+
}, [minute, clockRunning, receivedAtMs]);
|
|
1809
|
+
return displayMinute;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1721
1812
|
// src/components/dashboard/LiveScoreCard.tsx
|
|
1722
1813
|
import { jsx as jsx34, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
1723
|
-
function
|
|
1814
|
+
function initials3(name) {
|
|
1815
|
+
return name.trim().slice(0, 3).toUpperCase();
|
|
1816
|
+
}
|
|
1817
|
+
function LiveCrest({ url, name, side }) {
|
|
1818
|
+
const [failed, setFailed] = useState8(false);
|
|
1819
|
+
const cls = `ss-w-fandash-live-crest ss-w-fandash-live-crest--${side}`;
|
|
1820
|
+
if (url && !failed) {
|
|
1821
|
+
return /* @__PURE__ */ jsx34("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
1822
|
+
}
|
|
1823
|
+
return /* @__PURE__ */ jsx34("span", { className: `${cls} ss-w-fandash-live-crest--fallback`, "aria-hidden": "true", children: initials3(name) });
|
|
1824
|
+
}
|
|
1825
|
+
function scorersLine(goalEvents) {
|
|
1826
|
+
if (!goalEvents || goalEvents.length === 0) return null;
|
|
1827
|
+
const named = goalEvents.filter((e) => e.type === "goal" && e.player);
|
|
1828
|
+
if (named.length === 0) return null;
|
|
1829
|
+
return named.slice().sort((a, b) => a.minute - b.minute).map((e) => `${e.player} ${e.minuteLabel}`).join(", ");
|
|
1830
|
+
}
|
|
1831
|
+
function LiveScoreCard({
|
|
1832
|
+
card,
|
|
1833
|
+
goalEvents
|
|
1834
|
+
}) {
|
|
1724
1835
|
const { data } = card;
|
|
1836
|
+
const seedKey = `${data.minute}:${data.homeScore}:${data.awayScore}`;
|
|
1837
|
+
const seedRef = useRef3(null);
|
|
1838
|
+
if (!seedRef.current || seedRef.current.key !== seedKey) {
|
|
1839
|
+
seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
|
|
1840
|
+
}
|
|
1841
|
+
const displayMinute = useLiveClock(data.minute, data.clockRunning, seedRef.current.receivedAtMs);
|
|
1842
|
+
const minuteSuffix = data.minuteLabel.replace(/^\d+/, "");
|
|
1843
|
+
const scorers = scorersLine(goalEvents);
|
|
1725
1844
|
return /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-card ss-w-fandash-live-score", children: [
|
|
1726
1845
|
card.title ? /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
|
|
1727
|
-
/* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-
|
|
1728
|
-
/* @__PURE__ */
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-
|
|
1733
|
-
] }),
|
|
1734
|
-
/* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-meta", children: [
|
|
1735
|
-
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-minute", children: data.minuteLabel }),
|
|
1736
|
-
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-status", children: data.statusLabel }),
|
|
1846
|
+
/* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-top", children: [
|
|
1847
|
+
/* @__PURE__ */ jsxs30("span", { className: "ss-w-fandash-live-pill", children: [
|
|
1848
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-dot", "aria-hidden": "true" }),
|
|
1849
|
+
data.statusLabel
|
|
1850
|
+
] }),
|
|
1851
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-minute", children: `${displayMinute}${minuteSuffix}` }),
|
|
1737
1852
|
data.competition ? /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-competition", children: data.competition }) : null
|
|
1738
|
-
] })
|
|
1853
|
+
] }),
|
|
1854
|
+
/* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-main", children: [
|
|
1855
|
+
/* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-side ss-w-fandash-live-side--home", children: [
|
|
1856
|
+
/* @__PURE__ */ jsx34(LiveCrest, { url: data.homeCrestUrl, name: data.homeName, side: "home" }),
|
|
1857
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-team-name", children: data.homeName })
|
|
1858
|
+
] }),
|
|
1859
|
+
/* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-score-main", children: [
|
|
1860
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-score-value", children: data.homeScore }),
|
|
1861
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-score-sep", children: "\u2013" }),
|
|
1862
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-score-value", children: data.awayScore })
|
|
1863
|
+
] }),
|
|
1864
|
+
/* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-side ss-w-fandash-live-side--away", children: [
|
|
1865
|
+
/* @__PURE__ */ jsx34(LiveCrest, { url: data.awayCrestUrl, name: data.awayName, side: "away" }),
|
|
1866
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-team-name", children: data.awayName })
|
|
1867
|
+
] })
|
|
1868
|
+
] }),
|
|
1869
|
+
scorers ? /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-scorers", children: [
|
|
1870
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-scorers-icon", "aria-hidden": "true", children: "\u26BD" }),
|
|
1871
|
+
scorers
|
|
1872
|
+
] }) : null
|
|
1739
1873
|
] });
|
|
1740
1874
|
}
|
|
1741
1875
|
|
|
1742
1876
|
// src/components/dashboard/LiveTimelineCard.tsx
|
|
1877
|
+
import { useState as useState9 } from "react";
|
|
1743
1878
|
import { jsx as jsx35, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
1744
|
-
|
|
1879
|
+
var SHOWN = 3;
|
|
1880
|
+
function EventIcon({ type }) {
|
|
1881
|
+
if (type === "goal") {
|
|
1882
|
+
return /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-ico ss-w-fandash-live-tl-ico--goal", "aria-hidden": "true", children: /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-ball" }) });
|
|
1883
|
+
}
|
|
1884
|
+
if (type === "yellow" || type === "red") {
|
|
1885
|
+
return /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-ico", "aria-hidden": "true", children: /* @__PURE__ */ jsx35("span", { className: `ss-w-fandash-live-tl-card ss-w-fandash-live-tl-card--${type}` }) });
|
|
1886
|
+
}
|
|
1887
|
+
if (type === "sub") {
|
|
1888
|
+
return /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-ico", "aria-hidden": "true", children: /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-subarrows", children: "\u21C5" }) });
|
|
1889
|
+
}
|
|
1890
|
+
return /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-ico", "aria-hidden": "true", children: /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-dot" }) });
|
|
1891
|
+
}
|
|
1892
|
+
function EventLabel({ e }) {
|
|
1893
|
+
if (e.type === "sub") {
|
|
1894
|
+
return /* @__PURE__ */ jsxs31("span", { className: "ss-w-fandash-live-tl-who", children: [
|
|
1895
|
+
e.player ?? e.text,
|
|
1896
|
+
e.playerOff ? /* @__PURE__ */ jsxs31("span", { className: "ss-w-fandash-live-tl-off", children: [
|
|
1897
|
+
" for ",
|
|
1898
|
+
e.playerOff
|
|
1899
|
+
] }) : null
|
|
1900
|
+
] });
|
|
1901
|
+
}
|
|
1902
|
+
return /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-who", children: e.player ?? e.text });
|
|
1903
|
+
}
|
|
1904
|
+
function EventRow({ e }) {
|
|
1905
|
+
const cls = [
|
|
1906
|
+
"ss-w-fandash-live-tl-ev",
|
|
1907
|
+
e.side === "home" ? "ss-w-fandash-live-tl-ev--home" : "ss-w-fandash-live-tl-ev--away",
|
|
1908
|
+
e.type === "goal" ? "ss-w-fandash-live-tl-ev--goal" : ""
|
|
1909
|
+
].filter(Boolean).join(" ");
|
|
1910
|
+
return /* @__PURE__ */ jsxs31("div", { className: cls, children: [
|
|
1911
|
+
/* @__PURE__ */ jsxs31("span", { className: "ss-w-fandash-live-tl-txt", children: [
|
|
1912
|
+
/* @__PURE__ */ jsx35(EventLabel, { e }),
|
|
1913
|
+
/* @__PURE__ */ jsx35(EventIcon, { type: e.type })
|
|
1914
|
+
] }),
|
|
1915
|
+
/* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-badge", children: e.minuteLabel })
|
|
1916
|
+
] });
|
|
1917
|
+
}
|
|
1918
|
+
function LiveTimelineCard({ card, ui }) {
|
|
1919
|
+
const [expanded, setExpanded] = useState9(false);
|
|
1920
|
+
const events = card.data.events;
|
|
1921
|
+
if (!events || events.length === 0) return null;
|
|
1922
|
+
const sorted = [...events].sort((a, b) => b.minute - a.minute);
|
|
1923
|
+
const rest = sorted.length - SHOWN;
|
|
1924
|
+
const shown = expanded ? sorted : sorted.slice(0, SHOWN);
|
|
1925
|
+
const showMore = ui?.showMore ?? ((n) => `Show ${n} more`);
|
|
1926
|
+
const showLess = ui?.showLess ?? "Show less";
|
|
1745
1927
|
return /* @__PURE__ */ jsxs31("div", { className: "ss-w-fandash-card ss-w-fandash-live-timeline", children: [
|
|
1746
1928
|
card.title ? /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
|
|
1747
|
-
/* @__PURE__ */ jsx35("
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1929
|
+
/* @__PURE__ */ jsx35("div", { className: "ss-w-fandash-live-tl", children: shown.map((e, i) => /* @__PURE__ */ jsx35(EventRow, { e }, `${e.minute}-${e.side}-${e.type}-${i}`)) }),
|
|
1930
|
+
rest > 0 ? /* @__PURE__ */ jsx35(
|
|
1931
|
+
"button",
|
|
1932
|
+
{
|
|
1933
|
+
type: "button",
|
|
1934
|
+
className: "ss-w-fandash-live-tl-more",
|
|
1935
|
+
"aria-expanded": expanded,
|
|
1936
|
+
onClick: () => setExpanded((v) => !v),
|
|
1937
|
+
children: expanded ? showLess : showMore(rest)
|
|
1938
|
+
}
|
|
1939
|
+
) : null
|
|
1751
1940
|
] });
|
|
1752
1941
|
}
|
|
1753
1942
|
|
|
1754
|
-
// src/components/dashboard/
|
|
1943
|
+
// src/components/dashboard/MomentumWave.tsx
|
|
1944
|
+
import { useId } from "react";
|
|
1755
1945
|
import { jsx as jsx36, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
1946
|
+
var WIDTH = 270;
|
|
1947
|
+
var HEIGHT = 56;
|
|
1948
|
+
var BASELINE = HEIGHT / 2;
|
|
1949
|
+
var SCALE = 0.26;
|
|
1950
|
+
function buildPaths(points) {
|
|
1951
|
+
const lastIndex = points.length - 1;
|
|
1952
|
+
const px = (i) => lastIndex === 0 ? 0 : i / lastIndex * WIDTH;
|
|
1953
|
+
const py = (v) => BASELINE - v * SCALE;
|
|
1954
|
+
const linePts = points.map((p, i) => `${px(i).toFixed(1)},${py(p.value).toFixed(1)}`);
|
|
1955
|
+
const area = `M0,${BASELINE} L${linePts.join(" L")} L${WIDTH},${BASELINE} Z`;
|
|
1956
|
+
const line = `M${linePts.join(" L")}`;
|
|
1957
|
+
const lastValue = points[lastIndex]?.value ?? 0;
|
|
1958
|
+
return { area, line, lastX: px(lastIndex).toFixed(1), lastY: py(lastValue).toFixed(1) };
|
|
1959
|
+
}
|
|
1960
|
+
function MomentumWave({
|
|
1961
|
+
points,
|
|
1962
|
+
caption,
|
|
1963
|
+
clubIsHome
|
|
1964
|
+
}) {
|
|
1965
|
+
if (points.length === 0) return null;
|
|
1966
|
+
const { area, line, lastX, lastY } = buildPaths(points);
|
|
1967
|
+
const clipId = useId();
|
|
1968
|
+
const topClipId = `${clipId}-top`;
|
|
1969
|
+
const bottomClipId = `${clipId}-bottom`;
|
|
1970
|
+
const topClass = clubIsHome ? "ss-w-fandash-live-mom-home" : "ss-w-fandash-live-mom-away";
|
|
1971
|
+
const bottomClass = clubIsHome ? "ss-w-fandash-live-mom-away" : "ss-w-fandash-live-mom-home";
|
|
1972
|
+
return /* @__PURE__ */ jsxs32("div", { className: "ss-w-fandash-live-mom", children: [
|
|
1973
|
+
caption ? /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-mom-caption", children: caption }) : null,
|
|
1974
|
+
/* @__PURE__ */ jsxs32(
|
|
1975
|
+
"svg",
|
|
1976
|
+
{
|
|
1977
|
+
className: "ss-w-fandash-live-mom-svg",
|
|
1978
|
+
viewBox: `0 0 ${WIDTH} ${HEIGHT}`,
|
|
1979
|
+
preserveAspectRatio: "none",
|
|
1980
|
+
role: "img",
|
|
1981
|
+
"aria-label": caption ?? "Momentum",
|
|
1982
|
+
children: [
|
|
1983
|
+
/* @__PURE__ */ jsxs32("defs", { children: [
|
|
1984
|
+
/* @__PURE__ */ jsx36("clipPath", { id: topClipId, children: /* @__PURE__ */ jsx36("rect", { x: 0, y: 0, width: WIDTH, height: BASELINE }) }),
|
|
1985
|
+
/* @__PURE__ */ jsx36("clipPath", { id: bottomClipId, children: /* @__PURE__ */ jsx36("rect", { x: 0, y: BASELINE, width: WIDTH, height: HEIGHT - BASELINE }) })
|
|
1986
|
+
] }),
|
|
1987
|
+
/* @__PURE__ */ jsx36("path", { d: area, className: topClass, clipPath: `url(#${topClipId})` }),
|
|
1988
|
+
/* @__PURE__ */ jsx36("path", { d: area, className: bottomClass, clipPath: `url(#${bottomClipId})` }),
|
|
1989
|
+
/* @__PURE__ */ jsx36("line", { x1: 0, y1: BASELINE, x2: WIDTH, y2: BASELINE, className: "ss-w-fandash-live-mom-base" }),
|
|
1990
|
+
/* @__PURE__ */ jsx36("path", { d: line, className: "ss-w-fandash-live-mom-line", vectorEffect: "non-scaling-stroke" }),
|
|
1991
|
+
/* @__PURE__ */ jsx36("circle", { cx: lastX, cy: lastY, r: 3.2, className: "ss-w-fandash-live-mom-now", vectorEffect: "non-scaling-stroke" })
|
|
1992
|
+
]
|
|
1993
|
+
}
|
|
1994
|
+
)
|
|
1995
|
+
] });
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
// src/components/dashboard/LiveStatsCard.tsx
|
|
1999
|
+
import { jsx as jsx37, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
2000
|
+
function splitWidths(homeNum, awayNum) {
|
|
2001
|
+
const total = homeNum + awayNum;
|
|
2002
|
+
if (total <= 0) return { home: "50%", away: "50%" };
|
|
2003
|
+
const homePct = Math.round(homeNum / total * 1e3) / 10;
|
|
2004
|
+
return { home: `${homePct}%`, away: `${Math.round((100 - homePct) * 10) / 10}%` };
|
|
2005
|
+
}
|
|
1756
2006
|
function LiveStatsCard({ card }) {
|
|
1757
2007
|
const { data } = card;
|
|
1758
|
-
return /* @__PURE__ */
|
|
1759
|
-
card.title ? /* @__PURE__ */
|
|
1760
|
-
/* @__PURE__ */
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
2008
|
+
return /* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash-card ss-w-fandash-live-stats", children: [
|
|
2009
|
+
card.title ? /* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
|
|
2010
|
+
/* @__PURE__ */ jsx37("div", { className: "ss-w-fandash-live-stat-rows", children: data.rows.map((r, i) => {
|
|
2011
|
+
const homeValue = data.clubIsHome ? r.clubValue : r.oppValue;
|
|
2012
|
+
const awayValue = data.clubIsHome ? r.oppValue : r.clubValue;
|
|
2013
|
+
const homeNum = data.clubIsHome ? r.clubNum : r.oppNum;
|
|
2014
|
+
const awayNum = data.clubIsHome ? r.oppNum : r.clubNum;
|
|
2015
|
+
const { home, away } = splitWidths(homeNum, awayNum);
|
|
2016
|
+
return /* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash-live-stat", children: [
|
|
2017
|
+
/* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash-live-stat-row", children: [
|
|
2018
|
+
/* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-live-stat-value ss-w-fandash-live-stat-value--home", children: homeValue }),
|
|
2019
|
+
/* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-live-stat-label", children: r.label }),
|
|
2020
|
+
/* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-live-stat-value ss-w-fandash-live-stat-value--away", children: awayValue })
|
|
2021
|
+
] }),
|
|
2022
|
+
/* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash-live-bar", children: [
|
|
2023
|
+
/* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-live-bar-home", style: { width: home } }),
|
|
2024
|
+
/* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-live-bar-away", style: { width: away } })
|
|
2025
|
+
] })
|
|
2026
|
+
] }, `${r.label}-${i}`);
|
|
2027
|
+
}) }),
|
|
2028
|
+
data.momentum.length > 0 ? /* @__PURE__ */ jsx37(MomentumWave, { points: data.momentum, caption: data.momentumLabel, clubIsHome: data.clubIsHome }) : data.momentumLabel ? /* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-live-momentum", children: data.momentumLabel }) : null,
|
|
2029
|
+
data.rows.length > 0 ? /* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash-live-legend", children: [
|
|
2030
|
+
/* @__PURE__ */ jsxs33("span", { className: "ss-w-fandash-live-legend-item", children: [
|
|
2031
|
+
/* @__PURE__ */ jsx37("i", { className: "ss-w-fandash-live-legend-swatch ss-w-fandash-live-legend-swatch--home" }),
|
|
2032
|
+
data.homeName
|
|
2033
|
+
] }),
|
|
2034
|
+
/* @__PURE__ */ jsxs33("span", { className: "ss-w-fandash-live-legend-item", children: [
|
|
2035
|
+
/* @__PURE__ */ jsx37("i", { className: "ss-w-fandash-live-legend-swatch ss-w-fandash-live-legend-swatch--away" }),
|
|
2036
|
+
data.awayName
|
|
2037
|
+
] })
|
|
2038
|
+
] }) : null
|
|
1766
2039
|
] });
|
|
1767
2040
|
}
|
|
1768
2041
|
|
|
1769
2042
|
// src/components/dashboard/ClubFanDashboard.tsx
|
|
1770
|
-
import { jsx as
|
|
2043
|
+
import { jsx as jsx38, jsxs as jsxs34 } from "react/jsx-runtime";
|
|
1771
2044
|
function ClubFanDashboard({ dashboard, onTap, ui }) {
|
|
1772
2045
|
const orderedCards = [
|
|
1773
2046
|
...dashboard.cards.filter((c) => c.cardType === "countdown"),
|
|
1774
2047
|
...dashboard.cards.filter((c) => c.cardType !== "countdown")
|
|
1775
2048
|
];
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
2049
|
+
const timelineCard = dashboard.cards.find(
|
|
2050
|
+
(c) => c.cardType === "liveTimeline"
|
|
2051
|
+
);
|
|
2052
|
+
const goalEvents = timelineCard?.data.events.filter((e) => e.type === "goal");
|
|
2053
|
+
return /* @__PURE__ */ jsxs34("div", { className: "ss-w-fandash", children: [
|
|
2054
|
+
dashboard.greeting ? /* @__PURE__ */ jsx38("span", { className: "ss-w-fandash-greeting", children: dashboard.greeting }) : null,
|
|
2055
|
+
/* @__PURE__ */ jsx38("div", { className: "ss-w-fandash-cards", children: orderedCards.map((card, i) => /* @__PURE__ */ jsx38(FanDashboardCard, { card, onTap, ui, goalEvents }, `${card.cardType}-${i}`)) })
|
|
1779
2056
|
] });
|
|
1780
2057
|
}
|
|
1781
|
-
function FanDashboardCard({ card, onTap, ui }) {
|
|
2058
|
+
function FanDashboardCard({ card, onTap, ui, goalEvents }) {
|
|
1782
2059
|
switch (card.cardType) {
|
|
1783
2060
|
case "fixture":
|
|
1784
|
-
return /* @__PURE__ */
|
|
2061
|
+
return /* @__PURE__ */ jsx38(FixtureCard, { card, onTap });
|
|
1785
2062
|
case "countdown":
|
|
1786
|
-
return /* @__PURE__ */
|
|
2063
|
+
return /* @__PURE__ */ jsx38(CountdownCard, { card });
|
|
1787
2064
|
case "injuries":
|
|
1788
|
-
return /* @__PURE__ */
|
|
2065
|
+
return /* @__PURE__ */ jsx38(InjuriesCard, { card });
|
|
1789
2066
|
case "reasonToBet":
|
|
1790
|
-
return /* @__PURE__ */
|
|
2067
|
+
return /* @__PURE__ */ jsx38(ReasonToBetCard, { card });
|
|
1791
2068
|
case "whereToWatch":
|
|
1792
|
-
return /* @__PURE__ */
|
|
2069
|
+
return /* @__PURE__ */ jsx38(WhereToWatchCard, { card });
|
|
1793
2070
|
case "congestedWeek":
|
|
1794
|
-
return /* @__PURE__ */
|
|
2071
|
+
return /* @__PURE__ */ jsx38(CongestedWeekCard, { card, onTap });
|
|
1795
2072
|
case "transfers":
|
|
1796
|
-
return /* @__PURE__ */
|
|
2073
|
+
return /* @__PURE__ */ jsx38(TransfersCard, { card });
|
|
1797
2074
|
case "links":
|
|
1798
|
-
return /* @__PURE__ */
|
|
2075
|
+
return /* @__PURE__ */ jsx38(LinksCard, { card });
|
|
1799
2076
|
case "postMatchResult":
|
|
1800
|
-
return /* @__PURE__ */
|
|
2077
|
+
return /* @__PURE__ */ jsx38(PostMatchResultCard, { card });
|
|
1801
2078
|
case "postMatchGoals":
|
|
1802
|
-
return /* @__PURE__ */
|
|
2079
|
+
return /* @__PURE__ */ jsx38(PostMatchGoalsCard, { card, onTap });
|
|
1803
2080
|
case "postMatchStats":
|
|
1804
|
-
return /* @__PURE__ */
|
|
2081
|
+
return /* @__PURE__ */ jsx38(PostMatchStatsCard, { card, onTap });
|
|
1805
2082
|
case "postMatchTopPerformer":
|
|
1806
|
-
return /* @__PURE__ */
|
|
2083
|
+
return /* @__PURE__ */ jsx38(PostMatchTopPerformerCard, { card, onTap });
|
|
1807
2084
|
case "postMatchNext":
|
|
1808
|
-
return /* @__PURE__ */
|
|
2085
|
+
return /* @__PURE__ */ jsx38(PostMatchNextCard, { card });
|
|
1809
2086
|
case "restingNext":
|
|
1810
|
-
return /* @__PURE__ */
|
|
2087
|
+
return /* @__PURE__ */ jsx38(RestingNextCard, { card, onTap });
|
|
1811
2088
|
case "restingStandings":
|
|
1812
|
-
return /* @__PURE__ */
|
|
2089
|
+
return /* @__PURE__ */ jsx38(RestingStandingsCard, { card, onTap });
|
|
1813
2090
|
case "restingSeason":
|
|
1814
|
-
return /* @__PURE__ */
|
|
2091
|
+
return /* @__PURE__ */ jsx38(RestingSeasonCard, { card, onTap });
|
|
1815
2092
|
case "restingForm":
|
|
1816
|
-
return /* @__PURE__ */
|
|
2093
|
+
return /* @__PURE__ */ jsx38(RestingFormCard, { card });
|
|
1817
2094
|
case "liveScore":
|
|
1818
|
-
return /* @__PURE__ */
|
|
2095
|
+
return /* @__PURE__ */ jsx38(LiveScoreCard, { card, goalEvents });
|
|
1819
2096
|
case "liveTimeline":
|
|
1820
|
-
return /* @__PURE__ */
|
|
2097
|
+
return /* @__PURE__ */ jsx38(LiveTimelineCard, { card, ui });
|
|
1821
2098
|
case "liveStats":
|
|
1822
|
-
return /* @__PURE__ */
|
|
2099
|
+
return /* @__PURE__ */ jsx38(LiveStatsCard, { card });
|
|
1823
2100
|
default:
|
|
1824
2101
|
return null;
|
|
1825
2102
|
}
|
|
1826
2103
|
}
|
|
1827
2104
|
|
|
1828
2105
|
// src/components/ChatMessage.tsx
|
|
1829
|
-
import { jsx as
|
|
2106
|
+
import { jsx as jsx39, jsxs as jsxs35 } from "react/jsx-runtime";
|
|
1830
2107
|
function ChatMessage({ message, disabled, onAct, onTap, ui, slip }) {
|
|
1831
2108
|
if (message.role === "user") {
|
|
1832
|
-
return /* @__PURE__ */
|
|
2109
|
+
return /* @__PURE__ */ jsx39("div", { className: "ss-w-msg ss-w-user", "data-msg-id": message.id, children: /* @__PURE__ */ jsx39("div", { className: "ss-w-bubble", children: message.text }) });
|
|
1833
2110
|
}
|
|
1834
2111
|
const onFanDashboardTap = (card) => {
|
|
1835
2112
|
onAct({ message: card.actionLabel, actionRef: card.actionRef, label: card.actionLabel || card.title || "" });
|
|
1836
2113
|
};
|
|
1837
|
-
return /* @__PURE__ */
|
|
1838
|
-
message.text && !message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */
|
|
1839
|
-
message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */
|
|
1840
|
-
message.clubFanDashboard ? /* @__PURE__ */
|
|
1841
|
-
/* @__PURE__ */
|
|
1842
|
-
/* @__PURE__ */
|
|
1843
|
-
/* @__PURE__ */
|
|
1844
|
-
/* @__PURE__ */
|
|
1845
|
-
/* @__PURE__ */
|
|
2114
|
+
return /* @__PURE__ */ jsxs35("div", { className: "ss-w-msg ss-w-bot", "data-msg-id": message.id, children: [
|
|
2115
|
+
message.text && !message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx39(Markdown, { text: message.text }) : null,
|
|
2116
|
+
message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx39(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
|
|
2117
|
+
message.clubFanDashboard ? /* @__PURE__ */ jsx39(ClubFanDashboard, { dashboard: message.clubFanDashboard, onTap: onFanDashboardTap, ui }) : null,
|
|
2118
|
+
/* @__PURE__ */ jsx39(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
|
|
2119
|
+
/* @__PURE__ */ jsx39(BettingInsightList, { insights: message.insights, ui, slip }),
|
|
2120
|
+
/* @__PURE__ */ jsx39(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
|
|
2121
|
+
/* @__PURE__ */ jsx39(OddsMovementCardList, { cards: message.oddsMovement, ui }),
|
|
2122
|
+
/* @__PURE__ */ jsx39(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
|
|
1846
2123
|
] });
|
|
1847
2124
|
}
|
|
1848
2125
|
|
|
1849
2126
|
// src/components/IntentChips.tsx
|
|
1850
|
-
import { jsx as
|
|
2127
|
+
import { jsx as jsx40 } from "react/jsx-runtime";
|
|
1851
2128
|
function IntentChips({ chips, onPick }) {
|
|
1852
2129
|
if (chips.length === 0) return null;
|
|
1853
|
-
return /* @__PURE__ */
|
|
2130
|
+
return /* @__PURE__ */ jsx40("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ jsx40("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
|
|
1854
2131
|
}
|
|
1855
2132
|
|
|
1856
2133
|
// src/scroll.ts
|
|
@@ -1860,7 +2137,7 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
|
|
|
1860
2137
|
}
|
|
1861
2138
|
|
|
1862
2139
|
// src/components/MessageList.tsx
|
|
1863
|
-
import { jsx as
|
|
2140
|
+
import { jsx as jsx41, jsxs as jsxs36 } from "react/jsx-runtime";
|
|
1864
2141
|
function turnAnchor(messages) {
|
|
1865
2142
|
const last = messages[messages.length - 1];
|
|
1866
2143
|
if (!last || last.role !== "assistant") return null;
|
|
@@ -1869,11 +2146,11 @@ function turnAnchor(messages) {
|
|
|
1869
2146
|
}
|
|
1870
2147
|
function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
1871
2148
|
const { state, send } = controller;
|
|
1872
|
-
const listRef =
|
|
1873
|
-
const endRef =
|
|
1874
|
-
const pinnedRef =
|
|
1875
|
-
const anchoredTurnRef =
|
|
1876
|
-
|
|
2149
|
+
const listRef = useRef4(null);
|
|
2150
|
+
const endRef = useRef4(null);
|
|
2151
|
+
const pinnedRef = useRef4(true);
|
|
2152
|
+
const anchoredTurnRef = useRef4(null);
|
|
2153
|
+
useEffect6(() => {
|
|
1877
2154
|
const anchor = turnAnchor(state.messages);
|
|
1878
2155
|
const list = listRef.current;
|
|
1879
2156
|
if (anchor && anchor.turnId !== anchoredTurnRef.current) {
|
|
@@ -1888,7 +2165,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
|
1888
2165
|
}, [state.messages, state.status, state.isStreaming]);
|
|
1889
2166
|
const hasUserMessage = state.messages.some((m) => m.role === "user");
|
|
1890
2167
|
const hasDashboard = state.messages.some((m) => m.role === "assistant" && (m.greetingDashboard || m.clubFanDashboard));
|
|
1891
|
-
return /* @__PURE__ */
|
|
2168
|
+
return /* @__PURE__ */ jsxs36(
|
|
1892
2169
|
"div",
|
|
1893
2170
|
{
|
|
1894
2171
|
className: "ss-w-list",
|
|
@@ -1898,7 +2175,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
|
1898
2175
|
if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
|
|
1899
2176
|
},
|
|
1900
2177
|
children: [
|
|
1901
|
-
state.messages.map((m) => /* @__PURE__ */
|
|
2178
|
+
state.messages.map((m) => /* @__PURE__ */ jsx41(
|
|
1902
2179
|
ChatMessage,
|
|
1903
2180
|
{
|
|
1904
2181
|
message: m,
|
|
@@ -1913,25 +2190,25 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
|
1913
2190
|
},
|
|
1914
2191
|
m.id
|
|
1915
2192
|
)),
|
|
1916
|
-
state.isStreaming && state.actionsLoading ? /* @__PURE__ */
|
|
2193
|
+
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx41(ActionButtons, { actions: [], loading: true, onAct: () => {
|
|
1917
2194
|
} }) : null,
|
|
1918
|
-
state.status ? /* @__PURE__ */
|
|
1919
|
-
state.error ? /* @__PURE__ */
|
|
1920
|
-
/* @__PURE__ */
|
|
1921
|
-
/* @__PURE__ */
|
|
2195
|
+
state.status ? /* @__PURE__ */ jsx41("div", { className: "ss-w-status", children: state.status }) : null,
|
|
2196
|
+
state.error ? /* @__PURE__ */ jsxs36("div", { className: "ss-w-error", role: "alert", children: [
|
|
2197
|
+
/* @__PURE__ */ jsx41("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
|
|
2198
|
+
/* @__PURE__ */ jsx41("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
|
|
1922
2199
|
] }) : null,
|
|
1923
|
-
!hasUserMessage && !hasDashboard ? /* @__PURE__ */
|
|
1924
|
-
/* @__PURE__ */
|
|
2200
|
+
!hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx41(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
|
|
2201
|
+
/* @__PURE__ */ jsx41("div", { ref: endRef, className: "ss-w-turn-spacer", "aria-hidden": "true" })
|
|
1925
2202
|
]
|
|
1926
2203
|
}
|
|
1927
2204
|
);
|
|
1928
2205
|
}
|
|
1929
2206
|
|
|
1930
2207
|
// src/components/ChatInput.tsx
|
|
1931
|
-
import { useState as
|
|
1932
|
-
import { jsx as
|
|
2208
|
+
import { useState as useState10 } from "react";
|
|
2209
|
+
import { jsx as jsx42, jsxs as jsxs37 } from "react/jsx-runtime";
|
|
1933
2210
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
1934
|
-
const [value, setValue] =
|
|
2211
|
+
const [value, setValue] = useState10("");
|
|
1935
2212
|
const submit = () => {
|
|
1936
2213
|
const t = value.trim();
|
|
1937
2214
|
if (!t) return;
|
|
@@ -1944,18 +2221,18 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
|
|
|
1944
2221
|
submit();
|
|
1945
2222
|
}
|
|
1946
2223
|
};
|
|
1947
|
-
return /* @__PURE__ */
|
|
1948
|
-
/* @__PURE__ */
|
|
1949
|
-
streaming ? /* @__PURE__ */
|
|
2224
|
+
return /* @__PURE__ */ jsxs37("div", { className: "ss-w-input", children: [
|
|
2225
|
+
/* @__PURE__ */ jsx42("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
|
|
2226
|
+
streaming ? /* @__PURE__ */ jsx42("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ jsx42("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx42("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ jsx42(SendIcon, {}) })
|
|
1950
2227
|
] });
|
|
1951
2228
|
}
|
|
1952
2229
|
|
|
1953
2230
|
// src/components/ChatPanel.tsx
|
|
1954
|
-
import { jsx as
|
|
2231
|
+
import { jsx as jsx43, jsxs as jsxs38 } from "react/jsx-runtime";
|
|
1955
2232
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
1956
2233
|
function useIsModal() {
|
|
1957
|
-
const [modal, setModal] =
|
|
1958
|
-
|
|
2234
|
+
const [modal, setModal] = useState11(false);
|
|
2235
|
+
useEffect7(() => {
|
|
1959
2236
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
1960
2237
|
const mql = window.matchMedia(FULLSCREEN_QUERY);
|
|
1961
2238
|
const sync = () => setModal(mql.matches);
|
|
@@ -1968,17 +2245,17 @@ function useIsModal() {
|
|
|
1968
2245
|
function ChatPanel({ controller, ui, slip, onDashboardTap }) {
|
|
1969
2246
|
const { state, close, stop, send } = controller;
|
|
1970
2247
|
const isModal = useIsModal();
|
|
1971
|
-
|
|
2248
|
+
useEffect7(() => {
|
|
1972
2249
|
const onKey = (e) => {
|
|
1973
2250
|
if (e.key === "Escape") close();
|
|
1974
2251
|
};
|
|
1975
2252
|
window.addEventListener("keydown", onKey);
|
|
1976
2253
|
return () => window.removeEventListener("keydown", onKey);
|
|
1977
2254
|
}, [close]);
|
|
1978
|
-
return /* @__PURE__ */
|
|
1979
|
-
/* @__PURE__ */
|
|
1980
|
-
/* @__PURE__ */
|
|
1981
|
-
/* @__PURE__ */
|
|
2255
|
+
return /* @__PURE__ */ jsxs38("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
2256
|
+
/* @__PURE__ */ jsx43(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
|
|
2257
|
+
/* @__PURE__ */ jsx43(MessageList, { controller, ui, slip, onDashboardTap }),
|
|
2258
|
+
/* @__PURE__ */ jsx43(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
|
|
1982
2259
|
] });
|
|
1983
2260
|
}
|
|
1984
2261
|
|
|
@@ -1991,6 +2268,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
1991
2268
|
--ss-w-ink: #14181d; --ss-w-faint: #6b7580; --ss-w-line: #e4e8ec;
|
|
1992
2269
|
--ss-w-panel: #ffffff; --ss-w-bubble: #f3f5f7; --ss-w-nav: #0e1622;
|
|
1993
2270
|
--ss-w-pos: #1aa563; --ss-w-neg: #e0524b;
|
|
2271
|
+
--ss-w-home: #3a63a8; --ss-w-away: #c8102e; --ss-w-track: #e9edf1;
|
|
1994
2272
|
--ss-w-radius: 16px; --ss-w-offset-x: 20px; --ss-w-offset-y: 20px;
|
|
1995
2273
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
1996
2274
|
font-size: 13px; line-height: 1.5; color: var(--ss-w-ink);
|
|
@@ -2001,6 +2279,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
2001
2279
|
--ss-w-ink: #e8edf2; --ss-w-faint: #8b95a1;
|
|
2002
2280
|
--ss-w-line: #2a3441; --ss-w-panel: #141b24; --ss-w-bubble: #1e2732; --ss-w-nav: #0a0f16;
|
|
2003
2281
|
--ss-w-pos: #37c891; --ss-w-neg: #f0655d;
|
|
2282
|
+
--ss-w-home: #5b83c8; --ss-w-away: #e8384f; --ss-w-track: #232d3a;
|
|
2004
2283
|
}
|
|
2005
2284
|
}
|
|
2006
2285
|
.ss-w-root[data-ss-w-theme="dark"] {
|
|
@@ -2008,11 +2287,13 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
2008
2287
|
--ss-w-ink: #e8edf2; --ss-w-faint: #8b95a1;
|
|
2009
2288
|
--ss-w-line: #2a3441; --ss-w-panel: #141b24; --ss-w-bubble: #1e2732; --ss-w-nav: #0a0f16;
|
|
2010
2289
|
--ss-w-pos: #37c891; --ss-w-neg: #f0655d;
|
|
2290
|
+
--ss-w-home: #5b83c8; --ss-w-away: #e8384f; --ss-w-track: #232d3a;
|
|
2011
2291
|
}
|
|
2012
2292
|
.ss-w-root[data-ss-w-theme="light"] {
|
|
2013
2293
|
--ss-w-accent: #17936a; --ss-w-cta: #f5a623; --ss-w-cta-ink: #1b1300;
|
|
2014
2294
|
--ss-w-ink: #14181d; --ss-w-faint: #6b7580;
|
|
2015
2295
|
--ss-w-line: #e4e8ec; --ss-w-panel: #ffffff; --ss-w-bubble: #f3f5f7; --ss-w-nav: #0e1622;
|
|
2296
|
+
--ss-w-home: #3a63a8; --ss-w-away: #c8102e; --ss-w-track: #e9edf1;
|
|
2016
2297
|
}
|
|
2017
2298
|
|
|
2018
2299
|
/* launcher */
|
|
@@ -2501,34 +2782,90 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
2501
2782
|
/* resting form: reuses the fixture last-five pill classes (ss-w-fandash-form / -form-pill) */
|
|
2502
2783
|
.ss-w-fandash-resting-form .ss-w-fandash-form { margin-top: 2px; }
|
|
2503
2784
|
|
|
2504
|
-
/* live score:
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
.ss-w-fandash-live-
|
|
2509
|
-
.ss-w-fandash-live-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
.ss-w-fandash-live-
|
|
2514
|
-
|
|
2515
|
-
.ss-w-fandash-live-
|
|
2516
|
-
.ss-w-fandash-live-
|
|
2517
|
-
.ss-w-fandash-live-
|
|
2518
|
-
.ss-w-fandash-live-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
.ss-w-fandash-live-
|
|
2523
|
-
.ss-w-fandash-live-
|
|
2524
|
-
.ss-w-fandash-live-
|
|
2525
|
-
.ss-w-fandash-live-
|
|
2526
|
-
.ss-w-fandash-live-
|
|
2785
|
+
/* live score: pulsing Live pill, crests (real image or a coloured initials fallback), a big
|
|
2786
|
+
tabular score, and an optional scorers line. Also where the shared home/away venue-colour
|
|
2787
|
+
tokens (--ss-w-home / --ss-w-away / --ss-w-track) are defined above -- the timeline and stats
|
|
2788
|
+
cards reuse them. */
|
|
2789
|
+
.ss-w-fandash-live-top { display: flex; align-items: center; gap: 8px; }
|
|
2790
|
+
.ss-w-fandash-live-pill { display: inline-flex; flex: none; align-items: center; gap: 5px; font-size: 10.5px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: #fff; background: var(--ss-w-neg); border-radius: 6px; padding: 3px 8px; }
|
|
2791
|
+
.ss-w-fandash-live-dot { width: 5px; height: 5px; border-radius: 50%; background: #fff; animation: ss-w-fandash-live-pulse 1.3s ease-in-out infinite; }
|
|
2792
|
+
@keyframes ss-w-fandash-live-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
|
|
2793
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2794
|
+
.ss-w-fandash-live-dot { animation: none; }
|
|
2795
|
+
}
|
|
2796
|
+
.ss-w-fandash-live-minute { flex: none; font-size: 13.5px; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--ss-w-ink); }
|
|
2797
|
+
.ss-w-fandash-live-competition { margin-left: auto; font-size: 11px; font-weight: 650; color: var(--ss-w-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2798
|
+
.ss-w-fandash-live-main { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 10px; }
|
|
2799
|
+
.ss-w-fandash-live-side { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
2800
|
+
.ss-w-fandash-live-side--away { flex-direction: row-reverse; text-align: right; }
|
|
2801
|
+
.ss-w-fandash-live-crest { width: 38px; height: 38px; border-radius: 10px; flex: none; display: grid; place-items: center; color: #fff; font-weight: 800; font-size: 12.5px; object-fit: cover; box-shadow: 0 2px 6px rgba(0, 0, 0, .18); }
|
|
2802
|
+
.ss-w-fandash-live-crest--fallback.ss-w-fandash-live-crest--home { background: var(--ss-w-home); }
|
|
2803
|
+
.ss-w-fandash-live-crest--fallback.ss-w-fandash-live-crest--away { background: var(--ss-w-away); }
|
|
2804
|
+
.ss-w-fandash-live-team-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 15px; font-weight: 700; line-height: 1.15; color: var(--ss-w-ink); }
|
|
2805
|
+
.ss-w-fandash-live-score-main { display: flex; align-items: baseline; justify-content: center; font-size: 36px; font-weight: 850; font-variant-numeric: tabular-nums; line-height: 1; white-space: nowrap; color: var(--ss-w-ink); }
|
|
2806
|
+
.ss-w-fandash-live-score-sep { color: var(--ss-w-faint); font-weight: 500; margin: 0 5px; }
|
|
2807
|
+
.ss-w-fandash-live-scorers { margin-top: 12px; display: flex; justify-content: flex-end; gap: 7px; align-items: center; font-size: 12.5px; color: var(--ss-w-faint); overflow-wrap: anywhere; }
|
|
2808
|
+
.ss-w-fandash-live-scorers-icon { flex: none; color: var(--ss-w-away); }
|
|
2809
|
+
|
|
2810
|
+
/* live timeline: a compact two-sided event list down a centre spine -- each club's events sit on
|
|
2811
|
+
its own side (home left, away right), most-recent 3 shown with a "See more" toggle for the
|
|
2812
|
+
rest. A 3-column grid (txt | badge | txt) per row; the spine itself is a ::before on the list. */
|
|
2813
|
+
.ss-w-fandash-live-tl { position: relative; display: flex; flex-direction: column; gap: 6px; }
|
|
2814
|
+
.ss-w-fandash-live-tl::before { content: ""; position: absolute; left: 50%; top: 1px; bottom: 1px; width: 2px; background: var(--ss-w-line); transform: translateX(-1px); }
|
|
2815
|
+
.ss-w-fandash-live-tl-ev { display: grid; grid-template-columns: 1fr 32px 1fr; align-items: center; gap: 6px; }
|
|
2816
|
+
.ss-w-fandash-live-tl-txt { display: flex; align-items: center; gap: 7px; font-size: 13px; min-width: 0; }
|
|
2817
|
+
.ss-w-fandash-live-tl-ev--home .ss-w-fandash-live-tl-txt { grid-column: 1; justify-content: flex-end; }
|
|
2818
|
+
.ss-w-fandash-live-tl-ev--away .ss-w-fandash-live-tl-txt { grid-column: 3; justify-content: flex-start; flex-direction: row-reverse; }
|
|
2819
|
+
.ss-w-fandash-live-tl-who { font-weight: 650; color: var(--ss-w-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
2820
|
+
.ss-w-fandash-live-tl-off { font-weight: 500; color: var(--ss-w-faint); }
|
|
2821
|
+
.ss-w-fandash-live-tl-ev--goal .ss-w-fandash-live-tl-who { font-weight: 800; }
|
|
2822
|
+
.ss-w-fandash-live-tl-badge { grid-column: 2; justify-self: center; min-width: 29px; height: 19px; padding: 0 4px; border-radius: 6px; display: grid; place-items: center; font-size: 11px; font-weight: 800; color: var(--ss-w-ink); background: var(--ss-w-bubble); border: 1px solid var(--ss-w-line); font-variant-numeric: tabular-nums; z-index: 1; }
|
|
2823
|
+
.ss-w-fandash-live-tl-ev--goal .ss-w-fandash-live-tl-badge { background: var(--ss-w-accent); color: #fff; border-color: transparent; }
|
|
2824
|
+
.ss-w-fandash-live-tl-ico { width: 17px; height: 17px; flex: none; display: grid; place-items: center; }
|
|
2825
|
+
.ss-w-fandash-live-tl-ico--goal { border-radius: 50%; background: color-mix(in srgb, var(--ss-w-away) 16%, transparent); }
|
|
2826
|
+
.ss-w-fandash-live-tl-ball { width: 12px; height: 12px; border-radius: 50%; background: radial-gradient(circle at 50% 42%, #fff 0 34%, transparent 36%), var(--ss-w-ink); border: 1px solid var(--ss-w-ink); }
|
|
2827
|
+
.ss-w-fandash-live-tl-card { width: 9px; height: 12px; border-radius: 2px; display: block; }
|
|
2828
|
+
.ss-w-fandash-live-tl-card--yellow { background: var(--ss-w-cta); }
|
|
2829
|
+
.ss-w-fandash-live-tl-card--red { background: var(--ss-w-away); }
|
|
2830
|
+
.ss-w-fandash-live-tl-subarrows { font-size: 11px; font-weight: 900; line-height: 1; letter-spacing: -1px; color: var(--ss-w-accent); }
|
|
2831
|
+
.ss-w-fandash-live-tl-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--ss-w-faint); }
|
|
2832
|
+
.ss-w-fandash-live-tl-more { margin-top: 4px; width: 100%; background: var(--ss-w-bubble); border: 1px solid var(--ss-w-line); border-radius: 9px; padding: 7px; font: inherit; font-size: 11.5px; font-weight: 700; color: var(--ss-w-accent); cursor: pointer; }
|
|
2833
|
+
.ss-w-fandash-live-tl-more:focus-visible { outline: 2px solid var(--ss-w-accent); outline-offset: 2px; }
|
|
2834
|
+
|
|
2835
|
+
/* live stats: club value / stat label / opponent value, each row backed by a home/away split
|
|
2836
|
+
bar, plus an optional momentum wave (or its text fallback) and a club/opponent colour legend.
|
|
2837
|
+
No side signal reaches this card, so the club is always the home token and the opponent is
|
|
2838
|
+
always the away token -- see LiveStatsCard.tsx. */
|
|
2839
|
+
.ss-w-fandash-live-stat-rows { display: flex; flex-direction: column; gap: 15px; }
|
|
2840
|
+
.ss-w-fandash-live-stat { display: flex; flex-direction: column; gap: 7px; }
|
|
2841
|
+
.ss-w-fandash-live-stat-row { display: grid; grid-template-columns: 1fr auto 1fr; align-items: baseline; gap: 8px; }
|
|
2842
|
+
.ss-w-fandash-live-stat-value { font-size: 15px; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--ss-w-ink); }
|
|
2843
|
+
.ss-w-fandash-live-stat-value--home { color: var(--ss-w-home); }
|
|
2844
|
+
.ss-w-fandash-live-stat-value--away { color: var(--ss-w-away); text-align: right; }
|
|
2845
|
+
.ss-w-fandash-live-stat-label { font-size: 10.5px; font-weight: 700; text-align: center; color: var(--ss-w-faint); text-transform: uppercase; letter-spacing: .04em; }
|
|
2846
|
+
.ss-w-fandash-live-bar { position: relative; height: 8px; border-radius: 999px; background: var(--ss-w-track); overflow: hidden; display: flex; }
|
|
2847
|
+
.ss-w-fandash-live-bar-home { height: 100%; background: var(--ss-w-home); }
|
|
2848
|
+
.ss-w-fandash-live-bar-away { height: 100%; background: var(--ss-w-away); margin-left: auto; }
|
|
2849
|
+
|
|
2527
2850
|
.ss-w-fandash-live-momentum { font-size: 11.5px; font-weight: 650; color: var(--ss-w-accent); }
|
|
2851
|
+
.ss-w-fandash-live-mom { margin-top: 15px; padding-top: 13px; border-top: 1px solid var(--ss-w-line); }
|
|
2852
|
+
.ss-w-fandash-live-mom-caption { display: block; margin-bottom: 5px; font-size: 10.5px; font-weight: 600; color: var(--ss-w-faint); }
|
|
2853
|
+
.ss-w-fandash-live-mom-svg { width: 100%; height: 56px; display: block; overflow: visible; }
|
|
2854
|
+
.ss-w-fandash-live-mom-home { fill: var(--ss-w-home); fill-opacity: .85; }
|
|
2855
|
+
.ss-w-fandash-live-mom-away { fill: var(--ss-w-away); fill-opacity: .85; }
|
|
2856
|
+
.ss-w-fandash-live-mom-line { fill: none; stroke: color-mix(in srgb, var(--ss-w-ink) 55%, transparent); stroke-width: 1.5; }
|
|
2857
|
+
.ss-w-fandash-live-mom-base { stroke: var(--ss-w-line); stroke-width: 1; stroke-dasharray: 3 3; }
|
|
2858
|
+
.ss-w-fandash-live-mom-now { fill: var(--ss-w-panel); stroke: var(--ss-w-ink); stroke-width: 1.5; }
|
|
2859
|
+
|
|
2860
|
+
.ss-w-fandash-live-legend { display: flex; gap: 14px; margin-top: 13px; padding-top: 13px; border-top: 1px solid var(--ss-w-line); font-size: 11px; color: var(--ss-w-faint); }
|
|
2861
|
+
.ss-w-fandash-live-legend-item { display: inline-flex; align-items: center; gap: 6px; }
|
|
2862
|
+
.ss-w-fandash-live-legend-swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }
|
|
2863
|
+
.ss-w-fandash-live-legend-swatch--home { background: var(--ss-w-home); }
|
|
2864
|
+
.ss-w-fandash-live-legend-swatch--away { background: var(--ss-w-away); }
|
|
2528
2865
|
`;
|
|
2529
2866
|
|
|
2530
2867
|
// src/StatsWidget.tsx
|
|
2531
|
-
import { jsx as
|
|
2868
|
+
import { jsx as jsx44 } from "react/jsx-runtime";
|
|
2532
2869
|
function buildClient(props) {
|
|
2533
2870
|
try {
|
|
2534
2871
|
if ("client" in props && props.client) {
|
|
@@ -2555,7 +2892,7 @@ function StatsWidget(props) {
|
|
|
2555
2892
|
const cfg = props.config;
|
|
2556
2893
|
const built = useMemo2(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
|
|
2557
2894
|
const ui = useMemo2(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
|
|
2558
|
-
const rootRef =
|
|
2895
|
+
const rootRef = useRef5(null);
|
|
2559
2896
|
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
|
|
2560
2897
|
const slip = useMemo2(
|
|
2561
2898
|
() => ({
|
|
@@ -2574,14 +2911,14 @@ function StatsWidget(props) {
|
|
|
2574
2911
|
() => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
|
|
2575
2912
|
[built]
|
|
2576
2913
|
);
|
|
2577
|
-
|
|
2914
|
+
useEffect8(() => {
|
|
2578
2915
|
if (!built || !ui.injectStyles) return;
|
|
2579
2916
|
const node = rootRef.current?.getRootNode();
|
|
2580
2917
|
if (node) injectStyles(node);
|
|
2581
2918
|
}, [built, ui.injectStyles]);
|
|
2582
2919
|
if (!built) return null;
|
|
2583
2920
|
const attrs = themeAttrs(ui);
|
|
2584
|
-
return /* @__PURE__ */
|
|
2921
|
+
return /* @__PURE__ */ jsx44("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__ */ jsx44(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ jsx44(
|
|
2585
2922
|
ChatFab,
|
|
2586
2923
|
{
|
|
2587
2924
|
label: ui.launcherLabel,
|