@sensiblestats/widget-react 0.0.0 → 0.1.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 ADDED
@@ -0,0 +1,18 @@
1
+ # @sensiblestats/widget-react
2
+
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Republish with the `@sensiblestats/widget-sdk` dependency resolved to a real `^0.2.0` range. The `0.1.0` tarball shipped an unusable `workspace:^` range (npm fails with `Unsupported URL Type "workspace:"`); the release workflow now publishes via yarn, which rewrites workspace ranges to concrete versions.
8
+
9
+ ## 0.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 5857cb0: First published release of `StatsWidget`, the SensibleStats embeddable chat widget for React. Streams operator chat over the widget gateway and renders rich entity cards (image, subtitle, season stats), suggested action buttons, intent chips, and markdown answers with horizontally-scrollable tables. Theme-aware (light/dark/auto), style-isolated via injected CSS, and built on `@sensiblestats/widget-sdk`.
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [5857cb0]
18
+ - @sensiblestats/widget-sdk@0.2.0
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 import_react5 = require("react");
39
+ var import_react6 = require("react");
40
40
  var import_widget_sdk3 = require("@sensiblestats/widget-sdk");
41
41
 
42
42
  // src/config.ts
@@ -88,38 +88,33 @@ function str(v) {
88
88
  function num(v) {
89
89
  return typeof v === "number" && Number.isFinite(v) ? String(v) : void 0;
90
90
  }
91
- var STAT_KEYS = [
92
- ["goals", "Goals"],
93
- ["assists", "Assists"],
94
- ["appearances", "Apps"],
95
- ["xg", "xG"],
96
- ["form", "Form"],
97
- ["rating", "Rating"]
98
- ];
99
91
  function readStats(card) {
92
+ if (!Array.isArray(card.stats)) return [];
100
93
  const cells = [];
101
- for (const [key, label] of STAT_KEYS) {
102
- const value = num(card[key]) ?? str(card[key]);
103
- if (value !== void 0) cells.push({ label, value });
94
+ for (const entry of card.stats) {
95
+ const label = str(entry.label);
96
+ const value = num(entry.value) ?? str(entry.value);
97
+ if (label !== void 0 && value !== void 0) cells.push({ label, value });
104
98
  }
105
99
  return cells;
106
100
  }
107
101
  function toCardVM(card) {
108
- const kind = card.entityType === "Player" ? "player" : card.entityType === "Team" ? "team" : "generic";
102
+ const entityType = str(card.entityType)?.toLowerCase();
103
+ const kind = entityType === "player" ? "player" : entityType === "team" ? "team" : "generic";
109
104
  return {
110
105
  id: String(card.entityId),
111
106
  kind,
112
107
  name: card.canonicalName,
113
- image: str(card.imageUrl) ?? str(card.image_url) ?? str(card.logoUrl) ?? str(card.logo),
108
+ image: str(card.imagePath) ?? str(card.imageUrl) ?? str(card.image_url) ?? str(card.logoUrl) ?? str(card.logo),
114
109
  sub: str(card.subtitle) ?? str(card.teamName) ?? str(card.leagueName) ?? str(card.position),
115
110
  stats: readStats(card),
116
111
  query: card.canonicalName
117
112
  };
118
113
  }
119
114
  function toActionVM(action) {
120
- const label = str(action.label) ?? str(action.text) ?? str(action.title) ?? "Continue";
121
- const actionId = str(action.id) ?? str(action.actionId);
122
- const message = str(action.message) ?? str(action.query) ?? label;
115
+ const label = str(action.actionLabel) ?? str(action.label) ?? str(action.text) ?? str(action.title) ?? "Continue";
116
+ const actionId = str(action.actionId) ?? str(action.id);
117
+ const message = str(action.actionValue) ?? str(action.message) ?? str(action.query) ?? label;
123
118
  return { label, actionId, message };
124
119
  }
125
120
  function toChipVM(intent) {
@@ -132,7 +127,7 @@ function chipFromText(text) {
132
127
 
133
128
  // src/reducer.ts
134
129
  function initialState(starters) {
135
- return { isOpen: false, messages: [], isStreaming: false, status: null, actionsLoading: false, starters, error: null, lastUserInput: null };
130
+ return { isOpen: false, messages: [], isStreaming: false, status: null, actionsLoading: false, starters, error: null, lastUserInput: null, lastDisplayText: null };
136
131
  }
137
132
  function mapActive(state, fn) {
138
133
  const last = state.messages[state.messages.length - 1];
@@ -144,8 +139,9 @@ function applyEvent(state, event) {
144
139
  case "progress":
145
140
  return { ...state, status: event.data.message };
146
141
  case "answer":
147
- return { ...state, messages: mapActive(state, (t) => ({ ...t, text: t.text + event.data.text })) };
142
+ return { ...state, messages: mapActive(state, (t) => ({ ...t, text: event.data.text })) };
148
143
  case "entity_card":
144
+ if (event.data.card.isUsed === false) return state;
149
145
  return { ...state, messages: mapActive(state, (t) => ({ ...t, cards: [...t.cards, toCardVM(event.data.card)] })) };
150
146
  case "action_button":
151
147
  return { ...state, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, actions: [...t.actions, toActionVM(event.data.action)] })) };
@@ -168,9 +164,9 @@ function reducer(state, action) {
168
164
  case "CLOSE":
169
165
  return { ...state, isOpen: false, isStreaming: false, status: null, actionsLoading: false };
170
166
  case "USER_SENT": {
171
- const user = { role: "user", id: action.userId, text: action.input.message };
167
+ const user = { role: "user", id: action.userId, text: action.displayText ?? action.input.message };
172
168
  const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], done: false };
173
- return { ...state, messages: [...state.messages, user, assistant], isStreaming: true, status: null, actionsLoading: false, starters: [], error: null, lastUserInput: action.input };
169
+ return { ...state, messages: [...state.messages, user, assistant], isStreaming: true, status: null, actionsLoading: false, starters: [], error: null, lastUserInput: action.input, lastDisplayText: action.displayText ?? null };
174
170
  }
175
171
  case "EVENT":
176
172
  return applyEvent(state, action.event);
@@ -190,9 +186,9 @@ function useChatStream(conversation, starterTexts) {
190
186
  const stateRef = (0, import_react.useRef)(state);
191
187
  stateRef.current = state;
192
188
  const nextId = () => `m${idRef.current++}`;
193
- const run = (0, import_react.useCallback)((input) => {
189
+ const run = (0, import_react.useCallback)((input, displayText) => {
194
190
  handleRef.current?.cancel();
195
- dispatch({ kind: "USER_SENT", input, userId: nextId(), assistantId: nextId() });
191
+ dispatch({ kind: "USER_SENT", input, userId: nextId(), assistantId: nextId(), displayText });
196
192
  const handle = conversation.send(input);
197
193
  handleRef.current = handle;
198
194
  (async () => {
@@ -207,14 +203,14 @@ function useChatStream(conversation, starterTexts) {
207
203
  }
208
204
  })();
209
205
  }, [conversation]);
210
- const send = (0, import_react.useCallback)((text, actionId) => {
206
+ const send = (0, import_react.useCallback)((text, opts) => {
211
207
  const trimmed = text.trim();
212
208
  if (!trimmed) return;
213
- run({ message: trimmed, actionId });
209
+ run({ message: trimmed, actionId: opts?.actionId }, opts?.displayText);
214
210
  }, [run]);
215
211
  const retry = (0, import_react.useCallback)(() => {
216
212
  const last = stateRef.current.lastUserInput;
217
- if (last) run(last);
213
+ if (last) run(last, stateRef.current.lastDisplayText ?? void 0);
218
214
  }, [run]);
219
215
  const open = (0, import_react.useCallback)(() => dispatch({ kind: "OPEN" }), []);
220
216
  const close = (0, import_react.useCallback)(() => {
@@ -237,7 +233,13 @@ function useChatStream(conversation, starterTexts) {
237
233
  // src/components/Icons.tsx
238
234
  var import_jsx_runtime = require("react/jsx-runtime");
239
235
  function SsMark({ className }) {
240
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className, viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "#00F400", d: "M12 2l2.5 6.5L21 11l-6.5 2.5L12 20l-2.5-6.5L3 11l6.5-2.5z" }) });
236
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className, viewBox: "0 0 100 100", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { fill: "none", stroke: "#15c917", strokeWidth: "8.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
237
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "50", cy: "44", r: "37" }),
238
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "15", y1: "44", x2: "85", y2: "44" }),
239
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M36 44 V24 H64 V44" }),
240
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M36 44 V63 H64 V44" }),
241
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M46 63 L43 96 L61 61" })
242
+ ] }) });
241
243
  }
