@sensiblestats/widget-react 0.11.0 → 0.13.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 +28 -0
- package/dist/index.cjs +339 -107
- 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 +339 -107
- package/dist/index.js.map +1 -1
- package/dist/styles.css +31 -4
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -65,6 +65,19 @@ var STRINGS = {
|
|
|
65
65
|
oddsShortened: "shortened",
|
|
66
66
|
oddsDrifted: "drifted",
|
|
67
67
|
updatedLabel: "updated",
|
|
68
|
+
priceMovement: "Price movement",
|
|
69
|
+
openingLabel: "Opening",
|
|
70
|
+
currentLabel: "Current",
|
|
71
|
+
movementWindow: (hours) => hours % 24 === 0 ? `Last ${hours / 24}d` : `Last ${hours}h`,
|
|
72
|
+
notEnoughHistory: "No price history recorded yet for this selection.",
|
|
73
|
+
singlePricePoint: "Only one price recorded so far for this selection.",
|
|
74
|
+
selectionSuspended: "This selection is not currently available to bet.",
|
|
75
|
+
excludedSuspended: (n) => `${n} suspended price${n === 1 ? "" : "s"} excluded from this chart.`,
|
|
76
|
+
chartTimeLabel: (ms) => new Intl.DateTimeFormat("en-GB", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }).format(new Date(ms)),
|
|
77
|
+
chartAria: (opening, current, direction, hours) => {
|
|
78
|
+
const span = hours % 24 === 0 ? `${hours / 24} day${hours / 24 === 1 ? "" : "s"}` : `${hours} hour${hours === 1 ? "" : "s"}`;
|
|
79
|
+
return `Price movement over the last ${span}: opened at ${opening}, currently ${current}, ${direction}.`;
|
|
80
|
+
},
|
|
68
81
|
inSlip: "In slip",
|
|
69
82
|
slipAdded: (n) => n === void 0 ? "Added to slip" : `Added to slip (${n} selection${n === 1 ? "" : "s"})`,
|
|
70
83
|
slipDuplicate: "Already in your slip",
|
|
@@ -98,6 +111,19 @@ var STRINGS = {
|
|
|
98
111
|
oddsShortened: "d\xFC\u015Ft\xFC",
|
|
99
112
|
oddsDrifted: "y\xFCkseldi",
|
|
100
113
|
updatedLabel: "g\xFCncellendi",
|
|
114
|
+
priceMovement: "Oran hareketi",
|
|
115
|
+
openingLabel: "A\xE7\u0131l\u0131\u015F",
|
|
116
|
+
currentLabel: "G\xFCncel",
|
|
117
|
+
movementWindow: (hours) => hours % 24 === 0 ? `Son ${hours / 24} g\xFCn` : `Son ${hours} saat`,
|
|
118
|
+
notEnoughHistory: "Bu se\xE7im i\xE7in hen\xFCz oran ge\xE7mi\u015Fi kaydedilmedi.",
|
|
119
|
+
singlePricePoint: "Bu se\xE7im i\xE7in \u015Fimdiye kadar yaln\u0131zca bir fiyat kaydedildi.",
|
|
120
|
+
selectionSuspended: "Bu se\xE7im \u015Fu anda bahse kapal\u0131.",
|
|
121
|
+
excludedSuspended: (n) => `${n} ask\u0131ya al\u0131nm\u0131\u015F fiyat bu grafikten hari\xE7 tutuldu.`,
|
|
122
|
+
chartTimeLabel: (ms) => new Intl.DateTimeFormat("tr-TR", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }).format(new Date(ms)),
|
|
123
|
+
chartAria: (opening, current, direction, hours) => {
|
|
124
|
+
const span = hours % 24 === 0 ? `${hours / 24} g\xFCn` : `${hours} saat`;
|
|
125
|
+
return `Son ${span} i\xE7indeki oran hareketi: ${opening} ile a\xE7\u0131ld\u0131, \u015Fu anda ${current}, ${direction}.`;
|
|
126
|
+
},
|
|
101
127
|
inSlip: "Kuponda",
|
|
102
128
|
slipAdded: (n) => n === void 0 ? "Kupona eklendi" : `Kupona eklendi (${n} se\xE7im)`,
|
|
103
129
|
slipDuplicate: "Bu se\xE7im kuponunuzda zaten var",
|
|
@@ -147,6 +173,16 @@ function normalizeUi(cfg) {
|
|
|
147
173
|
oddsShortened: strings.oddsShortened,
|
|
148
174
|
oddsDrifted: strings.oddsDrifted,
|
|
149
175
|
updatedLabel: strings.updatedLabel,
|
|
176
|
+
priceMovement: strings.priceMovement,
|
|
177
|
+
openingLabel: strings.openingLabel,
|
|
178
|
+
currentLabel: strings.currentLabel,
|
|
179
|
+
movementWindow: strings.movementWindow,
|
|
180
|
+
notEnoughHistory: strings.notEnoughHistory,
|
|
181
|
+
singlePricePoint: strings.singlePricePoint,
|
|
182
|
+
selectionSuspended: strings.selectionSuspended,
|
|
183
|
+
excludedSuspended: strings.excludedSuspended,
|
|
184
|
+
chartTimeLabel: strings.chartTimeLabel,
|
|
185
|
+
chartAria: strings.chartAria,
|
|
150
186
|
addToSlip: strings.addToSlip,
|
|
151
187
|
inSlip: strings.inSlip,
|
|
152
188
|
slipAdded: strings.slipAdded,
|
|
@@ -185,6 +221,54 @@ var import_widget_sdk2 = require("@sensiblestats/widget-sdk");
|
|
|
185
221
|
// src/reducer.ts
|
|
186
222
|
var import_widget_sdk = require("@sensiblestats/widget-sdk");
|
|
187
223
|
|
|
224
|
+
// src/chart.ts
|
|
225
|
+
var CHART_VIEW_W = 320;
|
|
226
|
+
var CHART_VIEW_H = 140;
|
|
227
|
+
var PLOT_X0 = 40;
|
|
228
|
+
var PLOT_X1 = 312;
|
|
229
|
+
var PLOT_Y0 = 10;
|
|
230
|
+
var PLOT_Y1 = 110;
|
|
231
|
+
var MARKER_MAX_POINTS = 8;
|
|
232
|
+
var FLAT_DOMAIN_PAD = 0.05;
|
|
233
|
+
var round2 = (n) => Math.round(n * 100) / 100;
|
|
234
|
+
function projectSeries(samples) {
|
|
235
|
+
if (samples.length < 2) return null;
|
|
236
|
+
const tMin = samples[0].atMs;
|
|
237
|
+
const tMax = samples[samples.length - 1].atMs;
|
|
238
|
+
if (tMax <= tMin) return null;
|
|
239
|
+
const prices = samples.map((s) => s.price);
|
|
240
|
+
const pMin = Math.min(...prices);
|
|
241
|
+
const pMax = Math.max(...prices);
|
|
242
|
+
let yMin;
|
|
243
|
+
let yMax;
|
|
244
|
+
if (pMax === pMin) {
|
|
245
|
+
yMin = pMin - FLAT_DOMAIN_PAD;
|
|
246
|
+
yMax = pMax + FLAT_DOMAIN_PAD;
|
|
247
|
+
} else {
|
|
248
|
+
const pad = (pMax - pMin) * 0.1;
|
|
249
|
+
yMin = pMin - pad;
|
|
250
|
+
yMax = pMax + pad;
|
|
251
|
+
}
|
|
252
|
+
const x = (atMs) => round2(PLOT_X0 + (atMs - tMin) / (tMax - tMin) * (PLOT_X1 - PLOT_X0));
|
|
253
|
+
const y = (price) => round2(PLOT_Y1 - (price - yMin) / (yMax - yMin) * (PLOT_Y1 - PLOT_Y0));
|
|
254
|
+
const projected = samples.map((s) => ({ x: x(s.atMs), y: y(s.price) }));
|
|
255
|
+
const path = projected.map((p, i) => `${i === 0 ? "M" : "L"}${p.x} ${p.y}`).join(" ");
|
|
256
|
+
return {
|
|
257
|
+
path,
|
|
258
|
+
markers: samples.length <= MARKER_MAX_POINTS ? projected : [],
|
|
259
|
+
// The real extremes, never the padded bounds: a gridline label is a price, and the padded
|
|
260
|
+
// bounds are prices nobody was ever offered. A flat series has one real extreme, not two —
|
|
261
|
+
// two identical gridlines would collide on both React key and rendered text.
|
|
262
|
+
gridlines: pMax === pMin ? [{ y: y(pMax), price: pMax }] : [
|
|
263
|
+
{ y: y(pMax), price: pMax },
|
|
264
|
+
{ y: y(pMin), price: pMin }
|
|
265
|
+
],
|
|
266
|
+
viewBox: `0 0 ${CHART_VIEW_W} ${CHART_VIEW_H}`,
|
|
267
|
+
startMs: tMin,
|
|
268
|
+
endMs: tMax
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
188
272
|
// src/model.ts
|
|
189
273
|
function str(v) {
|
|
190
274
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
@@ -250,6 +334,9 @@ function toInsightVM(insight) {
|
|
|
250
334
|
const fixture = home && away ? `${home} vs ${away}` : void 0;
|
|
251
335
|
const competition = str(insight.competitionName);
|
|
252
336
|
const scopes = Array.isArray(insight.scopes) ? insight.scopes.map((s) => ({ key: str(s.key), label: str(s.label), stats: readStatCells(s.stats) })).filter((s) => !!s.key && !!s.label) : void 0;
|
|
337
|
+
const previousValue = insight.odds?.previousValue;
|
|
338
|
+
const previous = typeof previousValue === "number" && Number.isFinite(previousValue) ? previousValue : void 0;
|
|
339
|
+
const direction = insight.odds?.movementDirection === "shortened" || insight.odds?.movementDirection === "drifted" ? insight.odds.movementDirection : void 0;
|
|
253
340
|
return {
|
|
254
341
|
id: str(insight.insightId) ?? `${str(insight.ruleId) ?? "insight"}:${num(insight.matchId) ?? ""}`,
|
|
255
342
|
market,
|
|
@@ -269,7 +356,9 @@ function toInsightVM(insight) {
|
|
|
269
356
|
matchOddsId,
|
|
270
357
|
addToSlipEnabled: insight.addToSlipEnabled === true,
|
|
271
358
|
oddsDecimal: typeof oddsValue === "number" && Number.isFinite(oddsValue) ? oddsValue : void 0,
|
|
272
|
-
quotedAtUtc: str(insight.odds?.quotedAtUtc)
|
|
359
|
+
quotedAtUtc: str(insight.odds?.quotedAtUtc),
|
|
360
|
+
previousOdds: previous !== void 0 && direction !== void 0 ? previous.toFixed(2) : void 0,
|
|
361
|
+
movementDirection: previous !== void 0 && direction !== void 0 ? direction : void 0
|
|
273
362
|
};
|
|
274
363
|
}
|
|
275
364
|
function toSlipSelection(vm, conversationId) {
|
|
@@ -322,6 +411,41 @@ function toMarketOddsVM(card) {
|
|
|
322
411
|
addToSlipEnabled: card.addToSlipEnabled === true
|
|
323
412
|
};
|
|
324
413
|
}
|
|
414
|
+
function toOddsMovementVM(card) {
|
|
415
|
+
const raw = Array.isArray(card.points) ? card.points : [];
|
|
416
|
+
const samples = [];
|
|
417
|
+
for (const p of raw) {
|
|
418
|
+
const price = typeof p.price === "number" && Number.isFinite(p.price) ? p.price : void 0;
|
|
419
|
+
const atMs = typeof p.atUtc === "string" ? Date.parse(p.atUtc) : NaN;
|
|
420
|
+
if (price === void 0 || Number.isNaN(atMs)) continue;
|
|
421
|
+
samples.push({ atMs, price });
|
|
422
|
+
}
|
|
423
|
+
samples.sort((a, b) => a.atMs - b.atMs);
|
|
424
|
+
const asNumber = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
425
|
+
const opening = asNumber(card.openingPrice);
|
|
426
|
+
const current = asNumber(card.currentPrice);
|
|
427
|
+
const home = str(card.homeTeamName);
|
|
428
|
+
const away = str(card.awayTeamName);
|
|
429
|
+
const selection = [str(card.selectionLabel), str(card.line)].filter(Boolean).join(" ");
|
|
430
|
+
const bookmakerId = typeof card.bookmakerId === "number" ? card.bookmakerId : "";
|
|
431
|
+
return {
|
|
432
|
+
id: `${typeof card.matchId === "number" ? card.matchId : 0}:${str(card.marketDeveloperName) ?? ""}:${selection}:${bookmakerId}`,
|
|
433
|
+
market: str(card.marketLabel) ?? str(card.marketDeveloperName) ?? "",
|
|
434
|
+
selection,
|
|
435
|
+
fixture: home && away ? `${home} vs ${away}` : void 0,
|
|
436
|
+
competition: str(card.competitionName),
|
|
437
|
+
bookmaker: str(card.bookmakerName),
|
|
438
|
+
opening: opening !== void 0 ? opening.toFixed(2) : "",
|
|
439
|
+
current: current !== void 0 ? current.toFixed(2) : "",
|
|
440
|
+
direction: card.movementDirection === "shortened" || card.movementDirection === "drifted" ? card.movementDirection : void 0,
|
|
441
|
+
windowHours: asNumber(card.windowHours) ?? 0,
|
|
442
|
+
excludedCount: asNumber(card.excludedSuspendedCount) ?? 0,
|
|
443
|
+
isSuspended: card.isSuspended === true,
|
|
444
|
+
disclaimer: str(card.disclaimer),
|
|
445
|
+
geometry: projectSeries(samples),
|
|
446
|
+
observationCount: samples.length
|
|
447
|
+
};
|
|
448
|
+
}
|
|
325
449
|
function marketOddsRowToSlipSelection(vm, row, conversationId) {
|
|
326
450
|
if (row.matchOddsId === void 0 || row.oddsDecimal === void 0) return null;
|
|
327
451
|
const selection = { matchOddsId: row.matchOddsId, matchId: vm.matchId, oddsDecimal: row.oddsDecimal };
|
|
@@ -362,6 +486,8 @@ function applyEvent(state, event) {
|
|
|
362
486
|
return { ...state, messages: mapActive(state, (t) => ({ ...t, insights: [...t.insights, toInsightVM(event.data.insight)] })) };
|
|
363
487
|
case "market_odds":
|
|
364
488
|
return { ...state, messages: mapActive(state, (t) => ({ ...t, marketOdds: [...t.marketOdds, toMarketOddsVM(event.data.card)] })) };
|
|
489
|
+
case "odds_movement":
|
|
490
|
+
return { ...state, messages: mapActive(state, (t) => ({ ...t, oddsMovement: [...t.oddsMovement, toOddsMovementVM(event.data.card)] })) };
|
|
365
491
|
case "intent_chip": {
|
|
366
492
|
const chip = toChipVM(event.data.intent);
|
|
367
493
|
return chip ? { ...state, starters: [...state.starters, chip] } : state;
|
|
@@ -397,11 +523,11 @@ function reducer(state, action) {
|
|
|
397
523
|
return { ...state, isOpen: false, isStreaming: false, status: null, actionsLoading: false };
|
|
398
524
|
case "USER_SENT": {
|
|
399
525
|
const user = { role: "user", id: action.userId, text: action.displayText ?? action.input.message };
|
|
400
|
-
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], marketOdds: [], done: false };
|
|
526
|
+
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], marketOdds: [], oddsMovement: [], done: false };
|
|
401
527
|
return { ...state, messages: [...state.messages, user, assistant], isStreaming: true, status: null, actionsLoading: false, starters: [], error: null, lastUserInput: action.input, lastDisplayText: action.displayText ?? null };
|
|
402
528
|
}
|
|
403
529
|
case "GREETING_SENT": {
|
|
404
|
-
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], marketOdds: [], done: false, isGreeting: true };
|
|
530
|
+
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], marketOdds: [], oddsMovement: [], done: false, isGreeting: true };
|
|
405
531
|
return { ...state, messages: [...state.messages, assistant], isStreaming: true, status: null, error: null };
|
|
406
532
|
}
|
|
407
533
|
case "DISMISS_POPUP":
|
|
@@ -758,20 +884,41 @@ function AddToSlipButton({ it, slip, ui }) {
|
|
|
758
884
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SlipButton, { selection, shownOdds: it.odds ?? "", marketName: it.market, slip, ui });
|
|
759
885
|
}
|
|
760
886
|
|
|
887
|
+
// src/components/OddsMovementBadge.tsx
|
|
888
|
+
var import_jsx_runtime9 = (
|
|
889
|
+
// Strikethrough is invisible to screen readers, so the whole was/now/direction sentence rides
|
|
890
|
+
// on one aria-label and the visual glyphs are hidden from the accessibility tree.
|
|
891
|
+
require("react/jsx-runtime")
|
|
892
|
+
);
|
|
893
|
+
function OddsMovementBadge({
|
|
894
|
+
previous,
|
|
895
|
+
current,
|
|
896
|
+
direction,
|
|
897
|
+
ui
|
|
898
|
+
}) {
|
|
899
|
+
if (previous === void 0 || direction === void 0) return null;
|
|
900
|
+
const directionWord2 = direction === "shortened" ? ui?.oddsShortened ?? "shortened" : ui?.oddsDrifted ?? "drifted";
|
|
901
|
+
const label = ui?.oddsMoved?.(previous, current, directionWord2) ?? `was ${previous}, now ${current}, ${directionWord2}`;
|
|
902
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "ss-w-move", role: "img", "aria-label": label, children: [
|
|
903
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-move-prev", "aria-hidden": "true", children: previous }),
|
|
904
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-move-arrow", "aria-hidden": "true", children: direction === "shortened" ? "\u25BC" : "\u25B2" })
|
|
905
|
+
] });
|
|
906
|
+
}
|
|
907
|
+
|
|
761
908
|
// src/components/BettingInsightList.tsx
|
|
762
|
-
var
|
|
909
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
763
910
|
function StatRows2({ stats }) {
|
|
764
|
-
return /* @__PURE__ */ (0,
|
|
765
|
-
/* @__PURE__ */ (0,
|
|
766
|
-
/* @__PURE__ */ (0,
|
|
911
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "ss-w-bi-rows", children: stats.map((s) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-bi-row", children: [
|
|
912
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-row-lbl", children: s.label }),
|
|
913
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-row-num", children: s.value })
|
|
767
914
|
] }, `${s.label}:${s.value}`)) });
|
|
768
915
|
}
|
|
769
916
|
function ScopedStats({ scopes }) {
|
|
770
917
|
const [active, setActive] = (0, import_react5.useState)(0);
|
|
771
918
|
const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
|
|
772
|
-
return /* @__PURE__ */ (0,
|
|
773
|
-
/* @__PURE__ */ (0,
|
|
774
|
-
/* @__PURE__ */ (0,
|
|
919
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-bi-scoped", children: [
|
|
920
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "ss-w-bi-scopes", role: "group", children: scopes.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "ss-w-bi-chip", "aria-pressed": i === active, onClick: () => setActive(i), children: s.label }, s.key)) }),
|
|
921
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatRows2, { stats: current.stats }, current.key)
|
|
775
922
|
] });
|
|
776
923
|
}
|
|
777
924
|
function StatsDisclosure({ it, ui }) {
|
|
@@ -779,8 +926,8 @@ function StatsDisclosure({ it, ui }) {
|
|
|
779
926
|
const hasScopes = !!it.scopes && it.scopes.length >= 2;
|
|
780
927
|
const hasStats = hasScopes || it.stats.length > 0;
|
|
781
928
|
if (!hasStats) return null;
|
|
782
|
-
return /* @__PURE__ */ (0,
|
|
783
|
-
/* @__PURE__ */ (0,
|
|
929
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-bi-stats", children: [
|
|
930
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
784
931
|
"button",
|
|
785
932
|
{
|
|
786
933
|
type: "button",
|
|
@@ -788,131 +935,189 @@ function StatsDisclosure({ it, ui }) {
|
|
|
788
935
|
"aria-expanded": open,
|
|
789
936
|
onClick: () => setOpen((v) => !v),
|
|
790
937
|
children: [
|
|
791
|
-
/* @__PURE__ */ (0,
|
|
792
|
-
/* @__PURE__ */ (0,
|
|
938
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: open ? ui?.hideStats ?? "Hide stats" : ui?.showStats ?? "Show stats" }),
|
|
939
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: `ss-w-bi-chev${open ? " ss-w-bi-chev-up" : ""}`, "aria-hidden": "true" })
|
|
793
940
|
]
|
|
794
941
|
}
|
|
795
942
|
),
|
|
796
|
-
open ? hasScopes ? /* @__PURE__ */ (0,
|
|
943
|
+
open ? hasScopes ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ScopedStats, { scopes: it.scopes }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatRows2, { stats: it.stats }) : null
|
|
797
944
|
] });
|
|
798
945
|
}
|
|
799
946
|
function InsightCard({ it, ui, slip }) {
|
|
800
947
|
const live = it.isLive || it.phase === "live";
|
|
801
|
-
return /* @__PURE__ */ (0,
|
|
802
|
-
live ? /* @__PURE__ */ (0,
|
|
803
|
-
/* @__PURE__ */ (0,
|
|
948
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-binsight", children: [
|
|
949
|
+
live ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "ss-w-bi-status", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ss-w-bi-live", children: [
|
|
950
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
|
|
804
951
|
ui?.liveLabel ?? "Live"
|
|
805
952
|
] }) }) : null,
|
|
806
|
-
/* @__PURE__ */ (0,
|
|
807
|
-
/* @__PURE__ */ (0,
|
|
808
|
-
/* @__PURE__ */ (0,
|
|
809
|
-
it.selection ? /* @__PURE__ */ (0,
|
|
810
|
-
it.fixture || it.competition ? /* @__PURE__ */ (0,
|
|
811
|
-
it.fixture ? /* @__PURE__ */ (0,
|
|
812
|
-
it.competition ? /* @__PURE__ */ (0,
|
|
953
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-bi-head", children: [
|
|
954
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ss-w-bi-mkt", children: [
|
|
955
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-market", children: it.market }),
|
|
956
|
+
it.selection ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-sel", children: it.selection }) : null,
|
|
957
|
+
it.fixture || it.competition ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ss-w-bi-match", children: [
|
|
958
|
+
it.fixture ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-fixture", children: it.fixture }) : null,
|
|
959
|
+
it.competition ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-comp", children: it.competition }) : null
|
|
813
960
|
] }) : null
|
|
814
961
|
] }),
|
|
815
|
-
/* @__PURE__ */ (0,
|
|
816
|
-
it.odds ? /* @__PURE__ */ (0,
|
|
817
|
-
/* @__PURE__ */ (0,
|
|
818
|
-
/* @__PURE__ */ (0,
|
|
819
|
-
|
|
962
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-bi-action", children: [
|
|
963
|
+
it.odds ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ss-w-bi-odds", children: [
|
|
964
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-odds-cap", children: ui?.oddsLabel ?? "Odds" }),
|
|
965
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("b", { children: it.odds }),
|
|
966
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(OddsMovementBadge, { previous: it.previousOdds, current: it.odds, direction: it.movementDirection, ui }),
|
|
967
|
+
it.bookmaker ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-book", children: it.bookmaker }) : null
|
|
820
968
|
] }) : null,
|
|
821
|
-
/* @__PURE__ */ (0,
|
|
969
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AddToSlipButton, { it, slip, ui })
|
|
822
970
|
] })
|
|
823
971
|
] }),
|
|
824
|
-
it.text ? /* @__PURE__ */ (0,
|
|
825
|
-
/* @__PURE__ */ (0,
|
|
826
|
-
it.disclaimer ? /* @__PURE__ */ (0,
|
|
972
|
+
it.text ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "ss-w-bi-text", children: it.text }) : null,
|
|
973
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatsDisclosure, { it, ui }),
|
|
974
|
+
it.disclaimer ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "ss-w-bi-disc", children: it.disclaimer }) : null
|
|
827
975
|
] });
|
|
828
976
|
}
|
|
829
977
|
function BettingInsightList({ insights, ui, slip }) {
|
|
830
978
|
if (!insights || insights.length === 0) return null;
|
|
831
|
-
return /* @__PURE__ */ (0,
|
|
979
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "ss-w-binsights", children: insights.map((it) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(InsightCard, { it, ui, slip }, it.id)) });
|
|
832
980
|
}
|
|
833
981
|
|
|
834
982
|
// src/components/MarketOddsCardList.tsx
|
|
835
983
|
var import_react6 = require("react");
|
|
836
|
-
var
|
|
984
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
837
985
|
function OddsRow({ vm, row, ui, slip }) {
|
|
838
986
|
const selection = vm.addToSlipEnabled ? marketOddsRowToSlipSelection(vm, row, slip.conversationId) : null;
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
/* @__PURE__ */ (0,
|
|
843
|
-
row.
|
|
844
|
-
// Strikethrough is invisible to screen readers, so the whole was/now/direction sentence
|
|
845
|
-
// rides on one aria-label and the visual glyphs are hidden from the accessibility tree.
|
|
846
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ss-w-mo-move", role: "img", "aria-label": movementLabel, children: [
|
|
847
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-mo-prev", "aria-hidden": "true", children: row.previousOdds }),
|
|
848
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-mo-arrow", "aria-hidden": "true", children: row.movementDirection === "shortened" ? "\u25BC" : "\u25B2" })
|
|
849
|
-
] })
|
|
850
|
-
) : null,
|
|
851
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-mo-price", children: row.odds }),
|
|
852
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SlipButton, { selection, shownOdds: row.odds, marketName: vm.market, slip, ui })
|
|
987
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "ss-w-mo-row", children: [
|
|
988
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "ss-w-mo-sel", children: row.selection }),
|
|
989
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(OddsMovementBadge, { previous: row.previousOdds, current: row.odds, direction: row.movementDirection, ui }),
|
|
990
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "ss-w-mo-price", children: row.odds }),
|
|
991
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SlipButton, { selection, shownOdds: row.odds, marketName: vm.market, slip, ui })
|
|
853
992
|
] });
|
|
854
993
|
}
|
|
855
994
|
function MarketOddsCard({ vm, ui, slip }) {
|
|
856
995
|
const [open, setOpen] = (0, import_react6.useState)(false);
|
|
857
|
-
return /* @__PURE__ */ (0,
|
|
858
|
-
/* @__PURE__ */ (0,
|
|
859
|
-
/* @__PURE__ */ (0,
|
|
860
|
-
/* @__PURE__ */ (0,
|
|
996
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "ss-w-mocard", children: [
|
|
997
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "ss-w-mo-head", children: [
|
|
998
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "ss-w-mo-market", children: vm.market }),
|
|
999
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "ss-w-mo-tag", children: ui?.marketPrices ?? "Market prices" })
|
|
861
1000
|
] }),
|
|
862
|
-
vm.fixture || vm.competition ? /* @__PURE__ */ (0,
|
|
863
|
-
vm.fixture ? /* @__PURE__ */ (0,
|
|
864
|
-
vm.competition ? /* @__PURE__ */ (0,
|
|
1001
|
+
vm.fixture || vm.competition ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "ss-w-mo-fixture", children: [
|
|
1002
|
+
vm.fixture ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: vm.fixture }) : null,
|
|
1003
|
+
vm.competition ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "ss-w-mo-comp", children: vm.competition }) : null
|
|
865
1004
|
] }) : null,
|
|
866
|
-
/* @__PURE__ */ (0,
|
|
867
|
-
vm.mainRows.map((row) => /* @__PURE__ */ (0,
|
|
868
|
-
open ? vm.moreRows.map((row) => /* @__PURE__ */ (0,
|
|
1005
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "ss-w-mo-rows", children: [
|
|
1006
|
+
vm.mainRows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(OddsRow, { vm, row, ui, slip }, row.key)),
|
|
1007
|
+
open ? vm.moreRows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(OddsRow, { vm, row, ui, slip }, row.key)) : null
|
|
869
1008
|
] }),
|
|
870
|
-
vm.moreRows.length > 0 ? /* @__PURE__ */ (0,
|
|
871
|
-
/* @__PURE__ */ (0,
|
|
872
|
-
/* @__PURE__ */ (0,
|
|
1009
|
+
vm.moreRows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("button", { type: "button", className: "ss-w-bi-toggle", "aria-expanded": open, onClick: () => setOpen((v) => !v), children: [
|
|
1010
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: open ? ui?.hideLines ?? "Hide lines" : `${ui?.moreLines ?? "More lines"} (${vm.moreRows.length})` }),
|
|
1011
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: `ss-w-bi-chev${open ? " ss-w-bi-chev-up" : ""}`, "aria-hidden": "true" })
|
|
873
1012
|
] }) : null,
|
|
874
|
-
vm.bookmaker ? /* @__PURE__ */ (0,
|
|
875
|
-
vm.disclaimer ? /* @__PURE__ */ (0,
|
|
1013
|
+
vm.bookmaker ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "ss-w-mo-foot", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: vm.bookmaker }) }) : null,
|
|
1014
|
+
vm.disclaimer ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "ss-w-bi-disc", children: vm.disclaimer }) : null
|
|
876
1015
|
] });
|
|
877
1016
|
}
|
|
878
1017
|
function MarketOddsCardList({ cards, ui, slip }) {
|
|
879
1018
|
if (!cards || cards.length === 0) return null;
|
|
880
|
-
return /* @__PURE__ */ (0,
|
|
1019
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "ss-w-mocards", children: cards.map((vm) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MarketOddsCard, { vm, ui, slip }, vm.id)) });
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// src/components/OddsMovementCard.tsx
|
|
1023
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1024
|
+
function directionWord(vm, ui) {
|
|
1025
|
+
if (vm.direction === "shortened") return ui?.oddsShortened ?? "shortened";
|
|
1026
|
+
if (vm.direction === "drifted") return ui?.oddsDrifted ?? "drifted";
|
|
1027
|
+
return "";
|
|
1028
|
+
}
|
|
1029
|
+
function OddsMovementCard({ vm, ui }) {
|
|
1030
|
+
const aria = ui?.chartAria ? ui.chartAria(vm.opening, vm.current, directionWord(vm, ui), vm.windowHours) : `Price movement over the last ${vm.windowHours} hours: opened at ${vm.opening}, currently ${vm.current}, ${directionWord(vm, ui)}.`;
|
|
1031
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ss-w-omcard", children: [
|
|
1032
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ss-w-om-head", children: [
|
|
1033
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ss-w-om-market", children: vm.market }),
|
|
1034
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ss-w-om-tag", children: ui?.priceMovement ?? "Price movement" })
|
|
1035
|
+
] }),
|
|
1036
|
+
vm.fixture ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ss-w-om-fixture", children: [
|
|
1037
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: vm.fixture }),
|
|
1038
|
+
vm.competition ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ss-w-om-comp", children: vm.competition }) : null
|
|
1039
|
+
] }) : null,
|
|
1040
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "ss-w-om-sel", children: vm.selection }),
|
|
1041
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(OddsMovementBadge, { previous: vm.opening, current: vm.current, direction: vm.direction, ui }),
|
|
1042
|
+
vm.geometry === null ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "ss-w-om-empty", children: vm.observationCount === 1 ? ui?.singlePricePoint ?? "Only one price recorded so far for this selection." : ui?.notEnoughHistory ?? "No price history recorded yet for this selection." }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1043
|
+
"svg",
|
|
1044
|
+
{
|
|
1045
|
+
className: "ss-w-om-chart",
|
|
1046
|
+
viewBox: vm.geometry.viewBox,
|
|
1047
|
+
width: "100%",
|
|
1048
|
+
role: "img",
|
|
1049
|
+
"aria-label": aria,
|
|
1050
|
+
children: [
|
|
1051
|
+
vm.geometry.gridlines.map((g) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("g", { children: [
|
|
1052
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("line", { className: "ss-w-om-grid", x1: 40, y1: g.y, x2: CHART_VIEW_W - 8, y2: g.y }),
|
|
1053
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("text", { className: "ss-w-om-ylab", x: 34, y: g.y + 3, textAnchor: "end", children: g.price.toFixed(2) })
|
|
1054
|
+
] }, g.price)),
|
|
1055
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { className: "ss-w-om-line", d: vm.geometry.path, fill: "none" }),
|
|
1056
|
+
vm.geometry.markers.map((m, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("circle", { className: "ss-w-om-dot", cx: m.x, cy: m.y, r: 2.5 }, i)),
|
|
1057
|
+
ui?.chartTimeLabel ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
|
|
1058
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("text", { className: "ss-w-om-xlab", x: 40, y: CHART_VIEW_H - 8, textAnchor: "start", children: ui.chartTimeLabel(vm.geometry.startMs) }),
|
|
1059
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("text", { className: "ss-w-om-xlab", x: CHART_VIEW_W - 8, y: CHART_VIEW_H - 8, textAnchor: "end", children: ui.chartTimeLabel(vm.geometry.endMs) })
|
|
1060
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("text", { className: "ss-w-om-xlab", x: 40, y: CHART_VIEW_H - 8, children: ui?.movementWindow ? ui.movementWindow(vm.windowHours) : `Last ${vm.windowHours}h` })
|
|
1061
|
+
]
|
|
1062
|
+
}
|
|
1063
|
+
),
|
|
1064
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ss-w-om-foot", children: [
|
|
1065
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { children: [
|
|
1066
|
+
ui?.openingLabel ?? "Opening",
|
|
1067
|
+
" ",
|
|
1068
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("b", { children: vm.opening })
|
|
1069
|
+
] }),
|
|
1070
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { children: [
|
|
1071
|
+
ui?.currentLabel ?? "Current",
|
|
1072
|
+
" ",
|
|
1073
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("b", { children: vm.current })
|
|
1074
|
+
] })
|
|
1075
|
+
] }),
|
|
1076
|
+
vm.bookmaker ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "ss-w-om-foot", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: vm.bookmaker }) }) : null,
|
|
1077
|
+
vm.excludedCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "ss-w-om-excl", children: ui?.excludedSuspended ? ui.excludedSuspended(vm.excludedCount) : `${vm.excludedCount} suspended price(s) excluded from this chart.` }) : null,
|
|
1078
|
+
vm.isSuspended ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "ss-w-om-susp", children: ui?.selectionSuspended ?? "This selection is not currently available to bet." }) : null,
|
|
1079
|
+
vm.disclaimer ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "ss-w-om-disc", children: vm.disclaimer }) : null
|
|
1080
|
+
] });
|
|
1081
|
+
}
|
|
1082
|
+
function OddsMovementCardList({ cards, ui }) {
|
|
1083
|
+
if (!cards || cards.length === 0) return null;
|
|
1084
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "ss-w-omcards", children: cards.map((vm) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(OddsMovementCard, { vm, ui }, vm.id)) });
|
|
881
1085
|
}
|
|
882
1086
|
|
|
883
1087
|
// src/components/ActionButtons.tsx
|
|
884
|
-
var
|
|
1088
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
885
1089
|
function ActionButtons({ actions, loading, disabled, onAct }) {
|
|
886
1090
|
if (actions.length === 0 && !loading) return null;
|
|
887
|
-
return /* @__PURE__ */ (0,
|
|
888
|
-
actions.map((a, i) => /* @__PURE__ */ (0,
|
|
889
|
-
actions.length === 0 && loading ? /* @__PURE__ */ (0,
|
|
890
|
-
/* @__PURE__ */ (0,
|
|
891
|
-
/* @__PURE__ */ (0,
|
|
1091
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ss-w-actions", children: [
|
|
1092
|
+
actions.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
|
|
1093
|
+
actions.length === 0 && loading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
|
|
1094
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "ss-w-abtn-shimmer" }),
|
|
1095
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "ss-w-abtn-shimmer" })
|
|
892
1096
|
] }) : null
|
|
893
1097
|
] });
|
|
894
1098
|
}
|
|
895
1099
|
|
|
896
1100
|
// src/components/ChatMessage.tsx
|
|
897
|
-
var
|
|
1101
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
898
1102
|
function ChatMessage({ message, disabled, onAct, ui, slip }) {
|
|
899
1103
|
if (message.role === "user") {
|
|
900
|
-
return /* @__PURE__ */ (0,
|
|
1104
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
|
|
901
1105
|
}
|
|
902
|
-
return /* @__PURE__ */ (0,
|
|
903
|
-
message.text ? /* @__PURE__ */ (0,
|
|
904
|
-
/* @__PURE__ */ (0,
|
|
905
|
-
/* @__PURE__ */ (0,
|
|
906
|
-
/* @__PURE__ */ (0,
|
|
907
|
-
/* @__PURE__ */ (0,
|
|
1106
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
|
|
1107
|
+
message.text ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Markdown, { text: message.text }) : null,
|
|
1108
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
|
|
1109
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(BettingInsightList, { insights: message.insights, ui, slip }),
|
|
1110
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
|
|
1111
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(OddsMovementCardList, { cards: message.oddsMovement, ui }),
|
|
1112
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
|
|
908
1113
|
] });
|
|
909
1114
|
}
|
|
910
1115
|
|
|
911
1116
|
// src/components/IntentChips.tsx
|
|
912
|
-
var
|
|
1117
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
913
1118
|
function IntentChips({ chips, onPick }) {
|
|
914
1119
|
if (chips.length === 0) return null;
|
|
915
|
-
return /* @__PURE__ */ (0,
|
|
1120
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
|
|
916
1121
|
}
|
|
917
1122
|
|
|
918
1123
|
// src/scroll.ts
|
|
@@ -922,7 +1127,7 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
|
|
|
922
1127
|
}
|
|
923
1128
|
|
|
924
1129
|
// src/components/MessageList.tsx
|
|
925
|
-
var
|
|
1130
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
926
1131
|
function MessageList({ controller, ui, slip }) {
|
|
927
1132
|
const { state, send } = controller;
|
|
928
1133
|
const listRef = (0, import_react7.useRef)(null);
|
|
@@ -932,7 +1137,7 @@ function MessageList({ controller, ui, slip }) {
|
|
|
932
1137
|
if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
|
|
933
1138
|
}, [state.messages, state.status]);
|
|
934
1139
|
const empty = state.messages.length === 0;
|
|
935
|
-
return /* @__PURE__ */ (0,
|
|
1140
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
936
1141
|
"div",
|
|
937
1142
|
{
|
|
938
1143
|
className: "ss-w-list",
|
|
@@ -942,7 +1147,7 @@ function MessageList({ controller, ui, slip }) {
|
|
|
942
1147
|
if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
|
|
943
1148
|
},
|
|
944
1149
|
children: [
|
|
945
|
-
state.messages.map((m) => /* @__PURE__ */ (0,
|
|
1150
|
+
state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
946
1151
|
ChatMessage,
|
|
947
1152
|
{
|
|
948
1153
|
message: m,
|
|
@@ -953,15 +1158,15 @@ function MessageList({ controller, ui, slip }) {
|
|
|
953
1158
|
},
|
|
954
1159
|
m.id
|
|
955
1160
|
)),
|
|
956
|
-
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0,
|
|
1161
|
+
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
|
|
957
1162
|
} }) : null,
|
|
958
|
-
state.status ? /* @__PURE__ */ (0,
|
|
959
|
-
state.error ? /* @__PURE__ */ (0,
|
|
960
|
-
/* @__PURE__ */ (0,
|
|
961
|
-
/* @__PURE__ */ (0,
|
|
1163
|
+
state.status ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "ss-w-status", children: state.status }) : null,
|
|
1164
|
+
state.error ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "ss-w-error", role: "alert", children: [
|
|
1165
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
|
|
1166
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
|
|
962
1167
|
] }) : null,
|
|
963
|
-
empty ? /* @__PURE__ */ (0,
|
|
964
|
-
/* @__PURE__ */ (0,
|
|
1168
|
+
empty ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
|
|
1169
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref: endRef })
|
|
965
1170
|
]
|
|
966
1171
|
}
|
|
967
1172
|
);
|
|
@@ -969,7 +1174,7 @@ function MessageList({ controller, ui, slip }) {
|
|
|
969
1174
|
|
|
970
1175
|
// src/components/ChatInput.tsx
|
|
971
1176
|
var import_react8 = require("react");
|
|
972
|
-
var
|
|
1177
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
973
1178
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
974
1179
|
const [value, setValue] = (0, import_react8.useState)("");
|
|
975
1180
|
const submit = () => {
|
|
@@ -984,14 +1189,14 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
|
|
|
984
1189
|
submit();
|
|
985
1190
|
}
|
|
986
1191
|
};
|
|
987
|
-
return /* @__PURE__ */ (0,
|
|
988
|
-
/* @__PURE__ */ (0,
|
|
989
|
-
streaming ? /* @__PURE__ */ (0,
|
|
1192
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ss-w-input", children: [
|
|
1193
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
|
|
1194
|
+
streaming ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SendIcon, {}) })
|
|
990
1195
|
] });
|
|
991
1196
|
}
|
|
992
1197
|
|
|
993
1198
|
// src/components/ChatPanel.tsx
|
|
994
|
-
var
|
|
1199
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
995
1200
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
996
1201
|
function useIsModal() {
|
|
997
1202
|
const [modal, setModal] = (0, import_react9.useState)(false);
|
|
@@ -1015,10 +1220,10 @@ function ChatPanel({ controller, ui, slip }) {
|
|
|
1015
1220
|
window.addEventListener("keydown", onKey);
|
|
1016
1221
|
return () => window.removeEventListener("keydown", onKey);
|
|
1017
1222
|
}, [close]);
|
|
1018
|
-
return /* @__PURE__ */ (0,
|
|
1019
|
-
/* @__PURE__ */ (0,
|
|
1020
|
-
/* @__PURE__ */ (0,
|
|
1021
|
-
/* @__PURE__ */ (0,
|
|
1223
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
1224
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
|
|
1225
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MessageList, { controller, ui, slip }),
|
|
1226
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
|
|
1022
1227
|
] });
|
|
1023
1228
|
}
|
|
1024
1229
|
|
|
@@ -1207,13 +1412,40 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
1207
1412
|
.ss-w-mo-price { margin-left: auto; font-family: ui-monospace, monospace; font-variant-numeric: tabular-nums; font-size: 14px; font-weight: 700; color: var(--ss-w-accent); }
|
|
1208
1413
|
/* Movement: BOTH directions share --ss-w-faint on purpose. Green-good/red-bad would frame a
|
|
1209
1414
|
shortening price as a discount, which inverts what it actually means for the bettor. */
|
|
1210
|
-
.ss-w-
|
|
1211
|
-
.ss-w-
|
|
1212
|
-
.ss-w-
|
|
1213
|
-
|
|
1415
|
+
.ss-w-move { display: inline-flex; align-items: baseline; gap: 4px; color: var(--ss-w-faint); }
|
|
1416
|
+
.ss-w-move-prev { font-family: ui-monospace, monospace; font-variant-numeric: tabular-nums; font-size: 12px; text-decoration: line-through; }
|
|
1417
|
+
.ss-w-move-arrow { font-size: 9px; line-height: 1; }
|
|
1418
|
+
/* margin-left:auto is row layout, not badge styling -- it belongs to the market-odds row, which is
|
|
1419
|
+
a flex row, and must NOT follow the badge into the insight card's odds column. */
|
|
1420
|
+
.ss-w-mo-row .ss-w-move { margin-left: auto; }
|
|
1421
|
+
.ss-w-mo-row .ss-w-move + .ss-w-mo-price { margin-left: 0; }
|
|
1214
1422
|
.ss-w-mo-row .ss-w-slip { margin: 0; }
|
|
1215
1423
|
.ss-w-mo-foot { display: flex; align-items: center; gap: 8px; font-size: 10.5px; color: var(--ss-w-faint); }
|
|
1216
1424
|
|
|
1425
|
+
/* odds movement card */
|
|
1426
|
+
.ss-w-omcards { display: flex; flex-direction: column; gap: 7px; }
|
|
1427
|
+
.ss-w-omcard { background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; border-left: 3px solid var(--ss-w-accent); padding: 9px 11px; display: flex; flex-direction: column; gap: 6px; }
|
|
1428
|
+
.ss-w-om-head { display: flex; align-items: center; gap: 8px; }
|
|
1429
|
+
.ss-w-om-market { font-size: 13px; font-weight: 700; color: var(--ss-w-ink); }
|
|
1430
|
+
.ss-w-om-tag { margin-left: auto; flex: none; font-size: 9px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--ss-w-faint); border: 1px solid var(--ss-w-line); border-radius: 8px; padding: 2px 7px; }
|
|
1431
|
+
.ss-w-om-fixture { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--ss-w-faint); min-width: 0; }
|
|
1432
|
+
.ss-w-om-comp { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1433
|
+
.ss-w-om-sel { font-size: 12.5px; font-weight: 600; color: var(--ss-w-ink); }
|
|
1434
|
+
.ss-w-om-chart { display: block; width: 100%; height: auto; }
|
|
1435
|
+
/* Single neutral accent in BOTH directions, deliberately. Colouring a shortening price green
|
|
1436
|
+
frames it as a discount, which inverts what it means for the bettor -- the same reason
|
|
1437
|
+
.ss-w-move keeps both directions on --ss-w-faint. */
|
|
1438
|
+
.ss-w-om-line { stroke: var(--ss-w-accent); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
|
|
1439
|
+
.ss-w-om-dot { fill: var(--ss-w-accent); }
|
|
1440
|
+
.ss-w-om-grid { stroke: var(--ss-w-line); stroke-width: 1; }
|
|
1441
|
+
.ss-w-om-ylab, .ss-w-om-xlab { fill: var(--ss-w-faint); font-size: 9px; font-family: ui-monospace, monospace; }
|
|
1442
|
+
.ss-w-om-empty { font-size: 11.5px; color: var(--ss-w-faint); padding: 6px 0; }
|
|
1443
|
+
.ss-w-om-foot { display: flex; align-items: center; gap: 14px; font-size: 11.5px; color: var(--ss-w-faint); }
|
|
1444
|
+
.ss-w-om-foot b { font-family: ui-monospace, monospace; font-variant-numeric: tabular-nums; font-size: 13px; color: var(--ss-w-ink); }
|
|
1445
|
+
.ss-w-om-excl { font-size: 10.5px; color: var(--ss-w-faint); }
|
|
1446
|
+
.ss-w-om-susp { font-size: 11px; font-weight: 600; color: #c0392b; }
|
|
1447
|
+
.ss-w-om-disc { font-size: 10px; font-style: italic; color: var(--ss-w-faint); }
|
|
1448
|
+
|
|
1217
1449
|
/* action buttons */
|
|
1218
1450
|
.ss-w-actions { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
|
|
1219
1451
|
.ss-w-abtn { font-size: 12px; font-weight: 560; color: var(--ss-w-accent); background: var(--ss-w-panel); border: 1px solid var(--ss-w-accent); border-radius: 16px; padding: 6px 12px; cursor: pointer; }
|
|
@@ -1251,7 +1483,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
1251
1483
|
`;
|
|
1252
1484
|
|
|
1253
1485
|
// src/StatsWidget.tsx
|
|
1254
|
-
var
|
|
1486
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
1255
1487
|
function buildClient(props) {
|
|
1256
1488
|
try {
|
|
1257
1489
|
if ("client" in props && props.client) {
|
|
@@ -1300,7 +1532,7 @@ function StatsWidget(props) {
|
|
|
1300
1532
|
}, [built, ui.injectStyles]);
|
|
1301
1533
|
if (!built) return null;
|
|
1302
1534
|
const attrs = themeAttrs(ui);
|
|
1303
|
-
return /* @__PURE__ */ (0,
|
|
1535
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.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_runtime19.jsx)(ChatPanel, { controller, ui, slip }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
1304
1536
|
ChatFab,
|
|
1305
1537
|
{
|
|
1306
1538
|
label: ui.launcherLabel,
|