@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.js
CHANGED
|
@@ -51,38 +51,33 @@ function str(v) {
|
|
|
51
51
|
function num(v) {
|
|
52
52
|
return typeof v === "number" && Number.isFinite(v) ? String(v) : void 0;
|
|
53
53
|
}
|
|
54
|
-
var STAT_KEYS = [
|
|
55
|
-
["goals", "Goals"],
|
|
56
|
-
["assists", "Assists"],
|
|
57
|
-
["appearances", "Apps"],
|
|
58
|
-
["xg", "xG"],
|
|
59
|
-
["form", "Form"],
|
|
60
|
-
["rating", "Rating"]
|
|
61
|
-
];
|
|
62
54
|
function readStats(card) {
|
|
55
|
+
if (!Array.isArray(card.stats)) return [];
|
|
63
56
|
const cells = [];
|
|
64
|
-
for (const
|
|
65
|
-
const
|
|
66
|
-
|
|
57
|
+
for (const entry of card.stats) {
|
|
58
|
+
const label = str(entry.label);
|
|
59
|
+
const value = num(entry.value) ?? str(entry.value);
|
|
60
|
+
if (label !== void 0 && value !== void 0) cells.push({ label, value });
|
|
67
61
|
}
|
|
68
62
|
return cells;
|
|
69
63
|
}
|
|
70
64
|
function toCardVM(card) {
|
|
71
|
-
const
|
|
65
|
+
const entityType = str(card.entityType)?.toLowerCase();
|
|
66
|
+
const kind = entityType === "player" ? "player" : entityType === "team" ? "team" : "generic";
|
|
72
67
|
return {
|
|
73
68
|
id: String(card.entityId),
|
|
74
69
|
kind,
|
|
75
70
|
name: card.canonicalName,
|
|
76
|
-
image: str(card.imageUrl) ?? str(card.image_url) ?? str(card.logoUrl) ?? str(card.logo),
|
|
71
|
+
image: str(card.imagePath) ?? str(card.imageUrl) ?? str(card.image_url) ?? str(card.logoUrl) ?? str(card.logo),
|
|
77
72
|
sub: str(card.subtitle) ?? str(card.teamName) ?? str(card.leagueName) ?? str(card.position),
|
|
78
73
|
stats: readStats(card),
|
|
79
74
|
query: card.canonicalName
|
|
80
75
|
};
|
|
81
76
|
}
|
|
82
77
|
function toActionVM(action) {
|
|
83
|
-
const label = str(action.label) ?? str(action.text) ?? str(action.title) ?? "Continue";
|
|
84
|
-
const actionId = str(action.
|
|
85
|
-
const message = str(action.message) ?? str(action.query) ?? label;
|
|
78
|
+
const label = str(action.actionLabel) ?? str(action.label) ?? str(action.text) ?? str(action.title) ?? "Continue";
|
|
79
|
+
const actionId = str(action.actionId) ?? str(action.id);
|
|
80
|
+
const message = str(action.actionValue) ?? str(action.message) ?? str(action.query) ?? label;
|
|
86
81
|
return { label, actionId, message };
|
|
87
82
|
}
|
|
88
83
|
function toChipVM(intent) {
|
|
@@ -95,7 +90,7 @@ function chipFromText(text) {
|
|
|
95
90
|
|
|
96
91
|
// src/reducer.ts
|
|
97
92
|
function initialState(starters) {
|
|
98
|
-
return { isOpen: false, messages: [], isStreaming: false, status: null, actionsLoading: false, starters, error: null, lastUserInput: null };
|
|
93
|
+
return { isOpen: false, messages: [], isStreaming: false, status: null, actionsLoading: false, starters, error: null, lastUserInput: null, lastDisplayText: null };
|
|
99
94
|
}
|
|
100
95
|
function mapActive(state, fn) {
|
|
101
96
|
const last = state.messages[state.messages.length - 1];
|
|
@@ -107,8 +102,9 @@ function applyEvent(state, event) {
|
|
|
107
102
|
case "progress":
|
|
108
103
|
return { ...state, status: event.data.message };
|
|
109
104
|
case "answer":
|
|
110
|
-
return { ...state, messages: mapActive(state, (t) => ({ ...t, text:
|
|
105
|
+
return { ...state, messages: mapActive(state, (t) => ({ ...t, text: event.data.text })) };
|
|
111
106
|
case "entity_card":
|
|
107
|
+
if (event.data.card.isUsed === false) return state;
|
|
112
108
|
return { ...state, messages: mapActive(state, (t) => ({ ...t, cards: [...t.cards, toCardVM(event.data.card)] })) };
|
|
113
109
|
case "action_button":
|
|
114
110
|
return { ...state, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, actions: [...t.actions, toActionVM(event.data.action)] })) };
|
|
@@ -131,9 +127,9 @@ function reducer(state, action) {
|
|
|
131
127
|
case "CLOSE":
|
|
132
128
|
return { ...state, isOpen: false, isStreaming: false, status: null, actionsLoading: false };
|
|
133
129
|
case "USER_SENT": {
|
|
134
|
-
const user = { role: "user", id: action.userId, text: action.input.message };
|
|
130
|
+
const user = { role: "user", id: action.userId, text: action.displayText ?? action.input.message };
|
|
135
131
|
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], done: false };
|
|
136
|
-
return { ...state, messages: [...state.messages, user, assistant], isStreaming: true, status: null, actionsLoading: false, starters: [], error: null, lastUserInput: action.input };
|
|
132
|
+
return { ...state, messages: [...state.messages, user, assistant], isStreaming: true, status: null, actionsLoading: false, starters: [], error: null, lastUserInput: action.input, lastDisplayText: action.displayText ?? null };
|
|
137
133
|
}
|
|
138
134
|
case "EVENT":
|
|
139
135
|
return applyEvent(state, action.event);
|
|
@@ -153,9 +149,9 @@ function useChatStream(conversation, starterTexts) {
|
|
|
153
149
|
const stateRef = useRef(state);
|
|
154
150
|
stateRef.current = state;
|
|
155
151
|
const nextId = () => `m${idRef.current++}`;
|
|
156
|
-
const run = useCallback((input) => {
|
|
152
|
+
const run = useCallback((input, displayText) => {
|
|
157
153
|
handleRef.current?.cancel();
|
|
158
|
-
dispatch({ kind: "USER_SENT", input, userId: nextId(), assistantId: nextId() });
|
|
154
|
+
dispatch({ kind: "USER_SENT", input, userId: nextId(), assistantId: nextId(), displayText });
|
|
159
155
|
const handle = conversation.send(input);
|
|
160
156
|
handleRef.current = handle;
|
|
161
157
|
(async () => {
|
|
@@ -170,14 +166,14 @@ function useChatStream(conversation, starterTexts) {
|
|
|
170
166
|
}
|
|
171
167
|
})();
|
|
172
168
|
}, [conversation]);
|
|
173
|
-
const send = useCallback((text,
|
|
169
|
+
const send = useCallback((text, opts) => {
|
|
174
170
|
const trimmed = text.trim();
|
|
175
171
|
if (!trimmed) return;
|
|
176
|
-
run({ message: trimmed, actionId });
|
|
172
|
+
run({ message: trimmed, actionId: opts?.actionId }, opts?.displayText);
|
|
177
173
|
}, [run]);
|
|
178
174
|
const retry = useCallback(() => {
|
|
179
175
|
const last = stateRef.current.lastUserInput;
|
|
180
|
-
if (last) run(last);
|
|
176
|
+
if (last) run(last, stateRef.current.lastDisplayText ?? void 0);
|
|
181
177
|
}, [run]);
|
|
182
178
|
const open = useCallback(() => dispatch({ kind: "OPEN" }), []);
|
|
183
179
|
const close = useCallback(() => {
|
|
@@ -198,9 +194,15 @@ function useChatStream(conversation, starterTexts) {
|
|
|
198
194
|
}
|
|
199
195
|
|
|
200
196
|
// src/components/Icons.tsx
|
|
201
|
-
import { jsx } from "react/jsx-runtime";
|
|
197
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
202
198
|
function SsMark({ className }) {
|
|
203
|
-
return /* @__PURE__ */ jsx("svg", { className, viewBox: "0 0
|
|
199
|
+
return /* @__PURE__ */ jsx("svg", { className, viewBox: "0 0 100 100", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ jsxs("g", { fill: "none", stroke: "#15c917", strokeWidth: "8.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
200
|
+
/* @__PURE__ */ jsx("circle", { cx: "50", cy: "44", r: "37" }),
|
|
201
|
+
/* @__PURE__ */ jsx("line", { x1: "15", y1: "44", x2: "85", y2: "44" }),
|
|
202
|
+
/* @__PURE__ */ jsx("path", { d: "M36 44 V24 H64 V44" }),
|
|
203
|
+
/* @__PURE__ */ jsx("path", { d: "M36 44 V63 H64 V44" }),
|
|
204
|
+
/* @__PURE__ */ jsx("path", { d: "M46 63 L43 96 L61 61" })
|
|
205
|
+
] }) });
|
|
204
206
|
}
|
|
205
207
|
function SendIcon() {
|
|
206
208
|
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { fill: "currentColor", d: "M3 20l18-8L3 4v6l12 2-12 2z" }) });
|
|
@@ -219,21 +221,21 @@ function GreetingBubble({ text }) {
|
|
|
219
221
|
}
|
|
220
222
|
|
|
221
223
|
// src/components/ChatFab.tsx
|
|
222
|
-
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
224
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
223
225
|
function ChatFab({ label, open, greeting, onToggle }) {
|
|
224
|
-
return /* @__PURE__ */
|
|
226
|
+
return /* @__PURE__ */ jsxs2("div", { className: "ss-w-fab-wrap", children: [
|
|
225
227
|
!open && greeting ? /* @__PURE__ */ jsx3(GreetingBubble, { text: greeting }) : null,
|
|
226
228
|
/* @__PURE__ */ jsx3("button", { type: "button", className: "ss-w-fab", "aria-label": label, "aria-expanded": open, onClick: onToggle, children: /* @__PURE__ */ jsx3(SsMark, { className: "ss-w-fab-mark" }) })
|
|
227
229
|
] });
|
|
228
230
|
}
|
|
229
231
|
|
|
230
232
|
// src/components/ChatPanel.tsx
|
|
231
|
-
import { useEffect as useEffect2 } from "react";
|
|
233
|
+
import { useEffect as useEffect2, useState as useState3 } from "react";
|
|
232
234
|
|
|
233
235
|
// src/components/ChatHeader.tsx
|
|
234
|
-
import { jsx as jsx4, jsxs as
|
|
236
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
235
237
|
function ChatHeader({ onClose }) {
|
|
236
|
-
return /* @__PURE__ */
|
|
238
|
+
return /* @__PURE__ */ jsxs3("header", { className: "ss-w-header", children: [
|
|
237
239
|
/* @__PURE__ */ jsx4(SsMark, { className: "ss-w-header-mark" }),
|
|
238
240
|
/* @__PURE__ */ jsx4("span", { className: "ss-w-header-title", children: "SensibleStats" }),
|
|
239
241
|
/* @__PURE__ */ jsx4("button", { type: "button", className: "ss-w-close", "aria-label": "Close chat", onClick: onClose, children: /* @__PURE__ */ jsx4(CloseIcon, {}) })
|
|
@@ -259,40 +261,49 @@ function Markdown({ text }) {
|
|
|
259
261
|
}
|
|
260
262
|
|
|
261
263
|
// src/components/EntityCardList.tsx
|
|
262
|
-
import {
|
|
264
|
+
import { useState } from "react";
|
|
265
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
266
|
+
var MAX_VISIBLE = 3;
|
|
263
267
|
function initials(name) {
|
|
264
268
|
return name.split(/\s+/).slice(0, 2).map((w) => w[0] ?? "").join("").toUpperCase();
|
|
265
269
|
}
|
|
266
|
-
function EntityCardList({ cards, onSelect }) {
|
|
270
|
+
function EntityCardList({ cards, disabled, onSelect }) {
|
|
271
|
+
const [expanded, setExpanded] = useState(false);
|
|
267
272
|
if (cards.length === 0) return null;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
273
|
+
const visible = expanded ? cards : cards.slice(0, MAX_VISIBLE);
|
|
274
|
+
const overflow = cards.length - MAX_VISIBLE;
|
|
275
|
+
return /* @__PURE__ */ jsxs4("div", { className: "ss-w-cards", children: [
|
|
276
|
+
visible.map((c) => /* @__PURE__ */ jsxs4("button", { type: "button", className: "ss-w-ecard", "data-kind": c.kind, disabled, onClick: () => onSelect(c.query), children: [
|
|
277
|
+
/* @__PURE__ */ jsx6(
|
|
278
|
+
"span",
|
|
279
|
+
{
|
|
280
|
+
className: c.kind === "team" ? "ss-w-thumb ss-w-team" : "ss-w-thumb",
|
|
281
|
+
style: c.image ? { backgroundImage: `url(${c.image})` } : void 0,
|
|
282
|
+
children: c.image ? "" : initials(c.name)
|
|
283
|
+
}
|
|
284
|
+
),
|
|
285
|
+
/* @__PURE__ */ jsxs4("span", { className: "ss-w-meta", children: [
|
|
286
|
+
c.kind !== "generic" ? /* @__PURE__ */ jsx6("span", { className: "ss-w-kind", children: c.kind }) : null,
|
|
287
|
+
/* @__PURE__ */ jsx6("span", { className: "ss-w-nm", children: c.name }),
|
|
288
|
+
c.sub ? /* @__PURE__ */ jsx6("span", { className: "ss-w-sub", children: c.sub }) : null
|
|
289
|
+
] }),
|
|
290
|
+
c.stats.map((s) => /* @__PURE__ */ jsxs4("span", { className: "ss-w-stat", children: [
|
|
291
|
+
/* @__PURE__ */ jsx6("b", { children: s.value }),
|
|
292
|
+
/* @__PURE__ */ jsx6("span", { children: s.label })
|
|
293
|
+
] }, s.label)),
|
|
294
|
+
/* @__PURE__ */ jsx6("span", { className: "ss-w-chev", children: /* @__PURE__ */ jsx6(ChevronIcon, {}) })
|
|
295
|
+
] }, c.id)),
|
|
296
|
+
overflow > 0 ? /* @__PURE__ */ jsx6("button", { type: "button", className: "ss-w-cards-more", disabled, onClick: () => setExpanded((v) => !v), children: expanded ? "Show less" : `Show ${overflow} more` }) : null
|
|
297
|
+
] });
|
|
287
298
|
}
|
|
288
299
|
|
|
289
300
|
// src/components/ActionButtons.tsx
|
|
290
|
-
import { Fragment, jsx as jsx7, jsxs as
|
|
291
|
-
function ActionButtons({ actions, loading, onAct }) {
|
|
301
|
+
import { Fragment, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
302
|
+
function ActionButtons({ actions, loading, disabled, onAct }) {
|
|
292
303
|
if (actions.length === 0 && !loading) return null;
|
|
293
|
-
return /* @__PURE__ */
|
|
294
|
-
actions.map((a, i) => /* @__PURE__ */ jsx7("button", { type: "button", className: "ss-w-abtn", onClick: () => onAct(a
|
|
295
|
-
actions.length === 0 && loading ? /* @__PURE__ */
|
|
304
|
+
return /* @__PURE__ */ jsxs5("div", { className: "ss-w-actions", children: [
|
|
305
|
+
actions.map((a, i) => /* @__PURE__ */ jsx7("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
|
|
306
|
+
actions.length === 0 && loading ? /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
296
307
|
/* @__PURE__ */ jsx7("span", { className: "ss-w-abtn-shimmer" }),
|
|
297
308
|
/* @__PURE__ */ jsx7("span", { className: "ss-w-abtn-shimmer" })
|
|
298
309
|
] }) : null
|
|
@@ -300,15 +311,15 @@ function ActionButtons({ actions, loading, onAct }) {
|
|
|
300
311
|
}
|
|
301
312
|
|
|
302
313
|
// src/components/ChatMessage.tsx
|
|
303
|
-
import { jsx as jsx8, jsxs as
|
|
304
|
-
function ChatMessage({ message, onAct, onSelect }) {
|
|
314
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
315
|
+
function ChatMessage({ message, disabled, onAct, onSelect }) {
|
|
305
316
|
if (message.role === "user") {
|
|
306
317
|
return /* @__PURE__ */ jsx8("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx8("div", { className: "ss-w-bubble", children: message.text }) });
|
|
307
318
|
}
|
|
308
|
-
return /* @__PURE__ */
|
|
319
|
+
return /* @__PURE__ */ jsxs6("div", { className: "ss-w-msg ss-w-bot", children: [
|
|
309
320
|
message.text ? /* @__PURE__ */ jsx8(Markdown, { text: message.text }) : null,
|
|
310
|
-
/* @__PURE__ */ jsx8(EntityCardList, { cards: message.cards, onSelect }),
|
|
311
|
-
/* @__PURE__ */ jsx8(ActionButtons, { actions: message.actions, loading: false, onAct })
|
|
321
|
+
/* @__PURE__ */ jsx8(EntityCardList, { cards: message.cards, disabled, onSelect }),
|
|
322
|
+
/* @__PURE__ */ jsx8(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
|
|
312
323
|
] });
|
|
313
324
|
}
|
|
314
325
|
|
|
@@ -320,7 +331,7 @@ function IntentChips({ chips, onPick }) {
|
|
|
320
331
|
}
|
|
321
332
|
|
|
322
333
|
// src/components/MessageList.tsx
|
|
323
|
-
import { jsx as jsx10, jsxs as
|
|
334
|
+
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
324
335
|
function MessageList({ controller, ui }) {
|
|
325
336
|
void ui;
|
|
326
337
|
const { state, send } = controller;
|
|
@@ -329,12 +340,21 @@ function MessageList({ controller, ui }) {
|
|
|
329
340
|
endRef.current?.scrollIntoView({ block: "end" });
|
|
330
341
|
}, [state.messages, state.status]);
|
|
331
342
|
const empty = state.messages.length === 0;
|
|
332
|
-
return /* @__PURE__ */
|
|
333
|
-
state.messages.map((m) => /* @__PURE__ */ jsx10(
|
|
343
|
+
return /* @__PURE__ */ jsxs7("div", { className: "ss-w-list", "aria-live": "polite", children: [
|
|
344
|
+
state.messages.map((m) => /* @__PURE__ */ jsx10(
|
|
345
|
+
ChatMessage,
|
|
346
|
+
{
|
|
347
|
+
message: m,
|
|
348
|
+
disabled: state.isStreaming,
|
|
349
|
+
onAct: (a) => send(a.message, { actionId: a.actionId, displayText: a.label }),
|
|
350
|
+
onSelect: (q) => send(q)
|
|
351
|
+
},
|
|
352
|
+
m.id
|
|
353
|
+
)),
|
|
334
354
|
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx10(ActionButtons, { actions: [], loading: true, onAct: () => {
|
|
335
355
|
} }) : null,
|
|
336
356
|
state.status ? /* @__PURE__ */ jsx10("div", { className: "ss-w-status", children: state.status }) : null,
|
|
337
|
-
state.error ? /* @__PURE__ */
|
|
357
|
+
state.error ? /* @__PURE__ */ jsxs7("div", { className: "ss-w-error", role: "alert", children: [
|
|
338
358
|
/* @__PURE__ */ jsx10("span", { children: "message" in state.error ? state.error.message : "Something went wrong." }),
|
|
339
359
|
/* @__PURE__ */ jsx10("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: "Retry" })
|
|
340
360
|
] }) : null,
|
|
@@ -344,10 +364,10 @@ function MessageList({ controller, ui }) {
|
|
|
344
364
|
}
|
|
345
365
|
|
|
346
366
|
// src/components/ChatInput.tsx
|
|
347
|
-
import { useState } from "react";
|
|
348
|
-
import { jsx as jsx11, jsxs as
|
|
367
|
+
import { useState as useState2 } from "react";
|
|
368
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
349
369
|
function ChatInput({ placeholder, streaming, onSend, onStop }) {
|
|
350
|
-
const [value, setValue] =
|
|
370
|
+
const [value, setValue] = useState2("");
|
|
351
371
|
const submit = () => {
|
|
352
372
|
const t = value.trim();
|
|
353
373
|
if (!t) return;
|
|
@@ -360,16 +380,30 @@ function ChatInput({ placeholder, streaming, onSend, onStop }) {
|
|
|
360
380
|
submit();
|
|
361
381
|
}
|
|
362
382
|
};
|
|
363
|
-
return /* @__PURE__ */
|
|
364
|
-
/* @__PURE__ */ jsx11("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, onChange: (e) => setValue(e.target.value), onKeyDown }),
|
|
383
|
+
return /* @__PURE__ */ jsxs8("div", { className: "ss-w-input", children: [
|
|
384
|
+
/* @__PURE__ */ jsx11("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
|
|
365
385
|
streaming ? /* @__PURE__ */ jsx11("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": "Stop response", onClick: onStop, children: /* @__PURE__ */ jsx11("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx11("button", { type: "button", className: "ss-w-send", "aria-label": "Send message", onClick: submit, children: /* @__PURE__ */ jsx11(SendIcon, {}) })
|
|
366
386
|
] });
|
|
367
387
|
}
|
|
368
388
|
|
|
369
389
|
// src/components/ChatPanel.tsx
|
|
370
|
-
import { jsx as jsx12, jsxs as
|
|
390
|
+
import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
391
|
+
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
392
|
+
function useIsModal() {
|
|
393
|
+
const [modal, setModal] = useState3(false);
|
|
394
|
+
useEffect2(() => {
|
|
395
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
396
|
+
const mql = window.matchMedia(FULLSCREEN_QUERY);
|
|
397
|
+
const sync = () => setModal(mql.matches);
|
|
398
|
+
sync();
|
|
399
|
+
mql.addEventListener("change", sync);
|
|
400
|
+
return () => mql.removeEventListener("change", sync);
|
|
401
|
+
}, []);
|
|
402
|
+
return modal;
|
|
403
|
+
}
|
|
371
404
|
function ChatPanel({ controller, ui }) {
|
|
372
405
|
const { state, close, stop, send } = controller;
|
|
406
|
+
const isModal = useIsModal();
|
|
373
407
|
useEffect2(() => {
|
|
374
408
|
const onKey = (e) => {
|
|
375
409
|
if (e.key === "Escape") close();
|
|
@@ -377,7 +411,7 @@ function ChatPanel({ controller, ui }) {
|
|
|
377
411
|
window.addEventListener("keydown", onKey);
|
|
378
412
|
return () => window.removeEventListener("keydown", onKey);
|
|
379
413
|
}, [close]);
|
|
380
|
-
return /* @__PURE__ */
|
|
414
|
+
return /* @__PURE__ */ jsxs9("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": "SensibleStats chat", children: [
|
|
381
415
|
/* @__PURE__ */ jsx12(ChatHeader, { onClose: close }),
|
|
382
416
|
/* @__PURE__ */ jsx12(MessageList, { controller, ui }),
|
|
383
417
|
/* @__PURE__ */ jsx12(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop })
|
|
@@ -385,10 +419,10 @@ function ChatPanel({ controller, ui }) {
|
|
|
385
419
|
}
|
|
386
420
|
|
|
387
421
|
// src/styles/css.generated.ts
|
|
388
|
-
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';
|
|
422
|
+
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';
|
|
389
423
|
|
|
390
424
|
// src/StatsWidget.tsx
|
|
391
|
-
import { jsx as jsx13, jsxs as
|
|
425
|
+
import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
392
426
|
function buildClient(props) {
|
|
393
427
|
try {
|
|
394
428
|
if ("client" in props && props.client) {
|
|
@@ -424,7 +458,7 @@ function StatsWidget(props) {
|
|
|
424
458
|
}, [built, ui.injectStyles]);
|
|
425
459
|
if (!built) return null;
|
|
426
460
|
const attrs = themeAttrs(ui);
|
|
427
|
-
return /* @__PURE__ */
|
|
461
|
+
return /* @__PURE__ */ jsxs10("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: [
|
|
428
462
|
controller.state.isOpen ? /* @__PURE__ */ jsx13(ChatPanel, { controller, ui }) : null,
|
|
429
463
|
/* @__PURE__ */ jsx13(ChatFab, { label: ui.launcherLabel, open: controller.state.isOpen, greeting: ui.greeting, onToggle: controller.toggle })
|
|
430
464
|
] });
|