242
244
  function SendIcon() {
243
245
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "currentColor", d: "M3 20l18-8L3 4v6l12 2-12 2z" }) });
@@ -265,7 +267,7 @@ function ChatFab({ label, open, greeting, onToggle }) {
265
267
  }
266
268
 
267
269
  // src/components/ChatPanel.tsx
268
- var import_react4 = require("react");
270
+ var import_react5 = require("react");
269
271
 
270
272
  // src/components/ChatHeader.tsx
271
273
  var import_jsx_runtime4 = require("react/jsx-runtime");
@@ -278,7 +280,7 @@ function ChatHeader({ onClose }) {
278
280
  }
279
281
 
280
282
  // src/components/MessageList.tsx
281
- var import_react2 = require("react");
283
+ var import_react3 = require("react");
282
284
 
283
285
  // src/components/Markdown.tsx
284
286
  var import_react_markdown = __toESM(require("react-markdown"), 1);
@@ -296,39 +298,48 @@ function Markdown({ text }) {
296
298
  }
297
299
 
298
300
  // src/components/EntityCardList.tsx
301
+ var import_react2 = require("react");
299
302
  var import_jsx_runtime6 = require("react/jsx-runtime");
303
+ var MAX_VISIBLE = 3;
300
304
  function initials(name) {
301
305
  return name.split(/\s+/).slice(0, 2).map((w) => w[0] ?? "").join("").toUpperCase();
302
306
  }
