@sensiblestats/widget-react 0.6.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/dist/index.cjs +278 -98
- 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 +272 -92
- package/dist/index.js.map +1 -1
- package/dist/styles.css +15 -3
- 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];
|
|
@@ -244,7 +290,15 @@ function applyEvent(state, event) {
|
|
|
244
290
|
const active = activeTurn(state);
|
|
245
291
|
const text = active?.text.trim() ?? "";
|
|
246
292
|
const popupText = active?.isGreeting && !state.isOpen && text ? text.slice(0, POPUP_MAX) : state.popupText;
|
|
247
|
-
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
|
+
};
|
|
248
302
|
}
|
|
249
303
|
case "error": {
|
|
250
304
|
const suppress = activeTurn(state)?.isGreeting === true;
|
|
@@ -387,7 +441,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
|
|
|
387
441
|
}
|
|
388
442
|
|
|
389
443
|
// src/components/ChatPanel.tsx
|
|
390
|
-
var
|
|
444
|
+
var import_react8 = require("react");
|
|
391
445
|
|
|
392
446
|
// src/components/ChatHeader.tsx
|
|
393
447
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
@@ -400,7 +454,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat" }) {
|
|
|
400
454
|
}
|
|
401
455
|
|
|
402
456
|
// src/components/MessageList.tsx
|
|
403
|
-
var
|
|
457
|
+
var import_react6 = require("react");
|
|
404
458
|
|
|
405
459
|
// src/components/Markdown.tsx
|
|
406
460
|
var import_react_markdown = __toESM(require("react-markdown"), 1);
|
|
@@ -454,94 +508,194 @@ function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMore
|
|
|
454
508
|
}
|
|
455
509
|
|
|
456
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
|
|
457
546
|
var import_react3 = require("react");
|
|
458
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");
|
|
459
612
|
var MAX_ROWS = 3;
|
|
460
613
|
function StatRows({ stats, ui }) {
|
|
461
|
-
const [expanded, setExpanded] = (0,
|
|
614
|
+
const [expanded, setExpanded] = (0, import_react5.useState)(false);
|
|
462
615
|
const shown = expanded ? stats : stats.slice(0, MAX_ROWS);
|
|
463
616
|
const overflow = stats.length - MAX_ROWS;
|
|
464
|
-
return /* @__PURE__ */ (0,
|
|
465
|
-
shown.map((s) => /* @__PURE__ */ (0,
|
|
466
|
-
/* @__PURE__ */ (0,
|
|
467
|
-
/* @__PURE__ */ (0,
|
|
468
|
-
/* @__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 })
|
|
469
622
|
] }, `${s.label}:${s.value}`)),
|
|
470
|
-
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
|
|
471
624
|
] });
|
|
472
625
|
}
|
|
473
626
|
function ScopedStats({ scopes, ui }) {
|
|
474
|
-
const [active, setActive] = (0,
|
|
627
|
+
const [active, setActive] = (0, import_react5.useState)(0);
|
|
475
628
|
const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
|
|
476
|
-
return /* @__PURE__ */ (0,
|
|
477
|
-
/* @__PURE__ */ (0,
|
|
478
|
-
/* @__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)
|
|
479
632
|
] });
|
|
480
633
|
}
|
|
481
|
-
function InsightCard({ it, ui }) {
|
|
634
|
+
function InsightCard({ it, ui, slip }) {
|
|
482
635
|
const live = it.isLive || it.phase === "live";
|
|
483
|
-
return /* @__PURE__ */ (0,
|
|
484
|
-
live || it.fixture || it.competition ? /* @__PURE__ */ (0,
|
|
485
|
-
live ? /* @__PURE__ */ (0,
|
|
486
|
-
/* @__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" }),
|
|
487
640
|
ui?.liveLabel ?? "Live"
|
|
488
641
|
] }) : null,
|
|
489
|
-
it.fixture ? /* @__PURE__ */ (0,
|
|
490
|
-
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
|
|
491
644
|
] }) : null,
|
|
492
|
-
/* @__PURE__ */ (0,
|
|
493
|
-
/* @__PURE__ */ (0,
|
|
494
|
-
/* @__PURE__ */ (0,
|
|
495
|
-
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
|
|
496
649
|
] }),
|
|
497
|
-
it.odds ? /* @__PURE__ */ (0,
|
|
498
|
-
/* @__PURE__ */ (0,
|
|
499
|
-
/* @__PURE__ */ (0,
|
|
500
|
-
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
|
|
501
654
|
] }) : null
|
|
502
655
|
] }),
|
|
503
|
-
it.text ? /* @__PURE__ */ (0,
|
|
504
|
-
it.scopes && it.scopes.length >= 2 ? /* @__PURE__ */ (0,
|
|
505
|
-
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 })
|
|
506
660
|
] });
|
|
507
661
|
}
|
|
508
|
-
function BettingInsightList({ insights, ui }) {
|
|
662
|
+
function BettingInsightList({ insights, ui, slip }) {
|
|
509
663
|
if (!insights || insights.length === 0) return null;
|
|
510
|
-
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)) });
|
|
511
665
|
}
|
|
512
666
|
|
|
513
667
|
// src/components/ActionButtons.tsx
|
|
514
|
-
var
|
|
668
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
515
669
|
function ActionButtons({ actions, loading, disabled, onAct }) {
|
|
516
670
|
if (actions.length === 0 && !loading) return null;
|
|
517
|
-
return /* @__PURE__ */ (0,
|
|
518
|
-
actions.map((a, i) => /* @__PURE__ */ (0,
|
|
519
|
-
actions.length === 0 && loading ? /* @__PURE__ */ (0,
|
|
520
|
-
/* @__PURE__ */ (0,
|
|
521
|
-
/* @__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" })
|
|
522
676
|
] }) : null
|
|
523
677
|
] });
|
|
524
678
|
}
|
|
525
679
|
|
|
526
680
|
// src/components/ChatMessage.tsx
|
|
527
|
-
var
|
|
528
|
-
function ChatMessage({ message, disabled, onAct, ui }) {
|
|
681
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
682
|
+
function ChatMessage({ message, disabled, onAct, ui, slip }) {
|
|
529
683
|
if (message.role === "user") {
|
|
530
|
-
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 }) });
|
|
531
685
|
}
|
|
532
|
-
return /* @__PURE__ */ (0,
|
|
533
|
-
message.text ? /* @__PURE__ */ (0,
|
|
534
|
-
/* @__PURE__ */ (0,
|
|
535
|
-
/* @__PURE__ */ (0,
|
|
536
|
-
/* @__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 })
|
|
537
691
|
] });
|
|
538
692
|
}
|
|
539
693
|
|
|
540
694
|
// src/components/IntentChips.tsx
|
|
541
|
-
var
|
|
695
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
542
696
|
function IntentChips({ chips, onPick }) {
|
|
543
697
|
if (chips.length === 0) return null;
|
|
544
|
-
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}`)) });
|
|
545
699
|
}
|
|
546
700
|
|
|
547
701
|
// src/scroll.ts
|
|
@@ -551,17 +705,17 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
|
|
|
551
705
|
}
|
|
552
706
|
|
|
553
707
|
// src/components/MessageList.tsx
|
|
554
|
-
var
|
|
555
|
-
function MessageList({ controller, ui }) {
|
|
708
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
709
|
+
function MessageList({ controller, ui, slip }) {
|
|
556
710
|
const { state, send } = controller;
|
|
557
|
-
const listRef = (0,
|
|
558
|
-
const endRef = (0,
|
|
559
|
-
const pinnedRef = (0,
|
|
560
|
-
(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)(() => {
|
|
561
715
|
if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
|
|
562
716
|
}, [state.messages, state.status]);
|
|
563
717
|
const empty = state.messages.length === 0;
|
|
564
|
-
return /* @__PURE__ */ (0,
|
|
718
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
565
719
|
"div",
|
|
566
720
|
{
|
|
567
721
|
className: "ss-w-list",
|
|
@@ -571,35 +725,36 @@ function MessageList({ controller, ui }) {
|
|
|
571
725
|
if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
|
|
572
726
|
},
|
|
573
727
|
children: [
|
|
574
|
-
state.messages.map((m) => /* @__PURE__ */ (0,
|
|
728
|
+
state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
575
729
|
ChatMessage,
|
|
576
730
|
{
|
|
577
731
|
message: m,
|
|
578
732
|
disabled: state.isStreaming,
|
|
579
733
|
onAct: (a) => send(a.message, { actionId: a.actionId, displayText: a.label }),
|
|
580
|
-
ui
|
|
734
|
+
ui,
|
|
735
|
+
slip
|
|
581
736
|
},
|
|
582
737
|
m.id
|
|
583
738
|
)),
|
|
584
|
-
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0,
|
|
739
|
+
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
|
|
585
740
|
} }) : null,
|
|
586
|
-
state.status ? /* @__PURE__ */ (0,
|
|
587
|
-
state.error ? /* @__PURE__ */ (0,
|
|
588
|
-
/* @__PURE__ */ (0,
|
|
589
|
-
/* @__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 })
|
|
590
745
|
] }) : null,
|
|
591
|
-
empty ? /* @__PURE__ */ (0,
|
|
592
|
-
/* @__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 })
|
|
593
748
|
]
|
|
594
749
|
}
|
|
595
750
|
);
|
|
596
751
|
}
|
|
597
752
|
|
|
598
753
|
// src/components/ChatInput.tsx
|
|
599
|
-
var
|
|
600
|
-
var
|
|
754
|
+
var import_react7 = require("react");
|
|
755
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
601
756
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
602
|
-
const [value, setValue] = (0,
|
|
757
|
+
const [value, setValue] = (0, import_react7.useState)("");
|
|
603
758
|
const submit = () => {
|
|
604
759
|
const t = value.trim();
|
|
605
760
|
if (!t) return;
|
|
@@ -612,18 +767,18 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
|
|
|
612
767
|
submit();
|
|
613
768
|
}
|
|
614
769
|
};
|
|
615
|
-
return /* @__PURE__ */ (0,
|
|
616
|
-
/* @__PURE__ */ (0,
|
|
617
|
-
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, {}) })
|
|
618
773
|
] });
|
|
619
774
|
}
|
|
620
775
|
|
|
621
776
|
// src/components/ChatPanel.tsx
|
|
622
|
-
var
|
|
777
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
623
778
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
624
779
|
function useIsModal() {
|
|
625
|
-
const [modal, setModal] = (0,
|
|
626
|
-
(0,
|
|
780
|
+
const [modal, setModal] = (0, import_react8.useState)(false);
|
|
781
|
+
(0, import_react8.useEffect)(() => {
|
|
627
782
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
628
783
|
const mql = window.matchMedia(FULLSCREEN_QUERY);
|
|
629
784
|
const sync = () => setModal(mql.matches);
|
|
@@ -633,20 +788,20 @@ function useIsModal() {
|
|
|
633
788
|
}, []);
|
|
634
789
|
return modal;
|
|
635
790
|
}
|
|
636
|
-
function ChatPanel({ controller, ui }) {
|
|
791
|
+
function ChatPanel({ controller, ui, slip }) {
|
|
637
792
|
const { state, close, stop, send } = controller;
|
|
638
793
|
const isModal = useIsModal();
|
|
639
|
-
(0,
|
|
794
|
+
(0, import_react8.useEffect)(() => {
|
|
640
795
|
const onKey = (e) => {
|
|
641
796
|
if (e.key === "Escape") close();
|
|
642
797
|
};
|
|
643
798
|
window.addEventListener("keydown", onKey);
|
|
644
799
|
return () => window.removeEventListener("keydown", onKey);
|
|
645
800
|
}, [close]);
|
|
646
|
-
return /* @__PURE__ */ (0,
|
|
647
|
-
/* @__PURE__ */ (0,
|
|
648
|
-
/* @__PURE__ */ (0,
|
|
649
|
-
/* @__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 })
|
|
650
805
|
] });
|
|
651
806
|
}
|
|
652
807
|
|
|
@@ -655,6 +810,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
655
810
|
.ss-w-root, .ss-w-root * { box-sizing: border-box; margin: 0; padding: 0; }
|
|
656
811
|
.ss-w-root {
|
|
657
812
|
--ss-w-accent: #17936a;
|
|
813
|
+
--ss-w-cta: #f5a623; --ss-w-cta-ink: #1b1300;
|
|
658
814
|
--ss-w-ink: #14181d; --ss-w-faint: #6b7580; --ss-w-line: #e4e8ec;
|
|
659
815
|
--ss-w-panel: #ffffff; --ss-w-bubble: #f3f5f7; --ss-w-nav: #0e1622;
|
|
660
816
|
--ss-w-radius: 16px; --ss-w-offset-x: 20px; --ss-w-offset-y: 20px;
|
|
@@ -663,16 +819,19 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
663
819
|
}
|
|
664
820
|
@media (prefers-color-scheme: dark) {
|
|
665
821
|
.ss-w-root[data-ss-w-theme="auto"] {
|
|
666
|
-
--ss-w-accent: #37c891; --ss-w-
|
|
822
|
+
--ss-w-accent: #37c891; --ss-w-cta: #ffb638; --ss-w-cta-ink: #1b1300;
|
|
823
|
+
--ss-w-ink: #e8edf2; --ss-w-faint: #8b95a1;
|
|
667
824
|
--ss-w-line: #2a3441; --ss-w-panel: #141b24; --ss-w-bubble: #1e2732; --ss-w-nav: #0a0f16;
|
|
668
825
|
}
|
|
669
826
|
}
|
|
670
827
|
.ss-w-root[data-ss-w-theme="dark"] {
|
|
671
|
-
--ss-w-accent: #37c891; --ss-w-
|
|
828
|
+
--ss-w-accent: #37c891; --ss-w-cta: #ffb638; --ss-w-cta-ink: #1b1300;
|
|
829
|
+
--ss-w-ink: #e8edf2; --ss-w-faint: #8b95a1;
|
|
672
830
|
--ss-w-line: #2a3441; --ss-w-panel: #141b24; --ss-w-bubble: #1e2732; --ss-w-nav: #0a0f16;
|
|
673
831
|
}
|
|
674
832
|
.ss-w-root[data-ss-w-theme="light"] {
|
|
675
|
-
--ss-w-accent: #17936a; --ss-w-
|
|
833
|
+
--ss-w-accent: #17936a; --ss-w-cta: #f5a623; --ss-w-cta-ink: #1b1300;
|
|
834
|
+
--ss-w-ink: #14181d; --ss-w-faint: #6b7580;
|
|
676
835
|
--ss-w-line: #e4e8ec; --ss-w-panel: #ffffff; --ss-w-bubble: #f3f5f7; --ss-w-nav: #0e1622;
|
|
677
836
|
}
|
|
678
837
|
|
|
@@ -799,20 +958,28 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
799
958
|
.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; }
|
|
800
959
|
.ss-w-stop-glyph { width: 11px; height: 11px; background: currentColor; border-radius: 2px; }
|
|
801
960
|
|
|
961
|
+
/* add to slip */
|
|
962
|
+
.ss-w-slip { position: relative; display: flex; }
|
|
963
|
+
.ss-w-slip-btn { font-size: 11.5px; font-weight: 620; color: var(--ss-w-cta-ink); background: var(--ss-w-cta); border: 1px solid var(--ss-w-cta); border-radius: 14px; padding: 5px 12px; cursor: pointer; }
|
|
964
|
+
.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); }
|
|
965
|
+
.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); }
|
|
966
|
+
.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)); }
|
|
967
|
+
.ss-w-toast-error { color: #c0392b; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); }
|
|
968
|
+
|
|
802
969
|
@media (prefers-reduced-motion: reduce) {
|
|
803
970
|
.ss-w-abtn-shimmer { animation: none; }
|
|
804
971
|
}
|
|
805
972
|
`;
|
|
806
973
|
|
|
807
974
|
// src/StatsWidget.tsx
|
|
808
|
-
var
|
|
975
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
809
976
|
function buildClient(props) {
|
|
810
977
|
try {
|
|
811
978
|
if ("client" in props && props.client) {
|
|
812
979
|
return { client: props.client, conversation: props.client.conversation() };
|
|
813
980
|
}
|
|
814
981
|
const cfg = props.config;
|
|
815
|
-
const client = new
|
|
982
|
+
const client = new import_widget_sdk4.StatsWidgetClient(toClientOptions(cfg));
|
|
816
983
|
return { client, conversation: client.conversation(toUserContext(cfg)) };
|
|
817
984
|
} catch (err) {
|
|
818
985
|
console.error("[StatsWidget] failed to initialize:", err instanceof Error ? err.message : err);
|
|
@@ -830,18 +997,31 @@ function injectStyles(root) {
|
|
|
830
997
|
function StatsWidget(props) {
|
|
831
998
|
const injectedClient = "client" in props ? props.client : void 0;
|
|
832
999
|
const cfg = props.config;
|
|
833
|
-
const built = (0,
|
|
834
|
-
const ui = (0,
|
|
835
|
-
const rootRef = (0,
|
|
1000
|
+
const built = (0, import_react9.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
|
|
1001
|
+
const ui = (0, import_react9.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
|
|
1002
|
+
const rootRef = (0, import_react9.useRef)(null);
|
|
836
1003
|
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
|
|
837
|
-
(0,
|
|
1004
|
+
const slip = (0, import_react9.useMemo)(
|
|
1005
|
+
() => ({
|
|
1006
|
+
onAddToSlip: props.config?.onAddToSlip,
|
|
1007
|
+
conversationId: controller.state.conversationId ?? void 0,
|
|
1008
|
+
onMarketClicked: (marketName) => {
|
|
1009
|
+
try {
|
|
1010
|
+
void built?.conversation.sendSignal({ signalType: "InsightMarketClicked", entityType: "Market", entityName: marketName }).catch(() => void 0);
|
|
1011
|
+
} catch {
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}),
|
|
1015
|
+
[props.config?.onAddToSlip, controller.state.conversationId, built]
|
|
1016
|
+
);
|
|
1017
|
+
(0, import_react9.useEffect)(() => {
|
|
838
1018
|
if (!built || !ui.injectStyles) return;
|
|
839
1019
|
const node = rootRef.current?.getRootNode();
|
|
840
1020
|
if (node) injectStyles(node);
|
|
841
1021
|
}, [built, ui.injectStyles]);
|
|
842
1022
|
if (!built) return null;
|
|
843
1023
|
const attrs = themeAttrs(ui);
|
|
844
|
-
return /* @__PURE__ */ (0,
|
|
1024
|
+
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)(
|
|
845
1025
|
ChatFab,
|
|
846
1026
|
{
|
|
847
1027
|
label: ui.launcherLabel,
|