@sensiblestats/widget-react 0.3.0 → 0.5.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 +21 -0
- package/dist/index.cjs +183 -59
- 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 +171 -47
- package/dist/index.js.map +1 -1
- package/dist/styles.css +30 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @sensiblestats/widget-react
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Redesigned betting-insight card with scoped supporting stats.
|
|
8
|
+
|
|
9
|
+
- **widget-sdk:** extend `BettingInsight` with `homeTeamName`, `awayTeamName`, and `scopes` (`{ key, label, stats }[]`), so consumers can type the fixture line and the per-scope stat sets (this league / all competitions / head-to-head) the server now streams.
|
|
10
|
+
- **widget-react:** rebuild the betting card around the new payload — a phase-aware live/pre-match status line with the fixture, a promoted market with clearly labeled odds (and an optional bookmaker name), and scope chips that switch the supporting stats in place (shown only when ≥2 scopes are present, otherwise flat stat rows with a "show more" expander). The responsible-gambling disclaimer always renders. Type scale and spacing match the surrounding chat cards.
|
|
11
|
+
- **widget-embed:** rebuild against `@sensiblestats/widget-react` so the hosted embed renders the redesigned card.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies
|
|
16
|
+
- @sensiblestats/widget-sdk@0.4.0
|
|
17
|
+
|
|
18
|
+
## 0.4.0
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- Render `betting_insight` events as insight cards (market, selection + line, odds with an optional live badge, supporting stats, and the responsible-gambling disclaimer). **Bug fix:** the reducer previously had no case for `betting_insight` and no fallthrough, so any unmodelled event returned `undefined` and corrupted state — the next event then crashed the widget (`Cannot read properties of undefined (reading 'length')`). Unknown event types are now ignored safely and can never break the UI.
|
|
23
|
+
|
|
3
24
|
## 0.3.0
|
|
4
25
|
|
|
5
26
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -36,7 +36,7 @@ __export(index_exports, {
|
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
37
|
|
|
38
38
|
// src/StatsWidget.tsx
|
|
39
|
-
var
|
|
39
|
+
var import_react7 = require("react");
|
|
40
40
|
var import_widget_sdk3 = require("@sensiblestats/widget-sdk");
|
|
41
41
|
|
|
42
42
|
// src/i18n.ts
|
|
@@ -52,7 +52,9 @@ var STRINGS = {
|
|
|
52
52
|
somethingWentWrong: "Something went wrong.",
|
|
53
53
|
retry: "Retry",
|
|
54
54
|
showLess: "Show less",
|
|
55
|
-
showMore: (n) => `Show ${n} more
|
|
55
|
+
showMore: (n) => `Show ${n} more`,
|
|
56
|
+
oddsLabel: "Odds",
|
|
57
|
+
liveLabel: "Live"
|
|
56
58
|
},
|
|
57
59
|
tr: {
|
|
58
60
|
launcherLabel: "SensibleStats ile sohbet et",
|
|
@@ -65,7 +67,9 @@ var STRINGS = {
|
|
|
65
67
|
somethingWentWrong: "Bir \u015Feyler ters gitti.",
|
|
66
68
|
retry: "Tekrar dene",
|
|
67
69
|
showLess: "Daha az g\xF6ster",
|
|
68
|
-
showMore: (n) => `${n} tane daha g\xF6ster
|
|
70
|
+
showMore: (n) => `${n} tane daha g\xF6ster`,
|
|
71
|
+
oddsLabel: "Oran",
|
|
72
|
+
liveLabel: "Canl\u0131"
|
|
69
73
|
}
|
|
70
74
|
};
|
|
71
75
|
function resolveLocaleStrings(locale) {
|
|
@@ -95,7 +99,9 @@ function normalizeUi(cfg) {
|
|
|
95
99
|
somethingWentWrong: strings.somethingWentWrong,
|
|
96
100
|
retry: strings.retry,
|
|
97
101
|
showLess: strings.showLess,
|
|
98
|
-
showMore: strings.showMore
|
|
102
|
+
showMore: strings.showMore,
|
|
103
|
+
oddsLabel: strings.oddsLabel,
|
|
104
|
+
liveLabel: strings.liveLabel
|
|
99
105
|
};
|
|
100
106
|
}
|
|
101
107
|
function toClientOptions(cfg) {
|
|
@@ -132,16 +138,19 @@ function str(v) {
|
|
|
132
138
|
function num(v) {
|
|
133
139
|
return typeof v === "number" && Number.isFinite(v) ? String(v) : void 0;
|
|
134
140
|
}
|
|
135
|
-
function
|
|
136
|
-
if (!Array.isArray(
|
|
141
|
+
function readStatCells(raw) {
|
|
142
|
+
if (!Array.isArray(raw)) return [];
|
|
137
143
|
const cells = [];
|
|
138
|
-
for (const entry of
|
|
144
|
+
for (const entry of raw) {
|
|
139
145
|
const label = str(entry.label);
|
|
140
146
|
const value = num(entry.value) ?? str(entry.value);
|
|
141
147
|
if (label !== void 0 && value !== void 0) cells.push({ label, value });
|
|
142
148
|
}
|
|
143
149
|
return cells;
|
|
144
150
|
}
|
|
151
|
+
function readStats(card) {
|
|
152
|
+
return readStatCells(card.stats);
|
|
153
|
+
}
|
|
145
154
|
function toCardVM(card) {
|
|
146
155
|
const entityType = str(card.entityType)?.toLowerCase();
|
|
147
156
|
const kind = entityType === "player" ? "player" : entityType === "team" ? "team" : "generic";
|
|
@@ -167,6 +176,31 @@ function toChipVM(intent) {
|
|
|
167
176
|
function chipFromText(text) {
|
|
168
177
|
return { text };
|
|
169
178
|
}
|
|
179
|
+
function toInsightVM(insight) {
|
|
180
|
+
const market = str(insight.marketLabel) ?? str(insight.marketDeveloperName) ?? "";
|
|
181
|
+
const selection = [str(insight.selectionLabel), str(insight.line)].filter(Boolean).join(" ") || void 0;
|
|
182
|
+
const value = insight.odds?.value;
|
|
183
|
+
const odds = typeof value === "number" && Number.isFinite(value) ? value.toFixed(2) : void 0;
|
|
184
|
+
const home = str(insight.homeTeamName);
|
|
185
|
+
const away = str(insight.awayTeamName);
|
|
186
|
+
const fixture = home && away ? `${home} vs ${away}` : void 0;
|
|
187
|
+
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;
|
|
188
|
+
return {
|
|
189
|
+
id: str(insight.insightId) ?? `${str(insight.ruleId) ?? "insight"}:${num(insight.matchId) ?? ""}`,
|
|
190
|
+
market,
|
|
191
|
+
selection,
|
|
192
|
+
odds,
|
|
193
|
+
// The wire payload carries bookmakerId (not a name); render a name only if one is present.
|
|
194
|
+
bookmaker: str(insight.odds?.bookmakerName),
|
|
195
|
+
isLive: insight.odds?.isLive === true,
|
|
196
|
+
phase: str(insight.phase),
|
|
197
|
+
fixture,
|
|
198
|
+
scopes,
|
|
199
|
+
text: str(insight.text) ?? "",
|
|
200
|
+
stats: readStatCells(insight.stats),
|
|
201
|
+
disclaimer: str(insight.disclaimer)
|
|
202
|
+
};
|
|
203
|
+
}
|
|
170
204
|
|
|
171
205
|
// src/reducer.ts
|
|
172
206
|
var POPUP_MAX = 160;
|
|
@@ -193,6 +227,8 @@ function applyEvent(state, event) {
|
|
|
193
227
|
return { ...state, messages: mapActive(state, (t) => ({ ...t, cards: [...t.cards, toCardVM(event.data.card)] })) };
|
|
194
228
|
case "action_button":
|
|
195
229
|
return { ...state, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, actions: [...t.actions, toActionVM(event.data.action)] })) };
|
|
230
|
+
case "betting_insight":
|
|
231
|
+
return { ...state, messages: mapActive(state, (t) => ({ ...t, insights: [...t.insights, toInsightVM(event.data.insight)] })) };
|
|
196
232
|
case "intent_chip": {
|
|
197
233
|
const chip = toChipVM(event.data.intent);
|
|
198
234
|
return chip ? { ...state, starters: [...state.starters, chip] } : state;
|
|
@@ -210,6 +246,7 @@ function applyEvent(state, event) {
|
|
|
210
246
|
return { ...state, error: suppress ? state.error : event.data, isStreaming: false, status: null, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, done: true })) };
|
|
211
247
|
}
|
|
212
248
|
}
|
|
249
|
+
return state;
|
|
213
250
|
}
|
|
214
251
|
function reducer(state, action) {
|
|
215
252
|
switch (action.kind) {
|
|
@@ -219,11 +256,11 @@ function reducer(state, action) {
|
|
|
219
256
|
return { ...state, isOpen: false, isStreaming: false, status: null, actionsLoading: false };
|
|
220
257
|
case "USER_SENT": {
|
|
221
258
|
const user = { role: "user", id: action.userId, text: action.displayText ?? action.input.message };
|
|
222
|
-
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], done: false };
|
|
259
|
+
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], done: false };
|
|
223
260
|
return { ...state, messages: [...state.messages, user, assistant], isStreaming: true, status: null, actionsLoading: false, starters: [], error: null, lastUserInput: action.input, lastDisplayText: action.displayText ?? null };
|
|
224
261
|
}
|
|
225
262
|
case "GREETING_SENT": {
|
|
226
|
-
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], done: false, isGreeting: true };
|
|
263
|
+
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], done: false, isGreeting: true };
|
|
227
264
|
return { ...state, messages: [...state.messages, assistant], isStreaming: true, status: null, error: null };
|
|
228
265
|
}
|
|
229
266
|
case "DISMISS_POPUP":
|
|
@@ -345,7 +382,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
|
|
|
345
382
|
}
|
|
346
383
|
|
|
347
384
|
// src/components/ChatPanel.tsx
|
|
348
|
-
var
|
|
385
|
+
var import_react6 = require("react");
|
|
349
386
|
|
|
350
387
|
// src/components/ChatHeader.tsx
|
|
351
388
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
@@ -358,7 +395,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat" }) {
|
|
|
358
395
|
}
|
|
359
396
|
|
|
360
397
|
// src/components/MessageList.tsx
|
|
361
|
-
var
|
|
398
|
+
var import_react4 = require("react");
|
|
362
399
|
|
|
363
400
|
// src/components/Markdown.tsx
|
|
364
401
|
var import_react_markdown = __toESM(require("react-markdown"), 1);
|
|
@@ -411,37 +448,94 @@ function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMore
|
|
|
411
448
|
] });
|
|
412
449
|
}
|
|
413
450
|
|
|
414
|
-
// src/components/
|
|
451
|
+
// src/components/BettingInsightList.tsx
|
|
452
|
+
var import_react3 = require("react");
|
|
415
453
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
454
|
+
var MAX_ROWS = 3;
|
|
455
|
+
function StatRows({ stats, ui }) {
|
|
456
|
+
const [expanded, setExpanded] = (0, import_react3.useState)(false);
|
|
457
|
+
const shown = expanded ? stats : stats.slice(0, MAX_ROWS);
|
|
458
|
+
const overflow = stats.length - MAX_ROWS;
|
|
459
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ss-w-bi-rows", children: [
|
|
460
|
+
shown.map((s) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ss-w-bi-row", children: [
|
|
461
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ss-w-bi-row-lbl", children: s.label }),
|
|
462
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ss-w-bi-row-dots", "aria-hidden": "true" }),
|
|
463
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ss-w-bi-row-num", children: s.value })
|
|
464
|
+
] }, `${s.label}:${s.value}`)),
|
|
465
|
+
overflow > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.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
|
|
466
|
+
] });
|
|
467
|
+
}
|
|
468
|
+
function ScopedStats({ scopes, ui }) {
|
|
469
|
+
const [active, setActive] = (0, import_react3.useState)(0);
|
|
470
|
+
const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
|
|
471
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ss-w-bi-scoped", children: [
|
|
472
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "ss-w-bi-scopes", role: "group", children: scopes.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "ss-w-bi-chip", "aria-pressed": i === active, onClick: () => setActive(i), children: s.label }, s.key)) }),
|
|
473
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StatRows, { stats: current.stats, ui }, current.key)
|
|
474
|
+
] });
|
|
475
|
+
}
|
|
476
|
+
function InsightCard({ it, ui }) {
|
|
477
|
+
const live = it.isLive || it.phase === "live";
|
|
478
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ss-w-binsight", children: [
|
|
479
|
+
live || it.fixture ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ss-w-bi-status", children: [
|
|
480
|
+
live ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "ss-w-bi-live", children: [
|
|
481
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
|
|
482
|
+
ui?.liveLabel ?? "Live"
|
|
483
|
+
] }) : null,
|
|
484
|
+
it.fixture ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ss-w-bi-fixture", children: it.fixture }) : null
|
|
485
|
+
] }) : null,
|
|
486
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ss-w-bi-head", children: [
|
|
487
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "ss-w-bi-mkt", children: [
|
|
488
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ss-w-bi-market", children: it.market }),
|
|
489
|
+
it.selection ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ss-w-bi-sel", children: it.selection }) : null
|
|
490
|
+
] }),
|
|
491
|
+
it.odds ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "ss-w-bi-odds", children: [
|
|
492
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ss-w-bi-odds-cap", children: ui?.oddsLabel ?? "Odds" }),
|
|
493
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("b", { children: it.odds }),
|
|
494
|
+
it.bookmaker ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ss-w-bi-book", children: it.bookmaker }) : null
|
|
495
|
+
] }) : null
|
|
496
|
+
] }),
|
|
497
|
+
it.text ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "ss-w-bi-text", children: it.text }) : null,
|
|
498
|
+
it.scopes && it.scopes.length >= 2 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ScopedStats, { scopes: it.scopes, ui }) : it.stats.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StatRows, { stats: it.stats, ui }) : null,
|
|
499
|
+
it.disclaimer ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "ss-w-bi-disc", children: it.disclaimer }) : null
|
|
500
|
+
] });
|
|
501
|
+
}
|
|
502
|
+
function BettingInsightList({ insights, ui }) {
|
|
503
|
+
if (!insights || insights.length === 0) return null;
|
|
504
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "ss-w-binsights", children: insights.map((it) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(InsightCard, { it, ui }, it.id)) });
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// src/components/ActionButtons.tsx
|
|
508
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
416
509
|
function ActionButtons({ actions, loading, disabled, onAct }) {
|
|
417
510
|
if (actions.length === 0 && !loading) return null;
|
|
418
|
-
return /* @__PURE__ */ (0,
|
|
419
|
-
actions.map((a, i) => /* @__PURE__ */ (0,
|
|
420
|
-
actions.length === 0 && loading ? /* @__PURE__ */ (0,
|
|
421
|
-
/* @__PURE__ */ (0,
|
|
422
|
-
/* @__PURE__ */ (0,
|
|
511
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "ss-w-actions", children: [
|
|
512
|
+
actions.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
|
|
513
|
+
actions.length === 0 && loading ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
514
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "ss-w-abtn-shimmer" }),
|
|
515
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "ss-w-abtn-shimmer" })
|
|
423
516
|
] }) : null
|
|
424
517
|
] });
|
|
425
518
|
}
|
|
426
519
|
|
|
427
520
|
// src/components/ChatMessage.tsx
|
|
428
|
-
var
|
|
521
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
429
522
|
function ChatMessage({ message, disabled, onAct, ui }) {
|
|
430
523
|
if (message.role === "user") {
|
|
431
|
-
return /* @__PURE__ */ (0,
|
|
524
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
|
|
432
525
|
}
|
|
433
|
-
return /* @__PURE__ */ (0,
|
|
434
|
-
message.text ? /* @__PURE__ */ (0,
|
|
435
|
-
/* @__PURE__ */ (0,
|
|
436
|
-
/* @__PURE__ */ (0,
|
|
526
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
|
|
527
|
+
message.text ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Markdown, { text: message.text }) : null,
|
|
528
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
|
|
529
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(BettingInsightList, { insights: message.insights, ui }),
|
|
530
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
|
|
437
531
|
] });
|
|
438
532
|
}
|
|
439
533
|
|
|
440
534
|
// src/components/IntentChips.tsx
|
|
441
|
-
var
|
|
535
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
442
536
|
function IntentChips({ chips, onPick }) {
|
|
443
537
|
if (chips.length === 0) return null;
|
|
444
|
-
return /* @__PURE__ */ (0,
|
|
538
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
|
|
445
539
|
}
|
|
446
540
|
|
|
447
541
|
// src/scroll.ts
|
|
@@ -451,17 +545,17 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
|
|
|
451
545
|
}
|
|
452
546
|
|
|
453
547
|
// src/components/MessageList.tsx
|
|
454
|
-
var
|
|
548
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
455
549
|
function MessageList({ controller, ui }) {
|
|
456
550
|
const { state, send } = controller;
|
|
457
|
-
const listRef = (0,
|
|
458
|
-
const endRef = (0,
|
|
459
|
-
const pinnedRef = (0,
|
|
460
|
-
(0,
|
|
551
|
+
const listRef = (0, import_react4.useRef)(null);
|
|
552
|
+
const endRef = (0, import_react4.useRef)(null);
|
|
553
|
+
const pinnedRef = (0, import_react4.useRef)(true);
|
|
554
|
+
(0, import_react4.useEffect)(() => {
|
|
461
555
|
if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
|
|
462
556
|
}, [state.messages, state.status]);
|
|
463
557
|
const empty = state.messages.length === 0;
|
|
464
|
-
return /* @__PURE__ */ (0,
|
|
558
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
465
559
|
"div",
|
|
466
560
|
{
|
|
467
561
|
className: "ss-w-list",
|
|
@@ -471,7 +565,7 @@ function MessageList({ controller, ui }) {
|
|
|
471
565
|
if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
|
|
472
566
|
},
|
|
473
567
|
children: [
|
|
474
|
-
state.messages.map((m) => /* @__PURE__ */ (0,
|
|
568
|
+
state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
475
569
|
ChatMessage,
|
|
476
570
|
{
|
|
477
571
|
message: m,
|
|
@@ -481,25 +575,25 @@ function MessageList({ controller, ui }) {
|
|
|
481
575
|
},
|
|
482
576
|
m.id
|
|
483
577
|
)),
|
|
484
|
-
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0,
|
|
578
|
+
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
|
|
485
579
|
} }) : null,
|
|
486
|
-
state.status ? /* @__PURE__ */ (0,
|
|
487
|
-
state.error ? /* @__PURE__ */ (0,
|
|
488
|
-
/* @__PURE__ */ (0,
|
|
489
|
-
/* @__PURE__ */ (0,
|
|
580
|
+
state.status ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "ss-w-status", children: state.status }) : null,
|
|
581
|
+
state.error ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "ss-w-error", role: "alert", children: [
|
|
582
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
|
|
583
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
|
|
490
584
|
] }) : null,
|
|
491
|
-
empty ? /* @__PURE__ */ (0,
|
|
492
|
-
/* @__PURE__ */ (0,
|
|
585
|
+
empty ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
|
|
586
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { ref: endRef })
|
|
493
587
|
]
|
|
494
588
|
}
|
|
495
589
|
);
|
|
496
590
|
}
|
|
497
591
|
|
|
498
592
|
// src/components/ChatInput.tsx
|
|
499
|
-
var
|
|
500
|
-
var
|
|
593
|
+
var import_react5 = require("react");
|
|
594
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
501
595
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
502
|
-
const [value, setValue] = (0,
|
|
596
|
+
const [value, setValue] = (0, import_react5.useState)("");
|
|
503
597
|
const submit = () => {
|
|
504
598
|
const t = value.trim();
|
|
505
599
|
if (!t) return;
|
|
@@ -512,18 +606,18 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
|
|
|
512
606
|
submit();
|
|
513
607
|
}
|
|
514
608
|
};
|
|
515
|
-
return /* @__PURE__ */ (0,
|
|
516
|
-
/* @__PURE__ */ (0,
|
|
517
|
-
streaming ? /* @__PURE__ */ (0,
|
|
609
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ss-w-input", children: [
|
|
610
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
|
|
611
|
+
streaming ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SendIcon, {}) })
|
|
518
612
|
] });
|
|
519
613
|
}
|
|
520
614
|
|
|
521
615
|
// src/components/ChatPanel.tsx
|
|
522
|
-
var
|
|
616
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
523
617
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
524
618
|
function useIsModal() {
|
|
525
|
-
const [modal, setModal] = (0,
|
|
526
|
-
(0,
|
|
619
|
+
const [modal, setModal] = (0, import_react6.useState)(false);
|
|
620
|
+
(0, import_react6.useEffect)(() => {
|
|
527
621
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
528
622
|
const mql = window.matchMedia(FULLSCREEN_QUERY);
|
|
529
623
|
const sync = () => setModal(mql.matches);
|
|
@@ -536,17 +630,17 @@ function useIsModal() {
|
|
|
536
630
|
function ChatPanel({ controller, ui }) {
|
|
537
631
|
const { state, close, stop, send } = controller;
|
|
538
632
|
const isModal = useIsModal();
|
|
539
|
-
(0,
|
|
633
|
+
(0, import_react6.useEffect)(() => {
|
|
540
634
|
const onKey = (e) => {
|
|
541
635
|
if (e.key === "Escape") close();
|
|
542
636
|
};
|
|
543
637
|
window.addEventListener("keydown", onKey);
|
|
544
638
|
return () => window.removeEventListener("keydown", onKey);
|
|
545
639
|
}, [close]);
|
|
546
|
-
return /* @__PURE__ */ (0,
|
|
547
|
-
/* @__PURE__ */ (0,
|
|
548
|
-
/* @__PURE__ */ (0,
|
|
549
|
-
/* @__PURE__ */ (0,
|
|
640
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
641
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
|
|
642
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(MessageList, { controller, ui }),
|
|
643
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
|
|
550
644
|
] });
|
|
551
645
|
}
|
|
552
646
|
|
|
@@ -645,6 +739,36 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
645
739
|
.ss-w-cards-more:hover:not(:disabled) { text-decoration: underline; }
|
|
646
740
|
.ss-w-cards-more:disabled { opacity: .5; cursor: default; }
|
|
647
741
|
|
|
742
|
+
/* betting insights */
|
|
743
|
+
.ss-w-binsights { display: flex; flex-direction: column; gap: 7px; }
|
|
744
|
+
.ss-w-binsight { 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; }
|
|
745
|
+
.ss-w-bi-status { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
746
|
+
.ss-w-bi-fixture { font-size: 11px; color: var(--ss-w-ink); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
747
|
+
.ss-w-bi-live { font-size: 8.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: #fff; background: #c0392b; border-radius: 4px; padding: 1px 5px; display: inline-flex; align-items: center; gap: 4px; flex: none; }
|
|
748
|
+
.ss-w-bi-live-dot { width: 5px; height: 5px; border-radius: 50%; background: #fff; }
|
|
749
|
+
.ss-w-bi-head { display: flex; align-items: flex-start; gap: 10px; }
|
|
750
|
+
.ss-w-bi-mkt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
|
|
751
|
+
.ss-w-bi-market { font-size: 13px; font-weight: 660; line-height: 1.3; color: var(--ss-w-ink); }
|
|
752
|
+
.ss-w-bi-sel { font-size: 11px; line-height: 1.3; color: var(--ss-w-faint); }
|
|
753
|
+
.ss-w-bi-odds { margin-left: auto; flex: none; display: flex; flex-direction: column; align-items: center; justify-content: center; background: color-mix(in srgb, var(--ss-w-accent) 12%, var(--ss-w-panel)); border: 1px solid color-mix(in srgb, var(--ss-w-accent) 24%, var(--ss-w-line)); border-radius: 8px; padding: 3px 9px; min-width: 56px; }
|
|
754
|
+
.ss-w-bi-odds-cap { font-size: 8px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--ss-w-faint); }
|
|
755
|
+
.ss-w-bi-odds b { font-size: 15px; font-family: ui-monospace, monospace; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ss-w-accent); line-height: 1.15; }
|
|
756
|
+
.ss-w-bi-book { font-size: 9.5px; color: var(--ss-w-faint); }
|
|
757
|
+
.ss-w-bi-text { font-size: 12px; color: var(--ss-w-ink); }
|
|
758
|
+
.ss-w-bi-rows { display: flex; flex-direction: column; }
|
|
759
|
+
.ss-w-bi-row { display: flex; align-items: baseline; gap: 8px; padding: 4px 0; border-top: 1px solid var(--ss-w-line); }
|
|
760
|
+
.ss-w-bi-row:first-child { border-top: none; }
|
|
761
|
+
.ss-w-bi-row-lbl { font-size: 11px; color: var(--ss-w-faint); min-width: 0; }
|
|
762
|
+
.ss-w-bi-row-dots { flex: 1; border-bottom: 1px dotted var(--ss-w-line); transform: translateY(-3px); min-width: 12px; }
|
|
763
|
+
.ss-w-bi-row-num { font-family: ui-monospace, monospace; font-weight: 700; font-variant-numeric: tabular-nums; font-size: 12.5px; flex: none; color: var(--ss-w-ink); }
|
|
764
|
+
.ss-w-bi-more { align-self: flex-start; background: none; border: none; color: var(--ss-w-accent); font: inherit; font-size: 11px; font-weight: 560; padding: 2px 0; cursor: pointer; }
|
|
765
|
+
.ss-w-bi-more:hover { text-decoration: underline; }
|
|
766
|
+
.ss-w-bi-disc { font-size: 10px; font-style: italic; color: var(--ss-w-faint); }
|
|
767
|
+
.ss-w-bi-scoped { display: flex; flex-direction: column; gap: 6px; }
|
|
768
|
+
.ss-w-bi-scopes { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
|
|
769
|
+
.ss-w-bi-chip { font-size: 11px; font-weight: 550; border: 1px solid var(--ss-w-line); border-radius: 14px; padding: 3px 10px; color: var(--ss-w-ink); background: var(--ss-w-bubble); cursor: pointer; }
|
|
770
|
+
.ss-w-bi-chip[aria-pressed="true"] { color: #fff; background: var(--ss-w-accent); border-color: var(--ss-w-accent); }
|
|
771
|
+
|
|
648
772
|
/* action buttons */
|
|
649
773
|
.ss-w-actions { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
|
|
650
774
|
.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; }
|
|
@@ -674,7 +798,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
674
798
|
`;
|
|
675
799
|
|
|
676
800
|
// src/StatsWidget.tsx
|
|
677
|
-
var
|
|
801
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
678
802
|
function buildClient(props) {
|
|
679
803
|
try {
|
|
680
804
|
if ("client" in props && props.client) {
|
|
@@ -699,18 +823,18 @@ function injectStyles(root) {
|
|
|
699
823
|
function StatsWidget(props) {
|
|
700
824
|
const injectedClient = "client" in props ? props.client : void 0;
|
|
701
825
|
const cfg = props.config;
|
|
702
|
-
const built = (0,
|
|
703
|
-
const ui = (0,
|
|
704
|
-
const rootRef = (0,
|
|
826
|
+
const built = (0, import_react7.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
|
|
827
|
+
const ui = (0, import_react7.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
|
|
828
|
+
const rootRef = (0, import_react7.useRef)(null);
|
|
705
829
|
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
|
|
706
|
-
(0,
|
|
830
|
+
(0, import_react7.useEffect)(() => {
|
|
707
831
|
if (!built || !ui.injectStyles) return;
|
|
708
832
|
const node = rootRef.current?.getRootNode();
|
|
709
833
|
if (node) injectStyles(node);
|
|
710
834
|
}, [built, ui.injectStyles]);
|
|
711
835
|
if (!built) return null;
|
|
712
836
|
const attrs = themeAttrs(ui);
|
|
713
|
-
return /* @__PURE__ */ (0,
|
|
837
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.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_runtime14.jsx)(ChatPanel, { controller, ui }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
714
838
|
ChatFab,
|
|
715
839
|
{
|
|
716
840
|
label: ui.launcherLabel,
|