303
- function EntityCardList({ cards, onSelect }) {
307
+ function EntityCardList({ cards, disabled, onSelect }) {
308
+ const [expanded, setExpanded] = (0, import_react2.useState)(false);
304
309
  if (cards.length === 0) return null;
305
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "ss-w-cards", children: cards.map((c) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("button", { type: "button", className: "ss-w-ecard", onClick: () => onSelect(c.query), children: [
306
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
307
- "span",
308
- {
309
- className: c.kind === "team" ? "ss-w-thumb ss-w-team" : "ss-w-thumb",
310
- style: c.image ? { backgroundImage: `url(${c.image})` } : void 0,
311
- children: c.image ? "" : initials(c.name)
312
- }
313
- ),
314
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ss-w-meta", children: [
315
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ss-w-nm", children: c.name }),
316
- c.sub ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ss-w-sub", children: c.sub }) : null
317
- ] }),
318
- c.stats.map((s) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ss-w-stat", children: [
319
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("b", { children: s.value }),
320
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: s.label })
321
- ] }, s.label)),
322
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ss-w-chev", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChevronIcon, {}) })
323
- ] }, c.id)) });
310
+ const visible = expanded ? cards : cards.slice(0, MAX_VISIBLE);
311
+ const overflow = cards.length - MAX_VISIBLE;
312
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ss-w-cards", children: [
313
+ visible.map((c) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("button", { type: "button", className: "ss-w-ecard", "data-kind": c.kind, disabled, onClick: () => onSelect(c.query), children: [
314
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
315
+ "span",
316
+ {
317
+ className: c.kind === "team" ? "ss-w-thumb ss-w-team" : "ss-w-thumb",
318
+ style: c.image ? { backgroundImage: `url(${c.image})` } : void 0,
319
+ children: c.image ? "" : initials(c.name)
320
+ }
321
+ ),
322
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ss-w-meta", children: [
323
+ c.kind !== "generic" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ss-w-kind", children: c.kind }) : null,
324
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ss-w-nm", children: c.name }),
325
+ c.sub ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ss-w-sub", children: c.sub }) : null
326
+ ] }),
327
+ c.stats.map((s) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ss-w-stat", children: [
328
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("b", { children: s.value }),
329
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: s.label })
330
+ ] }, s.label)),
331
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ss-w-chev", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChevronIcon, {}) })
332
+ ] }, c.id)),
333
+ overflow > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "ss-w-cards-more", disabled, onClick: () => setExpanded((v) => !v), children: expanded ? "Show less" : `Show ${overflow} more` }) : null
334
+ ] });
324
335
  }
325
336
 
326
337
  // src/components/ActionButtons.tsx
327
338
  var import_jsx_runtime7 = require("react/jsx-runtime");
328
- function ActionButtons({ actions, loading, onAct }) {
339
+ function ActionButtons({ actions, loading, disabled, onAct }) {
329
340
  if (actions.length === 0 && !loading) return null;
330
341
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ss-w-actions", children: [
331
- actions.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "ss-w-abtn", onClick: () => onAct(a.message, a.actionId), children: a.label }, `${a.label}-${i}`)),
342
+ actions.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
332
343
  actions.length === 0 && loading ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
333
344
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ss-w-abtn-shimmer" }),
334
345
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ss-w-abtn-shimmer" })
@@ -338,14 +349,14 @@ function ActionButtons({ actions, loading, onAct }) {
338
349
 
339
350
  // src/components/ChatMessage.tsx
340
351
  var import_jsx_runtime8 = require("react/jsx-runtime");
341
- function ChatMessage({ message, onAct, onSelect }) {
352
+ function ChatMessage({ message, disabled, onAct, onSelect }) {
342
353
  if (message.role === "user") {
343
354
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
344
355
  }
345
356
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
346
357
  message.text ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Markdown, { text: message.text }) : null,
347
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(EntityCardList, { cards: message.cards, onSelect }),
348
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ActionButtons, { actions: message.actions, loading: false, onAct })
358
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(EntityCardList, { cards: message.cards, disabled, onSelect }),
359
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
349
360
  ] });
