@sensiblestats/widget-react 0.0.0 → 0.1.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/dist/index.cjs +101 -67
- 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 +111 -77
- package/dist/index.js.map +1 -1
- package/dist/styles.css +12 -4
- package/package.json +1 -1
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_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
|
|
102
|
-
const
|
|
103
|
-
|
|
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
|
|
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.
|
|
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:
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
|
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,
|
|
365
|
-
(0,
|
|
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)(
|
|
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
|
|
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,
|
|
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
|
-
|
|
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":
|
|
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,
|
|
454
|
-
const ui = (0,
|
|
455
|
-
const rootRef = (0,
|
|
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,
|
|
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);
|