@sensiblestats/widget-react 0.6.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/dist/index.cjs +278 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +272 -92
- package/dist/index.js.map +1 -1
- package/dist/styles.css +15 -3
- package/package.json +2 -2
package/dist/index.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];
|
|
@@ -207,7 +253,15 @@ function applyEvent(state, event) {
|
|
|
207
253
|
const active = activeTurn(state);
|
|
208
254
|
const text = active?.text.trim() ?? "";
|
|
209
255
|
const popupText = active?.isGreeting && !state.isOpen && text ? text.slice(0, POPUP_MAX) : state.popupText;
|
|
210
|
-
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
|
+
};
|
|
211
265
|
}
|
|
212
266
|
case "error": {
|
|
213
267
|
const suppress = activeTurn(state)?.isGreeting === true;
|
|
@@ -350,7 +404,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
|
|
|
350
404
|
}
|
|
351
405
|
|
|
352
406
|
// src/components/ChatPanel.tsx
|
|
353
|
-
import { useEffect as
|
|
407
|
+
import { useEffect as useEffect4, useState as useState5 } from "react";
|
|
354
408
|
|
|
355
409
|
// src/components/ChatHeader.tsx
|
|
356
410
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -363,7 +417,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat" }) {
|
|
|
363
417
|
}
|
|
364
418
|
|
|
365
419
|
// src/components/MessageList.tsx
|
|
366
|
-
import { useEffect as
|
|
420
|
+
import { useEffect as useEffect3, useRef as useRef2 } from "react";
|
|
367
421
|
|
|
368
422
|
// src/components/Markdown.tsx
|
|
369
423
|
import ReactMarkdown from "react-markdown";
|
|
@@ -417,94 +471,194 @@ function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMore
|
|
|
417
471
|
}
|
|
418
472
|
|
|
419
473
|
// src/components/BettingInsightList.tsx
|
|
420
|
-
import { useState as
|
|
421
|
-
|
|
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";
|
|
422
575
|
var MAX_ROWS = 3;
|
|
423
576
|
function StatRows({ stats, ui }) {
|
|
424
|
-
const [expanded, setExpanded] =
|
|
577
|
+
const [expanded, setExpanded] = useState3(false);
|
|
425
578
|
const shown = expanded ? stats : stats.slice(0, MAX_ROWS);
|
|
426
579
|
const overflow = stats.length - MAX_ROWS;
|
|
427
|
-
return /* @__PURE__ */
|
|
428
|
-
shown.map((s) => /* @__PURE__ */
|
|
429
|
-
/* @__PURE__ */
|
|
430
|
-
/* @__PURE__ */
|
|
431
|
-
/* @__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 })
|
|
432
585
|
] }, `${s.label}:${s.value}`)),
|
|
433
|
-
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
|
|
434
587
|
] });
|
|
435
588
|
}
|
|
436
589
|
function ScopedStats({ scopes, ui }) {
|
|
437
|
-
const [active, setActive] =
|
|
590
|
+
const [active, setActive] = useState3(0);
|
|
438
591
|
const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
|
|
439
|
-
return /* @__PURE__ */
|
|
440
|
-
/* @__PURE__ */
|
|
441
|
-
/* @__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)
|
|
442
595
|
] });
|
|
443
596
|
}
|
|
444
|
-
function InsightCard({ it, ui }) {
|
|
597
|
+
function InsightCard({ it, ui, slip }) {
|
|
445
598
|
const live = it.isLive || it.phase === "live";
|
|
446
|
-
return /* @__PURE__ */
|
|
447
|
-
live || it.fixture || it.competition ? /* @__PURE__ */
|
|
448
|
-
live ? /* @__PURE__ */
|
|
449
|
-
/* @__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" }),
|
|
450
603
|
ui?.liveLabel ?? "Live"
|
|
451
604
|
] }) : null,
|
|
452
|
-
it.fixture ? /* @__PURE__ */
|
|
453
|
-
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
|
|
454
607
|
] }) : null,
|
|
455
|
-
/* @__PURE__ */
|
|
456
|
-
/* @__PURE__ */
|
|
457
|
-
/* @__PURE__ */
|
|
458
|
-
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
|
|
459
612
|
] }),
|
|
460
|
-
it.odds ? /* @__PURE__ */
|
|
461
|
-
/* @__PURE__ */
|
|
462
|
-
/* @__PURE__ */
|
|
463
|
-
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
|
|
464
617
|
] }) : null
|
|
465
618
|
] }),
|
|
466
|
-
it.text ? /* @__PURE__ */
|
|
467
|
-
it.scopes && it.scopes.length >= 2 ? /* @__PURE__ */
|
|
468
|
-
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 })
|
|
469
623
|
] });
|
|
470
624
|
}
|
|
471
|
-
function BettingInsightList({ insights, ui }) {
|
|
625
|
+
function BettingInsightList({ insights, ui, slip }) {
|
|
472
626
|
if (!insights || insights.length === 0) return null;
|
|
473
|
-
return /* @__PURE__ */
|
|
627
|
+
return /* @__PURE__ */ jsx9("div", { className: "ss-w-binsights", children: insights.map((it) => /* @__PURE__ */ jsx9(InsightCard, { it, ui, slip }, it.id)) });
|
|
474
628
|
}
|
|
475
629
|
|
|
476
630
|
// src/components/ActionButtons.tsx
|
|
477
|
-
import { Fragment, jsx as
|
|
631
|
+
import { Fragment, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
478
632
|
function ActionButtons({ actions, loading, disabled, onAct }) {
|
|
479
633
|
if (actions.length === 0 && !loading) return null;
|
|
480
|
-
return /* @__PURE__ */
|
|
481
|
-
actions.map((a, i) => /* @__PURE__ */
|
|
482
|
-
actions.length === 0 && loading ? /* @__PURE__ */
|
|
483
|
-
/* @__PURE__ */
|
|
484
|
-
/* @__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" })
|
|
485
639
|
] }) : null
|
|
486
640
|
] });
|
|
487
641
|
}
|
|
488
642
|
|
|
489
643
|
// src/components/ChatMessage.tsx
|
|
490
|
-
import { jsx as
|
|
491
|
-
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 }) {
|
|
492
646
|
if (message.role === "user") {
|
|
493
|
-
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 }) });
|
|
494
648
|
}
|
|
495
|
-
return /* @__PURE__ */
|
|
496
|
-
message.text ? /* @__PURE__ */
|
|
497
|
-
/* @__PURE__ */
|
|
498
|
-
/* @__PURE__ */
|
|
499
|
-
/* @__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 })
|
|
500
654
|
] });
|
|
501
655
|
}
|
|
502
656
|
|
|
503
657
|
// src/components/IntentChips.tsx
|
|
504
|
-
import { jsx as
|
|
658
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
505
659
|
function IntentChips({ chips, onPick }) {
|
|
506
660
|
if (chips.length === 0) return null;
|
|
507
|
-
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}`)) });
|
|
508
662
|
}
|
|
509
663
|
|
|
510
664
|
// src/scroll.ts
|
|
@@ -514,17 +668,17 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
|
|
|
514
668
|
}
|
|
515
669
|
|
|
516
670
|
// src/components/MessageList.tsx
|
|
517
|
-
import { jsx as
|
|
518
|
-
function MessageList({ controller, ui }) {
|
|
671
|
+
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
672
|
+
function MessageList({ controller, ui, slip }) {
|
|
519
673
|
const { state, send } = controller;
|
|
520
674
|
const listRef = useRef2(null);
|
|
521
675
|
const endRef = useRef2(null);
|
|
522
676
|
const pinnedRef = useRef2(true);
|
|
523
|
-
|
|
677
|
+
useEffect3(() => {
|
|
524
678
|
if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
|
|
525
679
|
}, [state.messages, state.status]);
|
|
526
680
|
const empty = state.messages.length === 0;
|
|
527
|
-
return /* @__PURE__ */
|
|
681
|
+
return /* @__PURE__ */ jsxs9(
|
|
528
682
|
"div",
|
|
529
683
|
{
|
|
530
684
|
className: "ss-w-list",
|
|
@@ -534,35 +688,36 @@ function MessageList({ controller, ui }) {
|
|
|
534
688
|
if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
|
|
535
689
|
},
|
|
536
690
|
children: [
|
|
537
|
-
state.messages.map((m) => /* @__PURE__ */
|
|
691
|
+
state.messages.map((m) => /* @__PURE__ */ jsx13(
|
|
538
692
|
ChatMessage,
|
|
539
693
|
{
|
|
540
694
|
message: m,
|
|
541
695
|
disabled: state.isStreaming,
|
|
542
696
|
onAct: (a) => send(a.message, { actionId: a.actionId, displayText: a.label }),
|
|
543
|
-
ui
|
|
697
|
+
ui,
|
|
698
|
+
slip
|
|
544
699
|
},
|
|
545
700
|
m.id
|
|
546
701
|
)),
|
|
547
|
-
state.isStreaming && state.actionsLoading ? /* @__PURE__ */
|
|
702
|
+
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx13(ActionButtons, { actions: [], loading: true, onAct: () => {
|
|
548
703
|
} }) : null,
|
|
549
|
-
state.status ? /* @__PURE__ */
|
|
550
|
-
state.error ? /* @__PURE__ */
|
|
551
|
-
/* @__PURE__ */
|
|
552
|
-
/* @__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 })
|
|
553
708
|
] }) : null,
|
|
554
|
-
empty ? /* @__PURE__ */
|
|
555
|
-
/* @__PURE__ */
|
|
709
|
+
empty ? /* @__PURE__ */ jsx13(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
|
|
710
|
+
/* @__PURE__ */ jsx13("div", { ref: endRef })
|
|
556
711
|
]
|
|
557
712
|
}
|
|
558
713
|
);
|
|
559
714
|
}
|
|
560
715
|
|
|
561
716
|
// src/components/ChatInput.tsx
|
|
562
|
-
import { useState as
|
|
563
|
-
import { jsx as
|
|
717
|
+
import { useState as useState4 } from "react";
|
|
718
|
+
import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
564
719
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
565
|
-
const [value, setValue] =
|
|
720
|
+
const [value, setValue] = useState4("");
|
|
566
721
|
const submit = () => {
|
|
567
722
|
const t = value.trim();
|
|
568
723
|
if (!t) return;
|
|
@@ -575,18 +730,18 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
|
|
|
575
730
|
submit();
|
|
576
731
|
}
|
|
577
732
|
};
|
|
578
|
-
return /* @__PURE__ */
|
|
579
|
-
/* @__PURE__ */
|
|
580
|
-
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, {}) })
|
|
581
736
|
] });
|
|
582
737
|
}
|
|
583
738
|
|
|
584
739
|
// src/components/ChatPanel.tsx
|
|
585
|
-
import { jsx as
|
|
740
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
586
741
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
587
742
|
function useIsModal() {
|
|
588
|
-
const [modal, setModal] =
|
|
589
|
-
|
|
743
|
+
const [modal, setModal] = useState5(false);
|
|
744
|
+
useEffect4(() => {
|
|
590
745
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
591
746
|
const mql = window.matchMedia(FULLSCREEN_QUERY);
|
|
592
747
|
const sync = () => setModal(mql.matches);
|
|
@@ -596,20 +751,20 @@ function useIsModal() {
|
|
|
596
751
|
}, []);
|
|
597
752
|
return modal;
|
|
598
753
|
}
|
|
599
|
-
function ChatPanel({ controller, ui }) {
|
|
754
|
+
function ChatPanel({ controller, ui, slip }) {
|
|
600
755
|
const { state, close, stop, send } = controller;
|
|
601
756
|
const isModal = useIsModal();
|
|
602
|
-
|
|
757
|
+
useEffect4(() => {
|
|
603
758
|
const onKey = (e) => {
|
|
604
759
|
if (e.key === "Escape") close();
|
|
605
760
|
};
|
|
606
761
|
window.addEventListener("keydown", onKey);
|
|
607
762
|
return () => window.removeEventListener("keydown", onKey);
|
|
608
763
|
}, [close]);
|
|
609
|
-
return /* @__PURE__ */
|
|
610
|
-
/* @__PURE__ */
|
|
611
|
-
/* @__PURE__ */
|
|
612
|
-
/* @__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 })
|
|
613
768
|
] });
|
|
614
769
|
}
|
|
615
770
|
|
|
@@ -618,6 +773,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
618
773
|
.ss-w-root, .ss-w-root * { box-sizing: border-box; margin: 0; padding: 0; }
|
|
619
774
|
.ss-w-root {
|
|
620
775
|
--ss-w-accent: #17936a;
|
|
776
|
+
--ss-w-cta: #f5a623; --ss-w-cta-ink: #1b1300;
|
|
621
777
|
--ss-w-ink: #14181d; --ss-w-faint: #6b7580; --ss-w-line: #e4e8ec;
|
|
622
778
|
--ss-w-panel: #ffffff; --ss-w-bubble: #f3f5f7; --ss-w-nav: #0e1622;
|
|
623
779
|
--ss-w-radius: 16px; --ss-w-offset-x: 20px; --ss-w-offset-y: 20px;
|
|
@@ -626,16 +782,19 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
626
782
|
}
|
|
627
783
|
@media (prefers-color-scheme: dark) {
|
|
628
784
|
.ss-w-root[data-ss-w-theme="auto"] {
|
|
629
|
-
--ss-w-accent: #37c891; --ss-w-
|
|
785
|
+
--ss-w-accent: #37c891; --ss-w-cta: #ffb638; --ss-w-cta-ink: #1b1300;
|
|
786
|
+
--ss-w-ink: #e8edf2; --ss-w-faint: #8b95a1;
|
|
630
787
|
--ss-w-line: #2a3441; --ss-w-panel: #141b24; --ss-w-bubble: #1e2732; --ss-w-nav: #0a0f16;
|
|
631
788
|
}
|
|
632
789
|
}
|
|
633
790
|
.ss-w-root[data-ss-w-theme="dark"] {
|
|
634
|
-
--ss-w-accent: #37c891; --ss-w-
|
|
791
|
+
--ss-w-accent: #37c891; --ss-w-cta: #ffb638; --ss-w-cta-ink: #1b1300;
|
|
792
|
+
--ss-w-ink: #e8edf2; --ss-w-faint: #8b95a1;
|
|
635
793
|
--ss-w-line: #2a3441; --ss-w-panel: #141b24; --ss-w-bubble: #1e2732; --ss-w-nav: #0a0f16;
|
|
636
794
|
}
|
|
637
795
|
.ss-w-root[data-ss-w-theme="light"] {
|
|
638
|
-
--ss-w-accent: #17936a; --ss-w-
|
|
796
|
+
--ss-w-accent: #17936a; --ss-w-cta: #f5a623; --ss-w-cta-ink: #1b1300;
|
|
797
|
+
--ss-w-ink: #14181d; --ss-w-faint: #6b7580;
|
|
639
798
|
--ss-w-line: #e4e8ec; --ss-w-panel: #ffffff; --ss-w-bubble: #f3f5f7; --ss-w-nav: #0e1622;
|
|
640
799
|
}
|
|
641
800
|
|
|
@@ -762,13 +921,21 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
762
921
|
.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; }
|
|
763
922
|
.ss-w-stop-glyph { width: 11px; height: 11px; background: currentColor; border-radius: 2px; }
|
|
764
923
|
|
|
924
|
+
/* add to slip */
|
|
925
|
+
.ss-w-slip { position: relative; display: flex; }
|
|
926
|
+
.ss-w-slip-btn { font-size: 11.5px; font-weight: 620; color: var(--ss-w-cta-ink); background: var(--ss-w-cta); border: 1px solid var(--ss-w-cta); border-radius: 14px; padding: 5px 12px; cursor: pointer; }
|
|
927
|
+
.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); }
|
|
928
|
+
.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); }
|
|
929
|
+
.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)); }
|
|
930
|
+
.ss-w-toast-error { color: #c0392b; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); }
|
|
931
|
+
|
|
765
932
|
@media (prefers-reduced-motion: reduce) {
|
|
766
933
|
.ss-w-abtn-shimmer { animation: none; }
|
|
767
934
|
}
|
|
768
935
|
`;
|
|
769
936
|
|
|
770
937
|
// src/StatsWidget.tsx
|
|
771
|
-
import { jsx as
|
|
938
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
772
939
|
function buildClient(props) {
|
|
773
940
|
try {
|
|
774
941
|
if ("client" in props && props.client) {
|
|
@@ -797,14 +964,27 @@ function StatsWidget(props) {
|
|
|
797
964
|
const ui = useMemo2(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
|
|
798
965
|
const rootRef = useRef3(null);
|
|
799
966
|
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
|
|
800
|
-
|
|
967
|
+
const slip = useMemo2(
|
|
968
|
+
() => ({
|
|
969
|
+
onAddToSlip: props.config?.onAddToSlip,
|
|
970
|
+
conversationId: controller.state.conversationId ?? void 0,
|
|
971
|
+
onMarketClicked: (marketName) => {
|
|
972
|
+
try {
|
|
973
|
+
void built?.conversation.sendSignal({ signalType: "InsightMarketClicked", entityType: "Market", entityName: marketName }).catch(() => void 0);
|
|
974
|
+
} catch {
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}),
|
|
978
|
+
[props.config?.onAddToSlip, controller.state.conversationId, built]
|
|
979
|
+
);
|
|
980
|
+
useEffect5(() => {
|
|
801
981
|
if (!built || !ui.injectStyles) return;
|
|
802
982
|
const node = rootRef.current?.getRootNode();
|
|
803
983
|
if (node) injectStyles(node);
|
|
804
984
|
}, [built, ui.injectStyles]);
|
|
805
985
|
if (!built) return null;
|
|
806
986
|
const attrs = themeAttrs(ui);
|
|
807
|
-
return /* @__PURE__ */
|
|
987
|
+
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(
|
|
808
988
|
ChatFab,
|
|
809
989
|
{
|
|
810
990
|
label: ui.launcherLabel,
|