@sensiblestats/widget-react 0.37.3 → 0.39.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 +12 -0
- package/dist/index.cjs +318 -77
- 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 +534 -293
- package/dist/index.js.map +1 -1
- package/dist/styles.css +8 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -36,7 +36,7 @@ __export(index_exports, {
|
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
37
|
|
|
38
38
|
// src/StatsWidget.tsx
|
|
39
|
-
var
|
|
39
|
+
var import_react24 = require("react");
|
|
40
40
|
var import_widget_sdk5 = require("@sensiblestats/widget-sdk");
|
|
41
41
|
|
|
42
42
|
// src/kickoff.ts
|
|
@@ -57,6 +57,8 @@ var STRINGS = {
|
|
|
57
57
|
launcherLabel: "Chat with SensibleStats",
|
|
58
58
|
placeholder: "Ask about a team, player or match\u2026",
|
|
59
59
|
closeChat: "Close chat",
|
|
60
|
+
newConversation: "New conversation",
|
|
61
|
+
moreOptions: "More options",
|
|
60
62
|
chatDialogLabel: "SensibleStats chat",
|
|
61
63
|
stopResponse: "Stop response",
|
|
62
64
|
sendMessage: "Send message",
|
|
@@ -129,6 +131,8 @@ var STRINGS = {
|
|
|
129
131
|
launcherLabel: "SensibleStats ile sohbet et",
|
|
130
132
|
placeholder: "Bir tak\u0131m, oyuncu veya ma\xE7 hakk\u0131nda sor\u2026",
|
|
131
133
|
closeChat: "Sohbeti kapat",
|
|
134
|
+
newConversation: "Yeni sohbet",
|
|
135
|
+
moreOptions: "Di\u011Fer se\xE7enekler",
|
|
132
136
|
chatDialogLabel: "SensibleStats sohbet",
|
|
133
137
|
stopResponse: "Yan\u0131t\u0131 durdur",
|
|
134
138
|
sendMessage: "Mesaj g\xF6nder",
|
|
@@ -224,6 +228,8 @@ function normalizeUi(cfg) {
|
|
|
224
228
|
starters: cfg.starters ?? [],
|
|
225
229
|
injectStyles: cfg.injectStyles ?? true,
|
|
226
230
|
closeChat: strings.closeChat,
|
|
231
|
+
newConversation: strings.newConversation,
|
|
232
|
+
moreOptions: strings.moreOptions,
|
|
227
233
|
chatDialogLabel: strings.chatDialogLabel,
|
|
228
234
|
stopResponse: strings.stopResponse,
|
|
229
235
|
sendMessage: strings.sendMessage,
|
|
@@ -642,7 +648,7 @@ function marketOddsRowToSlipSelection(vm, row, conversationId) {
|
|
|
642
648
|
// src/reducer.ts
|
|
643
649
|
var POPUP_MAX = 160;
|
|
644
650
|
function initialState(starters) {
|
|
645
|
-
return { isOpen: false, messages: [], isStreaming: false, status: null, actionsLoading: false, starters, error: null, lastUserInput: null, lastDisplayText: null, popupText: null, conversationId: null };
|
|
651
|
+
return { isOpen: false, messages: [], isStreaming: false, status: null, actionsLoading: false, starters, configuredStarters: starters, error: null, lastUserInput: null, lastDisplayText: null, popupText: null, conversationId: null };
|
|
646
652
|
}
|
|
647
653
|
function activeTurn(state) {
|
|
648
654
|
const last = state.messages[state.messages.length - 1];
|
|
@@ -722,6 +728,19 @@ function reducer(state, action) {
|
|
|
722
728
|
}
|
|
723
729
|
case "DISMISS_POPUP":
|
|
724
730
|
return { ...state, popupText: null };
|
|
731
|
+
case "RESET":
|
|
732
|
+
return { ...initialState(state.configuredStarters), isOpen: state.isOpen };
|
|
733
|
+
case "RESTORE":
|
|
734
|
+
return {
|
|
735
|
+
...state,
|
|
736
|
+
messages: action.messages,
|
|
737
|
+
conversationId: action.conversationId,
|
|
738
|
+
isStreaming: false,
|
|
739
|
+
status: null,
|
|
740
|
+
actionsLoading: false,
|
|
741
|
+
starters: action.messages.length ? [] : state.configuredStarters,
|
|
742
|
+
error: null
|
|
743
|
+
};
|
|
725
744
|
case "EVENT":
|
|
726
745
|
return applyEvent(state, action.event);
|
|
727
746
|
case "STREAM_ERROR":
|
|
@@ -841,17 +860,82 @@ function useLiveDashboardPoll({ messages, dispatch, getLiveDashboard }) {
|
|
|
841
860
|
}, [anchorKey, messages, forceUpdate]);
|
|
842
861
|
}
|
|
843
862
|
|
|
863
|
+
// src/conversationStore.ts
|
|
864
|
+
var TTL_MS = 36e5;
|
|
865
|
+
var MAX_MESSAGES = 30;
|
|
866
|
+
function readConversation(storageKey, now) {
|
|
867
|
+
let raw;
|
|
868
|
+
try {
|
|
869
|
+
raw = window.localStorage.getItem(storageKey);
|
|
870
|
+
} catch {
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
if (!raw) return null;
|
|
874
|
+
let parsed;
|
|
875
|
+
try {
|
|
876
|
+
parsed = JSON.parse(raw);
|
|
877
|
+
} catch {
|
|
878
|
+
return "corrupt";
|
|
879
|
+
}
|
|
880
|
+
if (parsed?.v !== 1 || !Array.isArray(parsed.messages) || typeof parsed.updatedAt !== "number") return "corrupt";
|
|
881
|
+
if (now - parsed.updatedAt > TTL_MS) return "expired";
|
|
882
|
+
return { conversationId: parsed.conversationId ?? null, messages: parsed.messages };
|
|
883
|
+
}
|
|
884
|
+
function writeConversation(storageKey, conversationId, messages, now) {
|
|
885
|
+
let capped = messages.slice(-MAX_MESSAGES);
|
|
886
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
887
|
+
try {
|
|
888
|
+
window.localStorage.setItem(storageKey, JSON.stringify({ v: 1, updatedAt: now, conversationId, messages: capped }));
|
|
889
|
+
return;
|
|
890
|
+
} catch {
|
|
891
|
+
if (capped.length <= 1) return;
|
|
892
|
+
capped = capped.slice(Math.ceil(capped.length / 2));
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
function clearConversation(storageKey) {
|
|
897
|
+
try {
|
|
898
|
+
window.localStorage.removeItem(storageKey);
|
|
899
|
+
} catch {
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
844
903
|
// src/useChatStream.ts
|
|
904
|
+
function hasAnswerContent(t) {
|
|
905
|
+
return !!t.text || t.cards.length > 0 || t.insights.length > 0 || t.marketOdds.length > 0 || t.oddsMovement.length > 0 || !!t.greetingDashboard || !!t.clubFanDashboard;
|
|
906
|
+
}
|
|
845
907
|
var STREAM_FALLBACK_TIMEOUT_MS = 3e4;
|
|
846
|
-
function
|
|
847
|
-
|
|
848
|
-
const
|
|
849
|
-
|
|
908
|
+
function seedIdCounter(messages) {
|
|
909
|
+
let max = -1;
|
|
910
|
+
for (const m of messages) {
|
|
911
|
+
const n = Number(m.id.slice(1));
|
|
912
|
+
if (m.id[0] === "m" && Number.isFinite(n) && n > max) max = n;
|
|
913
|
+
}
|
|
914
|
+
return max + 1;
|
|
915
|
+
}
|
|
916
|
+
function useChatStream(conversation, starterTexts, greetingMessage, storageKey) {
|
|
917
|
+
const boot = (0, import_react2.useMemo)(() => {
|
|
918
|
+
const payload = storageKey ? readConversation(storageKey, Date.now()) : null;
|
|
919
|
+
const restored = payload != null && payload !== "expired" && payload !== "corrupt";
|
|
920
|
+
const base = initialState(starterTexts.map(chipFromText));
|
|
921
|
+
if (restored) {
|
|
922
|
+
return { state: { ...base, messages: payload.messages, conversationId: payload.conversationId, starters: [] }, restored: true, stale: false };
|
|
923
|
+
}
|
|
924
|
+
return { state: base, restored: false, stale: payload === "expired" || payload === "corrupt" };
|
|
925
|
+
}, []);
|
|
926
|
+
const [state, dispatch] = (0, import_react2.useReducer)(reducer, boot.state);
|
|
927
|
+
const idRef = (0, import_react2.useRef)(seedIdCounter(boot.state.messages));
|
|
850
928
|
const handleRef = (0, import_react2.useRef)(null);
|
|
851
929
|
const fallbackTimerRef = (0, import_react2.useRef)(null);
|
|
852
930
|
const stateRef = (0, import_react2.useRef)(state);
|
|
853
931
|
stateRef.current = state;
|
|
854
|
-
const greetedRef = (0, import_react2.useRef)(
|
|
932
|
+
const greetedRef = (0, import_react2.useRef)(boot.restored);
|
|
933
|
+
const hydratedRef = (0, import_react2.useRef)(false);
|
|
934
|
+
const restoreSeededRef = (0, import_react2.useRef)(false);
|
|
935
|
+
if (!restoreSeededRef.current) {
|
|
936
|
+
restoreSeededRef.current = true;
|
|
937
|
+
if (boot.restored && boot.state.conversationId) conversation.conversationId = boot.state.conversationId;
|
|
938
|
+
}
|
|
855
939
|
const nextId = () => `m${idRef.current++}`;
|
|
856
940
|
const clearFallbackTimer = (0, import_react2.useCallback)(() => {
|
|
857
941
|
if (fallbackTimerRef.current != null) {
|
|
@@ -907,10 +991,9 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
|
|
|
907
991
|
const last = stateRef.current.lastUserInput;
|
|
908
992
|
if (last) run(last, stateRef.current.lastDisplayText ?? void 0);
|
|
909
993
|
}, [run]);
|
|
910
|
-
(0, import_react2.
|
|
994
|
+
const fireGreeting = (0, import_react2.useCallback)(() => {
|
|
911
995
|
const message = greetingMessage?.trim();
|
|
912
|
-
if (!message
|
|
913
|
-
greetedRef.current = true;
|
|
996
|
+
if (!message) return;
|
|
914
997
|
try {
|
|
915
998
|
handleRef.current?.cancel();
|
|
916
999
|
clearFallbackTimer();
|
|
@@ -923,6 +1006,55 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
|
|
|
923
1006
|
dispatch({ kind: "STOP" });
|
|
924
1007
|
}
|
|
925
1008
|
}, [greetingMessage, conversation, consume, clearFallbackTimer, armFallbackTimer]);
|
|
1009
|
+
(0, import_react2.useEffect)(() => {
|
|
1010
|
+
if (greetedRef.current) return;
|
|
1011
|
+
greetedRef.current = true;
|
|
1012
|
+
fireGreeting();
|
|
1013
|
+
}, [fireGreeting]);
|
|
1014
|
+
(0, import_react2.useEffect)(() => {
|
|
1015
|
+
if (boot.stale && storageKey) clearConversation(storageKey);
|
|
1016
|
+
}, []);
|
|
1017
|
+
(0, import_react2.useEffect)(() => {
|
|
1018
|
+
if (!storageKey || state.isStreaming) return;
|
|
1019
|
+
if (hydratedRef.current) {
|
|
1020
|
+
hydratedRef.current = false;
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
const msgs = state.messages;
|
|
1024
|
+
const persistable = msgs.filter((m, i) => {
|
|
1025
|
+
if (m.role === "user") {
|
|
1026
|
+
const next = msgs[i + 1];
|
|
1027
|
+
return !!next && next.role === "assistant" && next.done && !next.isGreeting && hasAnswerContent(next);
|
|
1028
|
+
}
|
|
1029
|
+
return m.done && !m.isGreeting && hasAnswerContent(m);
|
|
1030
|
+
});
|
|
1031
|
+
if (persistable.length === 0) return;
|
|
1032
|
+
writeConversation(storageKey, state.conversationId, persistable, Date.now());
|
|
1033
|
+
}, [storageKey, state.isStreaming, state.messages, state.conversationId]);
|
|
1034
|
+
(0, import_react2.useEffect)(() => {
|
|
1035
|
+
if (!storageKey) return;
|
|
1036
|
+
const onStorage = (e) => {
|
|
1037
|
+
if (e.key !== storageKey || e.storageArea !== window.localStorage) return;
|
|
1038
|
+
if (stateRef.current.isStreaming) return;
|
|
1039
|
+
if (e.newValue == null) {
|
|
1040
|
+
idRef.current = 0;
|
|
1041
|
+
conversation.conversationId = void 0;
|
|
1042
|
+
hydratedRef.current = true;
|
|
1043
|
+
dispatch({ kind: "RESET" });
|
|
1044
|
+
greetedRef.current = true;
|
|
1045
|
+
fireGreeting();
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
const parsed = readConversation(storageKey, Date.now());
|
|
1049
|
+
if (!parsed || parsed === "expired" || parsed === "corrupt") return;
|
|
1050
|
+
idRef.current = seedIdCounter(parsed.messages);
|
|
1051
|
+
conversation.conversationId = parsed.conversationId ?? void 0;
|
|
1052
|
+
hydratedRef.current = true;
|
|
1053
|
+
dispatch({ kind: "RESTORE", messages: parsed.messages, conversationId: parsed.conversationId });
|
|
1054
|
+
};
|
|
1055
|
+
window.addEventListener("storage", onStorage);
|
|
1056
|
+
return () => window.removeEventListener("storage", onStorage);
|
|
1057
|
+
}, [storageKey, conversation, fireGreeting]);
|
|
926
1058
|
const open = (0, import_react2.useCallback)(() => dispatch({ kind: "OPEN" }), []);
|
|
927
1059
|
const close = (0, import_react2.useCallback)(() => {
|
|
928
1060
|
handleRef.current?.cancel();
|
|
@@ -941,9 +1073,20 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
|
|
|
941
1073
|
else open();
|
|
942
1074
|
}, [close, open]);
|
|
943
1075
|
const dismissPopup = (0, import_react2.useCallback)(() => dispatch({ kind: "DISMISS_POPUP" }), []);
|
|
1076
|
+
const reset = (0, import_react2.useCallback)(() => {
|
|
1077
|
+
handleRef.current?.cancel();
|
|
1078
|
+
handleRef.current = null;
|
|
1079
|
+
clearFallbackTimer();
|
|
1080
|
+
if (storageKey) clearConversation(storageKey);
|
|
1081
|
+
idRef.current = 0;
|
|
1082
|
+
conversation.conversationId = void 0;
|
|
1083
|
+
dispatch({ kind: "RESET" });
|
|
1084
|
+
greetedRef.current = true;
|
|
1085
|
+
fireGreeting();
|
|
1086
|
+
}, [storageKey, conversation, clearFallbackTimer, fireGreeting]);
|
|
944
1087
|
const getLiveDashboard = (0, import_react2.useCallback)(() => conversation.getLiveDashboard(), [conversation]);
|
|
945
1088
|
useLiveDashboardPoll({ messages: state.messages, dispatch, getLiveDashboard });
|
|
946
|
-
return { state, open, close, stop, toggle, send, retry, dismissPopup };
|
|
1089
|
+
return { state, open, close, stop, toggle, send, retry, dismissPopup, reset };
|
|
947
1090
|
}
|
|
948
1091
|
|
|
949
1092
|
// src/useLiveMonitor.ts
|
|
@@ -1288,6 +1431,16 @@ function CloseIcon() {
|
|
|
1288
1431
|
function ChevronIcon() {
|
|
1289
1432
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", d: "M9 5l7 7-7 7" }) });
|
|
1290
1433
|
}
|
|
1434
|
+
function NewChatIcon() {
|
|
1435
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "none", d: "M12 5v14M5 12h14", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) });
|
|
1436
|
+
}
|
|
1437
|
+
function KebabIcon() {
|
|
1438
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { fill: "currentColor", children: [
|
|
1439
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "5", r: "1.6" }),
|
|
1440
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "1.6" }),
|
|
1441
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "19", r: "1.6" })
|
|
1442
|
+
] }) });
|
|
1443
|
+
}
|
|
1291
1444
|
var DASHBOARD_EMOJI = {
|
|
1292
1445
|
football: "\u26BD",
|
|
1293
1446
|
"goal-net": "\u{1F945}",
|
|
@@ -1352,20 +1505,89 @@ function ChatFab({ label, greeting, popupText, logoUrl, onOpen, onDismissPopup,
|
|
|
1352
1505
|
}
|
|
1353
1506
|
|
|
1354
1507
|
// src/components/ChatPanel.tsx
|
|
1355
|
-
var
|
|
1508
|
+
var import_react23 = require("react");
|
|
1356
1509
|
|
|
1357
1510
|
// src/components/ChatHeader.tsx
|
|
1511
|
+
var import_react6 = require("react");
|
|
1358
1512
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1359
|
-
function ChatHeader({
|
|
1513
|
+
function ChatHeader({
|
|
1514
|
+
onClose,
|
|
1515
|
+
closeLabel = "Close chat",
|
|
1516
|
+
logoUrl,
|
|
1517
|
+
headerTitle,
|
|
1518
|
+
onNewConversation,
|
|
1519
|
+
canReset,
|
|
1520
|
+
newConversationLabel = "New conversation",
|
|
1521
|
+
moreOptionsLabel = "More options"
|
|
1522
|
+
}) {
|
|
1523
|
+
const [menuOpen, setMenuOpen] = (0, import_react6.useState)(false);
|
|
1524
|
+
const wrapRef = (0, import_react6.useRef)(null);
|
|
1525
|
+
const kebabRef = (0, import_react6.useRef)(null);
|
|
1526
|
+
const firstItemRef = (0, import_react6.useRef)(null);
|
|
1527
|
+
const hasMenu = !!(canReset && onNewConversation);
|
|
1528
|
+
(0, import_react6.useEffect)(() => {
|
|
1529
|
+
if (menuOpen) firstItemRef.current?.focus();
|
|
1530
|
+
}, [menuOpen]);
|
|
1531
|
+
(0, import_react6.useEffect)(() => {
|
|
1532
|
+
if (!menuOpen) return;
|
|
1533
|
+
const onPointer = (e) => {
|
|
1534
|
+
if (!wrapRef.current?.contains(e.target)) setMenuOpen(false);
|
|
1535
|
+
};
|
|
1536
|
+
const onKey = (e) => {
|
|
1537
|
+
if (e.key === "Escape") {
|
|
1538
|
+
setMenuOpen(false);
|
|
1539
|
+
kebabRef.current?.focus();
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
document.addEventListener("mousedown", onPointer);
|
|
1543
|
+
document.addEventListener("keydown", onKey);
|
|
1544
|
+
return () => {
|
|
1545
|
+
document.removeEventListener("mousedown", onPointer);
|
|
1546
|
+
document.removeEventListener("keydown", onKey);
|
|
1547
|
+
};
|
|
1548
|
+
}, [menuOpen]);
|
|
1549
|
+
const runNewConversation = () => {
|
|
1550
|
+
setMenuOpen(false);
|
|
1551
|
+
onNewConversation?.();
|
|
1552
|
+
};
|
|
1360
1553
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("header", { className: "ss-w-header", children: [
|
|
1361
1554
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(BrandMark, { logoUrl, className: "ss-w-header-mark" }),
|
|
1362
1555
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ss-w-header-title", children: headerTitle ?? "SensibleStats" }),
|
|
1556
|
+
hasMenu ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ss-w-menu-wrap", ref: wrapRef, children: [
|
|
1557
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1558
|
+
"button",
|
|
1559
|
+
{
|
|
1560
|
+
type: "button",
|
|
1561
|
+
className: "ss-w-kebab",
|
|
1562
|
+
ref: kebabRef,
|
|
1563
|
+
"aria-haspopup": "menu",
|
|
1564
|
+
"aria-expanded": menuOpen,
|
|
1565
|
+
"aria-label": moreOptionsLabel,
|
|
1566
|
+
onClick: () => setMenuOpen((open) => !open),
|
|
1567
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(KebabIcon, {})
|
|
1568
|
+
}
|
|
1569
|
+
),
|
|
1570
|
+
menuOpen ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "ss-w-menu", role: "menu", "aria-label": moreOptionsLabel, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
1571
|
+
"button",
|
|
1572
|
+
{
|
|
1573
|
+
type: "button",
|
|
1574
|
+
className: "ss-w-menu-item",
|
|
1575
|
+
role: "menuitem",
|
|
1576
|
+
ref: firstItemRef,
|
|
1577
|
+
onClick: runNewConversation,
|
|
1578
|
+
children: [
|
|
1579
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NewChatIcon, {}),
|
|
1580
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: newConversationLabel })
|
|
1581
|
+
]
|
|
1582
|
+
}
|
|
1583
|
+
) }) : null
|
|
1584
|
+
] }) : null,
|
|
1363
1585
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "ss-w-close", "aria-label": closeLabel, onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseIcon, {}) })
|
|
1364
1586
|
] });
|
|
1365
1587
|
}
|
|
1366
1588
|
|
|
1367
1589
|
// src/components/MessageList.tsx
|
|
1368
|
-
var
|
|
1590
|
+
var import_react21 = require("react");
|
|
1369
1591
|
|
|
1370
1592
|
// src/components/Markdown.tsx
|
|
1371
1593
|
var import_react_markdown = __toESM(require("react-markdown"), 1);
|
|
@@ -1383,7 +1605,7 @@ function Markdown({ text }) {
|
|
|
1383
1605
|
}
|
|
1384
1606
|
|
|
1385
1607
|
// src/components/EntityCardList.tsx
|
|
1386
|
-
var
|
|
1608
|
+
var import_react7 = require("react");
|
|
1387
1609
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1388
1610
|
var MAX_VISIBLE = 3;
|
|
1389
1611
|
var RECORD_RE = /^(\d+)-(\d+)-(\d+)$/;
|
|
@@ -1467,7 +1689,7 @@ function ScopeTabs({ scopes, active, onSelect }) {
|
|
|
1467
1689
|
)) });
|
|
1468
1690
|
}
|
|
1469
1691
|
function EntityCardItem({ c }) {
|
|
1470
|
-
const [active, setActive] = (0,
|
|
1692
|
+
const [active, setActive] = (0, import_react7.useState)(0);
|
|
1471
1693
|
const hasBody = c.scopes.length > 0;
|
|
1472
1694
|
if (!hasBody) {
|
|
1473
1695
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "ss-w-ecard ss-w-ecard-bare", "data-kind": c.kind, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CardHeader, { c, showScopeCrest: false }) });
|
|
@@ -1481,7 +1703,7 @@ function EntityCardItem({ c }) {
|
|
|
1481
1703
|
] });
|
|
1482
1704
|
}
|
|
1483
1705
|
function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMoreLabel = (n) => `Show ${n} more` }) {
|
|
1484
|
-
const [expanded, setExpanded] = (0,
|
|
1706
|
+
const [expanded, setExpanded] = (0, import_react7.useState)(false);
|
|
1485
1707
|
if (cards.length === 0) return null;
|
|
1486
1708
|
const visible = expanded ? cards : cards.slice(0, MAX_VISIBLE);
|
|
1487
1709
|
const overflow = cards.length - MAX_VISIBLE;
|
|
@@ -1492,18 +1714,18 @@ function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMore
|
|
|
1492
1714
|
}
|
|
1493
1715
|
|
|
1494
1716
|
// src/components/BettingInsightList.tsx
|
|
1495
|
-
var
|
|
1717
|
+
var import_react12 = require("react");
|
|
1496
1718
|
|
|
1497
1719
|
// src/components/AddToSlipButton.tsx
|
|
1498
|
-
var
|
|
1720
|
+
var import_react9 = require("react");
|
|
1499
1721
|
var import_widget_sdk4 = require("@sensiblestats/widget-sdk");
|
|
1500
1722
|
|
|
1501
1723
|
// src/components/Toast.tsx
|
|
1502
|
-
var
|
|
1724
|
+
var import_react8 = require("react");
|
|
1503
1725
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1504
1726
|
var DISMISS_MS = 4e3;
|
|
1505
1727
|
function Toast({ toast, onDismiss }) {
|
|
1506
|
-
(0,
|
|
1728
|
+
(0, import_react8.useEffect)(() => {
|
|
1507
1729
|
if (!toast) return;
|
|
1508
1730
|
const timer = setTimeout(onDismiss, DISMISS_MS);
|
|
1509
1731
|
return () => clearTimeout(timer);
|
|
@@ -1521,10 +1743,10 @@ function withTimeout(value) {
|
|
|
1521
1743
|
]);
|
|
1522
1744
|
}
|
|
1523
1745
|
function SlipButton({ selection, shownOdds, marketName, slip, ui }) {
|
|
1524
|
-
const [pending, setPending] = (0,
|
|
1525
|
-
const [settled, setSettled] = (0,
|
|
1526
|
-
const [toast, setToast] = (0,
|
|
1527
|
-
const dismiss = (0,
|
|
1746
|
+
const [pending, setPending] = (0, import_react9.useState)(false);
|
|
1747
|
+
const [settled, setSettled] = (0, import_react9.useState)(false);
|
|
1748
|
+
const [toast, setToast] = (0, import_react9.useState)(null);
|
|
1749
|
+
const dismiss = (0, import_react9.useCallback)(() => setToast(null), []);
|
|
1528
1750
|
const handler = slip.onAddToSlip;
|
|
1529
1751
|
if (!ui || !handler || !selection) return null;
|
|
1530
1752
|
const onClick = async () => {
|
|
@@ -1588,14 +1810,14 @@ function OddsMovementBadge({
|
|
|
1588
1810
|
}
|
|
1589
1811
|
|
|
1590
1812
|
// src/components/Crest.tsx
|
|
1591
|
-
var
|
|
1813
|
+
var import_react10 = require("react");
|
|
1592
1814
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1593
1815
|
function initials2(name) {
|
|
1594
1816
|
return (name ?? "").trim().slice(0, 1).toUpperCase() || "?";
|
|
1595
1817
|
}
|
|
1596
1818
|
function Crest2({ url, name, kind }) {
|
|
1597
1819
|
const cls = kind === "comp" ? "ss-w-bi-comp-logo" : "ss-w-bi-crest";
|
|
1598
|
-
const [failed, setFailed] = (0,
|
|
1820
|
+
const [failed, setFailed] = (0, import_react10.useState)(false);
|
|
1599
1821
|
if (url && !failed) {
|
|
1600
1822
|
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
1601
1823
|
}
|
|
@@ -1603,7 +1825,7 @@ function Crest2({ url, name, kind }) {
|
|
|
1603
1825
|
}
|
|
1604
1826
|
|
|
1605
1827
|
// src/components/PinToggle.tsx
|
|
1606
|
-
var
|
|
1828
|
+
var import_react11 = require("react");
|
|
1607
1829
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1608
1830
|
var PIN_GLYPHS = {
|
|
1609
1831
|
pin: { viewBox: "0 0 24 24", strokeWidth: 1.4, transform: "scale(1.5)", d: "M9.5 1.5 14.5 6.5 13 8l-.5-.5-3 3 .3 2.2-1.4 1.4L5.6 11.3l-3.3 3.3-1-1 3.3-3.3L1.9 7.5l1.4-1.4 2.2.3 3-3L8 2.9z" },
|
|
@@ -1611,8 +1833,8 @@ var PIN_GLYPHS = {
|
|
|
1611
1833
|
bookmark: { viewBox: "0 0 24 24", strokeWidth: 2, d: "M7 3h10a1.5 1.5 0 0 1 1.5 1.5V21l-6.5-3.6L5.5 21V4.5A1.5 1.5 0 0 1 7 3z" }
|
|
1612
1834
|
};
|
|
1613
1835
|
function PinToggle({ pin, monitor, ui }) {
|
|
1614
|
-
const [toast, setToast] = (0,
|
|
1615
|
-
const dismiss = (0,
|
|
1836
|
+
const [toast, setToast] = (0, import_react11.useState)(null);
|
|
1837
|
+
const dismiss = (0, import_react11.useCallback)(() => setToast(null), []);
|
|
1616
1838
|
if (!monitor || !monitor.enabled) return null;
|
|
1617
1839
|
const key = pinKey(pin);
|
|
1618
1840
|
const pinned = monitor.isPinned(key);
|
|
@@ -1664,7 +1886,7 @@ function StatRows({ stats }) {
|
|
|
1664
1886
|
] }, `${s.label}:${s.value}`)) });
|
|
1665
1887
|
}
|
|
1666
1888
|
function ScopedStats({ scopes }) {
|
|
1667
|
-
const [active, setActive] = (0,
|
|
1889
|
+
const [active, setActive] = (0, import_react12.useState)(0);
|
|
1668
1890
|
const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
|
|
1669
1891
|
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ss-w-bi-scoped", children: [
|
|
1670
1892
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "ss-w-bi-scopes", role: "group", children: scopes.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", className: "ss-w-bi-chip", "aria-pressed": i === active, onClick: () => setActive(i), children: s.label }, s.key)) }),
|
|
@@ -1675,7 +1897,7 @@ function hasStatsContent(it) {
|
|
|
1675
1897
|
return !!it.scopes && it.scopes.length >= 2 || it.stats.length > 0;
|
|
1676
1898
|
}
|
|
1677
1899
|
function ReasonStrip({ it, ui }) {
|
|
1678
|
-
const [open, setOpen] = (0,
|
|
1900
|
+
const [open, setOpen] = (0, import_react12.useState)(false);
|
|
1679
1901
|
const hasScopes = !!it.scopes && it.scopes.length >= 2;
|
|
1680
1902
|
const hasStats = hasStatsContent(it);
|
|
1681
1903
|
const hasText = !!it.text;
|
|
@@ -1785,7 +2007,7 @@ function BettingInsightList({ insights, ui, slip, monitor }) {
|
|
|
1785
2007
|
}
|
|
1786
2008
|
|
|
1787
2009
|
// src/components/MarketOddsCardList.tsx
|
|
1788
|
-
var
|
|
2010
|
+
var import_react13 = require("react");
|
|
1789
2011
|
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1790
2012
|
function OddsRow({ vm, row, ui, slip, inSlipIds }) {
|
|
1791
2013
|
const selection = vm.addToSlipEnabled ? marketOddsRowToSlipSelection(vm, row, slip.conversationId) : null;
|
|
@@ -1802,7 +2024,7 @@ function OddsRow({ vm, row, ui, slip, inSlipIds }) {
|
|
|
1802
2024
|
] });
|
|
1803
2025
|
}
|
|
1804
2026
|
function MarketOddsCard({ vm, ui, slip, monitor, inSlipIds, hideHeader, pinRef }) {
|
|
1805
|
-
const [open, setOpen] = (0,
|
|
2027
|
+
const [open, setOpen] = (0, import_react13.useState)(false);
|
|
1806
2028
|
const kickoff = vm.kickoffUtc && ui?.bettingKickoff ? ui.bettingKickoff(vm.kickoffUtc) : void 0;
|
|
1807
2029
|
const hasNamedTeams = !!vm.homeTeamName && !!vm.awayTeamName;
|
|
1808
2030
|
const showPin = !!monitor && vm.pinEnabled;
|
|
@@ -2097,7 +2319,7 @@ function FixtureCard({ card }) {
|
|
|
2097
2319
|
}
|
|
2098
2320
|
|
|
2099
2321
|
// src/components/dashboard/CountdownCard.tsx
|
|
2100
|
-
var
|
|
2322
|
+
var import_react14 = require("react");
|
|
2101
2323
|
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
2102
2324
|
var DAY_MS = 864e5;
|
|
2103
2325
|
function remainingSegments(kickoffUnix, nowMs, withSeconds) {
|
|
@@ -2116,12 +2338,12 @@ function remainingSegments(kickoffUnix, nowMs, withSeconds) {
|
|
|
2116
2338
|
function CountdownCard({ card }) {
|
|
2117
2339
|
const { data } = card;
|
|
2118
2340
|
const unitLabel = { d: data.daysUnit, h: data.hoursUnit, m: data.minutesUnit, s: data.secondsUnit };
|
|
2119
|
-
const [nowMs, setNowMs] = (0,
|
|
2120
|
-
const [reduced] = (0,
|
|
2341
|
+
const [nowMs, setNowMs] = (0, import_react14.useState)(() => Date.now());
|
|
2342
|
+
const [reduced] = (0, import_react14.useState)(
|
|
2121
2343
|
() => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
2122
2344
|
);
|
|
2123
2345
|
const withinDay = data.kickoffUnix * 1e3 - nowMs < DAY_MS;
|
|
2124
|
-
(0,
|
|
2346
|
+
(0, import_react14.useEffect)(() => {
|
|
2125
2347
|
if (reduced) return;
|
|
2126
2348
|
const id = window.setInterval(() => setNowMs(Date.now()), withinDay ? 1e3 : 6e4);
|
|
2127
2349
|
return () => window.clearInterval(id);
|
|
@@ -2129,7 +2351,7 @@ function CountdownCard({ card }) {
|
|
|
2129
2351
|
const segments = remainingSegments(data.kickoffUnix, nowMs, withinDay && !reduced);
|
|
2130
2352
|
return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "ss-w-fandash-card ss-w-fandash-countdown", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "ss-w-fandash-countdown-top", children: [
|
|
2131
2353
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-eyebrow", children: card.title ?? "Kick-off in" }),
|
|
2132
|
-
segments ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-countdown-clock", children: segments.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
2354
|
+
segments ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-countdown-clock", children: segments.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react14.Fragment, { children: [
|
|
2133
2355
|
i > 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-countdown-colon", children: ":" }) : null,
|
|
2134
2356
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "ss-w-fandash-countdown-seg", children: [
|
|
2135
2357
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-countdown-value", children: s.value }),
|
|
@@ -2140,7 +2362,7 @@ function CountdownCard({ card }) {
|
|
|
2140
2362
|
}
|
|
2141
2363
|
|
|
2142
2364
|
// src/components/dashboard/InjuriesCard.tsx
|
|
2143
|
-
var
|
|
2365
|
+
var import_react15 = require("react");
|
|
2144
2366
|
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
2145
2367
|
var SHOWN = 3;
|
|
2146
2368
|
function ColHead({ name, crestUrl }) {
|
|
@@ -2163,7 +2385,7 @@ function InjuryList({ rows, emptyLabel, limit }) {
|
|
|
2163
2385
|
}
|
|
2164
2386
|
function InjuriesCard({ card, ui }) {
|
|
2165
2387
|
const { data } = card;
|
|
2166
|
-
const [expanded, setExpanded] = (0,
|
|
2388
|
+
const [expanded, setExpanded] = (0, import_react15.useState)(false);
|
|
2167
2389
|
const hidden = Math.max(0, data.ours.length - SHOWN) + Math.max(0, data.theirs.length - SHOWN);
|
|
2168
2390
|
const limit = expanded ? void 0 : SHOWN;
|
|
2169
2391
|
const showMore = ui?.showMore ?? ((n) => `Show ${n} more`);
|
|
@@ -2267,7 +2489,7 @@ function LinksCard({ card }) {
|
|
|
2267
2489
|
}
|
|
2268
2490
|
|
|
2269
2491
|
// src/components/dashboard/PostMatchResultCard.tsx
|
|
2270
|
-
var
|
|
2492
|
+
var import_react16 = require("react");
|
|
2271
2493
|
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
2272
2494
|
function initials3(name) {
|
|
2273
2495
|
return name.trim().slice(0, 3).toUpperCase();
|
|
@@ -2277,7 +2499,7 @@ function PostMatchCrest({
|
|
|
2277
2499
|
name,
|
|
2278
2500
|
side
|
|
2279
2501
|
}) {
|
|
2280
|
-
const [failed, setFailed] = (0,
|
|
2502
|
+
const [failed, setFailed] = (0, import_react16.useState)(false);
|
|
2281
2503
|
const cls = `ss-w-fandash-postmatch-crest ss-w-fandash-postmatch-crest--${side}`;
|
|
2282
2504
|
if (url && !failed) {
|
|
2283
2505
|
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
@@ -2476,10 +2698,10 @@ function RestingFormCard({ card }) {
|
|
|
2476
2698
|
}
|
|
2477
2699
|
|
|
2478
2700
|
// src/components/dashboard/LiveScoreCard.tsx
|
|
2479
|
-
var
|
|
2701
|
+
var import_react18 = require("react");
|
|
2480
2702
|
|
|
2481
2703
|
// src/useLiveClock.ts
|
|
2482
|
-
var
|
|
2704
|
+
var import_react17 = require("react");
|
|
2483
2705
|
var TICK_MS = 6e4;
|
|
2484
2706
|
var MAX_LIVE_TICK_MINUTES = 15;
|
|
2485
2707
|
function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
@@ -2488,10 +2710,10 @@ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
|
2488
2710
|
return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
|
|
2489
2711
|
}
|
|
2490
2712
|
function useLiveClock(minute, clockRunning, receivedAtMs) {
|
|
2491
|
-
const [displayMinute, setDisplayMinute] = (0,
|
|
2713
|
+
const [displayMinute, setDisplayMinute] = (0, import_react17.useState)(
|
|
2492
2714
|
() => computeDisplayMinute(minute, clockRunning, receivedAtMs)
|
|
2493
2715
|
);
|
|
2494
|
-
(0,
|
|
2716
|
+
(0, import_react17.useEffect)(() => {
|
|
2495
2717
|
setDisplayMinute(computeDisplayMinute(minute, clockRunning, receivedAtMs));
|
|
2496
2718
|
if (!clockRunning) return;
|
|
2497
2719
|
const id = window.setInterval(() => {
|
|
@@ -2508,7 +2730,7 @@ function initials5(name) {
|
|
|
2508
2730
|
return name.trim().slice(0, 3).toUpperCase();
|
|
2509
2731
|
}
|
|
2510
2732
|
function LiveCrest({ url, name, side }) {
|
|
2511
|
-
const [failed, setFailed] = (0,
|
|
2733
|
+
const [failed, setFailed] = (0, import_react18.useState)(false);
|
|
2512
2734
|
const cls = `ss-w-fandash-live-crest ss-w-fandash-live-crest--${side}`;
|
|
2513
2735
|
if (url && !failed) {
|
|
2514
2736
|
return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
@@ -2527,7 +2749,7 @@ function LiveScoreCard({
|
|
|
2527
2749
|
}) {
|
|
2528
2750
|
const { data } = card;
|
|
2529
2751
|
const seedKey = `${data.minute}:${data.homeScore}:${data.awayScore}`;
|
|
2530
|
-
const seedRef = (0,
|
|
2752
|
+
const seedRef = (0, import_react18.useRef)(null);
|
|
2531
2753
|
if (!seedRef.current || seedRef.current.key !== seedKey) {
|
|
2532
2754
|
seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
|
|
2533
2755
|
}
|
|
@@ -2567,7 +2789,7 @@ function LiveScoreCard({
|
|
|
2567
2789
|
}
|
|
2568
2790
|
|
|
2569
2791
|
// src/components/dashboard/LiveTimelineCard.tsx
|
|
2570
|
-
var
|
|
2792
|
+
var import_react19 = require("react");
|
|
2571
2793
|
var import_jsx_runtime37 = require("react/jsx-runtime");
|
|
2572
2794
|
var SHOWN2 = 3;
|
|
2573
2795
|
function EventIcon({ type }) {
|
|
@@ -2609,7 +2831,7 @@ function EventRow({ e }) {
|
|
|
2609
2831
|
] });
|
|
2610
2832
|
}
|
|
2611
2833
|
function LiveTimelineCard({ card, ui }) {
|
|
2612
|
-
const [expanded, setExpanded] = (0,
|
|
2834
|
+
const [expanded, setExpanded] = (0, import_react19.useState)(false);
|
|
2613
2835
|
const events = card.data.events;
|
|
2614
2836
|
if (!events || events.length === 0) return null;
|
|
2615
2837
|
const sorted = [...events].sort((a, b) => b.minute - a.minute);
|
|
@@ -2634,7 +2856,7 @@ function LiveTimelineCard({ card, ui }) {
|
|
|
2634
2856
|
}
|
|
2635
2857
|
|
|
2636
2858
|
// src/components/dashboard/MomentumWave.tsx
|
|
2637
|
-
var
|
|
2859
|
+
var import_react20 = require("react");
|
|
2638
2860
|
var import_jsx_runtime38 = require("react/jsx-runtime");
|
|
2639
2861
|
var WIDTH = 270;
|
|
2640
2862
|
var HEIGHT = 56;
|
|
@@ -2658,7 +2880,7 @@ function MomentumWave({
|
|
|
2658
2880
|
}) {
|
|
2659
2881
|
if (points.length === 0) return null;
|
|
2660
2882
|
const { area, line, lastLeftPct, lastTopPx } = buildPaths(points);
|
|
2661
|
-
const clipId = (0,
|
|
2883
|
+
const clipId = (0, import_react20.useId)();
|
|
2662
2884
|
const topClipId = `${clipId}-top`;
|
|
2663
2885
|
const bottomClipId = `${clipId}-bottom`;
|
|
2664
2886
|
const topClass = clubIsHome ? "ss-w-fandash-live-mom-home" : "ss-w-fandash-live-mom-away";
|
|
@@ -2849,11 +3071,11 @@ function turnAnchor(messages) {
|
|
|
2849
3071
|
}
|
|
2850
3072
|
function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
2851
3073
|
const { state, send } = controller;
|
|
2852
|
-
const listRef = (0,
|
|
2853
|
-
const endRef = (0,
|
|
2854
|
-
const pinnedRef = (0,
|
|
2855
|
-
const anchoredTurnRef = (0,
|
|
2856
|
-
(0,
|
|
3074
|
+
const listRef = (0, import_react21.useRef)(null);
|
|
3075
|
+
const endRef = (0, import_react21.useRef)(null);
|
|
3076
|
+
const pinnedRef = (0, import_react21.useRef)(true);
|
|
3077
|
+
const anchoredTurnRef = (0, import_react21.useRef)(null);
|
|
3078
|
+
(0, import_react21.useEffect)(() => {
|
|
2857
3079
|
const anchor = turnAnchor(state.messages);
|
|
2858
3080
|
const list = listRef.current;
|
|
2859
3081
|
if (anchor && anchor.turnId !== anchoredTurnRef.current) {
|
|
@@ -2909,10 +3131,10 @@ function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
|
2909
3131
|
}
|
|
2910
3132
|
|
|
2911
3133
|
// src/components/ChatInput.tsx
|
|
2912
|
-
var
|
|
3134
|
+
var import_react22 = require("react");
|
|
2913
3135
|
var import_jsx_runtime44 = require("react/jsx-runtime");
|
|
2914
3136
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
2915
|
-
const [value, setValue] = (0,
|
|
3137
|
+
const [value, setValue] = (0, import_react22.useState)("");
|
|
2916
3138
|
const submit = () => {
|
|
2917
3139
|
const t = value.trim();
|
|
2918
3140
|
if (!t) return;
|
|
@@ -3219,8 +3441,8 @@ function OddsPanel({ monitor, ui, slip }) {
|
|
|
3219
3441
|
var import_jsx_runtime49 = require("react/jsx-runtime");
|
|
3220
3442
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
3221
3443
|
function useIsModal() {
|
|
3222
|
-
const [modal, setModal] = (0,
|
|
3223
|
-
(0,
|
|
3444
|
+
const [modal, setModal] = (0, import_react23.useState)(false);
|
|
3445
|
+
(0, import_react23.useEffect)(() => {
|
|
3224
3446
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
3225
3447
|
const mql = window.matchMedia(FULLSCREEN_QUERY);
|
|
3226
3448
|
const sync = () => setModal(mql.matches);
|
|
@@ -3231,9 +3453,9 @@ function useIsModal() {
|
|
|
3231
3453
|
return modal;
|
|
3232
3454
|
}
|
|
3233
3455
|
function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
3234
|
-
const { state, close, stop, send } = controller;
|
|
3456
|
+
const { state, close, stop, send, reset } = controller;
|
|
3235
3457
|
const isModal = useIsModal();
|
|
3236
|
-
(0,
|
|
3458
|
+
(0, import_react23.useEffect)(() => {
|
|
3237
3459
|
const onKey = (e) => {
|
|
3238
3460
|
if (e.key === "Escape") close();
|
|
3239
3461
|
};
|
|
@@ -3244,11 +3466,23 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
|
3244
3466
|
monitor?.setActiveTab("chat");
|
|
3245
3467
|
send(text);
|
|
3246
3468
|
};
|
|
3247
|
-
(0,
|
|
3469
|
+
(0, import_react23.useEffect)(() => {
|
|
3248
3470
|
if (monitor && monitor.activeTab !== "chat" && state.isStreaming) monitor.markChatActivity();
|
|
3249
3471
|
}, [monitor, state.isStreaming, state.messages.length]);
|
|
3250
3472
|
return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
3251
|
-
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
3473
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
3474
|
+
ChatHeader,
|
|
3475
|
+
{
|
|
3476
|
+
onClose: close,
|
|
3477
|
+
closeLabel: ui.closeChat,
|
|
3478
|
+
logoUrl: ui.logoUrl,
|
|
3479
|
+
headerTitle: ui.headerTitle,
|
|
3480
|
+
onNewConversation: reset,
|
|
3481
|
+
canReset: state.messages.length > 0,
|
|
3482
|
+
newConversationLabel: ui.newConversation,
|
|
3483
|
+
moreOptionsLabel: ui.moreOptions
|
|
3484
|
+
}
|
|
3485
|
+
),
|
|
3252
3486
|
monitor && /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
3253
3487
|
TabBar,
|
|
3254
3488
|
{
|
|
@@ -3363,8 +3597,14 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
3363
3597
|
}
|
|
3364
3598
|
.ss-w-header { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--ss-w-line); }
|
|
3365
3599
|
.ss-w-header-mark { width: 18px; height: 18px; display: block; object-fit: contain; }
|
|
3366
|
-
.ss-w-header-title { font-weight: 660; }
|
|
3367
|
-
.ss-w-close {
|
|
3600
|
+
.ss-w-header-title { font-weight: 660; flex: 1; }
|
|
3601
|
+
.ss-w-close { background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 16px; display: grid; place-items: center; }
|
|
3602
|
+
.ss-w-menu-wrap { position: relative; display: grid; place-items: center; }
|
|
3603
|
+
.ss-w-kebab { background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 16px; display: grid; place-items: center; }
|
|
3604
|
+
.ss-w-menu { position: absolute; top: calc(100% + 6px); right: 0; z-index: 5; min-width: 184px; padding: 6px; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,.18); }
|
|
3605
|
+
.ss-w-menu-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 10px; background: none; border: none; border-radius: 8px; color: var(--ss-w-ink); font: inherit; font-size: 13px; text-align: left; cursor: pointer; }
|
|
3606
|
+
.ss-w-menu-item:hover, .ss-w-menu-item:focus-visible { background: var(--ss-w-bubble); }
|
|
3607
|
+
.ss-w-menu-item svg { color: var(--ss-w-faint); font-size: 15px; flex: none; }
|
|
3368
3608
|
|
|
3369
3609
|
/* messages */
|
|
3370
3610
|
/* .ss-w-mon-list (Matches/Odds tab scrollers) shares .ss-w-list's box/scroll rules by design --
|
|
@@ -4104,21 +4344,22 @@ function injectStyles(root) {
|
|
|
4104
4344
|
function StatsWidget(props) {
|
|
4105
4345
|
const injectedClient = "client" in props ? props.client : void 0;
|
|
4106
4346
|
const cfg = props.config;
|
|
4107
|
-
const built = (0,
|
|
4108
|
-
const ui = (0,
|
|
4109
|
-
const rootRef = (0,
|
|
4110
|
-
const introDoneRef = (0,
|
|
4111
|
-
const markIntroDone = (0,
|
|
4347
|
+
const built = (0, import_react24.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
|
|
4348
|
+
const ui = (0, import_react24.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
|
|
4349
|
+
const rootRef = (0, import_react24.useRef)(null);
|
|
4350
|
+
const introDoneRef = (0, import_react24.useRef)(false);
|
|
4351
|
+
const markIntroDone = (0, import_react24.useCallback)(() => {
|
|
4112
4352
|
introDoneRef.current = true;
|
|
4113
4353
|
}, []);
|
|
4114
|
-
const
|
|
4354
|
+
const convStorageKey = `ss-w-conv:${cfg?.operatorId ?? ""}:${cfg?.userContext?.operatorUserId ?? "anon"}`;
|
|
4355
|
+
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0, convStorageKey);
|
|
4115
4356
|
const monitor = useLiveMonitor({
|
|
4116
4357
|
getLiveMonitor: (pins) => built ? built.conversation.getLiveMonitor(pins) : Promise.resolve(null),
|
|
4117
4358
|
getMarketOdds: (matchId, dev) => built ? built.conversation.getMarketOdds(matchId, dev) : Promise.resolve(null),
|
|
4118
4359
|
storageKey: `ss-w-pins:${cfg?.operatorId ?? ""}:${cfg?.userContext?.operatorUserId ?? "anon"}`,
|
|
4119
4360
|
isOpen: controller.state.isOpen
|
|
4120
4361
|
});
|
|
4121
|
-
const slip = (0,
|
|
4362
|
+
const slip = (0, import_react24.useMemo)(
|
|
4122
4363
|
() => {
|
|
4123
4364
|
const handler = props.config?.onAddToSlip;
|
|
4124
4365
|
return {
|
|
@@ -4141,11 +4382,11 @@ function StatsWidget(props) {
|
|
|
4141
4382
|
},
|
|
4142
4383
|
[props.config?.onAddToSlip, controller.state.conversationId, built, monitor.markInSlip]
|
|
4143
4384
|
);
|
|
4144
|
-
const onDashboardTap = (0,
|
|
4385
|
+
const onDashboardTap = (0, import_react24.useMemo)(
|
|
4145
4386
|
() => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
|
|
4146
4387
|
[built]
|
|
4147
4388
|
);
|
|
4148
|
-
(0,
|
|
4389
|
+
(0, import_react24.useEffect)(() => {
|
|
4149
4390
|
if (!built || !ui.injectStyles) return;
|
|
4150
4391
|
const node = rootRef.current?.getRootNode();
|
|
4151
4392
|
if (node) injectStyles(node);
|