@sensiblestats/widget-react 0.0.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.
@@ -0,0 +1,34 @@
1
+ import { UserContext, StatsWidgetClient } from '@sensiblestats/widget-sdk';
2
+
3
+ interface StatsWidgetConfig {
4
+ operatorId: string;
5
+ publicKey: string;
6
+ baseUrl?: string;
7
+ userContext?: UserContext;
8
+ theme?: 'light' | 'dark' | 'auto';
9
+ accent?: string;
10
+ position?: 'left' | 'right';
11
+ offset?: {
12
+ x?: number;
13
+ y?: number;
14
+ };
15
+ radius?: number;
16
+ launcherLabel?: string;
17
+ greeting?: string;
18
+ placeholder?: string;
19
+ starters?: string[];
20
+ injectStyles?: boolean;
21
+ }
22
+
23
+ type StatsWidgetProps = {
24
+ config: StatsWidgetConfig;
25
+ client?: never;
26
+ } | {
27
+ client: StatsWidgetClient;
28
+ config?: Omit<StatsWidgetConfig, 'operatorId' | 'publicKey' | 'baseUrl' | 'userContext'>;
29
+ };
30
+ declare function StatsWidget(props: StatsWidgetProps): JSX.Element | null;
31
+
32
+ declare const 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";
33
+
34
+ export { StatsWidget, type StatsWidgetConfig, type StatsWidgetProps, WIDGET_CSS };
@@ -0,0 +1,34 @@
1
+ import { UserContext, StatsWidgetClient } from '@sensiblestats/widget-sdk';
2
+
3
+ interface StatsWidgetConfig {
4
+ operatorId: string;
5
+ publicKey: string;
6
+ baseUrl?: string;
7
+ userContext?: UserContext;
8
+ theme?: 'light' | 'dark' | 'auto';
9
+ accent?: string;
10
+ position?: 'left' | 'right';
11
+ offset?: {
12
+ x?: number;
13
+ y?: number;
14
+ };
15
+ radius?: number;
16
+ launcherLabel?: string;
17
+ greeting?: string;
18
+ placeholder?: string;
19
+ starters?: string[];
20
+ injectStyles?: boolean;
21
+ }
22
+
23
+ type StatsWidgetProps = {
24
+ config: StatsWidgetConfig;
25
+ client?: never;
26
+ } | {
27
+ client: StatsWidgetClient;
28
+ config?: Omit<StatsWidgetConfig, 'operatorId' | 'publicKey' | 'baseUrl' | 'userContext'>;
29
+ };
30
+ declare function StatsWidget(props: StatsWidgetProps): JSX.Element | null;
31
+
32
+ declare const 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";
33
+
34
+ export { StatsWidget, type StatsWidgetConfig, type StatsWidgetProps, WIDGET_CSS };
package/dist/index.js ADDED
@@ -0,0 +1,441 @@
1
+ // src/StatsWidget.tsx
2
+ import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3 } from "react";
3
+ import { StatsWidgetClient } from "@sensiblestats/widget-sdk";
4
+
5
+ // src/config.ts
6
+ function normalizeUi(cfg) {
7
+ return {
8
+ theme: cfg.theme ?? "auto",
9
+ accent: cfg.accent,
10
+ position: cfg.position ?? "right",
11
+ offset: { x: cfg.offset?.x ?? 20, y: cfg.offset?.y ?? 20 },
12
+ radius: cfg.radius ?? 16,
13
+ launcherLabel: cfg.launcherLabel ?? "Chat with SensibleStats",
14
+ greeting: cfg.greeting,
15
+ placeholder: cfg.placeholder ?? "Ask about a team, player or match\u2026",
16
+ starters: cfg.starters ?? [],
17
+ injectStyles: cfg.injectStyles ?? true
18
+ };
19
+ }
20
+ function toClientOptions(cfg) {
21
+ const opts = { operatorId: cfg.operatorId, publicKey: cfg.publicKey };
22
+ if (cfg.baseUrl !== void 0) opts.baseUrl = cfg.baseUrl;
23
+ return opts;
24
+ }
25
+ function toUserContext(cfg) {
26
+ return cfg.userContext && Object.keys(cfg.userContext).length > 0 ? cfg.userContext : void 0;
27
+ }
28
+
29
+ // src/theme.ts
30
+ function themeAttrs(ui) {
31
+ const style = {
32
+ "--ss-w-offset-x": `${ui.offset.x}px`,
33
+ "--ss-w-offset-y": `${ui.offset.y}px`,
34
+ "--ss-w-radius": `${ui.radius}px`
35
+ };
36
+ if (ui.accent) style["--ss-w-accent"] = ui.accent;
37
+ return { "data-ss-w-theme": ui.theme, "data-ss-w-pos": ui.position, style };
38
+ }
39
+
40
+ // src/useChatStream.ts
41
+ import { useCallback, useMemo, useReducer, useRef } from "react";
42
+ import { WidgetSdkError as WidgetSdkError2 } from "@sensiblestats/widget-sdk";
43
+
44
+ // src/reducer.ts
45
+ import "@sensiblestats/widget-sdk";
46
+
47
+ // src/model.ts
48
+ function str(v) {
49
+ return typeof v === "string" && v.length > 0 ? v : void 0;
50
+ }
51
+ function num(v) {
52
+ return typeof v === "number" && Number.isFinite(v) ? String(v) : void 0;
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
+ function readStats(card) {
63
+ const cells = [];
64
+ for (const [key, label] of STAT_KEYS) {
65
+ const value = num(card[key]) ?? str(card[key]);
66
+ if (value !== void 0) cells.push({ label, value });
67
+ }
68
+ return cells;
69
+ }
70
+ function toCardVM(card) {
71
+ const kind = card.entityType === "Player" ? "player" : card.entityType === "Team" ? "team" : "generic";
72
+ return {
73
+ id: String(card.entityId),
74
+ kind,
75
+ name: card.canonicalName,
76
+ image: str(card.imageUrl) ?? str(card.image_url) ?? str(card.logoUrl) ?? str(card.logo),
77
+ sub: str(card.subtitle) ?? str(card.teamName) ?? str(card.leagueName) ?? str(card.position),
78
+ stats: readStats(card),
79
+ query: card.canonicalName
80
+ };
81
+ }
82
+ function toActionVM(action) {
83
+ const label = str(action.label) ?? str(action.text) ?? str(action.title) ?? "Continue";
84
+ const actionId = str(action.id) ?? str(action.actionId);
85
+ const message = str(action.message) ?? str(action.query) ?? label;
86
+ return { label, actionId, message };
87
+ }
88
+ function toChipVM(intent) {
89
+ const text = str(intent.text) ?? str(intent.label) ?? str(intent.title);
90
+ return text ? { text } : null;
91
+ }
92
+ function chipFromText(text) {
93
+ return { text };
94
+ }
95
+
96
+ // src/reducer.ts
97
+ function initialState(starters) {
98
+ return { isOpen: false, messages: [], isStreaming: false, status: null, actionsLoading: false, starters, error: null, lastUserInput: null };
99
+ }
100
+ function mapActive(state, fn) {
101
+ const last = state.messages[state.messages.length - 1];
102
+ if (!last || last.role !== "assistant" || last.done) return state.messages;
103
+ return [...state.messages.slice(0, -1), fn(last)];
104
+ }
105
+ function applyEvent(state, event) {
106
+ switch (event.type) {
107
+ case "progress":
108
+ return { ...state, status: event.data.message };
109
+ case "answer":
110
+ return { ...state, messages: mapActive(state, (t) => ({ ...t, text: t.text + event.data.text })) };
111
+ case "entity_card":
112
+ return { ...state, messages: mapActive(state, (t) => ({ ...t, cards: [...t.cards, toCardVM(event.data.card)] })) };
113
+ case "action_button":
114
+ return { ...state, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, actions: [...t.actions, toActionVM(event.data.action)] })) };
115
+ case "intent_chip": {
116
+ const chip = toChipVM(event.data.intent);
117
+ return chip ? { ...state, starters: [...state.starters, chip] } : state;
118
+ }
119
+ case "actions_loading":
120
+ return { ...state, actionsLoading: true, status: event.data.message };
121
+ case "turn_complete":
122
+ return { ...state, isStreaming: false, status: null, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, done: true })) };
123
+ case "error":
124
+ return { ...state, error: event.data, isStreaming: false, status: null, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, done: true })) };
125
+ }
126
+ }
127
+ function reducer(state, action) {
128
+ switch (action.kind) {
129
+ case "OPEN":
130
+ return { ...state, isOpen: true };
131
+ case "CLOSE":
132
+ return { ...state, isOpen: false, isStreaming: false, status: null, actionsLoading: false };
133
+ case "USER_SENT": {
134
+ const user = { role: "user", id: action.userId, text: action.input.message };
135
+ 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 };
137
+ }
138
+ case "EVENT":
139
+ return applyEvent(state, action.event);
140
+ case "STREAM_ERROR":
141
+ return { ...state, error: action.error, isStreaming: false, status: null, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, done: true })) };
142
+ case "STOP":
143
+ return { ...state, isStreaming: false, status: null, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, done: true })) };
144
+ }
145
+ }
146
+
147
+ // src/useChatStream.ts
148
+ function useChatStream(conversation, starterTexts) {
149
+ const seed = useMemo(() => initialState(starterTexts.map(chipFromText)), []);
150
+ const [state, dispatch] = useReducer(reducer, seed);
151
+ const idRef = useRef(0);
152
+ const handleRef = useRef(null);
153
+ const stateRef = useRef(state);
154
+ stateRef.current = state;
155
+ const nextId = () => `m${idRef.current++}`;
156
+ const run = useCallback((input) => {
157
+ handleRef.current?.cancel();
158
+ dispatch({ kind: "USER_SENT", input, userId: nextId(), assistantId: nextId() });
159
+ const handle = conversation.send(input);
160
+ handleRef.current = handle;
161
+ (async () => {
162
+ try {
163
+ for await (const event of handle) dispatch({ kind: "EVENT", event });
164
+ } catch (err) {
165
+ if (err instanceof WidgetSdkError2) dispatch({ kind: "STREAM_ERROR", error: err });
166
+ else if (err?.name === "AbortError") {
167
+ } else throw err;
168
+ } finally {
169
+ if (handleRef.current === handle) handleRef.current = null;
170
+ }
171
+ })();
172
+ }, [conversation]);
173
+ const send = useCallback((text, actionId) => {
174
+ const trimmed = text.trim();
175
+ if (!trimmed) return;
176
+ run({ message: trimmed, actionId });
177
+ }, [run]);
178
+ const retry = useCallback(() => {
179
+ const last = stateRef.current.lastUserInput;
180
+ if (last) run(last);
181
+ }, [run]);
182
+ const open = useCallback(() => dispatch({ kind: "OPEN" }), []);
183
+ const close = useCallback(() => {
184
+ handleRef.current?.cancel();
185
+ handleRef.current = null;
186
+ dispatch({ kind: "CLOSE" });
187
+ }, []);
188
+ const stop = useCallback(() => {
189
+ handleRef.current?.cancel();
190
+ handleRef.current = null;
191
+ dispatch({ kind: "STOP" });
192
+ }, []);
193
+ const toggle = useCallback(() => {
194
+ if (stateRef.current.isOpen) close();
195
+ else open();
196
+ }, [close, open]);
197
+ return { state, open, close, stop, toggle, send, retry };
198
+ }
199
+
200
+ // src/components/Icons.tsx
201
+ import { jsx } from "react/jsx-runtime";
202
+ function SsMark({ className }) {
203
+ return /* @__PURE__ */ jsx("svg", { className, viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { fill: "#00F400", d: "M12 2l2.5 6.5L21 11l-6.5 2.5L12 20l-2.5-6.5L3 11l6.5-2.5z" }) });
204
+ }
205
+ function SendIcon() {
206
+ 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" }) });
207
+ }
208
+ function CloseIcon() {
209
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { fill: "none", d: "M18 6L6 18M6 6l12 12", stroke: "currentColor", strokeWidth: "2" }) });
210
+ }
211
+ function ChevronIcon() {
212
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { fill: "none", stroke: "currentColor", strokeWidth: "2", d: "M9 6l6 6-6 6" }) });
213
+ }
214
+
215
+ // src/components/GreetingBubble.tsx
216
+ import { jsx as jsx2 } from "react/jsx-runtime";
217
+ function GreetingBubble({ text }) {
218
+ return /* @__PURE__ */ jsx2("div", { className: "ss-w-greeting", role: "note", children: text });
219
+ }
220
+
221
+ // src/components/ChatFab.tsx
222
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
223
+ function ChatFab({ label, open, greeting, onToggle }) {
224
+ return /* @__PURE__ */ jsxs("div", { className: "ss-w-fab-wrap", children: [
225
+ !open && greeting ? /* @__PURE__ */ jsx3(GreetingBubble, { text: greeting }) : null,
226
+ /* @__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
+ ] });
228
+ }
229
+
230
+ // src/components/ChatPanel.tsx
231
+ import { useEffect as useEffect2 } from "react";
232
+
233
+ // src/components/ChatHeader.tsx
234
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
235
+ function ChatHeader({ onClose }) {
236
+ return /* @__PURE__ */ jsxs2("header", { className: "ss-w-header", children: [
237
+ /* @__PURE__ */ jsx4(SsMark, { className: "ss-w-header-mark" }),
238
+ /* @__PURE__ */ jsx4("span", { className: "ss-w-header-title", children: "SensibleStats" }),
239
+ /* @__PURE__ */ jsx4("button", { type: "button", className: "ss-w-close", "aria-label": "Close chat", onClick: onClose, children: /* @__PURE__ */ jsx4(CloseIcon, {}) })
240
+ ] });
241
+ }
242
+
243
+ // src/components/MessageList.tsx
244
+ import { useEffect, useRef as useRef2 } from "react";
245
+
246
+ // src/components/Markdown.tsx
247
+ import ReactMarkdown from "react-markdown";
248
+ import remarkGfm from "remark-gfm";
249
+ import { jsx as jsx5 } from "react/jsx-runtime";
250
+ var components = {
251
+ a: ({ children, href }) => /* @__PURE__ */ jsx5("a", { href, target: "_blank", rel: "noopener noreferrer", className: "ss-w-md-link", children }),
252
+ table: ({ children }) => /* @__PURE__ */ jsx5("div", { className: "ss-w-table-wrap", children: /* @__PURE__ */ jsx5("table", { className: "ss-w-md-table", children }) }),
253
+ ul: ({ children }) => /* @__PURE__ */ jsx5("ul", { className: "ss-w-md-ul", children }),
254
+ ol: ({ children }) => /* @__PURE__ */ jsx5("ol", { className: "ss-w-md-ol", children }),
255
+ code: ({ children }) => /* @__PURE__ */ jsx5("code", { className: "ss-w-md-code", children })
256
+ };
257
+ function Markdown({ text }) {
258
+ return /* @__PURE__ */ jsx5("div", { className: "ss-w-md", children: /* @__PURE__ */ jsx5(ReactMarkdown, { remarkPlugins: [remarkGfm], components, children: text }) });
259
+ }
260
+
261
+ // src/components/EntityCardList.tsx
262
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
263
+ function initials(name) {
264
+ return name.split(/\s+/).slice(0, 2).map((w) => w[0] ?? "").join("").toUpperCase();
265
+ }
266
+ function EntityCardList({ cards, onSelect }) {
267
+ if (cards.length === 0) return null;
268
+ return /* @__PURE__ */ jsx6("div", { className: "ss-w-cards", children: cards.map((c) => /* @__PURE__ */ jsxs3("button", { type: "button", className: "ss-w-ecard", onClick: () => onSelect(c.query), children: [
269
+ /* @__PURE__ */ jsx6(
270
+ "span",
271
+ {
272
+ className: c.kind === "team" ? "ss-w-thumb ss-w-team" : "ss-w-thumb",
273
+ style: c.image ? { backgroundImage: `url(${c.image})` } : void 0,
274
+ children: c.image ? "" : initials(c.name)
275
+ }
276
+ ),
277
+ /* @__PURE__ */ jsxs3("span", { className: "ss-w-meta", children: [
278
+ /* @__PURE__ */ jsx6("span", { className: "ss-w-nm", children: c.name }),
279
+ c.sub ? /* @__PURE__ */ jsx6("span", { className: "ss-w-sub", children: c.sub }) : null
280
+ ] }),
281
+ c.stats.map((s) => /* @__PURE__ */ jsxs3("span", { className: "ss-w-stat", children: [
282
+ /* @__PURE__ */ jsx6("b", { children: s.value }),
283
+ /* @__PURE__ */ jsx6("span", { children: s.label })
284
+ ] }, s.label)),
285
+ /* @__PURE__ */ jsx6("span", { className: "ss-w-chev", children: /* @__PURE__ */ jsx6(ChevronIcon, {}) })
286
+ ] }, c.id)) });
287
+ }
288
+
289
+ // src/components/ActionButtons.tsx
290
+ import { Fragment, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
291
+ function ActionButtons({ actions, loading, onAct }) {
292
+ if (actions.length === 0 && !loading) return null;
293
+ return /* @__PURE__ */ jsxs4("div", { className: "ss-w-actions", children: [
294
+ actions.map((a, i) => /* @__PURE__ */ jsx7("button", { type: "button", className: "ss-w-abtn", onClick: () => onAct(a.message, a.actionId), children: a.label }, `${a.label}-${i}`)),
295
+ actions.length === 0 && loading ? /* @__PURE__ */ jsxs4(Fragment, { children: [
296
+ /* @__PURE__ */ jsx7("span", { className: "ss-w-abtn-shimmer" }),
297
+ /* @__PURE__ */ jsx7("span", { className: "ss-w-abtn-shimmer" })
298
+ ] }) : null
299
+ ] });
300
+ }
301
+
302
+ // src/components/ChatMessage.tsx
303
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
304
+ function ChatMessage({ message, onAct, onSelect }) {
305
+ if (message.role === "user") {
306
+ return /* @__PURE__ */ jsx8("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx8("div", { className: "ss-w-bubble", children: message.text }) });
307
+ }
308
+ return /* @__PURE__ */ jsxs5("div", { className: "ss-w-msg ss-w-bot", children: [
309
+ 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 })
312
+ ] });
313
+ }
314
+
315
+ // src/components/IntentChips.tsx
316
+ import { jsx as jsx9 } from "react/jsx-runtime";
317
+ function IntentChips({ chips, onPick }) {
318
+ if (chips.length === 0) return null;
319
+ return /* @__PURE__ */ jsx9("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ jsx9("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
320
+ }
321
+
322
+ // src/components/MessageList.tsx
323
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
324
+ function MessageList({ controller, ui }) {
325
+ void ui;
326
+ const { state, send } = controller;
327
+ const endRef = useRef2(null);
328
+ useEffect(() => {
329
+ endRef.current?.scrollIntoView({ block: "end" });
330
+ }, [state.messages, state.status]);
331
+ const empty = state.messages.length === 0;
332
+ return /* @__PURE__ */ jsxs6("div", { className: "ss-w-list", "aria-live": "polite", children: [
333
+ state.messages.map((m) => /* @__PURE__ */ jsx10(ChatMessage, { message: m, onAct: (msg, id) => send(msg, id), onSelect: (q) => send(q) }, m.id)),
334
+ state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx10(ActionButtons, { actions: [], loading: true, onAct: () => {
335
+ } }) : null,
336
+ state.status ? /* @__PURE__ */ jsx10("div", { className: "ss-w-status", children: state.status }) : null,
337
+ state.error ? /* @__PURE__ */ jsxs6("div", { className: "ss-w-error", role: "alert", children: [
338
+ /* @__PURE__ */ jsx10("span", { children: "message" in state.error ? state.error.message : "Something went wrong." }),
339
+ /* @__PURE__ */ jsx10("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: "Retry" })
340
+ ] }) : null,
341
+ empty ? /* @__PURE__ */ jsx10(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
342
+ /* @__PURE__ */ jsx10("div", { ref: endRef })
343
+ ] });
344
+ }
345
+
346
+ // src/components/ChatInput.tsx
347
+ import { useState } from "react";
348
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
349
+ function ChatInput({ placeholder, streaming, onSend, onStop }) {
350
+ const [value, setValue] = useState("");
351
+ const submit = () => {
352
+ const t = value.trim();
353
+ if (!t) return;
354
+ onSend(t);
355
+ setValue("");
356
+ };
357
+ const onKeyDown = (e) => {
358
+ if (e.key === "Enter" && !e.shiftKey) {
359
+ e.preventDefault();
360
+ submit();
361
+ }
362
+ };
363
+ return /* @__PURE__ */ jsxs7("div", { className: "ss-w-input", children: [
364
+ /* @__PURE__ */ jsx11("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, onChange: (e) => setValue(e.target.value), onKeyDown }),
365
+ 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
+ ] });
367
+ }
368
+
369
+ // src/components/ChatPanel.tsx
370
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
371
+ function ChatPanel({ controller, ui }) {
372
+ const { state, close, stop, send } = controller;
373
+ useEffect2(() => {
374
+ const onKey = (e) => {
375
+ if (e.key === "Escape") close();
376
+ };
377
+ window.addEventListener("keydown", onKey);
378
+ return () => window.removeEventListener("keydown", onKey);
379
+ }, [close]);
380
+ return /* @__PURE__ */ jsxs8("div", { className: "ss-w-panel", role: "dialog", "aria-modal": "true", "aria-label": "SensibleStats chat", children: [
381
+ /* @__PURE__ */ jsx12(ChatHeader, { onClose: close }),
382
+ /* @__PURE__ */ jsx12(MessageList, { controller, ui }),
383
+ /* @__PURE__ */ jsx12(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop })
384
+ ] });
385
+ }
386
+
387
+ // 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';
389
+
390
+ // src/StatsWidget.tsx
391
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
392
+ function buildClient(props) {
393
+ try {
394
+ if ("client" in props && props.client) {
395
+ return { client: props.client, conversation: props.client.conversation() };
396
+ }
397
+ const cfg = props.config;
398
+ const client = new StatsWidgetClient(toClientOptions(cfg));
399
+ return { client, conversation: client.conversation(toUserContext(cfg)) };
400
+ } catch (err) {
401
+ console.error("[StatsWidget] failed to initialize:", err instanceof Error ? err.message : err);
402
+ return null;
403
+ }
404
+ }
405
+ function injectStyles(root) {
406
+ const host = root instanceof ShadowRoot ? root : root.head;
407
+ if (host.querySelector("#ss-w-styles")) return;
408
+ const el = document.createElement("style");
409
+ el.id = "ss-w-styles";
410
+ el.textContent = WIDGET_CSS;
411
+ host.appendChild(el);
412
+ }
413
+ function StatsWidget(props) {
414
+ const injectedClient = "client" in props ? props.client : void 0;
415
+ const cfg = props.config;
416
+ const built = useMemo2(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
417
+ const ui = useMemo2(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
418
+ const rootRef = useRef3(null);
419
+ const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters);
420
+ useEffect3(() => {
421
+ if (!built || !ui.injectStyles) return;
422
+ const node = rootRef.current?.getRootNode();
423
+ if (node) injectStyles(node);
424
+ }, [built, ui.injectStyles]);
425
+ if (!built) return null;
426
+ const attrs = themeAttrs(ui);
427
+ return /* @__PURE__ */ jsxs9("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
+ controller.state.isOpen ? /* @__PURE__ */ jsx13(ChatPanel, { controller, ui }) : null,
429
+ /* @__PURE__ */ jsx13(ChatFab, { label: ui.launcherLabel, open: controller.state.isOpen, greeting: ui.greeting, onToggle: controller.toggle })
430
+ ] });
431
+ }
432
+ function emptyConversation() {
433
+ return { send: () => {
434
+ throw new Error("widget not initialized");
435
+ } };
436
+ }
437
+ export {
438
+ StatsWidget,
439
+ WIDGET_CSS
440
+ };
441
+ //# sourceMappingURL=index.js.map