@sensiblestats/widget-react 0.13.1 → 0.14.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 +22 -0
- package/dist/index.cjs +216 -44
- 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 +209 -37
- package/dist/index.js.map +1 -1
- package/dist/styles.css +68 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @sensiblestats/widget-react
|
|
2
2
|
|
|
3
|
+
## 0.14.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5dce658: Greeting dashboard UX refinements from design review: tile icons are now
|
|
8
|
+
emoji (matching the betting mockup) rendered at a larger size instead of the
|
|
9
|
+
small monochrome line glyphs; the greeting bubble text is suppressed when a
|
|
10
|
+
dashboard is present so the question shows once (as the dashboard title)
|
|
11
|
+
rather than twice; and the discovery full-span trends tile aligns its icon
|
|
12
|
+
and title on a single row.
|
|
13
|
+
|
|
14
|
+
## 0.14.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- Render the greeting dashboard carried on `turn_complete`: tile grid, CSS, and tap handling that turns a tile into a follow-up chat message. Older servers that never send `dashboard` render nothing new. `widget-embed` also now generates and persists a first-party anonymous visitor id (localStorage, falls back to in-memory) so greeting dashboards can be tiered for anonymous visitors.
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Updated dependencies
|
|
23
|
+
- @sensiblestats/widget-sdk@0.12.0
|
|
24
|
+
|
|
3
25
|
## 0.13.1
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -37,7 +37,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
37
37
|
|
|
38
38
|
// src/StatsWidget.tsx
|
|
39
39
|
var import_react10 = require("react");
|
|
40
|
-
var
|
|
40
|
+
var import_widget_sdk5 = require("@sensiblestats/widget-sdk");
|
|
41
41
|
|
|
42
42
|
// src/i18n.ts
|
|
43
43
|
var STRINGS = {
|
|
@@ -205,6 +205,27 @@ function toUserContext(cfg) {
|
|
|
205
205
|
return cfg.userContext && Object.keys(cfg.userContext).length > 0 ? cfg.userContext : void 0;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
// src/dashboardTap.ts
|
|
209
|
+
var import_widget_sdk = require("@sensiblestats/widget-sdk");
|
|
210
|
+
function isSignalEntityType(value) {
|
|
211
|
+
return Object.prototype.hasOwnProperty.call(import_widget_sdk.ENTITY_TYPE_CODES, value);
|
|
212
|
+
}
|
|
213
|
+
function createDashboardTapSignal(sendSignal) {
|
|
214
|
+
return (tile, index, layout) => {
|
|
215
|
+
if (!isSignalEntityType(tile.signal.entityType)) return;
|
|
216
|
+
try {
|
|
217
|
+
void sendSignal({
|
|
218
|
+
signalType: "MarketView",
|
|
219
|
+
entityType: tile.signal.entityType,
|
|
220
|
+
entityId: tile.signal.entityId,
|
|
221
|
+
dashboardLayout: layout,
|
|
222
|
+
tileIndex: index
|
|
223
|
+
}).catch(() => void 0);
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
208
229
|
// src/theme.ts
|
|
209
230
|
function themeAttrs(ui) {
|
|
210
231
|
const style = {
|
|
@@ -218,10 +239,10 @@ function themeAttrs(ui) {
|
|
|
218
239
|
|
|
219
240
|
// src/useChatStream.ts
|
|
220
241
|
var import_react = require("react");
|
|
221
|
-
var
|
|
242
|
+
var import_widget_sdk3 = require("@sensiblestats/widget-sdk");
|
|
222
243
|
|
|
223
244
|
// src/reducer.ts
|
|
224
|
-
var
|
|
245
|
+
var import_widget_sdk2 = require("@sensiblestats/widget-sdk");
|
|
225
246
|
|
|
226
247
|
// src/chart.ts
|
|
227
248
|
var CHART_VIEW_W = 320;
|
|
@@ -507,7 +528,11 @@ function applyEvent(state, event) {
|
|
|
507
528
|
actionsLoading: false,
|
|
508
529
|
popupText,
|
|
509
530
|
conversationId: event.data.conversationId ?? state.conversationId,
|
|
510
|
-
messages: mapActive(state, (t) => ({
|
|
531
|
+
messages: mapActive(state, (t) => ({
|
|
532
|
+
...t,
|
|
533
|
+
done: true,
|
|
534
|
+
greetingDashboard: event.data.dashboard ?? void 0
|
|
535
|
+
}))
|
|
511
536
|
};
|
|
512
537
|
}
|
|
513
538
|
case "error": {
|
|
@@ -559,7 +584,7 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
|
|
|
559
584
|
try {
|
|
560
585
|
for await (const event of handle) dispatch({ kind: "EVENT", event });
|
|
561
586
|
} catch (err) {
|
|
562
|
-
if (err instanceof
|
|
587
|
+
if (err instanceof import_widget_sdk3.WidgetSdkError) onError(err);
|
|
563
588
|
else if (err?.name === "AbortError") {
|
|
564
589
|
} else throw err;
|
|
565
590
|
} finally {
|
|
@@ -630,6 +655,28 @@ function SendIcon() {
|
|
|
630
655
|
function CloseIcon() {
|
|
631
656
|
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: "none", d: "M18 6L6 18M6 6l12 12", stroke: "currentColor", strokeWidth: "2" }) });
|
|
632
657
|
}
|
|
658
|
+
function ChevronIcon() {
|
|
659
|
+
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: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", d: "M9 5l7 7-7 7" }) });
|
|
660
|
+
}
|
|
661
|
+
var DASHBOARD_EMOJI = {
|
|
662
|
+
football: "\u26BD",
|
|
663
|
+
"goal-net": "\u{1F945}",
|
|
664
|
+
btts: "\u{1F91D}",
|
|
665
|
+
card: "\u{1F7E8}",
|
|
666
|
+
"corner-flag": "\u{1F6A9}",
|
|
667
|
+
player: "\u{1F464}",
|
|
668
|
+
target: "\u{1F3AF}",
|
|
669
|
+
trophy: "\u{1F3C6}",
|
|
670
|
+
"chart-line": "\u{1F4C8}",
|
|
671
|
+
"chart-bar": "\u{1F4CA}",
|
|
672
|
+
chat: "\u{1F4AC}",
|
|
673
|
+
fire: "\u{1F525}",
|
|
674
|
+
sparkle: "\u2728"
|
|
675
|
+
};
|
|
676
|
+
function DashboardIcon({ name }) {
|
|
677
|
+
const glyph = Object.prototype.hasOwnProperty.call(DASHBOARD_EMOJI, name) ? DASHBOARD_EMOJI[name] : DASHBOARD_EMOJI.target;
|
|
678
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ss-w-dash-emoji", "aria-hidden": "true", children: glyph });
|
|
679
|
+
}
|
|
633
680
|
|
|
634
681
|
// src/components/GreetingBubble.tsx
|
|
635
682
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
@@ -788,7 +835,7 @@ var import_react5 = require("react");
|
|
|
788
835
|
|
|
789
836
|
// src/components/AddToSlipButton.tsx
|
|
790
837
|
var import_react4 = require("react");
|
|
791
|
-
var
|
|
838
|
+
var import_widget_sdk4 = require("@sensiblestats/widget-sdk");
|
|
792
839
|
|
|
793
840
|
// src/slip.ts
|
|
794
841
|
var ADD_TO_SLIP_TIMEOUT_MS = 1e4;
|
|
@@ -851,7 +898,7 @@ function SlipButton({ selection, shownOdds, marketName, slip, ui }) {
|
|
|
851
898
|
setPending(true);
|
|
852
899
|
try {
|
|
853
900
|
const raw = await withTimeout(Promise.resolve(handler(selection)));
|
|
854
|
-
if (raw != null && !(0,
|
|
901
|
+
if (raw != null && !(0, import_widget_sdk4.isAddToSlipResult)(raw)) throw new Error("malformed add-to-slip result");
|
|
855
902
|
const result = raw ?? void 0;
|
|
856
903
|
setToast(resultToToast(result, shownOdds || selection.oddsDecimal.toFixed(2), ui));
|
|
857
904
|
setSettled(isTerminal(result));
|
|
@@ -1101,27 +1148,75 @@ function ActionButtons({ actions, loading, disabled, onAct }) {
|
|
|
1101
1148
|
] });
|
|
1102
1149
|
}
|
|
1103
1150
|
|
|
1104
|
-
// src/components/
|
|
1151
|
+
// src/components/dashboard/DashboardTile.tsx
|
|
1105
1152
|
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1106
|
-
|
|
1153
|
+
var MAX_CRESTS = 3;
|
|
1154
|
+
function DashboardTile({ tile, onTap }) {
|
|
1155
|
+
const spanClass = tile.span === "full" ? "ss-w-dash-tile--full" : "ss-w-dash-tile--half";
|
|
1156
|
+
const crests = (tile.imageUrls ?? []).slice(0, MAX_CRESTS);
|
|
1157
|
+
const hasMeta = Boolean(tile.badgeText) || crests.length > 0;
|
|
1158
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("button", { type: "button", className: `ss-w-dash-tile ${spanClass}`, onClick: onTap, children: [
|
|
1159
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-icon", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DashboardIcon, { name: tile.icon }) }),
|
|
1160
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "ss-w-dash-tile-body", children: [
|
|
1161
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-title", children: tile.title }),
|
|
1162
|
+
tile.subtitle ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-subtitle", children: tile.subtitle }) : null,
|
|
1163
|
+
hasMeta ? (
|
|
1164
|
+
// Badge + crests sit on their own row, below the title, instead of sharing the
|
|
1165
|
+
// icon/chevron row -- at the half-span tile's ~180px width a nowrap badge alone
|
|
1166
|
+
// (~70px) left the title column at ~22px and forced character-by-character wrapping.
|
|
1167
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "ss-w-dash-tile-meta", children: [
|
|
1168
|
+
tile.badgeText ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-badge", children: tile.badgeText }) : null,
|
|
1169
|
+
crests.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-crests", children: crests.map((url, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-crest", style: { backgroundImage: `url(${url})` }, "aria-hidden": "true" }, `${url}-${i}`)) }) : null
|
|
1170
|
+
] })
|
|
1171
|
+
) : null
|
|
1172
|
+
] }),
|
|
1173
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-chevron", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronIcon, {}) })
|
|
1174
|
+
] });
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// src/components/dashboard/GreetingDashboard.tsx
|
|
1178
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1179
|
+
var LAYOUT_CLASSES = {
|
|
1180
|
+
market: "ss-w-dash--market",
|
|
1181
|
+
goal: "ss-w-dash--goal",
|
|
1182
|
+
discovery: "ss-w-dash--discovery"
|
|
1183
|
+
};
|
|
1184
|
+
function layoutClass(layout) {
|
|
1185
|
+
return Object.prototype.hasOwnProperty.call(LAYOUT_CLASSES, layout) ? LAYOUT_CLASSES[layout] : LAYOUT_CLASSES.goal;
|
|
1186
|
+
}
|
|
1187
|
+
function GreetingDashboard({ dashboard, onTap }) {
|
|
1188
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `ss-w-dash ${layoutClass(dashboard.layout)}`, children: [
|
|
1189
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ss-w-dash-head", children: [
|
|
1190
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "ss-w-dash-title", children: dashboard.title }),
|
|
1191
|
+
dashboard.subtitle ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "ss-w-dash-subtitle", children: dashboard.subtitle }) : null
|
|
1192
|
+
] }),
|
|
1193
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "ss-w-dash-grid", children: dashboard.tiles.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DashboardTile, { tile: t, onTap: () => onTap(t, i) }, `${t.title}-${i}`)) }),
|
|
1194
|
+
dashboard.footerTip ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "ss-w-dash-foot", children: dashboard.footerTip }) : null
|
|
1195
|
+
] });
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// src/components/ChatMessage.tsx
|
|
1199
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1200
|
+
function ChatMessage({ message, disabled, onAct, onTap, ui, slip }) {
|
|
1107
1201
|
if (message.role === "user") {
|
|
1108
|
-
return /* @__PURE__ */ (0,
|
|
1202
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
|
|
1109
1203
|
}
|
|
1110
|
-
return /* @__PURE__ */ (0,
|
|
1111
|
-
message.text ? /* @__PURE__ */ (0,
|
|
1112
|
-
/* @__PURE__ */ (0,
|
|
1113
|
-
/* @__PURE__ */ (0,
|
|
1114
|
-
/* @__PURE__ */ (0,
|
|
1115
|
-
/* @__PURE__ */ (0,
|
|
1116
|
-
/* @__PURE__ */ (0,
|
|
1204
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
|
|
1205
|
+
message.text && !message.greetingDashboard ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Markdown, { text: message.text }) : null,
|
|
1206
|
+
message.greetingDashboard ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
|
|
1207
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
|
|
1208
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(BettingInsightList, { insights: message.insights, ui, slip }),
|
|
1209
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
|
|
1210
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(OddsMovementCardList, { cards: message.oddsMovement, ui }),
|
|
1211
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
|
|
1117
1212
|
] });
|
|
1118
1213
|
}
|
|
1119
1214
|
|
|
1120
1215
|
// src/components/IntentChips.tsx
|
|
1121
|
-
var
|
|
1216
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1122
1217
|
function IntentChips({ chips, onPick }) {
|
|
1123
1218
|
if (chips.length === 0) return null;
|
|
1124
|
-
return /* @__PURE__ */ (0,
|
|
1219
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
|
|
1125
1220
|
}
|
|
1126
1221
|
|
|
1127
1222
|
// src/scroll.ts
|
|
@@ -1131,8 +1226,8 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
|
|
|
1131
1226
|
}
|
|
1132
1227
|
|
|
1133
1228
|
// src/components/MessageList.tsx
|
|
1134
|
-
var
|
|
1135
|
-
function MessageList({ controller, ui, slip }) {
|
|
1229
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
1230
|
+
function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
1136
1231
|
const { state, send } = controller;
|
|
1137
1232
|
const listRef = (0, import_react7.useRef)(null);
|
|
1138
1233
|
const endRef = (0, import_react7.useRef)(null);
|
|
@@ -1140,8 +1235,9 @@ function MessageList({ controller, ui, slip }) {
|
|
|
1140
1235
|
(0, import_react7.useEffect)(() => {
|
|
1141
1236
|
if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
|
|
1142
1237
|
}, [state.messages, state.status]);
|
|
1143
|
-
const
|
|
1144
|
-
|
|
1238
|
+
const hasUserMessage = state.messages.some((m) => m.role === "user");
|
|
1239
|
+
const hasDashboard = state.messages.some((m) => m.role === "assistant" && m.greetingDashboard);
|
|
1240
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
1145
1241
|
"div",
|
|
1146
1242
|
{
|
|
1147
1243
|
className: "ss-w-list",
|
|
@@ -1151,26 +1247,30 @@ function MessageList({ controller, ui, slip }) {
|
|
|
1151
1247
|
if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
|
|
1152
1248
|
},
|
|
1153
1249
|
children: [
|
|
1154
|
-
state.messages.map((m) => /* @__PURE__ */ (0,
|
|
1250
|
+
state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
1155
1251
|
ChatMessage,
|
|
1156
1252
|
{
|
|
1157
1253
|
message: m,
|
|
1158
1254
|
disabled: state.isStreaming,
|
|
1159
1255
|
onAct: (a) => send(a.message, { actionId: a.actionId, displayText: a.label }),
|
|
1256
|
+
onTap: (tile, index) => {
|
|
1257
|
+
send(tile.actionValue, { displayText: tile.title });
|
|
1258
|
+
if (m.role === "assistant" && m.greetingDashboard) onDashboardTap?.(tile, index, m.greetingDashboard.layout);
|
|
1259
|
+
},
|
|
1160
1260
|
ui,
|
|
1161
1261
|
slip
|
|
1162
1262
|
},
|
|
1163
1263
|
m.id
|
|
1164
1264
|
)),
|
|
1165
|
-
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0,
|
|
1265
|
+
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
|
|
1166
1266
|
} }) : null,
|
|
1167
|
-
state.status ? /* @__PURE__ */ (0,
|
|
1168
|
-
state.error ? /* @__PURE__ */ (0,
|
|
1169
|
-
/* @__PURE__ */ (0,
|
|
1170
|
-
/* @__PURE__ */ (0,
|
|
1267
|
+
state.status ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "ss-w-status", children: state.status }) : null,
|
|
1268
|
+
state.error ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "ss-w-error", role: "alert", children: [
|
|
1269
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
|
|
1270
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
|
|
1171
1271
|
] }) : null,
|
|
1172
|
-
|
|
1173
|
-
/* @__PURE__ */ (0,
|
|
1272
|
+
!hasUserMessage && !hasDashboard ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
|
|
1273
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { ref: endRef })
|
|
1174
1274
|
]
|
|
1175
1275
|
}
|
|
1176
1276
|
);
|
|
@@ -1178,7 +1278,7 @@ function MessageList({ controller, ui, slip }) {
|
|
|
1178
1278
|
|
|
1179
1279
|
// src/components/ChatInput.tsx
|
|
1180
1280
|
var import_react8 = require("react");
|
|
1181
|
-
var
|
|
1281
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
1182
1282
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
1183
1283
|
const [value, setValue] = (0, import_react8.useState)("");
|
|
1184
1284
|
const submit = () => {
|
|
@@ -1193,14 +1293,14 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
|
|
|
1193
1293
|
submit();
|
|
1194
1294
|
}
|
|
1195
1295
|
};
|
|
1196
|
-
return /* @__PURE__ */ (0,
|
|
1197
|
-
/* @__PURE__ */ (0,
|
|
1198
|
-
streaming ? /* @__PURE__ */ (0,
|
|
1296
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "ss-w-input", children: [
|
|
1297
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
|
|
1298
|
+
streaming ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SendIcon, {}) })
|
|
1199
1299
|
] });
|
|
1200
1300
|
}
|
|
1201
1301
|
|
|
1202
1302
|
// src/components/ChatPanel.tsx
|
|
1203
|
-
var
|
|
1303
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
1204
1304
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
1205
1305
|
function useIsModal() {
|
|
1206
1306
|
const [modal, setModal] = (0, import_react9.useState)(false);
|
|
@@ -1214,7 +1314,7 @@ function useIsModal() {
|
|
|
1214
1314
|
}, []);
|
|
1215
1315
|
return modal;
|
|
1216
1316
|
}
|
|
1217
|
-
function ChatPanel({ controller, ui, slip }) {
|
|
1317
|
+
function ChatPanel({ controller, ui, slip, onDashboardTap }) {
|
|
1218
1318
|
const { state, close, stop, send } = controller;
|
|
1219
1319
|
const isModal = useIsModal();
|
|
1220
1320
|
(0, import_react9.useEffect)(() => {
|
|
@@ -1224,10 +1324,10 @@ function ChatPanel({ controller, ui, slip }) {
|
|
|
1224
1324
|
window.addEventListener("keydown", onKey);
|
|
1225
1325
|
return () => window.removeEventListener("keydown", onKey);
|
|
1226
1326
|
}, [close]);
|
|
1227
|
-
return /* @__PURE__ */ (0,
|
|
1228
|
-
/* @__PURE__ */ (0,
|
|
1229
|
-
/* @__PURE__ */ (0,
|
|
1230
|
-
/* @__PURE__ */ (0,
|
|
1327
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
1328
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
|
|
1329
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(MessageList, { controller, ui, slip, onDashboardTap }),
|
|
1330
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
|
|
1231
1331
|
] });
|
|
1232
1332
|
}
|
|
1233
1333
|
|
|
@@ -1484,17 +1584,85 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
1484
1584
|
@media (prefers-reduced-motion: reduce) {
|
|
1485
1585
|
.ss-w-abtn-shimmer { animation: none; }
|
|
1486
1586
|
}
|
|
1587
|
+
|
|
1588
|
+
/* greeting dashboard */
|
|
1589
|
+
.ss-w-dash { display: flex; flex-direction: column; gap: 12px; padding: 4px 0; }
|
|
1590
|
+
.ss-w-dash-head { display: flex; flex-direction: column; }
|
|
1591
|
+
.ss-w-dash-title { font-size: 18px; font-weight: 700; line-height: 1.25; color: var(--ss-w-ink); }
|
|
1592
|
+
.ss-w-dash-subtitle { font-size: 13px; color: var(--ss-w-faint); margin-top: 4px; }
|
|
1593
|
+
.ss-w-dash-foot { font-size: 11px; color: var(--ss-w-faint); margin-top: 2px; }
|
|
1594
|
+
|
|
1595
|
+
.ss-w-dash-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
|
|
1596
|
+
.ss-w-dash-tile--half { grid-column: span 1; }
|
|
1597
|
+
.ss-w-dash-tile--full { grid-column: 1 / -1; }
|
|
1598
|
+
|
|
1599
|
+
.ss-w-dash-tile {
|
|
1600
|
+
display: flex; align-items: center; gap: 10px;
|
|
1601
|
+
padding: 12px; border-radius: 14px; border: 1px solid var(--ss-w-line);
|
|
1602
|
+
background: var(--ss-w-panel); text-align: left; cursor: pointer; width: 100%;
|
|
1603
|
+
color: inherit; font: inherit;
|
|
1604
|
+
min-width: 0; /* the panel is min(400px, 100vw - 40px): without this,
|
|
1605
|
+
a long Turkish title blows the column out */
|
|
1606
|
+
}
|
|
1607
|
+
.ss-w-dash-tile-icon { flex: none; display: grid; place-items: center; width: 28px; height: 28px; font-size: 22px; }
|
|
1608
|
+
.ss-w-dash-emoji { line-height: 1; }
|
|
1609
|
+
.ss-w-dash-tile-body { min-width: 0; display: flex; flex-direction: column; gap: 2px; flex: 1; }
|
|
1610
|
+
.ss-w-dash-tile-title { min-width: 0; font-weight: 600; font-size: 14px; overflow-wrap: anywhere; }
|
|
1611
|
+
.ss-w-dash-tile-subtitle { min-width: 0; font-size: 11.5px; color: var(--ss-w-faint); overflow-wrap: anywhere; }
|
|
1612
|
+
.ss-w-dash-tile-chevron { flex: none; margin-left: auto; width: 14px; height: 14px; color: var(--ss-w-faint); }
|
|
1613
|
+
|
|
1614
|
+
/* Badge + crests share their own row, below the title -- NOT the icon/chevron row. A
|
|
1615
|
+
nowrap badge (~70px) sitting beside icon+chevron in a ~180px half-span tile left the
|
|
1616
|
+
title column at ~22px wide, wrapping every title character-by-character. Here the row
|
|
1617
|
+
gets the body's full width, so a badge this size is comfortably inside it. */
|
|
1618
|
+
.ss-w-dash-tile-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0; }
|
|
1619
|
+
.ss-w-dash-tile-badge { flex: none; font-size: 10.5px; font-weight: 700; color: var(--ss-w-accent); background: color-mix(in srgb, var(--ss-w-accent) 13%, var(--ss-w-panel)); border-radius: 999px; padding: 3px 9px; white-space: nowrap; }
|
|
1620
|
+
|
|
1621
|
+
.ss-w-dash-tile-crests { flex: none; display: flex; align-items: center; }
|
|
1622
|
+
.ss-w-dash-tile-crest {
|
|
1623
|
+
width: 18px; height: 18px; border-radius: 50%;
|
|
1624
|
+
background-size: cover; background-position: center; background-color: var(--ss-w-bubble);
|
|
1625
|
+
margin-left: -6px; border: 2px solid var(--ss-w-panel); /* overlapping stack, capped at 3 in DashboardTile */
|
|
1626
|
+
}
|
|
1627
|
+
.ss-w-dash-tile-crest:first-child { margin-left: 0; }
|
|
1628
|
+
|
|
1629
|
+
/* Market: default 2-column grid, tiles get the same accent rail as the other card
|
|
1630
|
+
families (.ss-w-binsight, .ss-w-mocard, .ss-w-omcard) so a market tile reads as
|
|
1631
|
+
part of that visual language rather than a plain unstyled button. */
|
|
1632
|
+
.ss-w-dash--market .ss-w-dash-tile { border-left: 3px solid var(--ss-w-accent); }
|
|
1633
|
+
|
|
1634
|
+
/* Goal: one column of pill rows. */
|
|
1635
|
+
.ss-w-dash--goal .ss-w-dash-grid { grid-template-columns: 1fr; }
|
|
1636
|
+
.ss-w-dash--goal .ss-w-dash-tile { border-radius: 999px; padding: 10px 14px; }
|
|
1637
|
+
|
|
1638
|
+
/* Discovery: tinted bento cards. Stacked (icon over text) so a two- or three-word title gets
|
|
1639
|
+
the tile's full width instead of the ~100px left of a horizontal icon+chevron -- that narrow
|
|
1640
|
+
column was wrapping "Most interesting angles" onto three lines. The chevron is dropped (the
|
|
1641
|
+
whole card is the tap target) and the badge + crest row is forced onto one line. */
|
|
1642
|
+
.ss-w-dash--discovery .ss-w-dash-tile { flex-direction: column; align-items: stretch; gap: 8px; }
|
|
1643
|
+
.ss-w-dash--discovery .ss-w-dash-tile-icon { align-self: flex-start; width: 32px; height: 32px; font-size: 26px; }
|
|
1644
|
+
.ss-w-dash--discovery .ss-w-dash-tile-chevron { display: none; }
|
|
1645
|
+
/* The full-span trends tile has the whole panel width, so its icon and title sit on one row
|
|
1646
|
+
(horizontal, centre-aligned) rather than stacking like the narrow half tiles. */
|
|
1647
|
+
.ss-w-dash--discovery .ss-w-dash-tile--full { flex-direction: row; align-items: center; }
|
|
1648
|
+
.ss-w-dash--discovery .ss-w-dash-tile--full .ss-w-dash-tile-icon { align-self: center; }
|
|
1649
|
+
.ss-w-dash--discovery .ss-w-dash-tile-meta { flex-wrap: nowrap; }
|
|
1650
|
+
.ss-w-dash--discovery .ss-w-dash-tile-crest { width: 22px; height: 22px; }
|
|
1651
|
+
.ss-w-dash--discovery .ss-w-dash-tile:nth-child(1) { background: #eef8f1; }
|
|
1652
|
+
.ss-w-dash--discovery .ss-w-dash-tile:nth-child(2) { background: #eef3fb; }
|
|
1653
|
+
.ss-w-dash--discovery .ss-w-dash-tile:nth-child(3) { background: #fdf6ec; }
|
|
1654
|
+
.ss-w-dash--discovery .ss-w-dash-tile:nth-child(4) { background: #f5eefb; }
|
|
1487
1655
|
`;
|
|
1488
1656
|
|
|
1489
1657
|
// src/StatsWidget.tsx
|
|
1490
|
-
var
|
|
1658
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
1491
1659
|
function buildClient(props) {
|
|
1492
1660
|
try {
|
|
1493
1661
|
if ("client" in props && props.client) {
|
|
1494
1662
|
return { client: props.client, conversation: props.client.conversation() };
|
|
1495
1663
|
}
|
|
1496
1664
|
const cfg = props.config;
|
|
1497
|
-
const client = new
|
|
1665
|
+
const client = new import_widget_sdk5.StatsWidgetClient(toClientOptions(cfg));
|
|
1498
1666
|
return { client, conversation: client.conversation(toUserContext(cfg)) };
|
|
1499
1667
|
} catch (err) {
|
|
1500
1668
|
console.error("[StatsWidget] failed to initialize:", err instanceof Error ? err.message : err);
|
|
@@ -1529,6 +1697,10 @@ function StatsWidget(props) {
|
|
|
1529
1697
|
}),
|
|
1530
1698
|
[props.config?.onAddToSlip, controller.state.conversationId, built]
|
|
1531
1699
|
);
|
|
1700
|
+
const onDashboardTap = (0, import_react10.useMemo)(
|
|
1701
|
+
() => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
|
|
1702
|
+
[built]
|
|
1703
|
+
);
|
|
1532
1704
|
(0, import_react10.useEffect)(() => {
|
|
1533
1705
|
if (!built || !ui.injectStyles) return;
|
|
1534
1706
|
const node = rootRef.current?.getRootNode();
|
|
@@ -1536,7 +1708,7 @@ function StatsWidget(props) {
|
|
|
1536
1708
|
}, [built, ui.injectStyles]);
|
|
1537
1709
|
if (!built) return null;
|
|
1538
1710
|
const attrs = themeAttrs(ui);
|
|
1539
|
-
return /* @__PURE__ */ (0,
|
|
1711
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { ref: rootRef, className: "ss-w-root", "data-ss-w-theme": attrs["data-ss-w-theme"], "data-ss-w-pos": attrs["data-ss-w-pos"], style: attrs.style, children: controller.state.isOpen ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
1540
1712
|
ChatFab,
|
|
1541
1713
|
{
|
|
1542
1714
|
label: ui.launcherLabel,
|