350
361
  }
351
362
 
@@ -361,13 +372,22 @@ var import_jsx_runtime10 = require("react/jsx-runtime");
361
372
  function MessageList({ controller, ui }) {
362
373
  void ui;
363
374
  const { state, send } = controller;
364
- const endRef = (0, import_react2.useRef)(null);
365
- (0, import_react2.useEffect)(() => {
375
+ const endRef = (0, import_react3.useRef)(null);
376
+ (0, import_react3.useEffect)(() => {
366
377
  endRef.current?.scrollIntoView({ block: "end" });
367
378
  }, [state.messages, state.status]);
368
379
  const empty = state.messages.length === 0;
369
380
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-list", "aria-live": "polite", children: [
370
- state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatMessage, { message: m, onAct: (msg, id) => send(msg, id), onSelect: (q) => send(q) }, m.id)),
381
+ state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
382
+ ChatMessage,
383
+ {
384
+ message: m,
385
+ disabled: state.isStreaming,
386
+ onAct: (a) => send(a.message, { actionId: a.actionId, displayText: a.label }),
387
+ onSelect: (q) => send(q)
388
+ },
389
+ m.id
390
+ )),
371
391
  state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
372
392
  } }) : null,
373
393
  state.status ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "ss-w-status", children: state.status }) : null,
@@ -381,10 +401,10 @@ function MessageList({ controller, ui }) {
381
401
  }
382
402
 
383
403
  // src/components/ChatInput.tsx
384
- var import_react3 = require("react");
404
+ var import_react4 = require("react");
385
405
  var import_jsx_runtime11 = require("react/jsx-runtime");
386
406
  function ChatInput({ placeholder, streaming, onSend, onStop }) {
387
- const [value, setValue] = (0, import_react3.useState)("");
407
+ const [value, setValue] = (0, import_react4.useState)("");
388
408
  const submit = () => {
389
409
  const t = value.trim();
390
410
  if (!t) return;
@@ -398,23 +418,37 @@ function ChatInput({ placeholder, streaming, onSend, onStop }) {
398
418
  }
399
419
  };
400
420
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "ss-w-input", children: [
401
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, onChange: (e) => setValue(e.target.value), onKeyDown }),
421
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
402
422
  streaming ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": "Stop response", onClick: onStop, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: "ss-w-send", "aria-label": "Send message", onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SendIcon, {}) })
403
423
  ] });
404
424
  }
405
425
 
406
426
  // src/components/ChatPanel.tsx
407
427
  var import_jsx_runtime12 = require("react/jsx-runtime");
428
+ var FULLSCREEN_QUERY = "(max-width: 639px)";
429
+ function useIsModal() {
430
+ const [modal, setModal] = (0, import_react5.useState)(false);
431
+ (0, import_react5.useEffect)(() => {
432
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
433
+ const mql = window.matchMedia(FULLSCREEN_QUERY);
434
+ const sync = () => setModal(mql.matches);
435
+ sync();
436
+ mql.addEventListener("change", sync);
437
+ return () => mql.removeEventListener("change", sync);
438
+ }, []);
439
+ return modal;
440
+ }
408
441
  function ChatPanel({ controller, ui }) {
409
442
  const { state, close, stop, send } = controller;
410
- (0, import_react4.useEffect)(() => {
443
+ const isModal = useIsModal();
444
+ (0, import_react5.useEffect)(() => {
411
445
  const onKey = (e) => {
412
446
  if (e.key === "Escape") close();
413
447
  };
414
448
  window.addEventListener("keydown", onKey);
415
449
  return () => window.removeEventListener("keydown", onKey);
416
450
  }, [close]);
417
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": "true", "aria-label": "SensibleStats chat", children: [
451
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": "SensibleStats chat", children: [
418
452
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ChatHeader, { onClose: close }),
419
453
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MessageList, { controller, ui }),
420
454
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop })
@@ -422,7 +456,7 @@ function ChatPanel({ controller, ui }) {
422
456
  }
423
457
 
424
458
  // src/styles/css.generated.ts
