@sensiblestats/widget-react 0.7.2 → 0.8.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 +11 -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 +170 -46
- package/dist/index.js.map +1 -1
- package/dist/styles.css +16 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @sensiblestats/widget-react
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8af5152: Render the new market_odds card: hero main-line rows, collapsible ladder with per-row add-to-slip, bookmaker/updated footer. Older servers simply never emit the event; older widgets ignore it.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [8af5152]
|
|
12
|
+
- @sensiblestats/widget-sdk@0.7.0
|
|
13
|
+
|
|
3
14
|
## 0.7.2
|
|
4
15
|
|
|
5
16
|
### Patch 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_react10 = require("react");
|
|
40
40
|
var import_widget_sdk4 = require("@sensiblestats/widget-sdk");
|
|
41
41
|
|
|
42
42
|
// src/i18n.ts
|
|
@@ -58,6 +58,10 @@ var STRINGS = {
|
|
|
58
58
|
oddsLabel: "Odds",
|
|
59
59
|
liveLabel: "Live",
|
|
60
60
|
addToSlip: "Add to slip",
|
|
61
|
+
marketPrices: "Market prices",
|
|
62
|
+
moreLines: "More lines",
|
|
63
|
+
hideLines: "Hide lines",
|
|
64
|
+
updatedLabel: "updated",
|
|
61
65
|
inSlip: "In slip",
|
|
62
66
|
slipAdded: (n) => n === void 0 ? "Added to slip" : `Added to slip (${n} selection${n === 1 ? "" : "s"})`,
|
|
63
67
|
slipDuplicate: "Already in your slip",
|
|
@@ -84,6 +88,10 @@ var STRINGS = {
|
|
|
84
88
|
oddsLabel: "Oran",
|
|
85
89
|
liveLabel: "Canl\u0131",
|
|
86
90
|
addToSlip: "Kupona ekle",
|
|
91
|
+
marketPrices: "Piyasa fiyatlar\u0131",
|
|
92
|
+
moreLines: "Di\u011Fer \xE7izgiler",
|
|
93
|
+
hideLines: "\xC7izgileri gizle",
|
|
94
|
+
updatedLabel: "g\xFCncellendi",
|
|
87
95
|
inSlip: "Kuponda",
|
|
88
96
|
slipAdded: (n) => n === void 0 ? "Kupona eklendi" : `Kupona eklendi (${n} se\xE7im)`,
|
|
89
97
|
slipDuplicate: "Bu se\xE7im kuponunuzda zaten var",
|
|
@@ -126,6 +134,10 @@ function normalizeUi(cfg) {
|
|
|
126
134
|
hideStats: strings.hideStats,
|
|
127
135
|
oddsLabel: strings.oddsLabel,
|
|
128
136
|
liveLabel: strings.liveLabel,
|
|
137
|
+
marketPrices: strings.marketPrices,
|
|
138
|
+
moreLines: strings.moreLines,
|
|
139
|
+
hideLines: strings.hideLines,
|
|
140
|
+
updatedLabel: strings.updatedLabel,
|
|
129
141
|
addToSlip: strings.addToSlip,
|
|
130
142
|
inSlip: strings.inSlip,
|
|
131
143
|
slipAdded: strings.slipAdded,
|
|
@@ -255,6 +267,47 @@ function toSlipSelection(vm, conversationId) {
|
|
|
255
267
|
if (conversationId !== void 0) selection.conversationId = conversationId;
|
|
256
268
|
return selection;
|
|
257
269
|
}
|
|
270
|
+
function toMarketOddsVM(card) {
|
|
271
|
+
const rows = Array.isArray(card.rows) ? card.rows : [];
|
|
272
|
+
const toRow = (r, i) => {
|
|
273
|
+
const selection = [str(r.selectionLabel), str(r.line)].filter(Boolean).join(" ");
|
|
274
|
+
const price = typeof r.price === "number" && Number.isFinite(r.price) ? r.price : void 0;
|
|
275
|
+
return {
|
|
276
|
+
key: `${selection}:${str(r.line) ?? ""}:${i}`,
|
|
277
|
+
selection,
|
|
278
|
+
odds: price !== void 0 ? price.toFixed(2) : "",
|
|
279
|
+
oddsDecimal: price,
|
|
280
|
+
matchOddsId: typeof r.matchOddsId === "number" ? r.matchOddsId : void 0,
|
|
281
|
+
quotedAtUtc: str(r.quotedAtUtc),
|
|
282
|
+
isMain: r.isMainLine !== false
|
|
283
|
+
};
|
|
284
|
+
};
|
|
285
|
+
const vms = rows.map(toRow).filter((r) => r.selection && r.odds);
|
|
286
|
+
const home = str(card.homeTeamName);
|
|
287
|
+
const away = str(card.awayTeamName);
|
|
288
|
+
const updated = rows.map((r) => str(r.quotedAtUtc)).filter((v) => !!v).sort();
|
|
289
|
+
return {
|
|
290
|
+
id: `${typeof card.matchId === "number" ? card.matchId : 0}:${str(card.marketDeveloperName) ?? ""}`,
|
|
291
|
+
market: str(card.marketLabel) ?? str(card.marketDeveloperName) ?? "",
|
|
292
|
+
fixture: home && away ? `${home} vs ${away}` : void 0,
|
|
293
|
+
competition: str(card.competitionName),
|
|
294
|
+
bookmaker: rows.map((r) => str(r.bookmakerName)).find(Boolean),
|
|
295
|
+
updatedAt: updated.length > 0 ? updated[updated.length - 1] : void 0,
|
|
296
|
+
isLive: rows.some((r) => r.isLive === true),
|
|
297
|
+
matchId: typeof card.matchId === "number" ? card.matchId : 0,
|
|
298
|
+
mainRows: vms.filter((r) => r.isMain),
|
|
299
|
+
moreRows: vms.filter((r) => !r.isMain),
|
|
300
|
+
disclaimer: str(card.disclaimer),
|
|
301
|
+
addToSlipEnabled: card.addToSlipEnabled === true
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function marketOddsRowToSlipSelection(vm, row, conversationId) {
|
|
305
|
+
if (row.matchOddsId === void 0 || row.oddsDecimal === void 0) return null;
|
|
306
|
+
const selection = { matchOddsId: row.matchOddsId, matchId: vm.matchId, oddsDecimal: row.oddsDecimal };
|
|
307
|
+
if (row.quotedAtUtc !== void 0) selection.quotedAtUtc = row.quotedAtUtc;
|
|
308
|
+
if (conversationId !== void 0) selection.conversationId = conversationId;
|
|
309
|
+
return selection;
|
|
310
|
+
}
|
|
258
311
|
|
|
259
312
|
// src/reducer.ts
|
|
260
313
|
var POPUP_MAX = 160;
|
|
@@ -286,6 +339,8 @@ function applyEvent(state, event) {
|
|
|
286
339
|
return { ...state, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, actions: [...t.actions, toActionVM(event.data.action)] })) };
|
|
287
340
|
case "betting_insight":
|
|
288
341
|
return { ...state, messages: mapActive(state, (t) => ({ ...t, insights: [...t.insights, toInsightVM(event.data.insight)] })) };
|
|
342
|
+
case "market_odds":
|
|
343
|
+
return { ...state, messages: mapActive(state, (t) => ({ ...t, marketOdds: [...t.marketOdds, toMarketOddsVM(event.data.card)] })) };
|
|
289
344
|
case "intent_chip": {
|
|
290
345
|
const chip = toChipVM(event.data.intent);
|
|
291
346
|
return chip ? { ...state, starters: [...state.starters, chip] } : state;
|
|
@@ -321,11 +376,11 @@ function reducer(state, action) {
|
|
|
321
376
|
return { ...state, isOpen: false, isStreaming: false, status: null, actionsLoading: false };
|
|
322
377
|
case "USER_SENT": {
|
|
323
378
|
const user = { role: "user", id: action.userId, text: action.displayText ?? action.input.message };
|
|
324
|
-
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], done: false };
|
|
379
|
+
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], marketOdds: [], done: false };
|
|
325
380
|
return { ...state, messages: [...state.messages, user, assistant], isStreaming: true, status: null, actionsLoading: false, starters: [], error: null, lastUserInput: action.input, lastDisplayText: action.displayText ?? null };
|
|
326
381
|
}
|
|
327
382
|
case "GREETING_SENT": {
|
|
328
|
-
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], done: false, isGreeting: true };
|
|
383
|
+
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], marketOdds: [], done: false, isGreeting: true };
|
|
329
384
|
return { ...state, messages: [...state.messages, assistant], isStreaming: true, status: null, error: null };
|
|
330
385
|
}
|
|
331
386
|
case "DISMISS_POPUP":
|
|
@@ -447,7 +502,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
|
|
|
447
502
|
}
|
|
448
503
|
|
|
449
504
|
// src/components/ChatPanel.tsx
|
|
450
|
-
var
|
|
505
|
+
var import_react9 = require("react");
|
|
451
506
|
|
|
452
507
|
// src/components/ChatHeader.tsx
|
|
453
508
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
@@ -460,7 +515,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat" }) {
|
|
|
460
515
|
}
|
|
461
516
|
|
|
462
517
|
// src/components/MessageList.tsx
|
|
463
|
-
var
|
|
518
|
+
var import_react7 = require("react");
|
|
464
519
|
|
|
465
520
|
// src/components/Markdown.tsx
|
|
466
521
|
var import_react_markdown = __toESM(require("react-markdown"), 1);
|
|
@@ -570,13 +625,12 @@ function withTimeout(value) {
|
|
|
570
625
|
new Promise((_, reject) => setTimeout(() => reject(new Error("add-to-slip timed out")), ADD_TO_SLIP_TIMEOUT_MS))
|
|
571
626
|
]);
|
|
572
627
|
}
|
|
573
|
-
function
|
|
628
|
+
function SlipButton({ selection, shownOdds, marketName, slip, ui }) {
|
|
574
629
|
const [pending, setPending] = (0, import_react4.useState)(false);
|
|
575
630
|
const [settled, setSettled] = (0, import_react4.useState)(false);
|
|
576
631
|
const [toast, setToast] = (0, import_react4.useState)(null);
|
|
577
632
|
const dismiss = (0, import_react4.useCallback)(() => setToast(null), []);
|
|
578
633
|
const handler = slip.onAddToSlip;
|
|
579
|
-
const selection = it.addToSlipEnabled ? toSlipSelection(it, slip.conversationId) : null;
|
|
580
634
|
if (!ui || !handler || !selection) return null;
|
|
581
635
|
const onClick = async () => {
|
|
582
636
|
setPending(true);
|
|
@@ -584,11 +638,11 @@ function AddToSlipButton({ it, slip, ui }) {
|
|
|
584
638
|
const raw = await withTimeout(Promise.resolve(handler(selection)));
|
|
585
639
|
if (raw != null && !(0, import_widget_sdk3.isAddToSlipResult)(raw)) throw new Error("malformed add-to-slip result");
|
|
586
640
|
const result = raw ?? void 0;
|
|
587
|
-
setToast(resultToToast(result,
|
|
641
|
+
setToast(resultToToast(result, shownOdds || selection.oddsDecimal.toFixed(2), ui));
|
|
588
642
|
setSettled(isTerminal(result));
|
|
589
643
|
if (wasAdded(result) && slip.onMarketClicked) {
|
|
590
644
|
try {
|
|
591
|
-
slip.onMarketClicked(
|
|
645
|
+
slip.onMarketClicked(marketName);
|
|
592
646
|
} catch {
|
|
593
647
|
}
|
|
594
648
|
}
|
|
@@ -612,6 +666,10 @@ function AddToSlipButton({ it, slip, ui }) {
|
|
|
612
666
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Toast, { toast, onDismiss: dismiss })
|
|
613
667
|
] });
|
|
614
668
|
}
|
|
669
|
+
function AddToSlipButton({ it, slip, ui }) {
|
|
670
|
+
const selection = it.addToSlipEnabled ? toSlipSelection(it, slip.conversationId) : null;
|
|
671
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SlipButton, { selection, shownOdds: it.odds ?? "", marketName: it.market, slip, ui });
|
|
672
|
+
}
|
|
615
673
|
|
|
616
674
|
// src/components/BettingInsightList.tsx
|
|
617
675
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
@@ -684,38 +742,88 @@ function BettingInsightList({ insights, ui, slip }) {
|
|
|
684
742
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ss-w-binsights", children: insights.map((it) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(InsightCard, { it, ui, slip }, it.id)) });
|
|
685
743
|
}
|
|
686
744
|
|
|
687
|
-
// src/components/
|
|
745
|
+
// src/components/MarketOddsCardList.tsx
|
|
746
|
+
var import_react6 = require("react");
|
|
688
747
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
748
|
+
function OddsRow({ vm, row, ui, slip }) {
|
|
749
|
+
const selection = vm.addToSlipEnabled ? marketOddsRowToSlipSelection(vm, row, slip.conversationId) : null;
|
|
750
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-mo-row", children: [
|
|
751
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-mo-sel", children: row.selection }),
|
|
752
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-mo-price", children: row.odds }),
|
|
753
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SlipButton, { selection, shownOdds: row.odds, marketName: vm.market, slip, ui })
|
|
754
|
+
] });
|
|
755
|
+
}
|
|
756
|
+
function formatUpdated(iso) {
|
|
757
|
+
if (!iso) return void 0;
|
|
758
|
+
const date = new Date(iso);
|
|
759
|
+
if (Number.isNaN(date.getTime())) return void 0;
|
|
760
|
+
return date.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" });
|
|
761
|
+
}
|
|
762
|
+
function MarketOddsCard({ vm, ui, slip }) {
|
|
763
|
+
const [open, setOpen] = (0, import_react6.useState)(false);
|
|
764
|
+
const updated = formatUpdated(vm.updatedAt);
|
|
765
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-mocard", children: [
|
|
766
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-mo-head", children: [
|
|
767
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-mo-market", children: vm.market }),
|
|
768
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-mo-tag", children: ui?.marketPrices ?? "Market prices" })
|
|
769
|
+
] }),
|
|
770
|
+
vm.fixture || vm.competition ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-mo-fixture", children: [
|
|
771
|
+
vm.fixture ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: vm.fixture }) : null,
|
|
772
|
+
vm.competition ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-mo-comp", children: vm.competition }) : null
|
|
773
|
+
] }) : null,
|
|
774
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-mo-rows", children: [
|
|
775
|
+
vm.mainRows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(OddsRow, { vm, row, ui, slip }, row.key)),
|
|
776
|
+
open ? vm.moreRows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(OddsRow, { vm, row, ui, slip }, row.key)) : null
|
|
777
|
+
] }),
|
|
778
|
+
vm.moreRows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "button", className: "ss-w-bi-toggle", "aria-expanded": open, onClick: () => setOpen((v) => !v), children: [
|
|
779
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: open ? ui?.hideLines ?? "Hide lines" : `${ui?.moreLines ?? "More lines"} (${vm.moreRows.length})` }),
|
|
780
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: `ss-w-bi-chev${open ? " ss-w-bi-chev-up" : ""}`, "aria-hidden": "true" })
|
|
781
|
+
] }) : null,
|
|
782
|
+
vm.bookmaker || updated ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-mo-foot", children: [
|
|
783
|
+
vm.bookmaker ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: vm.bookmaker }) : null,
|
|
784
|
+
updated ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: `${ui?.updatedLabel ?? "updated"} ${updated}` }) : null
|
|
785
|
+
] }) : null,
|
|
786
|
+
vm.disclaimer ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "ss-w-bi-disc", children: vm.disclaimer }) : null
|
|
787
|
+
] });
|
|
788
|
+
}
|
|
789
|
+
function MarketOddsCardList({ cards, ui, slip }) {
|
|
790
|
+
if (!cards || cards.length === 0) return null;
|
|
791
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "ss-w-mocards", children: cards.map((vm) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MarketOddsCard, { vm, ui, slip }, vm.id)) });
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// src/components/ActionButtons.tsx
|
|
795
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
689
796
|
function ActionButtons({ actions, loading, disabled, onAct }) {
|
|
690
797
|
if (actions.length === 0 && !loading) return null;
|
|
691
|
-
return /* @__PURE__ */ (0,
|
|
692
|
-
actions.map((a, i) => /* @__PURE__ */ (0,
|
|
693
|
-
actions.length === 0 && loading ? /* @__PURE__ */ (0,
|
|
694
|
-
/* @__PURE__ */ (0,
|
|
695
|
-
/* @__PURE__ */ (0,
|
|
798
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "ss-w-actions", children: [
|
|
799
|
+
actions.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
|
|
800
|
+
actions.length === 0 && loading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
|
|
801
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "ss-w-abtn-shimmer" }),
|
|
802
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "ss-w-abtn-shimmer" })
|
|
696
803
|
] }) : null
|
|
697
804
|
] });
|
|
698
805
|
}
|
|
699
806
|
|
|
700
807
|
// src/components/ChatMessage.tsx
|
|
701
|
-
var
|
|
808
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
702
809
|
function ChatMessage({ message, disabled, onAct, ui, slip }) {
|
|
703
810
|
if (message.role === "user") {
|
|
704
|
-
return /* @__PURE__ */ (0,
|
|
811
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
|
|
705
812
|
}
|
|
706
|
-
return /* @__PURE__ */ (0,
|
|
707
|
-
message.text ? /* @__PURE__ */ (0,
|
|
708
|
-
/* @__PURE__ */ (0,
|
|
709
|
-
/* @__PURE__ */ (0,
|
|
710
|
-
/* @__PURE__ */ (0,
|
|
813
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
|
|
814
|
+
message.text ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Markdown, { text: message.text }) : null,
|
|
815
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
|
|
816
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(BettingInsightList, { insights: message.insights, ui, slip }),
|
|
817
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
|
|
818
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
|
|
711
819
|
] });
|
|
712
820
|
}
|
|
713
821
|
|
|
714
822
|
// src/components/IntentChips.tsx
|
|
715
|
-
var
|
|
823
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
716
824
|
function IntentChips({ chips, onPick }) {
|
|
717
825
|
if (chips.length === 0) return null;
|
|
718
|
-
return /* @__PURE__ */ (0,
|
|
826
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
|
|
719
827
|
}
|
|
720
828
|
|
|
721
829
|
// src/scroll.ts
|
|
@@ -725,17 +833,17 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
|
|
|
725
833
|
}
|
|
726
834
|
|
|
727
835
|
// src/components/MessageList.tsx
|
|
728
|
-
var
|
|
836
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
729
837
|
function MessageList({ controller, ui, slip }) {
|
|
730
838
|
const { state, send } = controller;
|
|
731
|
-
const listRef = (0,
|
|
732
|
-
const endRef = (0,
|
|
733
|
-
const pinnedRef = (0,
|
|
734
|
-
(0,
|
|
839
|
+
const listRef = (0, import_react7.useRef)(null);
|
|
840
|
+
const endRef = (0, import_react7.useRef)(null);
|
|
841
|
+
const pinnedRef = (0, import_react7.useRef)(true);
|
|
842
|
+
(0, import_react7.useEffect)(() => {
|
|
735
843
|
if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
|
|
736
844
|
}, [state.messages, state.status]);
|
|
737
845
|
const empty = state.messages.length === 0;
|
|
738
|
-
return /* @__PURE__ */ (0,
|
|
846
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
739
847
|
"div",
|
|
740
848
|
{
|
|
741
849
|
className: "ss-w-list",
|
|
@@ -745,7 +853,7 @@ function MessageList({ controller, ui, slip }) {
|
|
|
745
853
|
if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
|
|
746
854
|
},
|
|
747
855
|
children: [
|
|
748
|
-
state.messages.map((m) => /* @__PURE__ */ (0,
|
|
856
|
+
state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
749
857
|
ChatMessage,
|
|
750
858
|
{
|
|
751
859
|
message: m,
|
|
@@ -756,25 +864,25 @@ function MessageList({ controller, ui, slip }) {
|
|
|
756
864
|
},
|
|
757
865
|
m.id
|
|
758
866
|
)),
|
|
759
|
-
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0,
|
|
867
|
+
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
|
|
760
868
|
} }) : null,
|
|
761
|
-
state.status ? /* @__PURE__ */ (0,
|
|
762
|
-
state.error ? /* @__PURE__ */ (0,
|
|
763
|
-
/* @__PURE__ */ (0,
|
|
764
|
-
/* @__PURE__ */ (0,
|
|
869
|
+
state.status ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "ss-w-status", children: state.status }) : null,
|
|
870
|
+
state.error ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "ss-w-error", role: "alert", children: [
|
|
871
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
|
|
872
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
|
|
765
873
|
] }) : null,
|
|
766
|
-
empty ? /* @__PURE__ */ (0,
|
|
767
|
-
/* @__PURE__ */ (0,
|
|
874
|
+
empty ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
|
|
875
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { ref: endRef })
|
|
768
876
|
]
|
|
769
877
|
}
|
|
770
878
|
);
|
|
771
879
|
}
|
|
772
880
|
|
|
773
881
|
// src/components/ChatInput.tsx
|
|
774
|
-
var
|
|
775
|
-
var
|
|
882
|
+
var import_react8 = require("react");
|
|
883
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
776
884
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
777
|
-
const [value, setValue] = (0,
|
|
885
|
+
const [value, setValue] = (0, import_react8.useState)("");
|
|
778
886
|
const submit = () => {
|
|
779
887
|
const t = value.trim();
|
|
780
888
|
if (!t) return;
|
|
@@ -787,18 +895,18 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
|
|
|
787
895
|
submit();
|
|
788
896
|
}
|
|
789
897
|
};
|
|
790
|
-
return /* @__PURE__ */ (0,
|
|
791
|
-
/* @__PURE__ */ (0,
|
|
792
|
-
streaming ? /* @__PURE__ */ (0,
|
|
898
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ss-w-input", children: [
|
|
899
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
|
|
900
|
+
streaming ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(SendIcon, {}) })
|
|
793
901
|
] });
|
|
794
902
|
}
|
|
795
903
|
|
|
796
904
|
// src/components/ChatPanel.tsx
|
|
797
|
-
var
|
|
905
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
798
906
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
799
907
|
function useIsModal() {
|
|
800
|
-
const [modal, setModal] = (0,
|
|
801
|
-
(0,
|
|
908
|
+
const [modal, setModal] = (0, import_react9.useState)(false);
|
|
909
|
+
(0, import_react9.useEffect)(() => {
|
|
802
910
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
803
911
|
const mql = window.matchMedia(FULLSCREEN_QUERY);
|
|
804
912
|
const sync = () => setModal(mql.matches);
|
|
@@ -811,17 +919,17 @@ function useIsModal() {
|
|
|
811
919
|
function ChatPanel({ controller, ui, slip }) {
|
|
812
920
|
const { state, close, stop, send } = controller;
|
|
813
921
|
const isModal = useIsModal();
|
|
814
|
-
(0,
|
|
922
|
+
(0, import_react9.useEffect)(() => {
|
|
815
923
|
const onKey = (e) => {
|
|
816
924
|
if (e.key === "Escape") close();
|
|
817
925
|
};
|
|
818
926
|
window.addEventListener("keydown", onKey);
|
|
819
927
|
return () => window.removeEventListener("keydown", onKey);
|
|
820
928
|
}, [close]);
|
|
821
|
-
return /* @__PURE__ */ (0,
|
|
822
|
-
/* @__PURE__ */ (0,
|
|
823
|
-
/* @__PURE__ */ (0,
|
|
824
|
-
/* @__PURE__ */ (0,
|
|
929
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
930
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
|
|
931
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MessageList, { controller, ui, slip }),
|
|
932
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
|
|
825
933
|
] });
|
|
826
934
|
}
|
|
827
935
|
|
|
@@ -957,6 +1065,22 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
957
1065
|
.ss-w-bi-chip { font-size: 11px; font-weight: 600; border: none; border-radius: 7px; padding: 4px 10px; color: var(--ss-w-faint); background: none; cursor: pointer; }
|
|
958
1066
|
.ss-w-bi-chip[aria-pressed="true"] { color: var(--ss-w-ink); background: var(--ss-w-panel); box-shadow: 0 1px 2px rgba(0,0,0,.08); }
|
|
959
1067
|
|
|
1068
|
+
/* market odds card */
|
|
1069
|
+
.ss-w-mocards { display: flex; flex-direction: column; gap: 7px; }
|
|
1070
|
+
.ss-w-mocard { 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; }
|
|
1071
|
+
.ss-w-mo-head { display: flex; align-items: center; gap: 8px; }
|
|
1072
|
+
.ss-w-mo-market { font-size: 13px; font-weight: 700; color: var(--ss-w-ink); }
|
|
1073
|
+
.ss-w-mo-tag { margin-left: auto; flex: none; font-size: 9px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--ss-w-faint); border: 1px solid var(--ss-w-line); border-radius: 8px; padding: 2px 7px; }
|
|
1074
|
+
.ss-w-mo-fixture { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--ss-w-faint); min-width: 0; }
|
|
1075
|
+
.ss-w-mo-comp { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1076
|
+
.ss-w-mo-rows { display: flex; flex-direction: column; gap: 4px; }
|
|
1077
|
+
.ss-w-mo-row { display: flex; align-items: center; gap: 10px; padding: 4px 0; border-bottom: 1px dashed var(--ss-w-line); }
|
|
1078
|
+
.ss-w-mo-row:last-child { border-bottom: none; }
|
|
1079
|
+
.ss-w-mo-sel { font-size: 12.5px; font-weight: 600; color: var(--ss-w-ink); }
|
|
1080
|
+
.ss-w-mo-price { margin-left: auto; font-family: ui-monospace, monospace; font-variant-numeric: tabular-nums; font-size: 14px; font-weight: 700; color: var(--ss-w-accent); }
|
|
1081
|
+
.ss-w-mo-row .ss-w-slip { margin: 0; }
|
|
1082
|
+
.ss-w-mo-foot { display: flex; align-items: center; gap: 8px; font-size: 10.5px; color: var(--ss-w-faint); }
|
|
1083
|
+
|
|
960
1084
|
/* action buttons */
|
|
961
1085
|
.ss-w-actions { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
|
|
962
1086
|
.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; }
|
|
@@ -994,7 +1118,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
994
1118
|
`;
|
|
995
1119
|
|
|
996
1120
|
// src/StatsWidget.tsx
|
|
997
|
-
var
|
|
1121
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
998
1122
|
function buildClient(props) {
|
|
999
1123
|
try {
|
|
1000
1124
|
if ("client" in props && props.client) {
|
|
@@ -1019,11 +1143,11 @@ function injectStyles(root) {
|
|
|
1019
1143
|
function StatsWidget(props) {
|
|
1020
1144
|
const injectedClient = "client" in props ? props.client : void 0;
|
|
1021
1145
|
const cfg = props.config;
|
|
1022
|
-
const built = (0,
|
|
1023
|
-
const ui = (0,
|
|
1024
|
-
const rootRef = (0,
|
|
1146
|
+
const built = (0, import_react10.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
|
|
1147
|
+
const ui = (0, import_react10.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
|
|
1148
|
+
const rootRef = (0, import_react10.useRef)(null);
|
|
1025
1149
|
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
|
|
1026
|
-
const slip = (0,
|
|
1150
|
+
const slip = (0, import_react10.useMemo)(
|
|
1027
1151
|
() => ({
|
|
1028
1152
|
onAddToSlip: props.config?.onAddToSlip,
|
|
1029
1153
|
conversationId: controller.state.conversationId ?? void 0,
|
|
@@ -1036,14 +1160,14 @@ function StatsWidget(props) {
|
|
|
1036
1160
|
}),
|
|
1037
1161
|
[props.config?.onAddToSlip, controller.state.conversationId, built]
|
|
1038
1162
|
);
|
|
1039
|
-
(0,
|
|
1163
|
+
(0, import_react10.useEffect)(() => {
|
|
1040
1164
|
if (!built || !ui.injectStyles) return;
|
|
1041
1165
|
const node = rootRef.current?.getRootNode();
|
|
1042
1166
|
if (node) injectStyles(node);
|
|
1043
1167
|
}, [built, ui.injectStyles]);
|
|
1044
1168
|
if (!built) return null;
|
|
1045
1169
|
const attrs = themeAttrs(ui);
|
|
1046
|
-
return /* @__PURE__ */ (0,
|
|
1170
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.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_runtime17.jsx)(ChatPanel, { controller, ui, slip }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1047
1171
|
ChatFab,
|
|
1048
1172
|
{
|
|
1049
1173
|
label: ui.launcherLabel,
|