@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.
- package/dist/index.cjs +479 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +34 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +441 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +102 -0
- package/package.json +63 -0
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|