425
- var WIDGET_CSS = '.ss-w-root { all: revert; }\n.ss-w-root, .ss-w-root * { box-sizing: border-box; margin: 0; padding: 0; }\n.ss-w-root {\n --ss-w-accent: #17936a;\n --ss-w-ink: #14181d; --ss-w-faint: #6b7580; --ss-w-line: #e4e8ec;\n --ss-w-panel: #ffffff; --ss-w-bubble: #f3f5f7; --ss-w-nav: #0e1622;\n --ss-w-radius: 16px; --ss-w-offset-x: 20px; --ss-w-offset-y: 20px;\n font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;\n font-size: 13px; line-height: 1.5; color: var(--ss-w-ink);\n}\n@media (prefers-color-scheme: dark) {\n .ss-w-root[data-ss-w-theme="auto"] {\n --ss-w-accent: #37c891; --ss-w-ink: #e8edf2; --ss-w-faint: #8b95a1;\n --ss-w-line: #2a3441; --ss-w-panel: #141b24; --ss-w-bubble: #1e2732; --ss-w-nav: #0a0f16;\n }\n}\n.ss-w-root[data-ss-w-theme="dark"] {\n --ss-w-accent: #37c891; --ss-w-ink: #e8edf2; --ss-w-faint: #8b95a1;\n --ss-w-line: #2a3441; --ss-w-panel: #141b24; --ss-w-bubble: #1e2732; --ss-w-nav: #0a0f16;\n}\n.ss-w-root[data-ss-w-theme="light"] {\n --ss-w-accent: #17936a; --ss-w-ink: #14181d; --ss-w-faint: #6b7580;\n --ss-w-line: #e4e8ec; --ss-w-panel: #ffffff; --ss-w-bubble: #f3f5f7; --ss-w-nav: #0e1622;\n}\n\n/* launcher */\n.ss-w-fab-wrap { position: fixed; bottom: var(--ss-w-offset-y); z-index: 2147483000; display: flex; flex-direction: column; align-items: flex-end; gap: 10px; }\n.ss-w-root[data-ss-w-pos="right"] .ss-w-fab-wrap { right: var(--ss-w-offset-x); }\n.ss-w-root[data-ss-w-pos="left"] .ss-w-fab-wrap { left: var(--ss-w-offset-x); align-items: flex-start; }\n.ss-w-fab { width: 56px; height: 56px; border: none; border-radius: 50%; background: var(--ss-w-nav); color: #fff; cursor: pointer; display: grid; place-items: center; box-shadow: 0 6px 20px rgba(0,0,0,.28); }\n.ss-w-fab-mark { font-size: 30px; }\n.ss-w-greeting { max-width: 220px; background: var(--ss-w-panel); color: var(--ss-w-ink); border: 1px solid var(--ss-w-line); border-radius: 12px; padding: 9px 12px; box-shadow: 0 4px 14px rgba(0,0,0,.14); }\n\n/* panel */\n.ss-w-panel { position: fixed; bottom: calc(var(--ss-w-offset-y) + 72px); z-index: 2147483000; display: flex; flex-direction: column; width: min(400px, calc(100vw - 40px)); height: min(620px, calc(100vh - 120px)); background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: var(--ss-w-radius); overflow: hidden; box-shadow: 0 12px 40px rgba(0,0,0,.24); }\n.ss-w-root[data-ss-w-pos="right"] .ss-w-panel { right: var(--ss-w-offset-x); }\n.ss-w-root[data-ss-w-pos="left"] .ss-w-panel { left: var(--ss-w-offset-x); }\n@media (max-width: 639px) {\n .ss-w-panel { inset: 0; width: 100vw; height: 100vh; border: none; border-radius: 0; }\n}\n.ss-w-header { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--ss-w-line); }\n.ss-w-header-mark { font-size: 18px; }\n.ss-w-header-title { font-weight: 660; }\n.ss-w-close { margin-left: auto; background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 16px; display: grid; place-items: center; }\n\n/* messages */\n.ss-w-list { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 12px; }\n.ss-w-msg { display: flex; }\n.ss-w-user { justify-content: flex-end; }\n.ss-w-user .ss-w-bubble { 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)); border-radius: 14px; padding: 8px 12px; max-width: 80%; }\n.ss-w-bot { flex-direction: column; gap: 8px; }\n.ss-w-status { font-size: 11px; color: var(--ss-w-faint); font-style: italic; }\n.ss-w-error { display: flex; align-items: center; gap: 8px; font-size: 12px; color: #c0392b; }\n.ss-w-retry { background: none; border: 1px solid currentColor; border-radius: 10px; padding: 2px 8px; cursor: pointer; color: inherit; }\n\n/* markdown */\n.ss-w-md { display: flex; flex-direction: column; gap: 8px; }\n.ss-w-md :where(p, ul, ol, table, pre, blockquote, h1, h2, h3, h4) { margin: 0; }\n.ss-w-md > *:first-child { margin-top: 0; }\n.ss-w-md > *:last-child { margin-bottom: 0; }\n.ss-w-md-ul { list-style: disc outside; padding-left: 18px; }\n.ss-w-md-ol { list-style: decimal outside; padding-left: 18px; }\n.ss-w-md-ul li::marker, .ss-w-md-ol li::marker { color: var(--ss-w-accent); }\n.ss-w-md-link { color: var(--ss-w-accent); text-decoration: underline; }\n.ss-w-table-wrap { overflow-x: auto; }\n.ss-w-md-table { border-collapse: collapse; width: 100%; font-variant-numeric: tabular-nums; }\n.ss-w-md-table td, .ss-w-md-table th { border: 1px solid var(--ss-w-line); padding: 4px 8px; text-align: left; }\n.ss-w-md-table tr:nth-child(even) { background: var(--ss-w-bubble); }\n.ss-w-md-code { background: var(--ss-w-bubble); border-radius: 5px; padding: 1px 5px; font-family: ui-monospace, monospace; }\n\n/* entity cards */\n.ss-w-cards { display: flex; flex-direction: column; gap: 7px; }\n.ss-w-ecard { display: flex; align-items: center; gap: 11px; width: 100%; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; padding: 8px 11px; text-align: left; cursor: pointer; }\n.ss-w-thumb { width: 40px; height: 40px; flex: none; border-radius: 50%; display: grid; place-items: center; font-size: 13px; font-weight: 700; color: #fff; background-color: var(--ss-w-nav); background-size: cover; background-position: center; }\n.ss-w-thumb.ss-w-team { border-radius: 10px; }\n.ss-w-meta { display: flex; flex-direction: column; min-width: 0; }\n.ss-w-nm { font-weight: 660; }\n.ss-w-sub { font-size: 11px; color: var(--ss-w-faint); }\n.ss-w-stat { margin-left: auto; text-align: center; }\n.ss-w-stat b { display: block; font-variant-numeric: tabular-nums; }\n.ss-w-stat span { font-size: 9px; text-transform: uppercase; color: var(--ss-w-faint); }\n.ss-w-chev { color: var(--ss-w-faint); font-size: 16px; display: grid; place-items: center; }\n\n/* action buttons */\n.ss-w-actions { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }\n.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; }\n.ss-w-abtn-shimmer { width: 140px; height: 30px; border-radius: 16px; background: linear-gradient(90deg, var(--ss-w-bubble), var(--ss-w-line), var(--ss-w-bubble)); background-size: 200% 100%; animation: ss-w-shimmer 1.2s infinite; }\n@keyframes ss-w-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }\n\n/* intent chips */\n.ss-w-chips { display: flex; flex-wrap: wrap; gap: 6px; }\n.ss-w-chip { font-size: 11.5px; font-weight: 550; color: var(--ss-w-ink); background: var(--ss-w-bubble); border: 1px solid var(--ss-w-line); border-radius: 14px; padding: 5px 11px; cursor: pointer; }\n\n/* input */\n.ss-w-input { display: flex; align-items: flex-end; gap: 8px; padding: 10px 12px; border-top: 1px solid var(--ss-w-line); }\n.ss-w-textarea { flex: 1; resize: none; border: 1px solid var(--ss-w-line); border-radius: 12px; padding: 8px 10px; font: inherit; color: inherit; background: var(--ss-w-panel); max-height: 96px; }\n.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; }\n.ss-w-stop-glyph { width: 11px; height: 11px; background: currentColor; border-radius: 2px; }\n\n@media (prefers-reduced-motion: reduce) {\n .ss-w-abtn-shimmer { animation: none; }\n}\n';
459
+ var WIDGET_CSS = '.ss-w-root { all: revert; }\n.ss-w-root, .ss-w-root * { box-sizing: border-box; margin: 0; padding: 0; }\n.ss-w-root {\n --ss-w-accent: #17936a;\n --ss-w-ink: #14181d; --ss-w-faint: #6b7580; --ss-w-line: #e4e8ec;\n --ss-w-panel: #ffffff; --ss-w-bubble: #f3f5f7; --ss-w-nav: #0e1622;\n --ss-w-radius: 16px; --ss-w-offset-x: 20px; --ss-w-offset-y: 20px;\n font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;\n font-size: 13px; line-height: 1.5; color: var(--ss-w-ink);\n}\n@media (prefers-color-scheme: dark) {\n .ss-w-root[data-ss-w-theme="auto"] {\n --ss-w-accent: #37c891; --ss-w-ink: #e8edf2; --ss-w-faint: #8b95a1;\n --ss-w-line: #2a3441; --ss-w-panel: #141b24; --ss-w-bubble: #1e2732; --ss-w-nav: #0a0f16;\n }\n}\n.ss-w-root[data-ss-w-theme="dark"] {\n --ss-w-accent: #37c891; --ss-w-ink: #e8edf2; --ss-w-faint: #8b95a1;\n --ss-w-line: #2a3441; --ss-w-panel: #141b24; --ss-w-bubble: #1e2732; --ss-w-nav: #0a0f16;\n}\n.ss-w-root[data-ss-w-theme="light"] {\n --ss-w-accent: #17936a; --ss-w-ink: #14181d; --ss-w-faint: #6b7580;\n --ss-w-line: #e4e8ec; --ss-w-panel: #ffffff; --ss-w-bubble: #f3f5f7; --ss-w-nav: #0e1622;\n}\n\n/* launcher */\n.ss-w-fab-wrap { position: fixed; bottom: var(--ss-w-offset-y); z-index: 2147483000; display: flex; flex-direction: column; align-items: flex-end; gap: 10px; }\n.ss-w-root[data-ss-w-pos="right"] .ss-w-fab-wrap { right: var(--ss-w-offset-x); }\n.ss-w-root[data-ss-w-pos="left"] .ss-w-fab-wrap { left: var(--ss-w-offset-x); align-items: flex-start; }\n.ss-w-fab { width: 56px; height: 56px; border: none; border-radius: 50%; background: var(--ss-w-nav); color: #fff; cursor: pointer; display: grid; place-items: center; box-shadow: 0 6px 20px rgba(0,0,0,.28); }\n.ss-w-fab-mark { font-size: 30px; }\n.ss-w-greeting { max-width: 220px; background: var(--ss-w-panel); color: var(--ss-w-ink); border: 1px solid var(--ss-w-line); border-radius: 12px; padding: 9px 12px; box-shadow: 0 4px 14px rgba(0,0,0,.14); }\n\n/* panel */\n.ss-w-panel { position: fixed; bottom: calc(var(--ss-w-offset-y) + 72px); z-index: 2147483000; display: flex; flex-direction: column; width: min(400px, calc(100vw - 40px)); height: min(620px, calc(100vh - 120px)); background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: var(--ss-w-radius); overflow: hidden; box-shadow: 0 12px 40px rgba(0,0,0,.24); }\n.ss-w-root[data-ss-w-pos="right"] .ss-w-panel { right: var(--ss-w-offset-x); }\n.ss-w-root[data-ss-w-pos="left"] .ss-w-panel { left: var(--ss-w-offset-x); }\n@media (max-width: 639px) {\n .ss-w-panel { inset: 0; width: 100vw; height: 100vh; border: none; border-radius: 0; }\n}\n.ss-w-header { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--ss-w-line); }\n.ss-w-header-mark { font-size: 18px; }\n.ss-w-header-title { font-weight: 660; }\n.ss-w-close { margin-left: auto; background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 16px; display: grid; place-items: center; }\n\n/* messages */\n.ss-w-list { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 12px; }\n.ss-w-msg { display: flex; }\n.ss-w-user { justify-content: flex-end; }\n.ss-w-user .ss-w-bubble { 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)); border-radius: 14px; padding: 8px 12px; max-width: 80%; }\n.ss-w-bot { flex-direction: column; gap: 8px; }\n.ss-w-status { font-size: 11px; color: var(--ss-w-faint); font-style: italic; }\n.ss-w-error { display: flex; align-items: center; gap: 8px; font-size: 12px; color: #c0392b; }\n.ss-w-retry { background: none; border: 1px solid currentColor; border-radius: 10px; padding: 2px 8px; cursor: pointer; color: inherit; }\n\n/* markdown */\n.ss-w-md { display: flex; flex-direction: column; gap: 8px; }\n.ss-w-md :where(p, ul, ol, table, pre, blockquote, h1, h2, h3, h4) { margin: 0; }\n.ss-w-md > *:first-child { margin-top: 0; }\n.ss-w-md > *:last-child { margin-bottom: 0; }\n.ss-w-md-ul { list-style: disc outside; padding-left: 18px; }\n.ss-w-md-ol { list-style: decimal outside; padding-left: 18px; }\n.ss-w-md-ul li::marker, .ss-w-md-ol li::marker { color: var(--ss-w-accent); }\n.ss-w-md-link { color: var(--ss-w-accent); text-decoration: underline; }\n.ss-w-table-wrap { overflow-x: auto; max-width: 100%; -webkit-overflow-scrolling: touch; }\n.ss-w-md-table { border-collapse: collapse; width: max-content; min-width: 100%; font-variant-numeric: tabular-nums; }\n.ss-w-md-table td, .ss-w-md-table th { border: 1px solid var(--ss-w-line); padding: 4px 8px; text-align: left; white-space: nowrap; }\n.ss-w-md-table th { background: color-mix(in srgb, var(--ss-w-accent) 12%, var(--ss-w-panel)); font-weight: 700; }\n.ss-w-md-table tbody tr:nth-child(even) { background: var(--ss-w-bubble); }\n.ss-w-md-code { background: var(--ss-w-bubble); border-radius: 5px; padding: 1px 5px; font-family: ui-monospace, monospace; }\n\n/* entity cards */\n.ss-w-cards { display: flex; flex-direction: column; gap: 7px; }\n.ss-w-ecard { display: flex; align-items: center; gap: 11px; width: 100%; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; padding: 8px 11px; text-align: left; cursor: pointer; }\n.ss-w-thumb { width: 40px; height: 40px; flex: none; border-radius: 50%; display: grid; place-items: center; font-size: 13px; font-weight: 700; color: #fff; background-color: var(--ss-w-nav); background-size: cover; background-position: center; }\n.ss-w-thumb.ss-w-team { border-radius: 10px; }\n.ss-w-ecard[data-kind="player"] .ss-w-thumb { background-color: var(--ss-w-accent); }\n.ss-w-meta { display: flex; flex-direction: column; min-width: 0; }\n.ss-w-kind { font-size: 8.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--ss-w-faint); line-height: 1.3; }\n.ss-w-nm { font-weight: 660; }\n.ss-w-sub { font-size: 11px; color: var(--ss-w-faint); }\n.ss-w-stat { margin-left: auto; text-align: center; }\n.ss-w-stat b { display: block; font-variant-numeric: tabular-nums; }\n.ss-w-stat span { font-size: 9px; text-transform: uppercase; color: var(--ss-w-faint); }\n.ss-w-chev { color: var(--ss-w-faint); font-size: 16px; display: grid; place-items: center; }\n.ss-w-cards-more { align-self: flex-start; font-size: 11.5px; font-weight: 560; color: var(--ss-w-accent); background: none; border: none; cursor: pointer; padding: 3px 2px; }\n.ss-w-cards-more:hover:not(:disabled) { text-decoration: underline; }\n.ss-w-ecard:disabled, .ss-w-cards-more:disabled { opacity: .5; cursor: default; }\n\n/* action buttons */\n.ss-w-actions { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }\n.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; }\n.ss-w-abtn:disabled { opacity: .5; cursor: default; }\n.ss-w-abtn-shimmer { width: 140px; height: 30px; border-radius: 16px; background: linear-gradient(90deg, var(--ss-w-bubble), var(--ss-w-line), var(--ss-w-bubble)); background-size: 200% 100%; animation: ss-w-shimmer 1.2s infinite; }\n@keyframes ss-w-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }\n\n/* intent chips */\n.ss-w-chips { display: flex; flex-wrap: wrap; gap: 6px; }\n.ss-w-chip { font-size: 11.5px; font-weight: 550; color: var(--ss-w-ink); background: var(--ss-w-bubble); border: 1px solid var(--ss-w-line); border-radius: 14px; padding: 5px 11px; cursor: pointer; }\n\n/* input */\n.ss-w-input { display: flex; align-items: flex-end; gap: 8px; padding: 10px 12px; border-top: 1px solid var(--ss-w-line); }\n.ss-w-textarea { flex: 1; resize: none; border: 1px solid var(--ss-w-line); border-radius: 12px; padding: 8px 10px; font: inherit; color: inherit; background: var(--ss-w-panel); max-height: 96px; }\n.ss-w-textarea:disabled { opacity: .55; cursor: not-allowed; }\n.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; }\n.ss-w-stop-glyph { width: 11px; height: 11px; background: currentColor; border-radius: 2px; }\n\n@media (prefers-reduced-motion: reduce) {\n .ss-w-abtn-shimmer { animation: none; }\n}\n';
426
460
 
427
461
  // src/StatsWidget.tsx
428
462
  var import_jsx_runtime13 = require("react/jsx-runtime");
@@ -450,11 +484,11 @@ function injectStyles(root) {
450
484
  function StatsWidget(props) {
451
485
  const injectedClient = "client" in props ? props.client : void 0;
452
486
  const cfg = props.config;
453
- const built = (0, import_react5.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
454
- const ui = (0, import_react5.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
455
- const rootRef = (0, import_react5.useRef)(null);
487
+ const built = (0, import_react6.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
488
+ const ui = (0, import_react6.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
489
+ const rootRef = (0, import_react6.useRef)(null);
456
490
  const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters);
457
- (0, import_react5.useEffect)(() => {
491
+ (0, import_react6.useEffect)(() => {
458
492
  if (!built || !ui.injectStyles) return;
459
493
  const node = rootRef.current?.getRootNode();
460
494
  if (node) injectStyles(node);