@sensiblestats/widget-react 0.6.0 → 0.7.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 +44 -0
- package/dist/index.cjs +276 -97
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +270 -91
- package/dist/index.js.map +1 -1
- package/dist/styles.css +8 -0
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -36,8 +36,8 @@ __export(index_exports, {
|
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
37
|
|
|
38
38
|
// src/StatsWidget.tsx
|
|
39
|
-
var
|
|
40
|
-
var
|
|
39
|
+
var import_react9 = require("react");
|
|
40
|
+
var import_widget_sdk4 = require("@sensiblestats/widget-sdk");
|
|
41
41
|
|
|
42
42
|
// src/i18n.ts
|
|
43
43
|
var STRINGS = {
|
|
@@ -54,7 +54,16 @@ var STRINGS = {
|
|
|
54
54
|
showLess: "Show less",
|
|
55
55
|
showMore: (n) => `Show ${n} more`,
|
|
56
56
|
oddsLabel: "Odds",
|
|
57
|
-
liveLabel: "Live"
|
|
57
|
+
liveLabel: "Live",
|
|
58
|
+
addToSlip: "Add to slip",
|
|
59
|
+
inSlip: "In slip",
|
|
60
|
+
slipAdded: (n) => n === void 0 ? "Added to slip" : `Added to slip (${n} selection${n === 1 ? "" : "s"})`,
|
|
61
|
+
slipDuplicate: "Already in your slip",
|
|
62
|
+
slipPriceChanged: (from, to) => `Added at ${to} (was ${from})`,
|
|
63
|
+
slipPriceNotAccepted: (from, to) => `Price moved to ${to} (was ${from}). Tap to add.`,
|
|
64
|
+
slipLoginRequired: "Sign in to add to your slip",
|
|
65
|
+
slipSuspended: "Market suspended",
|
|
66
|
+
slipFailed: "Couldn't add to slip. Try again."
|
|
58
67
|
},
|
|
59
68
|
tr: {
|
|
60
69
|
launcherLabel: "SensibleStats ile sohbet et",
|
|
@@ -69,7 +78,16 @@ var STRINGS = {
|
|
|
69
78
|
showLess: "Daha az g\xF6ster",
|
|
70
79
|
showMore: (n) => `${n} tane daha g\xF6ster`,
|
|
71
80
|
oddsLabel: "Oran",
|
|
72
|
-
liveLabel: "Canl\u0131"
|
|
81
|
+
liveLabel: "Canl\u0131",
|
|
82
|
+
addToSlip: "Kupona ekle",
|
|
83
|
+
inSlip: "Kuponda",
|
|
84
|
+
slipAdded: (n) => n === void 0 ? "Kupona eklendi" : `Kupona eklendi (${n} se\xE7im)`,
|
|
85
|
+
slipDuplicate: "Bu se\xE7im kuponunuzda zaten var",
|
|
86
|
+
slipPriceChanged: (from, to) => `${to} oran\u0131yla eklendi (\xF6nceki ${from})`,
|
|
87
|
+
slipPriceNotAccepted: (from, to) => `Oran ${to} oldu (\xF6nceki ${from}). Eklemek i\xE7in dokunun.`,
|
|
88
|
+
slipLoginRequired: "Kupona eklemek i\xE7in giri\u015F yap\u0131n",
|
|
89
|
+
slipSuspended: "Bu market ask\u0131ya al\u0131nd\u0131",
|
|
90
|
+
slipFailed: "Kupona eklenemedi. Tekrar deneyin."
|
|
73
91
|
}
|
|
74
92
|
};
|
|
75
93
|
function resolveLocaleStrings(locale) {
|
|
@@ -101,7 +119,16 @@ function normalizeUi(cfg) {
|
|
|
101
119
|
showLess: strings.showLess,
|
|
102
120
|
showMore: strings.showMore,
|
|
103
121
|
oddsLabel: strings.oddsLabel,
|
|
104
|
-
liveLabel: strings.liveLabel
|
|
122
|
+
liveLabel: strings.liveLabel,
|
|
123
|
+
addToSlip: strings.addToSlip,
|
|
124
|
+
inSlip: strings.inSlip,
|
|
125
|
+
slipAdded: strings.slipAdded,
|
|
126
|
+
slipDuplicate: strings.slipDuplicate,
|
|
127
|
+
slipPriceChanged: strings.slipPriceChanged,
|
|
128
|
+
slipPriceNotAccepted: strings.slipPriceNotAccepted,
|
|
129
|
+
slipLoginRequired: strings.slipLoginRequired,
|
|
130
|
+
slipSuspended: strings.slipSuspended,
|
|
131
|
+
slipFailed: strings.slipFailed
|
|
105
132
|
};
|
|
106
133
|
}
|
|
107
134
|
function toClientOptions(cfg) {
|
|
@@ -181,6 +208,8 @@ function toInsightVM(insight) {
|
|
|
181
208
|
const selection = [str(insight.selectionLabel), str(insight.line)].filter(Boolean).join(" ") || void 0;
|
|
182
209
|
const value = insight.odds?.value;
|
|
183
210
|
const odds = typeof value === "number" && Number.isFinite(value) ? value.toFixed(2) : void 0;
|
|
211
|
+
const oddsValue = insight.odds?.value;
|
|
212
|
+
const matchOddsId = typeof insight.odds?.matchOddsId === "number" ? insight.odds.matchOddsId : void 0;
|
|
184
213
|
const home = str(insight.homeTeamName);
|
|
185
214
|
const away = str(insight.awayTeamName);
|
|
186
215
|
const fixture = home && away ? `${home} vs ${away}` : void 0;
|
|
@@ -200,14 +229,31 @@ function toInsightVM(insight) {
|
|
|
200
229
|
scopes,
|
|
201
230
|
text: str(insight.text) ?? "",
|
|
202
231
|
stats: readStatCells(insight.stats),
|
|
203
|
-
disclaimer: str(insight.disclaimer)
|
|
232
|
+
disclaimer: str(insight.disclaimer),
|
|
233
|
+
matchId: typeof insight.matchId === "number" ? insight.matchId : 0,
|
|
234
|
+
matchOddsId,
|
|
235
|
+
addToSlipEnabled: insight.addToSlipEnabled === true,
|
|
236
|
+
oddsDecimal: typeof oddsValue === "number" && Number.isFinite(oddsValue) ? oddsValue : void 0,
|
|
237
|
+
quotedAtUtc: str(insight.odds?.quotedAtUtc)
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function toSlipSelection(vm, conversationId) {
|
|
241
|
+
if (vm.matchOddsId === void 0 || vm.oddsDecimal === void 0) return null;
|
|
242
|
+
const selection = {
|
|
243
|
+
matchOddsId: vm.matchOddsId,
|
|
244
|
+
matchId: vm.matchId,
|
|
245
|
+
oddsDecimal: vm.oddsDecimal
|
|
204
246
|
};
|
|
247
|
+
if (vm.quotedAtUtc !== void 0) selection.quotedAtUtc = vm.quotedAtUtc;
|
|
248
|
+
if (vm.id !== void 0) selection.insightId = vm.id;
|
|
249
|
+
if (conversationId !== void 0) selection.conversationId = conversationId;
|
|
250
|
+
return selection;
|
|
205
251
|
}
|
|
206
252
|
|
|
207
253
|
// src/reducer.ts
|
|
208
254
|
var POPUP_MAX = 160;
|
|
209
255
|
function initialState(starters) {
|
|
210
|
-
return { isOpen: false, messages: [], isStreaming: false, status: null, actionsLoading: false, starters, error: null, lastUserInput: null, lastDisplayText: null, popupText: null };
|
|
256
|
+
return { isOpen: false, messages: [], isStreaming: false, status: null, actionsLoading: false, starters, error: null, lastUserInput: null, lastDisplayText: null, popupText: null, conversationId: null };
|
|
211
257
|
}
|
|
212
258
|
function activeTurn(state) {
|
|
213
259
|
const last = state.messages[state.messages.length - 1];
|
|
@@ -224,9 +270,12 @@ function applyEvent(state, event) {
|
|
|
224
270
|
return { ...state, status: event.data.message };
|
|
225
271
|
case "answer":
|
|
226
272
|
return { ...state, messages: mapActive(state, (t) => ({ ...t, text: event.data.text })) };
|
|
227
|
-
case "entity_card":
|
|
273
|
+
case "entity_card": {
|
|
228
274
|
if (event.data.card.isUsed === false) return state;
|
|
229
|
-
|
|
275
|
+
const card = toCardVM(event.data.card);
|
|
276
|
+
if (card.kind === "generic") return state;
|
|
277
|
+
return { ...state, messages: mapActive(state, (t) => ({ ...t, cards: [...t.cards, card] })) };
|
|
278
|
+
}
|
|
230
279
|
case "action_button":
|
|
231
280
|
return { ...state, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, actions: [...t.actions, toActionVM(event.data.action)] })) };
|
|
232
281
|
case "betting_insight":
|
|
@@ -241,7 +290,15 @@ function applyEvent(state, event) {
|
|
|
241
290
|
const active = activeTurn(state);
|
|
242
291
|
const text = active?.text.trim() ?? "";
|
|
243
292
|
const popupText = active?.isGreeting && !state.isOpen && text ? text.slice(0, POPUP_MAX) : state.popupText;
|
|
244
|
-
return {
|
|
293
|
+
return {
|
|
294
|
+
...state,
|
|
295
|
+
isStreaming: false,
|
|
296
|
+
status: null,
|
|
297
|
+
actionsLoading: false,
|
|
298
|
+
popupText,
|
|
299
|
+
conversationId: event.data.conversationId ?? state.conversationId,
|
|
300
|
+
messages: mapActive(state, (t) => ({ ...t, done: true }))
|
|
301
|
+
};
|
|
245
302
|
}
|
|
246
303
|
case "error": {
|
|
247
304
|
const suppress = activeTurn(state)?.isGreeting === true;
|
|
@@ -384,7 +441,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
|
|
|
384
441
|
}
|
|
385
442
|
|
|
386
443
|
// src/components/ChatPanel.tsx
|
|
387
|
-
var
|
|
444
|
+
var import_react8 = require("react");
|
|
388
445
|
|
|
389
446
|
// src/components/ChatHeader.tsx
|
|
390
447
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
@@ -397,7 +454,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat" }) {
|
|
|
397
454
|
}
|
|
398
455
|
|
|
399
456
|
// src/components/MessageList.tsx
|
|
400
|
-
var
|
|
457
|
+
var import_react6 = require("react");
|
|
401
458
|
|
|
402
459
|
// src/components/Markdown.tsx
|
|
403
460
|
var import_react_markdown = __toESM(require("react-markdown"), 1);
|
|
@@ -451,94 +508,194 @@ function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMore
|
|
|
451
508
|
}
|
|
452
509
|
|
|
453
510
|
// src/components/BettingInsightList.tsx
|
|
511
|
+
var import_react5 = require("react");
|
|
512
|
+
|
|
513
|
+
// src/components/AddToSlipButton.tsx
|
|
514
|
+
var import_react4 = require("react");
|
|
515
|
+
var import_widget_sdk3 = require("@sensiblestats/widget-sdk");
|
|
516
|
+
|
|
517
|
+
// src/slip.ts
|
|
518
|
+
var ADD_TO_SLIP_TIMEOUT_MS = 1e4;
|
|
519
|
+
function wasAdded(result) {
|
|
520
|
+
if (!result) return true;
|
|
521
|
+
if (result.status === "price_changed") return result.accepted;
|
|
522
|
+
return result.status === "added";
|
|
523
|
+
}
|
|
524
|
+
function isTerminal(result) {
|
|
525
|
+
if (!result) return true;
|
|
526
|
+
if (result.status === "price_changed") return result.accepted;
|
|
527
|
+
return result.status === "added" || result.status === "duplicate";
|
|
528
|
+
}
|
|
529
|
+
function resultToToast(result, shownOdds, ui) {
|
|
530
|
+
if (!result) return { kind: "success", text: ui.slipAdded(void 0) };
|
|
531
|
+
switch (result.status) {
|
|
532
|
+
case "added":
|
|
533
|
+
return { kind: "success", text: ui.slipAdded(result.slip?.selectionCount) };
|
|
534
|
+
case "duplicate":
|
|
535
|
+
return { kind: "success", text: ui.slipDuplicate };
|
|
536
|
+
case "price_changed":
|
|
537
|
+
return result.accepted ? { kind: "success", text: ui.slipPriceChanged(shownOdds, result.newOddsDecimal.toFixed(2)) } : { kind: "error", text: ui.slipPriceNotAccepted(shownOdds, result.newOddsDecimal.toFixed(2)) };
|
|
538
|
+
case "rejected":
|
|
539
|
+
if (result.reason === "login_required") return { kind: "error", text: ui.slipLoginRequired };
|
|
540
|
+
if (result.reason === "suspended") return { kind: "error", text: ui.slipSuspended };
|
|
541
|
+
return { kind: "error", text: result.message ?? ui.slipFailed };
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// src/components/Toast.tsx
|
|
454
546
|
var import_react3 = require("react");
|
|
455
547
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
548
|
+
var DISMISS_MS = 4e3;
|
|
549
|
+
function Toast({ toast, onDismiss }) {
|
|
550
|
+
(0, import_react3.useEffect)(() => {
|
|
551
|
+
if (!toast) return;
|
|
552
|
+
const timer = setTimeout(onDismiss, DISMISS_MS);
|
|
553
|
+
return () => clearTimeout(timer);
|
|
554
|
+
}, [toast, onDismiss]);
|
|
555
|
+
if (!toast) return null;
|
|
556
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: `ss-w-toast ss-w-toast-${toast.kind}`, role: "status", "aria-live": "polite", children: toast.text });
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// src/components/AddToSlipButton.tsx
|
|
560
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
561
|
+
function withTimeout(value) {
|
|
562
|
+
return Promise.race([
|
|
563
|
+
value,
|
|
564
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("add-to-slip timed out")), ADD_TO_SLIP_TIMEOUT_MS))
|
|
565
|
+
]);
|
|
566
|
+
}
|
|
567
|
+
function AddToSlipButton({ it, slip, ui }) {
|
|
568
|
+
const [pending, setPending] = (0, import_react4.useState)(false);
|
|
569
|
+
const [settled, setSettled] = (0, import_react4.useState)(false);
|
|
570
|
+
const [toast, setToast] = (0, import_react4.useState)(null);
|
|
571
|
+
const dismiss = (0, import_react4.useCallback)(() => setToast(null), []);
|
|
572
|
+
const handler = slip.onAddToSlip;
|
|
573
|
+
const selection = it.addToSlipEnabled ? toSlipSelection(it, slip.conversationId) : null;
|
|
574
|
+
if (!ui || !handler || !selection) return null;
|
|
575
|
+
const onClick = async () => {
|
|
576
|
+
setPending(true);
|
|
577
|
+
try {
|
|
578
|
+
const raw = await withTimeout(Promise.resolve(handler(selection)));
|
|
579
|
+
if (raw != null && !(0, import_widget_sdk3.isAddToSlipResult)(raw)) throw new Error("malformed add-to-slip result");
|
|
580
|
+
const result = raw ?? void 0;
|
|
581
|
+
setToast(resultToToast(result, it.odds ?? selection.oddsDecimal.toFixed(2), ui));
|
|
582
|
+
setSettled(isTerminal(result));
|
|
583
|
+
if (wasAdded(result) && slip.onMarketClicked) {
|
|
584
|
+
try {
|
|
585
|
+
slip.onMarketClicked(it.market);
|
|
586
|
+
} catch {
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
} catch {
|
|
590
|
+
setToast({ kind: "error", text: ui.slipFailed });
|
|
591
|
+
} finally {
|
|
592
|
+
setPending(false);
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "ss-w-slip", children: [
|
|
596
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
597
|
+
"button",
|
|
598
|
+
{
|
|
599
|
+
type: "button",
|
|
600
|
+
className: "ss-w-slip-btn",
|
|
601
|
+
disabled: pending || settled,
|
|
602
|
+
onClick,
|
|
603
|
+
children: settled ? ui.inSlip : ui.addToSlip
|
|
604
|
+
}
|
|
605
|
+
),
|
|
606
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Toast, { toast, onDismiss: dismiss })
|
|
607
|
+
] });
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// src/components/BettingInsightList.tsx
|
|
611
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
456
612
|
var MAX_ROWS = 3;
|
|
457
613
|
function StatRows({ stats, ui }) {
|
|
458
|
-
const [expanded, setExpanded] = (0,
|
|
614
|
+
const [expanded, setExpanded] = (0, import_react5.useState)(false);
|
|
459
615
|
const shown = expanded ? stats : stats.slice(0, MAX_ROWS);
|
|
460
616
|
const overflow = stats.length - MAX_ROWS;
|
|
461
|
-
return /* @__PURE__ */ (0,
|
|
462
|
-
shown.map((s) => /* @__PURE__ */ (0,
|
|
463
|
-
/* @__PURE__ */ (0,
|
|
464
|
-
/* @__PURE__ */ (0,
|
|
465
|
-
/* @__PURE__ */ (0,
|
|
617
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-bi-rows", children: [
|
|
618
|
+
shown.map((s) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-bi-row", children: [
|
|
619
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-row-lbl", children: s.label }),
|
|
620
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-row-dots", "aria-hidden": "true" }),
|
|
621
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-row-num", children: s.value })
|
|
466
622
|
] }, `${s.label}:${s.value}`)),
|
|
467
|
-
overflow > 0 ? /* @__PURE__ */ (0,
|
|
623
|
+
overflow > 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "ss-w-bi-more", onClick: () => setExpanded((v) => !v), children: expanded ? ui?.showLess ?? "Show less" : (ui?.showMore ?? ((n) => `Show ${n} more`))(overflow) }) : null
|
|
468
624
|
] });
|
|
469
625
|
}
|
|
470
626
|
function ScopedStats({ scopes, ui }) {
|
|
471
|
-
const [active, setActive] = (0,
|
|
627
|
+
const [active, setActive] = (0, import_react5.useState)(0);
|
|
472
628
|
const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
|
|
473
|
-
return /* @__PURE__ */ (0,
|
|
474
|
-
/* @__PURE__ */ (0,
|
|
475
|
-
/* @__PURE__ */ (0,
|
|
629
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-bi-scoped", children: [
|
|
630
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ss-w-bi-scopes", role: "group", children: scopes.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "ss-w-bi-chip", "aria-pressed": i === active, onClick: () => setActive(i), children: s.label }, s.key)) }),
|
|
631
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StatRows, { stats: current.stats, ui }, current.key)
|
|
476
632
|
] });
|
|
477
633
|
}
|
|
478
|
-
function InsightCard({ it, ui }) {
|
|
634
|
+
function InsightCard({ it, ui, slip }) {
|
|
479
635
|
const live = it.isLive || it.phase === "live";
|
|
480
|
-
return /* @__PURE__ */ (0,
|
|
481
|
-
live || it.fixture || it.competition ? /* @__PURE__ */ (0,
|
|
482
|
-
live ? /* @__PURE__ */ (0,
|
|
483
|
-
/* @__PURE__ */ (0,
|
|
636
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-binsight", children: [
|
|
637
|
+
live || it.fixture || it.competition ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-bi-status", children: [
|
|
638
|
+
live ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "ss-w-bi-live", children: [
|
|
639
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
|
|
484
640
|
ui?.liveLabel ?? "Live"
|
|
485
641
|
] }) : null,
|
|
486
|
-
it.fixture ? /* @__PURE__ */ (0,
|
|
487
|
-
it.competition ? /* @__PURE__ */ (0,
|
|
642
|
+
it.fixture ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-fixture", children: it.fixture }) : null,
|
|
643
|
+
it.competition ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-comp", children: it.competition }) : null
|
|
488
644
|
] }) : null,
|
|
489
|
-
/* @__PURE__ */ (0,
|
|
490
|
-
/* @__PURE__ */ (0,
|
|
491
|
-
/* @__PURE__ */ (0,
|
|
492
|
-
it.selection ? /* @__PURE__ */ (0,
|
|
645
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-bi-head", children: [
|
|
646
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "ss-w-bi-mkt", children: [
|
|
647
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-market", children: it.market }),
|
|
648
|
+
it.selection ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-sel", children: it.selection }) : null
|
|
493
649
|
] }),
|
|
494
|
-
it.odds ? /* @__PURE__ */ (0,
|
|
495
|
-
/* @__PURE__ */ (0,
|
|
496
|
-
/* @__PURE__ */ (0,
|
|
497
|
-
it.bookmaker ? /* @__PURE__ */ (0,
|
|
650
|
+
it.odds ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "ss-w-bi-odds", children: [
|
|
651
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-odds-cap", children: ui?.oddsLabel ?? "Odds" }),
|
|
652
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("b", { children: it.odds }),
|
|
653
|
+
it.bookmaker ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-book", children: it.bookmaker }) : null
|
|
498
654
|
] }) : null
|
|
499
655
|
] }),
|
|
500
|
-
it.text ? /* @__PURE__ */ (0,
|
|
501
|
-
it.scopes && it.scopes.length >= 2 ? /* @__PURE__ */ (0,
|
|
502
|
-
it.disclaimer ? /* @__PURE__ */ (0,
|
|
656
|
+
it.text ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ss-w-bi-text", children: it.text }) : null,
|
|
657
|
+
it.scopes && it.scopes.length >= 2 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ScopedStats, { scopes: it.scopes, ui }) : it.stats.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StatRows, { stats: it.stats, ui }) : null,
|
|
658
|
+
it.disclaimer ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ss-w-bi-disc", children: it.disclaimer }) : null,
|
|
659
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AddToSlipButton, { it, slip, ui })
|
|
503
660
|
] });
|
|
504
661
|
}
|
|
505
|
-
function BettingInsightList({ insights, ui }) {
|
|
662
|
+
function BettingInsightList({ insights, ui, slip }) {
|
|
506
663
|
if (!insights || insights.length === 0) return null;
|
|
507
|
-
return /* @__PURE__ */ (0,
|
|
664
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ss-w-binsights", children: insights.map((it) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(InsightCard, { it, ui, slip }, it.id)) });
|
|
508
665
|
}
|
|
509
666
|
|
|
510
667
|
// src/components/ActionButtons.tsx
|
|
511
|
-
var
|
|
668
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
512
669
|
function ActionButtons({ actions, loading, disabled, onAct }) {
|
|
513
670
|
if (actions.length === 0 && !loading) return null;
|
|
514
|
-
return /* @__PURE__ */ (0,
|
|
515
|
-
actions.map((a, i) => /* @__PURE__ */ (0,
|
|
516
|
-
actions.length === 0 && loading ? /* @__PURE__ */ (0,
|
|
517
|
-
/* @__PURE__ */ (0,
|
|
518
|
-
/* @__PURE__ */ (0,
|
|
671
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-actions", children: [
|
|
672
|
+
actions.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
|
|
673
|
+
actions.length === 0 && loading ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
674
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-abtn-shimmer" }),
|
|
675
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-abtn-shimmer" })
|
|
519
676
|
] }) : null
|
|
520
677
|
] });
|
|
521
678
|
}
|
|
522
679
|
|
|
523
680
|
// src/components/ChatMessage.tsx
|
|
524
|
-
var
|
|
525
|
-
function ChatMessage({ message, disabled, onAct, ui }) {
|
|
681
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
682
|
+
function ChatMessage({ message, disabled, onAct, ui, slip }) {
|
|
526
683
|
if (message.role === "user") {
|
|
527
|
-
return /* @__PURE__ */ (0,
|
|
684
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
|
|
528
685
|
}
|
|
529
|
-
return /* @__PURE__ */ (0,
|
|
530
|
-
message.text ? /* @__PURE__ */ (0,
|
|
531
|
-
/* @__PURE__ */ (0,
|
|
532
|
-
/* @__PURE__ */ (0,
|
|
533
|
-
/* @__PURE__ */ (0,
|
|
686
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
|
|
687
|
+
message.text ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Markdown, { text: message.text }) : null,
|
|
688
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
|
|
689
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(BettingInsightList, { insights: message.insights, ui, slip }),
|
|
690
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
|
|
534
691
|
] });
|
|
535
692
|
}
|
|
536
693
|
|
|
537
694
|
// src/components/IntentChips.tsx
|
|
538
|
-
var
|
|
695
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
539
696
|
function IntentChips({ chips, onPick }) {
|
|
540
697
|
if (chips.length === 0) return null;
|
|
541
|
-
return /* @__PURE__ */ (0,
|
|
698
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
|
|
542
699
|
}
|
|
543
700
|
|
|
544
701
|
// src/scroll.ts
|
|
@@ -548,17 +705,17 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
|
|
|
548
705
|
}
|
|
549
706
|
|
|
550
707
|
// src/components/MessageList.tsx
|
|
551
|
-
var
|
|
552
|
-
function MessageList({ controller, ui }) {
|
|
708
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
709
|
+
function MessageList({ controller, ui, slip }) {
|
|
553
710
|
const { state, send } = controller;
|
|
554
|
-
const listRef = (0,
|
|
555
|
-
const endRef = (0,
|
|
556
|
-
const pinnedRef = (0,
|
|
557
|
-
(0,
|
|
711
|
+
const listRef = (0, import_react6.useRef)(null);
|
|
712
|
+
const endRef = (0, import_react6.useRef)(null);
|
|
713
|
+
const pinnedRef = (0, import_react6.useRef)(true);
|
|
714
|
+
(0, import_react6.useEffect)(() => {
|
|
558
715
|
if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
|
|
559
716
|
}, [state.messages, state.status]);
|
|
560
717
|
const empty = state.messages.length === 0;
|
|
561
|
-
return /* @__PURE__ */ (0,
|
|
718
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
562
719
|
"div",
|
|
563
720
|
{
|
|
564
721
|
className: "ss-w-list",
|
|
@@ -568,35 +725,36 @@ function MessageList({ controller, ui }) {
|
|
|
568
725
|
if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
|
|
569
726
|
},
|
|
570
727
|
children: [
|
|
571
|
-
state.messages.map((m) => /* @__PURE__ */ (0,
|
|
728
|
+
state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
572
729
|
ChatMessage,
|
|
573
730
|
{
|
|
574
731
|
message: m,
|
|
575
732
|
disabled: state.isStreaming,
|
|
576
733
|
onAct: (a) => send(a.message, { actionId: a.actionId, displayText: a.label }),
|
|
577
|
-
ui
|
|
734
|
+
ui,
|
|
735
|
+
slip
|
|
578
736
|
},
|
|
579
737
|
m.id
|
|
580
738
|
)),
|
|
581
|
-
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0,
|
|
739
|
+
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
|
|
582
740
|
} }) : null,
|
|
583
|
-
state.status ? /* @__PURE__ */ (0,
|
|
584
|
-
state.error ? /* @__PURE__ */ (0,
|
|
585
|
-
/* @__PURE__ */ (0,
|
|
586
|
-
/* @__PURE__ */ (0,
|
|
741
|
+
state.status ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "ss-w-status", children: state.status }) : null,
|
|
742
|
+
state.error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ss-w-error", role: "alert", children: [
|
|
743
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
|
|
744
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
|
|
587
745
|
] }) : null,
|
|
588
|
-
empty ? /* @__PURE__ */ (0,
|
|
589
|
-
/* @__PURE__ */ (0,
|
|
746
|
+
empty ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
|
|
747
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { ref: endRef })
|
|
590
748
|
]
|
|
591
749
|
}
|
|
592
750
|
);
|
|
593
751
|
}
|
|
594
752
|
|
|
595
753
|
// src/components/ChatInput.tsx
|
|
596
|
-
var
|
|
597
|
-
var
|
|
754
|
+
var import_react7 = require("react");
|
|
755
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
598
756
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
599
|
-
const [value, setValue] = (0,
|
|
757
|
+
const [value, setValue] = (0, import_react7.useState)("");
|
|
600
758
|
const submit = () => {
|
|
601
759
|
const t = value.trim();
|
|
602
760
|
if (!t) return;
|
|
@@ -609,18 +767,18 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
|
|
|
609
767
|
submit();
|
|
610
768
|
}
|
|
611
769
|
};
|
|
612
|
-
return /* @__PURE__ */ (0,
|
|
613
|
-
/* @__PURE__ */ (0,
|
|
614
|
-
streaming ? /* @__PURE__ */ (0,
|
|
770
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "ss-w-input", children: [
|
|
771
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
|
|
772
|
+
streaming ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SendIcon, {}) })
|
|
615
773
|
] });
|
|
616
774
|
}
|
|
617
775
|
|
|
618
776
|
// src/components/ChatPanel.tsx
|
|
619
|
-
var
|
|
777
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
620
778
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
621
779
|
function useIsModal() {
|
|
622
|
-
const [modal, setModal] = (0,
|
|
623
|
-
(0,
|
|
780
|
+
const [modal, setModal] = (0, import_react8.useState)(false);
|
|
781
|
+
(0, import_react8.useEffect)(() => {
|
|
624
782
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
625
783
|
const mql = window.matchMedia(FULLSCREEN_QUERY);
|
|
626
784
|
const sync = () => setModal(mql.matches);
|
|
@@ -630,20 +788,20 @@ function useIsModal() {
|
|
|
630
788
|
}, []);
|
|
631
789
|
return modal;
|
|
632
790
|
}
|
|
633
|
-
function ChatPanel({ controller, ui }) {
|
|
791
|
+
function ChatPanel({ controller, ui, slip }) {
|
|
634
792
|
const { state, close, stop, send } = controller;
|
|
635
793
|
const isModal = useIsModal();
|
|
636
|
-
(0,
|
|
794
|
+
(0, import_react8.useEffect)(() => {
|
|
637
795
|
const onKey = (e) => {
|
|
638
796
|
if (e.key === "Escape") close();
|
|
639
797
|
};
|
|
640
798
|
window.addEventListener("keydown", onKey);
|
|
641
799
|
return () => window.removeEventListener("keydown", onKey);
|
|
642
800
|
}, [close]);
|
|
643
|
-
return /* @__PURE__ */ (0,
|
|
644
|
-
/* @__PURE__ */ (0,
|
|
645
|
-
/* @__PURE__ */ (0,
|
|
646
|
-
/* @__PURE__ */ (0,
|
|
801
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
802
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
|
|
803
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(MessageList, { controller, ui, slip }),
|
|
804
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
|
|
647
805
|
] });
|
|
648
806
|
}
|
|
649
807
|
|
|
@@ -796,20 +954,28 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
796
954
|
.ss-w-send { width: 36px; height: 36px; flex: none; border: none; border-radius: 50%; background: var(--ss-w-accent); color: #fff; cursor: pointer; display: grid; place-items: center; font-size: 16px; }
|
|
797
955
|
.ss-w-stop-glyph { width: 11px; height: 11px; background: currentColor; border-radius: 2px; }
|
|
798
956
|
|
|
957
|
+
/* add to slip */
|
|
958
|
+
.ss-w-slip { position: relative; display: flex; }
|
|
959
|
+
.ss-w-slip-btn { font-size: 11.5px; font-weight: 620; color: #fff; background: var(--ss-w-accent); border: 1px solid var(--ss-w-accent); border-radius: 14px; padding: 5px 12px; cursor: pointer; }
|
|
960
|
+
.ss-w-slip-btn:disabled { opacity: .6; cursor: default; background: var(--ss-w-bubble); color: var(--ss-w-faint); border-color: var(--ss-w-line); }
|
|
961
|
+
.ss-w-toast { position: absolute; top: calc(100% + 6px); left: 0; z-index: 1; max-width: 100%; font-size: 11px; font-weight: 560; border-radius: 10px; padding: 5px 10px; overflow-wrap: anywhere; box-shadow: 0 4px 14px rgba(0,0,0,.14); }
|
|
962
|
+
.ss-w-toast-success { color: var(--ss-w-accent); background: color-mix(in srgb, var(--ss-w-accent) 14%, var(--ss-w-panel)); border: 1px solid color-mix(in srgb, var(--ss-w-accent) 30%, var(--ss-w-line)); }
|
|
963
|
+
.ss-w-toast-error { color: #c0392b; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); }
|
|
964
|
+
|
|
799
965
|
@media (prefers-reduced-motion: reduce) {
|
|
800
966
|
.ss-w-abtn-shimmer { animation: none; }
|
|
801
967
|
}
|
|
802
968
|
`;
|
|
803
969
|
|
|
804
970
|
// src/StatsWidget.tsx
|
|
805
|
-
var
|
|
971
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
806
972
|
function buildClient(props) {
|
|
807
973
|
try {
|
|
808
974
|
if ("client" in props && props.client) {
|
|
809
975
|
return { client: props.client, conversation: props.client.conversation() };
|
|
810
976
|
}
|
|
811
977
|
const cfg = props.config;
|
|
812
|
-
const client = new
|
|
978
|
+
const client = new import_widget_sdk4.StatsWidgetClient(toClientOptions(cfg));
|
|
813
979
|
return { client, conversation: client.conversation(toUserContext(cfg)) };
|
|
814
980
|
} catch (err) {
|
|
815
981
|
console.error("[StatsWidget] failed to initialize:", err instanceof Error ? err.message : err);
|
|
@@ -827,18 +993,31 @@ function injectStyles(root) {
|
|
|
827
993
|
function StatsWidget(props) {
|
|
828
994
|
const injectedClient = "client" in props ? props.client : void 0;
|
|
829
995
|
const cfg = props.config;
|
|
830
|
-
const built = (0,
|
|
831
|
-
const ui = (0,
|
|
832
|
-
const rootRef = (0,
|
|
996
|
+
const built = (0, import_react9.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
|
|
997
|
+
const ui = (0, import_react9.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
|
|
998
|
+
const rootRef = (0, import_react9.useRef)(null);
|
|
833
999
|
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
|
|
834
|
-
(0,
|
|
1000
|
+
const slip = (0, import_react9.useMemo)(
|
|
1001
|
+
() => ({
|
|
1002
|
+
onAddToSlip: props.config?.onAddToSlip,
|
|
1003
|
+
conversationId: controller.state.conversationId ?? void 0,
|
|
1004
|
+
onMarketClicked: (marketName) => {
|
|
1005
|
+
try {
|
|
1006
|
+
void built?.conversation.sendSignal({ signalType: "InsightMarketClicked", entityType: "Market", entityName: marketName }).catch(() => void 0);
|
|
1007
|
+
} catch {
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}),
|
|
1011
|
+
[props.config?.onAddToSlip, controller.state.conversationId, built]
|
|
1012
|
+
);
|
|
1013
|
+
(0, import_react9.useEffect)(() => {
|
|
835
1014
|
if (!built || !ui.injectStyles) return;
|
|
836
1015
|
const node = rootRef.current?.getRootNode();
|
|
837
1016
|
if (node) injectStyles(node);
|
|
838
1017
|
}, [built, ui.injectStyles]);
|
|
839
1018
|
if (!built) return null;
|
|
840
1019
|
const attrs = themeAttrs(ui);
|
|
841
|
-
return /* @__PURE__ */ (0,
|
|
1020
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("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__ */ (0, import_jsx_runtime16.jsx)(ChatPanel, { controller, ui, slip }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
842
1021
|
ChatFab,
|
|
843
1022
|
{
|
|
844
1023
|
label: ui.launcherLabel,
|