@sensiblestats/widget-react 0.37.2 → 0.38.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 +214 -60
- 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 +214 -60
- package/dist/index.js.map +1 -1
- package/dist/styles.css +8 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @sensiblestats/widget-react
|
|
2
2
|
|
|
3
|
+
## 0.38.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Persist the chat conversation in the browser for a sliding hour: on return within the hour the widget silently restores the prior thread (and continues the same server thread), otherwise it starts fresh. Adds a "New conversation" control to clear and start over. Client-only (localStorage); no SDK or server change. Conversations now sync live across open browser tabs (a write or "New conversation" in one tab mirrors into the others).
|
|
8
|
+
|
|
9
|
+
## 0.37.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Odds tab: show a single market-odds card per pin instead of a card-inside-a-card. The fixture/date is no longer duplicated — each pinned market renders one betting card (competition, fixture, market, odds) with the unpin in its own header, plus a slim live strip (score, minute, suspended/settled) shown only for in-play markets and hidden for pre-match. The unpin threads the exact stored pin key so it always removes the right pin.
|
|
14
|
+
|
|
3
15
|
## 0.37.2
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -57,6 +57,7 @@ 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",
|
|
60
61
|
chatDialogLabel: "SensibleStats chat",
|
|
61
62
|
stopResponse: "Stop response",
|
|
62
63
|
sendMessage: "Send message",
|
|
@@ -129,6 +130,7 @@ var STRINGS = {
|
|
|
129
130
|
launcherLabel: "SensibleStats ile sohbet et",
|
|
130
131
|
placeholder: "Bir tak\u0131m, oyuncu veya ma\xE7 hakk\u0131nda sor\u2026",
|
|
131
132
|
closeChat: "Sohbeti kapat",
|
|
133
|
+
newConversation: "Yeni sohbet",
|
|
132
134
|
chatDialogLabel: "SensibleStats sohbet",
|
|
133
135
|
stopResponse: "Yan\u0131t\u0131 durdur",
|
|
134
136
|
sendMessage: "Mesaj g\xF6nder",
|
|
@@ -224,6 +226,7 @@ function normalizeUi(cfg) {
|
|
|
224
226
|
starters: cfg.starters ?? [],
|
|
225
227
|
injectStyles: cfg.injectStyles ?? true,
|
|
226
228
|
closeChat: strings.closeChat,
|
|
229
|
+
newConversation: strings.newConversation,
|
|
227
230
|
chatDialogLabel: strings.chatDialogLabel,
|
|
228
231
|
stopResponse: strings.stopResponse,
|
|
229
232
|
sendMessage: strings.sendMessage,
|
|
@@ -642,7 +645,7 @@ function marketOddsRowToSlipSelection(vm, row, conversationId) {
|
|
|
642
645
|
// src/reducer.ts
|
|
643
646
|
var POPUP_MAX = 160;
|
|
644
647
|
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 };
|
|
648
|
+
return { isOpen: false, messages: [], isStreaming: false, status: null, actionsLoading: false, starters, configuredStarters: starters, error: null, lastUserInput: null, lastDisplayText: null, popupText: null, conversationId: null };
|
|
646
649
|
}
|
|
647
650
|
function activeTurn(state) {
|
|
648
651
|
const last = state.messages[state.messages.length - 1];
|
|
@@ -722,6 +725,19 @@ function reducer(state, action) {
|
|
|
722
725
|
}
|
|
723
726
|
case "DISMISS_POPUP":
|
|
724
727
|
return { ...state, popupText: null };
|
|
728
|
+
case "RESET":
|
|
729
|
+
return { ...initialState(state.configuredStarters), isOpen: state.isOpen };
|
|
730
|
+
case "RESTORE":
|
|
731
|
+
return {
|
|
732
|
+
...state,
|
|
733
|
+
messages: action.messages,
|
|
734
|
+
conversationId: action.conversationId,
|
|
735
|
+
isStreaming: false,
|
|
736
|
+
status: null,
|
|
737
|
+
actionsLoading: false,
|
|
738
|
+
starters: action.messages.length ? [] : state.configuredStarters,
|
|
739
|
+
error: null
|
|
740
|
+
};
|
|
725
741
|
case "EVENT":
|
|
726
742
|
return applyEvent(state, action.event);
|
|
727
743
|
case "STREAM_ERROR":
|
|
@@ -841,17 +857,82 @@ function useLiveDashboardPoll({ messages, dispatch, getLiveDashboard }) {
|
|
|
841
857
|
}, [anchorKey, messages, forceUpdate]);
|
|
842
858
|
}
|
|
843
859
|
|
|
860
|
+
// src/conversationStore.ts
|
|
861
|
+
var TTL_MS = 36e5;
|
|
862
|
+
var MAX_MESSAGES = 30;
|
|
863
|
+
function readConversation(storageKey, now) {
|
|
864
|
+
let raw;
|
|
865
|
+
try {
|
|
866
|
+
raw = window.localStorage.getItem(storageKey);
|
|
867
|
+
} catch {
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
if (!raw) return null;
|
|
871
|
+
let parsed;
|
|
872
|
+
try {
|
|
873
|
+
parsed = JSON.parse(raw);
|
|
874
|
+
} catch {
|
|
875
|
+
return "corrupt";
|
|
876
|
+
}
|
|
877
|
+
if (parsed?.v !== 1 || !Array.isArray(parsed.messages) || typeof parsed.updatedAt !== "number") return "corrupt";
|
|
878
|
+
if (now - parsed.updatedAt > TTL_MS) return "expired";
|
|
879
|
+
return { conversationId: parsed.conversationId ?? null, messages: parsed.messages };
|
|
880
|
+
}
|
|
881
|
+
function writeConversation(storageKey, conversationId, messages, now) {
|
|
882
|
+
let capped = messages.slice(-MAX_MESSAGES);
|
|
883
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
884
|
+
try {
|
|
885
|
+
window.localStorage.setItem(storageKey, JSON.stringify({ v: 1, updatedAt: now, conversationId, messages: capped }));
|
|
886
|
+
return;
|
|
887
|
+
} catch {
|
|
888
|
+
if (capped.length <= 1) return;
|
|
889
|
+
capped = capped.slice(Math.ceil(capped.length / 2));
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function clearConversation(storageKey) {
|
|
894
|
+
try {
|
|
895
|
+
window.localStorage.removeItem(storageKey);
|
|
896
|
+
} catch {
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
844
900
|
// src/useChatStream.ts
|
|
901
|
+
function hasAnswerContent(t) {
|
|
902
|
+
return !!t.text || t.cards.length > 0 || t.insights.length > 0 || t.marketOdds.length > 0 || t.oddsMovement.length > 0 || !!t.greetingDashboard || !!t.clubFanDashboard;
|
|
903
|
+
}
|
|
845
904
|
var STREAM_FALLBACK_TIMEOUT_MS = 3e4;
|
|
846
|
-
function
|
|
847
|
-
|
|
848
|
-
const
|
|
849
|
-
|
|
905
|
+
function seedIdCounter(messages) {
|
|
906
|
+
let max = -1;
|
|
907
|
+
for (const m of messages) {
|
|
908
|
+
const n = Number(m.id.slice(1));
|
|
909
|
+
if (m.id[0] === "m" && Number.isFinite(n) && n > max) max = n;
|
|
910
|
+
}
|
|
911
|
+
return max + 1;
|
|
912
|
+
}
|
|
913
|
+
function useChatStream(conversation, starterTexts, greetingMessage, storageKey) {
|
|
914
|
+
const boot = (0, import_react2.useMemo)(() => {
|
|
915
|
+
const payload = storageKey ? readConversation(storageKey, Date.now()) : null;
|
|
916
|
+
const restored = payload != null && payload !== "expired" && payload !== "corrupt";
|
|
917
|
+
const base = initialState(starterTexts.map(chipFromText));
|
|
918
|
+
if (restored) {
|
|
919
|
+
return { state: { ...base, messages: payload.messages, conversationId: payload.conversationId, starters: [] }, restored: true, stale: false };
|
|
920
|
+
}
|
|
921
|
+
return { state: base, restored: false, stale: payload === "expired" || payload === "corrupt" };
|
|
922
|
+
}, []);
|
|
923
|
+
const [state, dispatch] = (0, import_react2.useReducer)(reducer, boot.state);
|
|
924
|
+
const idRef = (0, import_react2.useRef)(seedIdCounter(boot.state.messages));
|
|
850
925
|
const handleRef = (0, import_react2.useRef)(null);
|
|
851
926
|
const fallbackTimerRef = (0, import_react2.useRef)(null);
|
|
852
927
|
const stateRef = (0, import_react2.useRef)(state);
|
|
853
928
|
stateRef.current = state;
|
|
854
|
-
const greetedRef = (0, import_react2.useRef)(
|
|
929
|
+
const greetedRef = (0, import_react2.useRef)(boot.restored);
|
|
930
|
+
const hydratedRef = (0, import_react2.useRef)(false);
|
|
931
|
+
const restoreSeededRef = (0, import_react2.useRef)(false);
|
|
932
|
+
if (!restoreSeededRef.current) {
|
|
933
|
+
restoreSeededRef.current = true;
|
|
934
|
+
if (boot.restored && boot.state.conversationId) conversation.conversationId = boot.state.conversationId;
|
|
935
|
+
}
|
|
855
936
|
const nextId = () => `m${idRef.current++}`;
|
|
856
937
|
const clearFallbackTimer = (0, import_react2.useCallback)(() => {
|
|
857
938
|
if (fallbackTimerRef.current != null) {
|
|
@@ -907,10 +988,9 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
|
|
|
907
988
|
const last = stateRef.current.lastUserInput;
|
|
908
989
|
if (last) run(last, stateRef.current.lastDisplayText ?? void 0);
|
|
909
990
|
}, [run]);
|
|
910
|
-
(0, import_react2.
|
|
991
|
+
const fireGreeting = (0, import_react2.useCallback)(() => {
|
|
911
992
|
const message = greetingMessage?.trim();
|
|
912
|
-
if (!message
|
|
913
|
-
greetedRef.current = true;
|
|
993
|
+
if (!message) return;
|
|
914
994
|
try {
|
|
915
995
|
handleRef.current?.cancel();
|
|
916
996
|
clearFallbackTimer();
|
|
@@ -923,6 +1003,55 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
|
|
|
923
1003
|
dispatch({ kind: "STOP" });
|
|
924
1004
|
}
|
|
925
1005
|
}, [greetingMessage, conversation, consume, clearFallbackTimer, armFallbackTimer]);
|
|
1006
|
+
(0, import_react2.useEffect)(() => {
|
|
1007
|
+
if (greetedRef.current) return;
|
|
1008
|
+
greetedRef.current = true;
|
|
1009
|
+
fireGreeting();
|
|
1010
|
+
}, [fireGreeting]);
|
|
1011
|
+
(0, import_react2.useEffect)(() => {
|
|
1012
|
+
if (boot.stale && storageKey) clearConversation(storageKey);
|
|
1013
|
+
}, []);
|
|
1014
|
+
(0, import_react2.useEffect)(() => {
|
|
1015
|
+
if (!storageKey || state.isStreaming) return;
|
|
1016
|
+
if (hydratedRef.current) {
|
|
1017
|
+
hydratedRef.current = false;
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
const msgs = state.messages;
|
|
1021
|
+
const persistable = msgs.filter((m, i) => {
|
|
1022
|
+
if (m.role === "user") {
|
|
1023
|
+
const next = msgs[i + 1];
|
|
1024
|
+
return !!next && next.role === "assistant" && next.done && !next.isGreeting && hasAnswerContent(next);
|
|
1025
|
+
}
|
|
1026
|
+
return m.done && !m.isGreeting && hasAnswerContent(m);
|
|
1027
|
+
});
|
|
1028
|
+
if (persistable.length === 0) return;
|
|
1029
|
+
writeConversation(storageKey, state.conversationId, persistable, Date.now());
|
|
1030
|
+
}, [storageKey, state.isStreaming, state.messages, state.conversationId]);
|
|
1031
|
+
(0, import_react2.useEffect)(() => {
|
|
1032
|
+
if (!storageKey) return;
|
|
1033
|
+
const onStorage = (e) => {
|
|
1034
|
+
if (e.key !== storageKey || e.storageArea !== window.localStorage) return;
|
|
1035
|
+
if (stateRef.current.isStreaming) return;
|
|
1036
|
+
if (e.newValue == null) {
|
|
1037
|
+
idRef.current = 0;
|
|
1038
|
+
conversation.conversationId = void 0;
|
|
1039
|
+
hydratedRef.current = true;
|
|
1040
|
+
dispatch({ kind: "RESET" });
|
|
1041
|
+
greetedRef.current = true;
|
|
1042
|
+
fireGreeting();
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
const parsed = readConversation(storageKey, Date.now());
|
|
1046
|
+
if (!parsed || parsed === "expired" || parsed === "corrupt") return;
|
|
1047
|
+
idRef.current = seedIdCounter(parsed.messages);
|
|
1048
|
+
conversation.conversationId = parsed.conversationId ?? void 0;
|
|
1049
|
+
hydratedRef.current = true;
|
|
1050
|
+
dispatch({ kind: "RESTORE", messages: parsed.messages, conversationId: parsed.conversationId });
|
|
1051
|
+
};
|
|
1052
|
+
window.addEventListener("storage", onStorage);
|
|
1053
|
+
return () => window.removeEventListener("storage", onStorage);
|
|
1054
|
+
}, [storageKey, conversation, fireGreeting]);
|
|
926
1055
|
const open = (0, import_react2.useCallback)(() => dispatch({ kind: "OPEN" }), []);
|
|
927
1056
|
const close = (0, import_react2.useCallback)(() => {
|
|
928
1057
|
handleRef.current?.cancel();
|
|
@@ -941,9 +1070,20 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
|
|
|
941
1070
|
else open();
|
|
942
1071
|
}, [close, open]);
|
|
943
1072
|
const dismissPopup = (0, import_react2.useCallback)(() => dispatch({ kind: "DISMISS_POPUP" }), []);
|
|
1073
|
+
const reset = (0, import_react2.useCallback)(() => {
|
|
1074
|
+
handleRef.current?.cancel();
|
|
1075
|
+
handleRef.current = null;
|
|
1076
|
+
clearFallbackTimer();
|
|
1077
|
+
if (storageKey) clearConversation(storageKey);
|
|
1078
|
+
idRef.current = 0;
|
|
1079
|
+
conversation.conversationId = void 0;
|
|
1080
|
+
dispatch({ kind: "RESET" });
|
|
1081
|
+
greetedRef.current = true;
|
|
1082
|
+
fireGreeting();
|
|
1083
|
+
}, [storageKey, conversation, clearFallbackTimer, fireGreeting]);
|
|
944
1084
|
const getLiveDashboard = (0, import_react2.useCallback)(() => conversation.getLiveDashboard(), [conversation]);
|
|
945
1085
|
useLiveDashboardPoll({ messages: state.messages, dispatch, getLiveDashboard });
|
|
946
|
-
return { state, open, close, stop, toggle, send, retry, dismissPopup };
|
|
1086
|
+
return { state, open, close, stop, toggle, send, retry, dismissPopup, reset };
|
|
947
1087
|
}
|
|
948
1088
|
|
|
949
1089
|
// src/useLiveMonitor.ts
|
|
@@ -1288,6 +1428,9 @@ function CloseIcon() {
|
|
|
1288
1428
|
function ChevronIcon() {
|
|
1289
1429
|
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
1430
|
}
|
|
1431
|
+
function NewChatIcon() {
|
|
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", d: "M12 5v14M5 12h14", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) });
|
|
1433
|
+
}
|
|
1291
1434
|
var DASHBOARD_EMOJI = {
|
|
1292
1435
|
football: "\u26BD",
|
|
1293
1436
|
"goal-net": "\u{1F945}",
|
|
@@ -1356,10 +1499,11 @@ var import_react22 = require("react");
|
|
|
1356
1499
|
|
|
1357
1500
|
// src/components/ChatHeader.tsx
|
|
1358
1501
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1359
|
-
function ChatHeader({ onClose, closeLabel = "Close chat", logoUrl, headerTitle }) {
|
|
1502
|
+
function ChatHeader({ onClose, closeLabel = "Close chat", logoUrl, headerTitle, onNewConversation, canReset, newConversationLabel = "New conversation" }) {
|
|
1360
1503
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("header", { className: "ss-w-header", children: [
|
|
1361
1504
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(BrandMark, { logoUrl, className: "ss-w-header-mark" }),
|
|
1362
1505
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ss-w-header-title", children: headerTitle ?? "SensibleStats" }),
|
|
1506
|
+
canReset && onNewConversation ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "ss-w-newconv", "aria-label": newConversationLabel, onClick: onNewConversation, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NewChatIcon, {}) }) : null,
|
|
1363
1507
|
/* @__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
1508
|
] });
|
|
1365
1509
|
}
|
|
@@ -1801,7 +1945,7 @@ function OddsRow({ vm, row, ui, slip, inSlipIds }) {
|
|
|
1801
1945
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SlipButton, { selection, shownOdds: row.odds, marketName: vm.market, slip, ui })
|
|
1802
1946
|
] });
|
|
1803
1947
|
}
|
|
1804
|
-
function MarketOddsCard({ vm, ui, slip, monitor, inSlipIds, hideHeader }) {
|
|
1948
|
+
function MarketOddsCard({ vm, ui, slip, monitor, inSlipIds, hideHeader, pinRef }) {
|
|
1805
1949
|
const [open, setOpen] = (0, import_react12.useState)(false);
|
|
1806
1950
|
const kickoff = vm.kickoffUtc && ui?.bettingKickoff ? ui.bettingKickoff(vm.kickoffUtc) : void 0;
|
|
1807
1951
|
const hasNamedTeams = !!vm.homeTeamName && !!vm.awayTeamName;
|
|
@@ -1829,7 +1973,7 @@ function MarketOddsCard({ vm, ui, slip, monitor, inSlipIds, hideHeader }) {
|
|
|
1829
1973
|
{
|
|
1830
1974
|
ui,
|
|
1831
1975
|
monitor,
|
|
1832
|
-
pin: { kind: "market", matchId: vm.matchId, marketDeveloperName: vm.marketDeveloperName, line: vm.mainLine }
|
|
1976
|
+
pin: pinRef ?? { kind: "market", matchId: vm.matchId, marketDeveloperName: vm.marketDeveloperName, line: vm.mainLine }
|
|
1833
1977
|
}
|
|
1834
1978
|
) : null
|
|
1835
1979
|
] })
|
|
@@ -1857,9 +2001,9 @@ function MarketOddsCard({ vm, ui, slip, monitor, inSlipIds, hideHeader }) {
|
|
|
1857
2001
|
vm.disclaimer ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "ss-w-bi-disc", children: vm.disclaimer }) : null
|
|
1858
2002
|
] });
|
|
1859
2003
|
}
|
|
1860
|
-
function MarketOddsCardList({ cards, ui, slip, monitor, inSlipIds, hideHeader }) {
|
|
2004
|
+
function MarketOddsCardList({ cards, ui, slip, monitor, inSlipIds, hideHeader, pinRef }) {
|
|
1861
2005
|
if (!cards || cards.length === 0) return null;
|
|
1862
|
-
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "ss-w-mocards", children: cards.map((vm) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(MarketOddsCard, { vm, ui, slip, monitor, inSlipIds, hideHeader }, vm.id)) });
|
|
2006
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "ss-w-mocards", children: cards.map((vm) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(MarketOddsCard, { vm, ui, slip, monitor, inSlipIds, hideHeader, pinRef }, vm.id)) });
|
|
1863
2007
|
}
|
|
1864
2008
|
|
|
1865
2009
|
// src/components/OddsMovementCard.tsx
|
|
@@ -3144,36 +3288,24 @@ function MonitorMatchCard({ match, promotedPins, monitor, ui, slip }) {
|
|
|
3144
3288
|
|
|
3145
3289
|
// src/components/MonitorTabs.tsx
|
|
3146
3290
|
var import_jsx_runtime48 = require("react/jsx-runtime");
|
|
3147
|
-
function
|
|
3148
|
-
const
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
m.phase === "preMatch" ? kick && /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("span", { className: "ss-w-mon-pill", children: [
|
|
3155
|
-
kick.dayLabel,
|
|
3291
|
+
function OddsLiveStrip({ m, ui }) {
|
|
3292
|
+
const showScore = m.phase !== "preMatch" && typeof m.homeScore === "number";
|
|
3293
|
+
const stale = m.status === "stale";
|
|
3294
|
+
if (!showScore && !m.minuteLabel && !(m.isSuspended && !m.isSettled) && !m.isSettled && !stale) return null;
|
|
3295
|
+
return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "ss-w-mk-live", children: [
|
|
3296
|
+
showScore && /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("b", { className: "ss-w-mk-live-score", children: [
|
|
3297
|
+
m.homeTeamName,
|
|
3156
3298
|
" ",
|
|
3157
|
-
kick.time
|
|
3158
|
-
] }) : typeof m.homeScore === "number" && /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("b", { children: [
|
|
3159
3299
|
m.homeScore,
|
|
3160
3300
|
"\u2013",
|
|
3161
|
-
m.awayScore
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "ss-w-mon-name", children: m.awayTeamName }),
|
|
3165
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(Crest2, { kind: "team", url: m.awayCrestUrl ?? void 0, name: m.awayTeamName ?? void 0 })
|
|
3301
|
+
m.awayScore,
|
|
3302
|
+
" ",
|
|
3303
|
+
m.awayTeamName
|
|
3166
3304
|
] }),
|
|
3167
3305
|
m.minuteLabel && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "ss-w-mon-min", children: m.minuteLabel }),
|
|
3168
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
m.status === "stale" && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "ss-w-mon-asof", children: ui.asOf(new Date(m.asOfUtc).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })) }),
|
|
3172
|
-
(() => {
|
|
3173
|
-
const pin = monitor.pins.find((p) => p.key === m.key);
|
|
3174
|
-
return pin ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(PinToggle, { ui, monitor, pin }) : null;
|
|
3175
|
-
})()
|
|
3176
|
-
] })
|
|
3306
|
+
m.isSuspended && !m.isSettled && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "ss-w-mon-susp", children: ui.suspendedLabel }),
|
|
3307
|
+
m.isSettled && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "ss-w-mon-pill", children: ui.fullTimeLabel }),
|
|
3308
|
+
stale && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "ss-w-mon-asof", children: ui.asOf(new Date(m.asOfUtc).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })) })
|
|
3177
3309
|
] });
|
|
3178
3310
|
}
|
|
3179
3311
|
function MatchesPanel({ monitor, ui, slip }) {
|
|
@@ -3206,20 +3338,25 @@ function withSinceViewMovement(vm, prev) {
|
|
|
3206
3338
|
function OddsPanel({ monitor, ui, slip }) {
|
|
3207
3339
|
const markets = (monitor.snapshot?.markets ?? []).filter((m) => monitor.pins.some((p) => p.key === m.key));
|
|
3208
3340
|
if (markets.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "ss-w-mon-empty", children: ui.emptyOddsTab });
|
|
3209
|
-
return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "ss-w-mon-list", children: markets.map((m) =>
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3341
|
+
return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "ss-w-mon-list", children: markets.map((m) => {
|
|
3342
|
+
const pin = monitor.pins.find((p) => p.key === m.key);
|
|
3343
|
+
return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "ss-w-mon-oddspin", "data-settled": m.isSettled || void 0, children: [
|
|
3344
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(OddsLiveStrip, { m, ui }),
|
|
3345
|
+
m.card && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
3346
|
+
MarketOddsCardList,
|
|
3347
|
+
{
|
|
3348
|
+
cards: [withSinceViewMovement(toMarketOddsVM(m.card), monitor.lastViewedPrices[m.key])],
|
|
3349
|
+
ui,
|
|
3350
|
+
slip,
|
|
3351
|
+
monitor,
|
|
3352
|
+
pinRef: pin ?? void 0,
|
|
3353
|
+
inSlipIds: monitor.inSlip
|
|
3354
|
+
}
|
|
3355
|
+
),
|
|
3356
|
+
m.history && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(OddsMovementCardList, { cards: [toOddsMovementVM(m.history)], ui }),
|
|
3357
|
+
!m.card && !m.history && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "ss-w-mon-empty-card", children: ui.oddsUnavailable })
|
|
3358
|
+
] }, m.key);
|
|
3359
|
+
}) });
|
|
3223
3360
|
}
|
|
3224
3361
|
|
|
3225
3362
|
// src/components/ChatPanel.tsx
|
|
@@ -3238,7 +3375,7 @@ function useIsModal() {
|
|
|
3238
3375
|
return modal;
|
|
3239
3376
|
}
|
|
3240
3377
|
function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
3241
|
-
const { state, close, stop, send } = controller;
|
|
3378
|
+
const { state, close, stop, send, reset } = controller;
|
|
3242
3379
|
const isModal = useIsModal();
|
|
3243
3380
|
(0, import_react22.useEffect)(() => {
|
|
3244
3381
|
const onKey = (e) => {
|
|
@@ -3255,7 +3392,18 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
|
3255
3392
|
if (monitor && monitor.activeTab !== "chat" && state.isStreaming) monitor.markChatActivity();
|
|
3256
3393
|
}, [monitor, state.isStreaming, state.messages.length]);
|
|
3257
3394
|
return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
3258
|
-
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
3395
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
3396
|
+
ChatHeader,
|
|
3397
|
+
{
|
|
3398
|
+
onClose: close,
|
|
3399
|
+
closeLabel: ui.closeChat,
|
|
3400
|
+
logoUrl: ui.logoUrl,
|
|
3401
|
+
headerTitle: ui.headerTitle,
|
|
3402
|
+
onNewConversation: reset,
|
|
3403
|
+
canReset: state.messages.length > 0,
|
|
3404
|
+
newConversationLabel: ui.newConversation
|
|
3405
|
+
}
|
|
3406
|
+
),
|
|
3259
3407
|
monitor && /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
3260
3408
|
TabBar,
|
|
3261
3409
|
{
|
|
@@ -3370,8 +3518,9 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
3370
3518
|
}
|
|
3371
3519
|
.ss-w-header { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--ss-w-line); }
|
|
3372
3520
|
.ss-w-header-mark { width: 18px; height: 18px; display: block; object-fit: contain; }
|
|
3373
|
-
.ss-w-header-title { font-weight: 660; }
|
|
3374
|
-
.ss-w-
|
|
3521
|
+
.ss-w-header-title { font-weight: 660; flex: 1; }
|
|
3522
|
+
.ss-w-newconv { background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 16px; display: grid; place-items: center; }
|
|
3523
|
+
.ss-w-close { background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 16px; display: grid; place-items: center; }
|
|
3375
3524
|
|
|
3376
3525
|
/* messages */
|
|
3377
3526
|
/* .ss-w-mon-list (Matches/Odds tab scrollers) shares .ss-w-list's box/scroll rules by design --
|
|
@@ -4059,7 +4208,11 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
4059
4208
|
a bare context line with nothing under it. */
|
|
4060
4209
|
.ss-w-mon-empty-card { padding: 8px 0 2px; font-size: 12px; color: var(--ss-w-faint); }
|
|
4061
4210
|
.ss-w-mon-inslip { display: inline-flex; flex: none; align-items: center; gap: 3px; font-size: 10.5px; font-weight: 700; color: var(--ss-w-pos); }
|
|
4062
|
-
|
|
4211
|
+
/* Odds-tab item is bare: the visible card is the market-odds card itself (no outer wrapper card),
|
|
4212
|
+
with an optional slim live strip above it. */
|
|
4213
|
+
.ss-w-mon-oddspin { display: flex; flex-direction: column; gap: 8px; }
|
|
4214
|
+
.ss-w-mk-live { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding: 0 2px; font-size: 11.5px; color: var(--ss-w-faint); }
|
|
4215
|
+
.ss-w-mk-live-score { color: var(--ss-w-ink); font-weight: 700; font-variant-numeric: tabular-nums; }
|
|
4063
4216
|
.ss-w-mon-oddspin[data-settled] .ss-w-mo-price,
|
|
4064
4217
|
.ss-w-mon-oddspin[data-settled] .ss-w-mon-price { text-decoration: line-through; color: var(--ss-w-faint); }
|
|
4065
4218
|
|
|
@@ -4114,7 +4267,8 @@ function StatsWidget(props) {
|
|
|
4114
4267
|
const markIntroDone = (0, import_react23.useCallback)(() => {
|
|
4115
4268
|
introDoneRef.current = true;
|
|
4116
4269
|
}, []);
|
|
4117
|
-
const
|
|
4270
|
+
const convStorageKey = `ss-w-conv:${cfg?.operatorId ?? ""}:${cfg?.userContext?.operatorUserId ?? "anon"}`;
|
|
4271
|
+
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0, convStorageKey);
|
|
4118
4272
|
const monitor = useLiveMonitor({
|
|
4119
4273
|
getLiveMonitor: (pins) => built ? built.conversation.getLiveMonitor(pins) : Promise.resolve(null),
|
|
4120
4274
|
getMarketOdds: (matchId, dev) => built ? built.conversation.getMarketOdds(matchId, dev) : Promise.resolve(null),
|