@sensiblestats/widget-react 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/dist/index.cjs +276 -97
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +270 -91
- package/dist/index.js.map +1 -1
- package/dist/styles.css +8 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/StatsWidget.tsx
|
|
2
|
-
import { useEffect as
|
|
2
|
+
import { useEffect as useEffect5, useMemo as useMemo2, useRef as useRef3 } from "react";
|
|
3
3
|
import { StatsWidgetClient } from "@sensiblestats/widget-sdk";
|
|
4
4
|
|
|
5
5
|
// src/i18n.ts
|
|
@@ -17,7 +17,16 @@ var STRINGS = {
|
|
|
17
17
|
showLess: "Show less",
|
|
18
18
|
showMore: (n) => `Show ${n} more`,
|
|
19
19
|
oddsLabel: "Odds",
|
|
20
|
-
liveLabel: "Live"
|
|
20
|
+
liveLabel: "Live",
|
|
21
|
+
addToSlip: "Add to slip",
|
|
22
|
+
inSlip: "In slip",
|
|
23
|
+
slipAdded: (n) => n === void 0 ? "Added to slip" : `Added to slip (${n} selection${n === 1 ? "" : "s"})`,
|
|
24
|
+
slipDuplicate: "Already in your slip",
|
|
25
|
+
slipPriceChanged: (from, to) => `Added at ${to} (was ${from})`,
|
|
26
|
+
slipPriceNotAccepted: (from, to) => `Price moved to ${to} (was ${from}). Tap to add.`,
|
|
27
|
+
slipLoginRequired: "Sign in to add to your slip",
|
|
28
|
+
slipSuspended: "Market suspended",
|
|
29
|
+
slipFailed: "Couldn't add to slip. Try again."
|
|
21
30
|
},
|
|
22
31
|
tr: {
|
|
23
32
|
launcherLabel: "SensibleStats ile sohbet et",
|
|
@@ -32,7 +41,16 @@ var STRINGS = {
|
|
|
32
41
|
showLess: "Daha az g\xF6ster",
|
|
33
42
|
showMore: (n) => `${n} tane daha g\xF6ster`,
|
|
34
43
|
oddsLabel: "Oran",
|
|
35
|
-
liveLabel: "Canl\u0131"
|
|
44
|
+
liveLabel: "Canl\u0131",
|
|
45
|
+
addToSlip: "Kupona ekle",
|
|
46
|
+
inSlip: "Kuponda",
|
|
47
|
+
slipAdded: (n) => n === void 0 ? "Kupona eklendi" : `Kupona eklendi (${n} se\xE7im)`,
|
|
48
|
+
slipDuplicate: "Bu se\xE7im kuponunuzda zaten var",
|
|
49
|
+
slipPriceChanged: (from, to) => `${to} oran\u0131yla eklendi (\xF6nceki ${from})`,
|
|
50
|
+
slipPriceNotAccepted: (from, to) => `Oran ${to} oldu (\xF6nceki ${from}). Eklemek i\xE7in dokunun.`,
|
|
51
|
+
slipLoginRequired: "Kupona eklemek i\xE7in giri\u015F yap\u0131n",
|
|
52
|
+
slipSuspended: "Bu market ask\u0131ya al\u0131nd\u0131",
|
|
53
|
+
slipFailed: "Kupona eklenemedi. Tekrar deneyin."
|
|
36
54
|
}
|
|
37
55
|
};
|
|
38
56
|
function resolveLocaleStrings(locale) {
|
|
@@ -64,7 +82,16 @@ function normalizeUi(cfg) {
|
|
|
64
82
|
showLess: strings.showLess,
|
|
65
83
|
showMore: strings.showMore,
|
|
66
84
|
oddsLabel: strings.oddsLabel,
|
|
67
|
-
liveLabel: strings.liveLabel
|
|
85
|
+
liveLabel: strings.liveLabel,
|
|
86
|
+
addToSlip: strings.addToSlip,
|
|
87
|
+
inSlip: strings.inSlip,
|
|
88
|
+
slipAdded: strings.slipAdded,
|
|
89
|
+
slipDuplicate: strings.slipDuplicate,
|
|
90
|
+
slipPriceChanged: strings.slipPriceChanged,
|
|
91
|
+
slipPriceNotAccepted: strings.slipPriceNotAccepted,
|
|
92
|
+
slipLoginRequired: strings.slipLoginRequired,
|
|
93
|
+
slipSuspended: strings.slipSuspended,
|
|
94
|
+
slipFailed: strings.slipFailed
|
|
68
95
|
};
|
|
69
96
|
}
|
|
70
97
|
function toClientOptions(cfg) {
|
|
@@ -144,6 +171,8 @@ function toInsightVM(insight) {
|
|
|
144
171
|
const selection = [str(insight.selectionLabel), str(insight.line)].filter(Boolean).join(" ") || void 0;
|
|
145
172
|
const value = insight.odds?.value;
|
|
146
173
|
const odds = typeof value === "number" && Number.isFinite(value) ? value.toFixed(2) : void 0;
|
|
174
|
+
const oddsValue = insight.odds?.value;
|
|
175
|
+
const matchOddsId = typeof insight.odds?.matchOddsId === "number" ? insight.odds.matchOddsId : void 0;
|
|
147
176
|
const home = str(insight.homeTeamName);
|
|
148
177
|
const away = str(insight.awayTeamName);
|
|
149
178
|
const fixture = home && away ? `${home} vs ${away}` : void 0;
|
|
@@ -163,14 +192,31 @@ function toInsightVM(insight) {
|
|
|
163
192
|
scopes,
|
|
164
193
|
text: str(insight.text) ?? "",
|
|
165
194
|
stats: readStatCells(insight.stats),
|
|
166
|
-
disclaimer: str(insight.disclaimer)
|
|
195
|
+
disclaimer: str(insight.disclaimer),
|
|
196
|
+
matchId: typeof insight.matchId === "number" ? insight.matchId : 0,
|
|
197
|
+
matchOddsId,
|
|
198
|
+
addToSlipEnabled: insight.addToSlipEnabled === true,
|
|
199
|
+
oddsDecimal: typeof oddsValue === "number" && Number.isFinite(oddsValue) ? oddsValue : void 0,
|
|
200
|
+
quotedAtUtc: str(insight.odds?.quotedAtUtc)
|
|
167
201
|
};
|
|
168
202
|
}
|
|
203
|
+
function toSlipSelection(vm, conversationId) {
|
|
204
|
+
if (vm.matchOddsId === void 0 || vm.oddsDecimal === void 0) return null;
|
|
205
|
+
const selection = {
|
|
206
|
+
matchOddsId: vm.matchOddsId,
|
|
207
|
+
matchId: vm.matchId,
|
|
208
|
+
oddsDecimal: vm.oddsDecimal
|
|
209
|
+
};
|
|
210
|
+
if (vm.quotedAtUtc !== void 0) selection.quotedAtUtc = vm.quotedAtUtc;
|
|
211
|
+
if (vm.id !== void 0) selection.insightId = vm.id;
|
|
212
|
+
if (conversationId !== void 0) selection.conversationId = conversationId;
|
|
213
|
+
return selection;
|
|
214
|
+
}
|
|
169
215
|
|
|
170
216
|
// src/reducer.ts
|
|
171
217
|
var POPUP_MAX = 160;
|
|
172
218
|
function initialState(starters) {
|
|
173
|
-
return { isOpen: false, messages: [], isStreaming: false, status: null, actionsLoading: false, starters, error: null, lastUserInput: null, lastDisplayText: null, popupText: null };
|
|
219
|
+
return { isOpen: false, messages: [], isStreaming: false, status: null, actionsLoading: false, starters, error: null, lastUserInput: null, lastDisplayText: null, popupText: null, conversationId: null };
|
|
174
220
|
}
|
|
175
221
|
function activeTurn(state) {
|
|
176
222
|
const last = state.messages[state.messages.length - 1];
|
|
@@ -187,9 +233,12 @@ function applyEvent(state, event) {
|
|
|
187
233
|
return { ...state, status: event.data.message };
|
|
188
234
|
case "answer":
|
|
189
235
|
return { ...state, messages: mapActive(state, (t) => ({ ...t, text: event.data.text })) };
|
|
190
|
-
case "entity_card":
|
|
236
|
+
case "entity_card": {
|
|
191
237
|
if (event.data.card.isUsed === false) return state;
|
|
192
|
-
|
|
238
|
+
const card = toCardVM(event.data.card);
|
|
239
|
+
if (card.kind === "generic") return state;
|
|
240
|
+
return { ...state, messages: mapActive(state, (t) => ({ ...t, cards: [...t.cards, card] })) };
|
|
241
|
+
}
|
|
193
242
|
case "action_button":
|
|
194
243
|
return { ...state, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, actions: [...t.actions, toActionVM(event.data.action)] })) };
|
|
195
244
|
case "betting_insight":
|
|
@@ -204,7 +253,15 @@ function applyEvent(state, event) {
|
|
|
204
253
|
const active = activeTurn(state);
|
|
205
254
|
const text = active?.text.trim() ?? "";
|
|
206
255
|
const popupText = active?.isGreeting && !state.isOpen && text ? text.slice(0, POPUP_MAX) : state.popupText;
|
|
207
|
-
return {
|
|
256
|
+
return {
|
|
257
|
+
...state,
|
|
258
|
+
isStreaming: false,
|
|
259
|
+
status: null,
|
|
260
|
+
actionsLoading: false,
|
|
261
|
+
popupText,
|
|
262
|
+
conversationId: event.data.conversationId ?? state.conversationId,
|
|
263
|
+
messages: mapActive(state, (t) => ({ ...t, done: true }))
|
|
264
|
+
};
|
|
208
265
|
}
|
|
209
266
|
case "error": {
|
|
210
267
|
const suppress = activeTurn(state)?.isGreeting === true;
|
|
@@ -347,7 +404,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
|
|
|
347
404
|
}
|
|
348
405
|
|
|
349
406
|
// src/components/ChatPanel.tsx
|
|
350
|
-
import { useEffect as
|
|
407
|
+
import { useEffect as useEffect4, useState as useState5 } from "react";
|
|
351
408
|
|
|
352
409
|
// src/components/ChatHeader.tsx
|
|
353
410
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -360,7 +417,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat" }) {
|
|
|
360
417
|
}
|
|
361
418
|
|
|
362
419
|
// src/components/MessageList.tsx
|
|
363
|
-
import { useEffect as
|
|
420
|
+
import { useEffect as useEffect3, useRef as useRef2 } from "react";
|
|
364
421
|
|
|
365
422
|
// src/components/Markdown.tsx
|
|
366
423
|
import ReactMarkdown from "react-markdown";
|
|
@@ -414,94 +471,194 @@ function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMore
|
|
|
414
471
|
}
|
|
415
472
|
|
|
416
473
|
// src/components/BettingInsightList.tsx
|
|
417
|
-
import { useState as
|
|
418
|
-
|
|
474
|
+
import { useState as useState3 } from "react";
|
|
475
|
+
|
|
476
|
+
// src/components/AddToSlipButton.tsx
|
|
477
|
+
import { useCallback as useCallback2, useState as useState2 } from "react";
|
|
478
|
+
import { isAddToSlipResult } from "@sensiblestats/widget-sdk";
|
|
479
|
+
|
|
480
|
+
// src/slip.ts
|
|
481
|
+
var ADD_TO_SLIP_TIMEOUT_MS = 1e4;
|
|
482
|
+
function wasAdded(result) {
|
|
483
|
+
if (!result) return true;
|
|
484
|
+
if (result.status === "price_changed") return result.accepted;
|
|
485
|
+
return result.status === "added";
|
|
486
|
+
}
|
|
487
|
+
function isTerminal(result) {
|
|
488
|
+
if (!result) return true;
|
|
489
|
+
if (result.status === "price_changed") return result.accepted;
|
|
490
|
+
return result.status === "added" || result.status === "duplicate";
|
|
491
|
+
}
|
|
492
|
+
function resultToToast(result, shownOdds, ui) {
|
|
493
|
+
if (!result) return { kind: "success", text: ui.slipAdded(void 0) };
|
|
494
|
+
switch (result.status) {
|
|
495
|
+
case "added":
|
|
496
|
+
return { kind: "success", text: ui.slipAdded(result.slip?.selectionCount) };
|
|
497
|
+
case "duplicate":
|
|
498
|
+
return { kind: "success", text: ui.slipDuplicate };
|
|
499
|
+
case "price_changed":
|
|
500
|
+
return result.accepted ? { kind: "success", text: ui.slipPriceChanged(shownOdds, result.newOddsDecimal.toFixed(2)) } : { kind: "error", text: ui.slipPriceNotAccepted(shownOdds, result.newOddsDecimal.toFixed(2)) };
|
|
501
|
+
case "rejected":
|
|
502
|
+
if (result.reason === "login_required") return { kind: "error", text: ui.slipLoginRequired };
|
|
503
|
+
if (result.reason === "suspended") return { kind: "error", text: ui.slipSuspended };
|
|
504
|
+
return { kind: "error", text: result.message ?? ui.slipFailed };
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// src/components/Toast.tsx
|
|
509
|
+
import { useEffect as useEffect2 } from "react";
|
|
510
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
511
|
+
var DISMISS_MS = 4e3;
|
|
512
|
+
function Toast({ toast, onDismiss }) {
|
|
513
|
+
useEffect2(() => {
|
|
514
|
+
if (!toast) return;
|
|
515
|
+
const timer = setTimeout(onDismiss, DISMISS_MS);
|
|
516
|
+
return () => clearTimeout(timer);
|
|
517
|
+
}, [toast, onDismiss]);
|
|
518
|
+
if (!toast) return null;
|
|
519
|
+
return /* @__PURE__ */ jsx7("div", { className: `ss-w-toast ss-w-toast-${toast.kind}`, role: "status", "aria-live": "polite", children: toast.text });
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// src/components/AddToSlipButton.tsx
|
|
523
|
+
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
524
|
+
function withTimeout(value) {
|
|
525
|
+
return Promise.race([
|
|
526
|
+
value,
|
|
527
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("add-to-slip timed out")), ADD_TO_SLIP_TIMEOUT_MS))
|
|
528
|
+
]);
|
|
529
|
+
}
|
|
530
|
+
function AddToSlipButton({ it, slip, ui }) {
|
|
531
|
+
const [pending, setPending] = useState2(false);
|
|
532
|
+
const [settled, setSettled] = useState2(false);
|
|
533
|
+
const [toast, setToast] = useState2(null);
|
|
534
|
+
const dismiss = useCallback2(() => setToast(null), []);
|
|
535
|
+
const handler = slip.onAddToSlip;
|
|
536
|
+
const selection = it.addToSlipEnabled ? toSlipSelection(it, slip.conversationId) : null;
|
|
537
|
+
if (!ui || !handler || !selection) return null;
|
|
538
|
+
const onClick = async () => {
|
|
539
|
+
setPending(true);
|
|
540
|
+
try {
|
|
541
|
+
const raw = await withTimeout(Promise.resolve(handler(selection)));
|
|
542
|
+
if (raw != null && !isAddToSlipResult(raw)) throw new Error("malformed add-to-slip result");
|
|
543
|
+
const result = raw ?? void 0;
|
|
544
|
+
setToast(resultToToast(result, it.odds ?? selection.oddsDecimal.toFixed(2), ui));
|
|
545
|
+
setSettled(isTerminal(result));
|
|
546
|
+
if (wasAdded(result) && slip.onMarketClicked) {
|
|
547
|
+
try {
|
|
548
|
+
slip.onMarketClicked(it.market);
|
|
549
|
+
} catch {
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
} catch {
|
|
553
|
+
setToast({ kind: "error", text: ui.slipFailed });
|
|
554
|
+
} finally {
|
|
555
|
+
setPending(false);
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
return /* @__PURE__ */ jsxs5("div", { className: "ss-w-slip", children: [
|
|
559
|
+
/* @__PURE__ */ jsx8(
|
|
560
|
+
"button",
|
|
561
|
+
{
|
|
562
|
+
type: "button",
|
|
563
|
+
className: "ss-w-slip-btn",
|
|
564
|
+
disabled: pending || settled,
|
|
565
|
+
onClick,
|
|
566
|
+
children: settled ? ui.inSlip : ui.addToSlip
|
|
567
|
+
}
|
|
568
|
+
),
|
|
569
|
+
/* @__PURE__ */ jsx8(Toast, { toast, onDismiss: dismiss })
|
|
570
|
+
] });
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// src/components/BettingInsightList.tsx
|
|
574
|
+
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
419
575
|
var MAX_ROWS = 3;
|
|
420
576
|
function StatRows({ stats, ui }) {
|
|
421
|
-
const [expanded, setExpanded] =
|
|
577
|
+
const [expanded, setExpanded] = useState3(false);
|
|
422
578
|
const shown = expanded ? stats : stats.slice(0, MAX_ROWS);
|
|
423
579
|
const overflow = stats.length - MAX_ROWS;
|
|
424
|
-
return /* @__PURE__ */
|
|
425
|
-
shown.map((s) => /* @__PURE__ */
|
|
426
|
-
/* @__PURE__ */
|
|
427
|
-
/* @__PURE__ */
|
|
428
|
-
/* @__PURE__ */
|
|
580
|
+
return /* @__PURE__ */ jsxs6("div", { className: "ss-w-bi-rows", children: [
|
|
581
|
+
shown.map((s) => /* @__PURE__ */ jsxs6("div", { className: "ss-w-bi-row", children: [
|
|
582
|
+
/* @__PURE__ */ jsx9("span", { className: "ss-w-bi-row-lbl", children: s.label }),
|
|
583
|
+
/* @__PURE__ */ jsx9("span", { className: "ss-w-bi-row-dots", "aria-hidden": "true" }),
|
|
584
|
+
/* @__PURE__ */ jsx9("span", { className: "ss-w-bi-row-num", children: s.value })
|
|
429
585
|
] }, `${s.label}:${s.value}`)),
|
|
430
|
-
overflow > 0 ? /* @__PURE__ */
|
|
586
|
+
overflow > 0 ? /* @__PURE__ */ jsx9("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
|
|
431
587
|
] });
|
|
432
588
|
}
|
|
433
589
|
function ScopedStats({ scopes, ui }) {
|
|
434
|
-
const [active, setActive] =
|
|
590
|
+
const [active, setActive] = useState3(0);
|
|
435
591
|
const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
|
|
436
|
-
return /* @__PURE__ */
|
|
437
|
-
/* @__PURE__ */
|
|
438
|
-
/* @__PURE__ */
|
|
592
|
+
return /* @__PURE__ */ jsxs6("div", { className: "ss-w-bi-scoped", children: [
|
|
593
|
+
/* @__PURE__ */ jsx9("div", { className: "ss-w-bi-scopes", role: "group", children: scopes.map((s, i) => /* @__PURE__ */ jsx9("button", { type: "button", className: "ss-w-bi-chip", "aria-pressed": i === active, onClick: () => setActive(i), children: s.label }, s.key)) }),
|
|
594
|
+
/* @__PURE__ */ jsx9(StatRows, { stats: current.stats, ui }, current.key)
|
|
439
595
|
] });
|
|
440
596
|
}
|
|
441
|
-
function InsightCard({ it, ui }) {
|
|
597
|
+
function InsightCard({ it, ui, slip }) {
|
|
442
598
|
const live = it.isLive || it.phase === "live";
|
|
443
|
-
return /* @__PURE__ */
|
|
444
|
-
live || it.fixture || it.competition ? /* @__PURE__ */
|
|
445
|
-
live ? /* @__PURE__ */
|
|
446
|
-
/* @__PURE__ */
|
|
599
|
+
return /* @__PURE__ */ jsxs6("div", { className: "ss-w-binsight", children: [
|
|
600
|
+
live || it.fixture || it.competition ? /* @__PURE__ */ jsxs6("div", { className: "ss-w-bi-status", children: [
|
|
601
|
+
live ? /* @__PURE__ */ jsxs6("span", { className: "ss-w-bi-live", children: [
|
|
602
|
+
/* @__PURE__ */ jsx9("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
|
|
447
603
|
ui?.liveLabel ?? "Live"
|
|
448
604
|
] }) : null,
|
|
449
|
-
it.fixture ? /* @__PURE__ */
|
|
450
|
-
it.competition ? /* @__PURE__ */
|
|
605
|
+
it.fixture ? /* @__PURE__ */ jsx9("span", { className: "ss-w-bi-fixture", children: it.fixture }) : null,
|
|
606
|
+
it.competition ? /* @__PURE__ */ jsx9("span", { className: "ss-w-bi-comp", children: it.competition }) : null
|
|
451
607
|
] }) : null,
|
|
452
|
-
/* @__PURE__ */
|
|
453
|
-
/* @__PURE__ */
|
|
454
|
-
/* @__PURE__ */
|
|
455
|
-
it.selection ? /* @__PURE__ */
|
|
608
|
+
/* @__PURE__ */ jsxs6("div", { className: "ss-w-bi-head", children: [
|
|
609
|
+
/* @__PURE__ */ jsxs6("span", { className: "ss-w-bi-mkt", children: [
|
|
610
|
+
/* @__PURE__ */ jsx9("span", { className: "ss-w-bi-market", children: it.market }),
|
|
611
|
+
it.selection ? /* @__PURE__ */ jsx9("span", { className: "ss-w-bi-sel", children: it.selection }) : null
|
|
456
612
|
] }),
|
|
457
|
-
it.odds ? /* @__PURE__ */
|
|
458
|
-
/* @__PURE__ */
|
|
459
|
-
/* @__PURE__ */
|
|
460
|
-
it.bookmaker ? /* @__PURE__ */
|
|
613
|
+
it.odds ? /* @__PURE__ */ jsxs6("span", { className: "ss-w-bi-odds", children: [
|
|
614
|
+
/* @__PURE__ */ jsx9("span", { className: "ss-w-bi-odds-cap", children: ui?.oddsLabel ?? "Odds" }),
|
|
615
|
+
/* @__PURE__ */ jsx9("b", { children: it.odds }),
|
|
616
|
+
it.bookmaker ? /* @__PURE__ */ jsx9("span", { className: "ss-w-bi-book", children: it.bookmaker }) : null
|
|
461
617
|
] }) : null
|
|
462
618
|
] }),
|
|
463
|
-
it.text ? /* @__PURE__ */
|
|
464
|
-
it.scopes && it.scopes.length >= 2 ? /* @__PURE__ */
|
|
465
|
-
it.disclaimer ? /* @__PURE__ */
|
|
619
|
+
it.text ? /* @__PURE__ */ jsx9("p", { className: "ss-w-bi-text", children: it.text }) : null,
|
|
620
|
+
it.scopes && it.scopes.length >= 2 ? /* @__PURE__ */ jsx9(ScopedStats, { scopes: it.scopes, ui }) : it.stats.length > 0 ? /* @__PURE__ */ jsx9(StatRows, { stats: it.stats, ui }) : null,
|
|
621
|
+
it.disclaimer ? /* @__PURE__ */ jsx9("p", { className: "ss-w-bi-disc", children: it.disclaimer }) : null,
|
|
622
|
+
/* @__PURE__ */ jsx9(AddToSlipButton, { it, slip, ui })
|
|
466
623
|
] });
|
|
467
624
|
}
|
|
468
|
-
function BettingInsightList({ insights, ui }) {
|
|
625
|
+
function BettingInsightList({ insights, ui, slip }) {
|
|
469
626
|
if (!insights || insights.length === 0) return null;
|
|
470
|
-
return /* @__PURE__ */
|
|
627
|
+
return /* @__PURE__ */ jsx9("div", { className: "ss-w-binsights", children: insights.map((it) => /* @__PURE__ */ jsx9(InsightCard, { it, ui, slip }, it.id)) });
|
|
471
628
|
}
|
|
472
629
|
|
|
473
630
|
// src/components/ActionButtons.tsx
|
|
474
|
-
import { Fragment, jsx as
|
|
631
|
+
import { Fragment, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
475
632
|
function ActionButtons({ actions, loading, disabled, onAct }) {
|
|
476
633
|
if (actions.length === 0 && !loading) return null;
|
|
477
|
-
return /* @__PURE__ */
|
|
478
|
-
actions.map((a, i) => /* @__PURE__ */
|
|
479
|
-
actions.length === 0 && loading ? /* @__PURE__ */
|
|
480
|
-
/* @__PURE__ */
|
|
481
|
-
/* @__PURE__ */
|
|
634
|
+
return /* @__PURE__ */ jsxs7("div", { className: "ss-w-actions", children: [
|
|
635
|
+
actions.map((a, i) => /* @__PURE__ */ jsx10("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
|
|
636
|
+
actions.length === 0 && loading ? /* @__PURE__ */ jsxs7(Fragment, { children: [
|
|
637
|
+
/* @__PURE__ */ jsx10("span", { className: "ss-w-abtn-shimmer" }),
|
|
638
|
+
/* @__PURE__ */ jsx10("span", { className: "ss-w-abtn-shimmer" })
|
|
482
639
|
] }) : null
|
|
483
640
|
] });
|
|
484
641
|
}
|
|
485
642
|
|
|
486
643
|
// src/components/ChatMessage.tsx
|
|
487
|
-
import { jsx as
|
|
488
|
-
function ChatMessage({ message, disabled, onAct, ui }) {
|
|
644
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
645
|
+
function ChatMessage({ message, disabled, onAct, ui, slip }) {
|
|
489
646
|
if (message.role === "user") {
|
|
490
|
-
return /* @__PURE__ */
|
|
647
|
+
return /* @__PURE__ */ jsx11("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx11("div", { className: "ss-w-bubble", children: message.text }) });
|
|
491
648
|
}
|
|
492
|
-
return /* @__PURE__ */
|
|
493
|
-
message.text ? /* @__PURE__ */
|
|
494
|
-
/* @__PURE__ */
|
|
495
|
-
/* @__PURE__ */
|
|
496
|
-
/* @__PURE__ */
|
|
649
|
+
return /* @__PURE__ */ jsxs8("div", { className: "ss-w-msg ss-w-bot", children: [
|
|
650
|
+
message.text ? /* @__PURE__ */ jsx11(Markdown, { text: message.text }) : null,
|
|
651
|
+
/* @__PURE__ */ jsx11(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
|
|
652
|
+
/* @__PURE__ */ jsx11(BettingInsightList, { insights: message.insights, ui, slip }),
|
|
653
|
+
/* @__PURE__ */ jsx11(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
|
|
497
654
|
] });
|
|
498
655
|
}
|
|
499
656
|
|
|
500
657
|
// src/components/IntentChips.tsx
|
|
501
|
-
import { jsx as
|
|
658
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
502
659
|
function IntentChips({ chips, onPick }) {
|
|
503
660
|
if (chips.length === 0) return null;
|
|
504
|
-
return /* @__PURE__ */
|
|
661
|
+
return /* @__PURE__ */ jsx12("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ jsx12("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
|
|
505
662
|
}
|
|
506
663
|
|
|
507
664
|
// src/scroll.ts
|
|
@@ -511,17 +668,17 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
|
|
|
511
668
|
}
|
|
512
669
|
|
|
513
670
|
// src/components/MessageList.tsx
|
|
514
|
-
import { jsx as
|
|
515
|
-
function MessageList({ controller, ui }) {
|
|
671
|
+
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
672
|
+
function MessageList({ controller, ui, slip }) {
|
|
516
673
|
const { state, send } = controller;
|
|
517
674
|
const listRef = useRef2(null);
|
|
518
675
|
const endRef = useRef2(null);
|
|
519
676
|
const pinnedRef = useRef2(true);
|
|
520
|
-
|
|
677
|
+
useEffect3(() => {
|
|
521
678
|
if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
|
|
522
679
|
}, [state.messages, state.status]);
|
|
523
680
|
const empty = state.messages.length === 0;
|
|
524
|
-
return /* @__PURE__ */
|
|
681
|
+
return /* @__PURE__ */ jsxs9(
|
|
525
682
|
"div",
|
|
526
683
|
{
|
|
527
684
|
className: "ss-w-list",
|
|
@@ -531,35 +688,36 @@ function MessageList({ controller, ui }) {
|
|
|
531
688
|
if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
|
|
532
689
|
},
|
|
533
690
|
children: [
|
|
534
|
-
state.messages.map((m) => /* @__PURE__ */
|
|
691
|
+
state.messages.map((m) => /* @__PURE__ */ jsx13(
|
|
535
692
|
ChatMessage,
|
|
536
693
|
{
|
|
537
694
|
message: m,
|
|
538
695
|
disabled: state.isStreaming,
|
|
539
696
|
onAct: (a) => send(a.message, { actionId: a.actionId, displayText: a.label }),
|
|
540
|
-
ui
|
|
697
|
+
ui,
|
|
698
|
+
slip
|
|
541
699
|
},
|
|
542
700
|
m.id
|
|
543
701
|
)),
|
|
544
|
-
state.isStreaming && state.actionsLoading ? /* @__PURE__ */
|
|
702
|
+
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx13(ActionButtons, { actions: [], loading: true, onAct: () => {
|
|
545
703
|
} }) : null,
|
|
546
|
-
state.status ? /* @__PURE__ */
|
|
547
|
-
state.error ? /* @__PURE__ */
|
|
548
|
-
/* @__PURE__ */
|
|
549
|
-
/* @__PURE__ */
|
|
704
|
+
state.status ? /* @__PURE__ */ jsx13("div", { className: "ss-w-status", children: state.status }) : null,
|
|
705
|
+
state.error ? /* @__PURE__ */ jsxs9("div", { className: "ss-w-error", role: "alert", children: [
|
|
706
|
+
/* @__PURE__ */ jsx13("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
|
|
707
|
+
/* @__PURE__ */ jsx13("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
|
|
550
708
|
] }) : null,
|
|
551
|
-
empty ? /* @__PURE__ */
|
|
552
|
-
/* @__PURE__ */
|
|
709
|
+
empty ? /* @__PURE__ */ jsx13(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
|
|
710
|
+
/* @__PURE__ */ jsx13("div", { ref: endRef })
|
|
553
711
|
]
|
|
554
712
|
}
|
|
555
713
|
);
|
|
556
714
|
}
|
|
557
715
|
|
|
558
716
|
// src/components/ChatInput.tsx
|
|
559
|
-
import { useState as
|
|
560
|
-
import { jsx as
|
|
717
|
+
import { useState as useState4 } from "react";
|
|
718
|
+
import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
561
719
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
562
|
-
const [value, setValue] =
|
|
720
|
+
const [value, setValue] = useState4("");
|
|
563
721
|
const submit = () => {
|
|
564
722
|
const t = value.trim();
|
|
565
723
|
if (!t) return;
|
|
@@ -572,18 +730,18 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
|
|
|
572
730
|
submit();
|
|
573
731
|
}
|
|
574
732
|
};
|
|
575
|
-
return /* @__PURE__ */
|
|
576
|
-
/* @__PURE__ */
|
|
577
|
-
streaming ? /* @__PURE__ */
|
|
733
|
+
return /* @__PURE__ */ jsxs10("div", { className: "ss-w-input", children: [
|
|
734
|
+
/* @__PURE__ */ jsx14("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
|
|
735
|
+
streaming ? /* @__PURE__ */ jsx14("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ jsx14("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx14("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ jsx14(SendIcon, {}) })
|
|
578
736
|
] });
|
|
579
737
|
}
|
|
580
738
|
|
|
581
739
|
// src/components/ChatPanel.tsx
|
|
582
|
-
import { jsx as
|
|
740
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
583
741
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
584
742
|
function useIsModal() {
|
|
585
|
-
const [modal, setModal] =
|
|
586
|
-
|
|
743
|
+
const [modal, setModal] = useState5(false);
|
|
744
|
+
useEffect4(() => {
|
|
587
745
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
588
746
|
const mql = window.matchMedia(FULLSCREEN_QUERY);
|
|
589
747
|
const sync = () => setModal(mql.matches);
|
|
@@ -593,20 +751,20 @@ function useIsModal() {
|
|
|
593
751
|
}, []);
|
|
594
752
|
return modal;
|
|
595
753
|
}
|
|
596
|
-
function ChatPanel({ controller, ui }) {
|
|
754
|
+
function ChatPanel({ controller, ui, slip }) {
|
|
597
755
|
const { state, close, stop, send } = controller;
|
|
598
756
|
const isModal = useIsModal();
|
|
599
|
-
|
|
757
|
+
useEffect4(() => {
|
|
600
758
|
const onKey = (e) => {
|
|
601
759
|
if (e.key === "Escape") close();
|
|
602
760
|
};
|
|
603
761
|
window.addEventListener("keydown", onKey);
|
|
604
762
|
return () => window.removeEventListener("keydown", onKey);
|
|
605
763
|
}, [close]);
|
|
606
|
-
return /* @__PURE__ */
|
|
607
|
-
/* @__PURE__ */
|
|
608
|
-
/* @__PURE__ */
|
|
609
|
-
/* @__PURE__ */
|
|
764
|
+
return /* @__PURE__ */ jsxs11("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
765
|
+
/* @__PURE__ */ jsx15(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
|
|
766
|
+
/* @__PURE__ */ jsx15(MessageList, { controller, ui, slip }),
|
|
767
|
+
/* @__PURE__ */ jsx15(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
|
|
610
768
|
] });
|
|
611
769
|
}
|
|
612
770
|
|
|
@@ -759,13 +917,21 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
759
917
|
.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; }
|
|
760
918
|
.ss-w-stop-glyph { width: 11px; height: 11px; background: currentColor; border-radius: 2px; }
|
|
761
919
|
|
|
920
|
+
/* add to slip */
|
|
921
|
+
.ss-w-slip { position: relative; display: flex; }
|
|
922
|
+
.ss-w-slip-btn { font-size: 11.5px; font-weight: 620; color: #fff; background: var(--ss-w-accent); border: 1px solid var(--ss-w-accent); border-radius: 14px; padding: 5px 12px; cursor: pointer; }
|
|
923
|
+
.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); }
|
|
924
|
+
.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); }
|
|
925
|
+
.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)); }
|
|
926
|
+
.ss-w-toast-error { color: #c0392b; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); }
|
|
927
|
+
|
|
762
928
|
@media (prefers-reduced-motion: reduce) {
|
|
763
929
|
.ss-w-abtn-shimmer { animation: none; }
|
|
764
930
|
}
|
|
765
931
|
`;
|
|
766
932
|
|
|
767
933
|
// src/StatsWidget.tsx
|
|
768
|
-
import { jsx as
|
|
934
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
769
935
|
function buildClient(props) {
|
|
770
936
|
try {
|
|
771
937
|
if ("client" in props && props.client) {
|
|
@@ -794,14 +960,27 @@ function StatsWidget(props) {
|
|
|
794
960
|
const ui = useMemo2(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
|
|
795
961
|
const rootRef = useRef3(null);
|
|
796
962
|
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
|
|
797
|
-
|
|
963
|
+
const slip = useMemo2(
|
|
964
|
+
() => ({
|
|
965
|
+
onAddToSlip: props.config?.onAddToSlip,
|
|
966
|
+
conversationId: controller.state.conversationId ?? void 0,
|
|
967
|
+
onMarketClicked: (marketName) => {
|
|
968
|
+
try {
|
|
969
|
+
void built?.conversation.sendSignal({ signalType: "InsightMarketClicked", entityType: "Market", entityName: marketName }).catch(() => void 0);
|
|
970
|
+
} catch {
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}),
|
|
974
|
+
[props.config?.onAddToSlip, controller.state.conversationId, built]
|
|
975
|
+
);
|
|
976
|
+
useEffect5(() => {
|
|
798
977
|
if (!built || !ui.injectStyles) return;
|
|
799
978
|
const node = rootRef.current?.getRootNode();
|
|
800
979
|
if (node) injectStyles(node);
|
|
801
980
|
}, [built, ui.injectStyles]);
|
|
802
981
|
if (!built) return null;
|
|
803
982
|
const attrs = themeAttrs(ui);
|
|
804
|
-
return /* @__PURE__ */
|
|
983
|
+
return /* @__PURE__ */ jsx16("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__ */ jsx16(ChatPanel, { controller, ui, slip }) : /* @__PURE__ */ jsx16(
|
|
805
984
|
ChatFab,
|
|
806
985
|
{
|
|
807
986
|
label: ui.launcherLabel,
|