@sensiblestats/widget-react 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/index.cjs +190 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +221 -57
- package/dist/index.js.map +1 -1
- package/dist/styles.css +8 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @sensiblestats/widget-react
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add an auto-greeting: set `greetingMessage` (e.g. `"hello"`) and the widget silently sends it on load, surfacing the response — with suggested actions — as a dismissable launcher popup that clears when the panel opens. Also fixes two bugs: the launcher button is now hidden while the panel is open, and the mobile panel no longer pushes the text input off-screen when the keyboard/URL-bar shrinks the viewport (fills via `inset` instead of a fixed `100vh`).
|
|
8
|
+
|
|
3
9
|
## 0.1.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -49,6 +49,7 @@ function normalizeUi(cfg) {
|
|
|
49
49
|
radius: cfg.radius ?? 16,
|
|
50
50
|
launcherLabel: cfg.launcherLabel ?? "Chat with SensibleStats",
|
|
51
51
|
greeting: cfg.greeting,
|
|
52
|
+
greetingMessage: cfg.greetingMessage,
|
|
52
53
|
placeholder: cfg.placeholder ?? "Ask about a team, player or match\u2026",
|
|
53
54
|
starters: cfg.starters ?? [],
|
|
54
55
|
injectStyles: cfg.injectStyles ?? true
|
|
@@ -126,8 +127,13 @@ function chipFromText(text) {
|
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
// src/reducer.ts
|
|
130
|
+
var POPUP_MAX = 160;
|
|
129
131
|
function initialState(starters) {
|
|
130
|
-
return { isOpen: false, messages: [], isStreaming: false, status: null, actionsLoading: false, starters, error: null, lastUserInput: null, lastDisplayText: null };
|
|
132
|
+
return { isOpen: false, messages: [], isStreaming: false, status: null, actionsLoading: false, starters, error: null, lastUserInput: null, lastDisplayText: null, popupText: null };
|
|
133
|
+
}
|
|
134
|
+
function activeTurn(state) {
|
|
135
|
+
const last = state.messages[state.messages.length - 1];
|
|
136
|
+
return last && last.role === "assistant" && !last.done ? last : null;
|
|
131
137
|
}
|
|
132
138
|
function mapActive(state, fn) {
|
|
133
139
|
const last = state.messages[state.messages.length - 1];
|
|
@@ -151,16 +157,22 @@ function applyEvent(state, event) {
|
|
|
151
157
|
}
|
|
152
158
|
case "actions_loading":
|
|
153
159
|
return { ...state, actionsLoading: true, status: event.data.message };
|
|
154
|
-
case "turn_complete":
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
160
|
+
case "turn_complete": {
|
|
161
|
+
const active = activeTurn(state);
|
|
162
|
+
const text = active?.text.trim() ?? "";
|
|
163
|
+
const popupText = active?.isGreeting && !state.isOpen && text ? text.slice(0, POPUP_MAX) : state.popupText;
|
|
164
|
+
return { ...state, isStreaming: false, status: null, actionsLoading: false, popupText, messages: mapActive(state, (t) => ({ ...t, done: true })) };
|
|
165
|
+
}
|
|
166
|
+
case "error": {
|
|
167
|
+
const suppress = activeTurn(state)?.isGreeting === true;
|
|
168
|
+
return { ...state, error: suppress ? state.error : event.data, isStreaming: false, status: null, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, done: true })) };
|
|
169
|
+
}
|
|
158
170
|
}
|
|
159
171
|
}
|
|
160
172
|
function reducer(state, action) {
|
|
161
173
|
switch (action.kind) {
|
|
162
174
|
case "OPEN":
|
|
163
|
-
return { ...state, isOpen: true };
|
|
175
|
+
return { ...state, isOpen: true, popupText: null };
|
|
164
176
|
case "CLOSE":
|
|
165
177
|
return { ...state, isOpen: false, isStreaming: false, status: null, actionsLoading: false };
|
|
166
178
|
case "USER_SENT": {
|
|
@@ -168,6 +180,12 @@ function reducer(state, action) {
|
|
|
168
180
|
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], done: false };
|
|
169
181
|
return { ...state, messages: [...state.messages, user, assistant], isStreaming: true, status: null, actionsLoading: false, starters: [], error: null, lastUserInput: action.input, lastDisplayText: action.displayText ?? null };
|
|
170
182
|
}
|
|
183
|
+
case "GREETING_SENT": {
|
|
184
|
+
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], done: false, isGreeting: true };
|
|
185
|
+
return { ...state, messages: [...state.messages, assistant], isStreaming: true, status: null, error: null };
|
|
186
|
+
}
|
|
187
|
+
case "DISMISS_POPUP":
|
|
188
|
+
return { ...state, popupText: null };
|
|
171
189
|
case "EVENT":
|
|
172
190
|
return applyEvent(state, action.event);
|
|
173
191
|
case "STREAM_ERROR":
|
|
@@ -178,31 +196,36 @@ function reducer(state, action) {
|
|
|
178
196
|
}
|
|
179
197
|
|
|
180
198
|
// src/useChatStream.ts
|
|
181
|
-
function useChatStream(conversation, starterTexts) {
|
|
199
|
+
function useChatStream(conversation, starterTexts, greetingMessage) {
|
|
182
200
|
const seed = (0, import_react.useMemo)(() => initialState(starterTexts.map(chipFromText)), []);
|
|
183
201
|
const [state, dispatch] = (0, import_react.useReducer)(reducer, seed);
|
|
184
202
|
const idRef = (0, import_react.useRef)(0);
|
|
185
203
|
const handleRef = (0, import_react.useRef)(null);
|
|
186
204
|
const stateRef = (0, import_react.useRef)(state);
|
|
187
205
|
stateRef.current = state;
|
|
206
|
+
const greetedRef = (0, import_react.useRef)(false);
|
|
188
207
|
const nextId = () => `m${idRef.current++}`;
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
dispatch({ kind: "USER_SENT", input, userId: nextId(), assistantId: nextId(), displayText });
|
|
192
|
-
const handle = conversation.send(input);
|
|
193
|
-
handleRef.current = handle;
|
|
208
|
+
const consume = (0, import_react.useCallback)((handle, onError) => {
|
|
209
|
+
;
|
|
194
210
|
(async () => {
|
|
195
211
|
try {
|
|
196
212
|
for await (const event of handle) dispatch({ kind: "EVENT", event });
|
|
197
213
|
} catch (err) {
|
|
198
|
-
if (err instanceof import_widget_sdk2.WidgetSdkError)
|
|
214
|
+
if (err instanceof import_widget_sdk2.WidgetSdkError) onError(err);
|
|
199
215
|
else if (err?.name === "AbortError") {
|
|
200
216
|
} else throw err;
|
|
201
217
|
} finally {
|
|
202
218
|
if (handleRef.current === handle) handleRef.current = null;
|
|
203
219
|
}
|
|
204
220
|
})();
|
|
205
|
-
}, [
|
|
221
|
+
}, []);
|
|
222
|
+
const run = (0, import_react.useCallback)((input, displayText) => {
|
|
223
|
+
handleRef.current?.cancel();
|
|
224
|
+
dispatch({ kind: "USER_SENT", input, userId: nextId(), assistantId: nextId(), displayText });
|
|
225
|
+
const handle = conversation.send(input);
|
|
226
|
+
handleRef.current = handle;
|
|
227
|
+
consume(handle, (err) => dispatch({ kind: "STREAM_ERROR", error: err }));
|
|
228
|
+
}, [conversation, consume]);
|
|
206
229
|
const send = (0, import_react.useCallback)((text, opts) => {
|
|
207
230
|
const trimmed = text.trim();
|
|
208
231
|
if (!trimmed) return;
|
|
@@ -212,6 +235,20 @@ function useChatStream(conversation, starterTexts) {
|
|
|
212
235
|
const last = stateRef.current.lastUserInput;
|
|
213
236
|
if (last) run(last, stateRef.current.lastDisplayText ?? void 0);
|
|
214
237
|
}, [run]);
|
|
238
|
+
(0, import_react.useEffect)(() => {
|
|
239
|
+
const message = greetingMessage?.trim();
|
|
240
|
+
if (!message || greetedRef.current) return;
|
|
241
|
+
greetedRef.current = true;
|
|
242
|
+
try {
|
|
243
|
+
handleRef.current?.cancel();
|
|
244
|
+
dispatch({ kind: "GREETING_SENT", assistantId: nextId() });
|
|
245
|
+
const handle = conversation.send({ message });
|
|
246
|
+
handleRef.current = handle;
|
|
247
|
+
consume(handle, () => dispatch({ kind: "STOP" }));
|
|
248
|
+
} catch {
|
|
249
|
+
dispatch({ kind: "STOP" });
|
|
250
|
+
}
|
|
251
|
+
}, [greetingMessage, conversation, consume]);
|
|
215
252
|
const open = (0, import_react.useCallback)(() => dispatch({ kind: "OPEN" }), []);
|
|
216
253
|
const close = (0, import_react.useCallback)(() => {
|
|
217
254
|
handleRef.current?.cancel();
|
|
@@ -227,7 +264,8 @@ function useChatStream(conversation, starterTexts) {
|
|
|
227
264
|
if (stateRef.current.isOpen) close();
|
|
228
265
|
else open();
|
|
229
266
|
}, [close, open]);
|
|
230
|
-
|
|
267
|
+
const dismissPopup = (0, import_react.useCallback)(() => dispatch({ kind: "DISMISS_POPUP" }), []);
|
|
268
|
+
return { state, open, close, stop, toggle, send, retry, dismissPopup };
|
|
231
269
|
}
|
|
232
270
|
|
|
233
271
|
// src/components/Icons.tsx
|
|
@@ -253,16 +291,20 @@ function ChevronIcon() {
|
|
|
253
291
|
|
|
254
292
|
// src/components/GreetingBubble.tsx
|
|
255
293
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
256
|
-
function GreetingBubble({ text }) {
|
|
257
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.
|
|
294
|
+
function GreetingBubble({ text, onOpen, onDismiss }) {
|
|
295
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ss-w-greeting", role: "note", children: [
|
|
296
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "ss-w-greeting-body", onClick: onOpen, children: text }),
|
|
297
|
+
onDismiss ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "ss-w-greeting-x", "aria-label": "Dismiss", onClick: onDismiss, children: "\xD7" }) : null
|
|
298
|
+
] });
|
|
258
299
|
}
|
|
259
300
|
|
|
260
301
|
// src/components/ChatFab.tsx
|
|
261
302
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
262
|
-
function ChatFab({ label,
|
|
303
|
+
function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup }) {
|
|
304
|
+
const bubble = popupText ?? greeting;
|
|
263
305
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ss-w-fab-wrap", children: [
|
|
264
|
-
|
|
265
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "ss-w-fab", "aria-label": label,
|
|
306
|
+
bubble ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(GreetingBubble, { text: bubble, onOpen, onDismiss: popupText ? onDismissPopup : void 0 }) : null,
|
|
307
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "ss-w-fab", "aria-label": label, onClick: onOpen, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SsMark, { className: "ss-w-fab-mark" }) })
|
|
266
308
|
] });
|
|
267
309
|
}
|
|
268
310
|
|
|
@@ -456,7 +498,123 @@ function ChatPanel({ controller, ui }) {
|
|
|
456
498
|
}
|
|
457
499
|
|
|
458
500
|
// src/styles/css.generated.ts
|
|
459
|
-
var WIDGET_CSS =
|
|
501
|
+
var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
502
|
+
.ss-w-root, .ss-w-root * { box-sizing: border-box; margin: 0; padding: 0; }
|
|
503
|
+
.ss-w-root {
|
|
504
|
+
--ss-w-accent: #17936a;
|
|
505
|
+
--ss-w-ink: #14181d; --ss-w-faint: #6b7580; --ss-w-line: #e4e8ec;
|
|
506
|
+
--ss-w-panel: #ffffff; --ss-w-bubble: #f3f5f7; --ss-w-nav: #0e1622;
|
|
507
|
+
--ss-w-radius: 16px; --ss-w-offset-x: 20px; --ss-w-offset-y: 20px;
|
|
508
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
509
|
+
font-size: 13px; line-height: 1.5; color: var(--ss-w-ink);
|
|
510
|
+
}
|
|
511
|
+
@media (prefers-color-scheme: dark) {
|
|
512
|
+
.ss-w-root[data-ss-w-theme="auto"] {
|
|
513
|
+
--ss-w-accent: #37c891; --ss-w-ink: #e8edf2; --ss-w-faint: #8b95a1;
|
|
514
|
+
--ss-w-line: #2a3441; --ss-w-panel: #141b24; --ss-w-bubble: #1e2732; --ss-w-nav: #0a0f16;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
.ss-w-root[data-ss-w-theme="dark"] {
|
|
518
|
+
--ss-w-accent: #37c891; --ss-w-ink: #e8edf2; --ss-w-faint: #8b95a1;
|
|
519
|
+
--ss-w-line: #2a3441; --ss-w-panel: #141b24; --ss-w-bubble: #1e2732; --ss-w-nav: #0a0f16;
|
|
520
|
+
}
|
|
521
|
+
.ss-w-root[data-ss-w-theme="light"] {
|
|
522
|
+
--ss-w-accent: #17936a; --ss-w-ink: #14181d; --ss-w-faint: #6b7580;
|
|
523
|
+
--ss-w-line: #e4e8ec; --ss-w-panel: #ffffff; --ss-w-bubble: #f3f5f7; --ss-w-nav: #0e1622;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/* launcher */
|
|
527
|
+
.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; }
|
|
528
|
+
.ss-w-root[data-ss-w-pos="right"] .ss-w-fab-wrap { right: var(--ss-w-offset-x); }
|
|
529
|
+
.ss-w-root[data-ss-w-pos="left"] .ss-w-fab-wrap { left: var(--ss-w-offset-x); align-items: flex-start; }
|
|
530
|
+
.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); }
|
|
531
|
+
.ss-w-fab-mark { font-size: 30px; }
|
|
532
|
+
.ss-w-greeting { max-width: 240px; display: flex; align-items: flex-start; background: var(--ss-w-panel); color: var(--ss-w-ink); border: 1px solid var(--ss-w-line); border-radius: 12px; box-shadow: 0 4px 14px rgba(0,0,0,.14); }
|
|
533
|
+
.ss-w-greeting-body { flex: 1; min-width: 0; text-align: left; background: none; border: none; color: inherit; font: inherit; cursor: pointer; padding: 9px 4px 9px 12px; }
|
|
534
|
+
.ss-w-greeting-x { flex: none; background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 17px; line-height: 1; padding: 7px 9px 0 2px; }
|
|
535
|
+
|
|
536
|
+
/* panel */
|
|
537
|
+
.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); }
|
|
538
|
+
.ss-w-root[data-ss-w-pos="right"] .ss-w-panel { right: var(--ss-w-offset-x); }
|
|
539
|
+
.ss-w-root[data-ss-w-pos="left"] .ss-w-panel { left: var(--ss-w-offset-x); }
|
|
540
|
+
@media (max-width: 639px) {
|
|
541
|
+
/* Fill via inset with no explicit height (like the site's working panel) so the
|
|
542
|
+
input stays visible when the mobile keyboard/URL-bar shrinks the viewport. The
|
|
543
|
+
position-specific selectors override the right/left offsets set above. */
|
|
544
|
+
.ss-w-root[data-ss-w-pos="right"] .ss-w-panel,
|
|
545
|
+
.ss-w-root[data-ss-w-pos="left"] .ss-w-panel { inset: 0; width: auto; height: auto; border: none; border-radius: 0; }
|
|
546
|
+
}
|
|
547
|
+
.ss-w-header { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--ss-w-line); }
|
|
548
|
+
.ss-w-header-mark { font-size: 18px; }
|
|
549
|
+
.ss-w-header-title { font-weight: 660; }
|
|
550
|
+
.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; }
|
|
551
|
+
|
|
552
|
+
/* messages */
|
|
553
|
+
.ss-w-list { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 12px; }
|
|
554
|
+
.ss-w-msg { display: flex; }
|
|
555
|
+
.ss-w-user { justify-content: flex-end; }
|
|
556
|
+
.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%; }
|
|
557
|
+
.ss-w-bot { flex-direction: column; gap: 8px; }
|
|
558
|
+
.ss-w-status { font-size: 11px; color: var(--ss-w-faint); font-style: italic; }
|
|
559
|
+
.ss-w-error { display: flex; align-items: center; gap: 8px; font-size: 12px; color: #c0392b; }
|
|
560
|
+
.ss-w-retry { background: none; border: 1px solid currentColor; border-radius: 10px; padding: 2px 8px; cursor: pointer; color: inherit; }
|
|
561
|
+
|
|
562
|
+
/* markdown */
|
|
563
|
+
.ss-w-md { display: flex; flex-direction: column; gap: 8px; }
|
|
564
|
+
.ss-w-md :where(p, ul, ol, table, pre, blockquote, h1, h2, h3, h4) { margin: 0; }
|
|
565
|
+
.ss-w-md > *:first-child { margin-top: 0; }
|
|
566
|
+
.ss-w-md > *:last-child { margin-bottom: 0; }
|
|
567
|
+
.ss-w-md-ul { list-style: disc outside; padding-left: 18px; }
|
|
568
|
+
.ss-w-md-ol { list-style: decimal outside; padding-left: 18px; }
|
|
569
|
+
.ss-w-md-ul li::marker, .ss-w-md-ol li::marker { color: var(--ss-w-accent); }
|
|
570
|
+
.ss-w-md-link { color: var(--ss-w-accent); text-decoration: underline; }
|
|
571
|
+
.ss-w-table-wrap { overflow-x: auto; max-width: 100%; -webkit-overflow-scrolling: touch; }
|
|
572
|
+
.ss-w-md-table { border-collapse: collapse; width: max-content; min-width: 100%; font-variant-numeric: tabular-nums; }
|
|
573
|
+
.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; }
|
|
574
|
+
.ss-w-md-table th { background: color-mix(in srgb, var(--ss-w-accent) 12%, var(--ss-w-panel)); font-weight: 700; }
|
|
575
|
+
.ss-w-md-table tbody tr:nth-child(even) { background: var(--ss-w-bubble); }
|
|
576
|
+
.ss-w-md-code { background: var(--ss-w-bubble); border-radius: 5px; padding: 1px 5px; font-family: ui-monospace, monospace; }
|
|
577
|
+
|
|
578
|
+
/* entity cards */
|
|
579
|
+
.ss-w-cards { display: flex; flex-direction: column; gap: 7px; }
|
|
580
|
+
.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; }
|
|
581
|
+
.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; }
|
|
582
|
+
.ss-w-thumb.ss-w-team { border-radius: 10px; }
|
|
583
|
+
.ss-w-ecard[data-kind="player"] .ss-w-thumb { background-color: var(--ss-w-accent); }
|
|
584
|
+
.ss-w-meta { display: flex; flex-direction: column; min-width: 0; }
|
|
585
|
+
.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; }
|
|
586
|
+
.ss-w-nm { font-weight: 660; }
|
|
587
|
+
.ss-w-sub { font-size: 11px; color: var(--ss-w-faint); }
|
|
588
|
+
.ss-w-stat { margin-left: auto; text-align: center; }
|
|
589
|
+
.ss-w-stat b { display: block; font-variant-numeric: tabular-nums; }
|
|
590
|
+
.ss-w-stat span { font-size: 9px; text-transform: uppercase; color: var(--ss-w-faint); }
|
|
591
|
+
.ss-w-chev { color: var(--ss-w-faint); font-size: 16px; display: grid; place-items: center; }
|
|
592
|
+
.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; }
|
|
593
|
+
.ss-w-cards-more:hover:not(:disabled) { text-decoration: underline; }
|
|
594
|
+
.ss-w-ecard:disabled, .ss-w-cards-more:disabled { opacity: .5; cursor: default; }
|
|
595
|
+
|
|
596
|
+
/* action buttons */
|
|
597
|
+
.ss-w-actions { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
|
|
598
|
+
.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; }
|
|
599
|
+
.ss-w-abtn:disabled { opacity: .5; cursor: default; }
|
|
600
|
+
.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; }
|
|
601
|
+
@keyframes ss-w-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
|
|
602
|
+
|
|
603
|
+
/* intent chips */
|
|
604
|
+
.ss-w-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
605
|
+
.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; }
|
|
606
|
+
|
|
607
|
+
/* input */
|
|
608
|
+
.ss-w-input { display: flex; align-items: flex-end; gap: 8px; padding: 10px 12px; border-top: 1px solid var(--ss-w-line); }
|
|
609
|
+
.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; }
|
|
610
|
+
.ss-w-textarea:disabled { opacity: .55; cursor: not-allowed; }
|
|
611
|
+
.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; }
|
|
612
|
+
.ss-w-stop-glyph { width: 11px; height: 11px; background: currentColor; border-radius: 2px; }
|
|
613
|
+
|
|
614
|
+
@media (prefers-reduced-motion: reduce) {
|
|
615
|
+
.ss-w-abtn-shimmer { animation: none; }
|
|
616
|
+
}
|
|
617
|
+
`;
|
|
460
618
|
|
|
461
619
|
// src/StatsWidget.tsx
|
|
462
620
|
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
@@ -487,7 +645,7 @@ function StatsWidget(props) {
|
|
|
487
645
|
const built = (0, import_react6.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
|
|
488
646
|
const ui = (0, import_react6.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
|
|
489
647
|
const rootRef = (0, import_react6.useRef)(null);
|
|
490
|
-
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters);
|
|
648
|
+
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
|
|
491
649
|
(0, import_react6.useEffect)(() => {
|
|
492
650
|
if (!built || !ui.injectStyles) return;
|
|
493
651
|
const node = rootRef.current?.getRootNode();
|
|
@@ -495,10 +653,16 @@ function StatsWidget(props) {
|
|
|
495
653
|
}, [built, ui.injectStyles]);
|
|
496
654
|
if (!built) return null;
|
|
497
655
|
const attrs = themeAttrs(ui);
|
|
498
|
-
return /* @__PURE__ */ (0, import_jsx_runtime13.
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
656
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.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_runtime13.jsx)(ChatPanel, { controller, ui }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
657
|
+
ChatFab,
|
|
658
|
+
{
|
|
659
|
+
label: ui.launcherLabel,
|
|
660
|
+
greeting: ui.greeting,
|
|
661
|
+
popupText: controller.state.popupText,
|
|
662
|
+
onOpen: controller.open,
|
|
663
|
+
onDismissPopup: controller.dismissPopup
|
|
664
|
+
}
|
|
665
|
+
) });
|
|
502
666
|
}
|
|
503
667
|
function emptyConversation() {
|
|
504
668
|
return { send: () => {
|