@smooai/chat-widget 0.10.0 → 0.10.2
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/README.md +1 -0
- package/dist/chat-widget.global.js +56 -12
- package/dist/chat-widget.global.js.map +1 -1
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +56 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/config.ts +7 -0
- package/src/element.ts +44 -4
- package/src/styles.ts +30 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smooai/chat-widget",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Embeddable AI chat as a framework-light web component — the Aurora Glass design, streaming replies, grounded sources, and per-brand theming. Speaks the smooth-operator WebSocket protocol.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "SmooAI",
|
package/src/config.ts
CHANGED
|
@@ -95,6 +95,12 @@ export interface ChatWidgetConfig {
|
|
|
95
95
|
connectionErrorMessage?: string;
|
|
96
96
|
/** Start the panel open instead of collapsed to the launcher. */
|
|
97
97
|
startOpen?: boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Hide the "powered by smooth-operator" branding in the header tag and the
|
|
100
|
+
* composer footer. Defaults to `false` (branding shown). The `hide-branding`
|
|
101
|
+
* HTML attribute maps to this.
|
|
102
|
+
*/
|
|
103
|
+
hideBranding?: boolean;
|
|
98
104
|
/**
|
|
99
105
|
* Suggested starter prompts shown as clickable chips before the first message.
|
|
100
106
|
* Clicking one sends it. Capped at 5 for layout.
|
|
@@ -172,6 +178,7 @@ export function resolveConfig(config: ChatWidgetConfig): ResolvedConfig {
|
|
|
172
178
|
greeting: config.greeting ?? 'Hi! How can I help you today?',
|
|
173
179
|
connectionErrorMessage: config.connectionErrorMessage ?? "We couldn't reach the chat. Please try again in a moment.",
|
|
174
180
|
startOpen: config.startOpen ?? false,
|
|
181
|
+
hideBranding: config.hideBranding ?? false,
|
|
175
182
|
examplePrompts: (config.examplePrompts ?? []).filter((p) => p.trim().length > 0).slice(0, 5),
|
|
176
183
|
requireName: config.requireName ?? false,
|
|
177
184
|
requireEmail: config.requireEmail ?? false,
|
package/src/element.ts
CHANGED
|
@@ -49,7 +49,10 @@ function phoneToE164(value: string): string | null {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
/** Public smooth-operator repo — the "powered by" header tag + footer link here. */
|
|
53
|
+
const SMOOTH_OPERATOR_URL = 'https://github.com/SmooAI/smooth-operator';
|
|
54
|
+
|
|
55
|
+
const OBSERVED = ['endpoint', 'agent-id', 'agent-name', 'logo-url', 'placeholder', 'greeting', 'start-open', 'mode', 'hide-branding'] as const;
|
|
53
56
|
|
|
54
57
|
/**
|
|
55
58
|
* Inline SVG icons (static, trusted strings — never interpolated with user data).
|
|
@@ -204,6 +207,7 @@ export class SmoothAgentChatElement extends HTMLElement {
|
|
|
204
207
|
greeting: this.overrides.greeting ?? this.getAttribute('greeting') ?? undefined,
|
|
205
208
|
connectionErrorMessage: this.overrides.connectionErrorMessage,
|
|
206
209
|
startOpen: this.overrides.startOpen ?? this.hasAttribute('start-open'),
|
|
210
|
+
hideBranding: this.overrides.hideBranding ?? this.hasAttribute('hide-branding'),
|
|
207
211
|
examplePrompts: this.overrides.examplePrompts,
|
|
208
212
|
requireName: this.overrides.requireName,
|
|
209
213
|
requireEmail: this.overrides.requireEmail,
|
|
@@ -282,7 +286,11 @@ export class SmoothAgentChatElement extends HTMLElement {
|
|
|
282
286
|
<span class="title">${escapeHtml(resolved.agentName)}</span>
|
|
283
287
|
<span class="status"><span class="dot off"></span><span class="status-text"></span></span>
|
|
284
288
|
</div>
|
|
285
|
-
|
|
289
|
+
${
|
|
290
|
+
resolved.hideBranding
|
|
291
|
+
? ''
|
|
292
|
+
: `<a class="powered" href="${SMOOTH_OPERATOR_URL}" target="_blank" rel="noopener noreferrer">powered by smooth-operator</a>`
|
|
293
|
+
}
|
|
286
294
|
</div>`
|
|
287
295
|
: `<div class="header">
|
|
288
296
|
<div class="avatar">${monogram}</div>
|
|
@@ -329,7 +337,15 @@ export class SmoothAgentChatElement extends HTMLElement {
|
|
|
329
337
|
<button type="submit" class="pc-submit">Start chat</button>
|
|
330
338
|
</form>
|
|
331
339
|
</div>`;
|
|
332
|
-
|
|
340
|
+
// Footer: optional "powered by" branding (hidden by hide-branding) and an
|
|
341
|
+
// optional "Restore my chats" affordance. The " · " separator only appears
|
|
342
|
+
// when both are present, and the footer is omitted entirely when neither is.
|
|
343
|
+
const brandingHtml = resolved.hideBranding
|
|
344
|
+
? ''
|
|
345
|
+
: `<a href="${SMOOTH_OPERATOR_URL}" target="_blank" rel="noopener noreferrer">powered by <b>smooth‑operator</b></a>`;
|
|
346
|
+
const restoreBtn = this.allowChatRestore ? `<button type="button" class="restore-link">Restore my chats</button>` : '';
|
|
347
|
+
const footerInner = [brandingHtml, restoreBtn].filter(Boolean).join(' · ');
|
|
348
|
+
const footerHtml = footerInner ? `<div class="footer">${footerInner}</div>` : '';
|
|
333
349
|
const chatHtml = `
|
|
334
350
|
<div class="messages"></div>
|
|
335
351
|
<div class="interrupt hidden"></div>
|
|
@@ -338,10 +354,11 @@ export class SmoothAgentChatElement extends HTMLElement {
|
|
|
338
354
|
<textarea rows="1" placeholder="${escapeHtml(resolved.placeholder)}"></textarea>
|
|
339
355
|
<button class="send" type="button" aria-label="Send message">${ICON.send}</button>
|
|
340
356
|
</div>
|
|
341
|
-
|
|
357
|
+
${footerHtml}
|
|
342
358
|
</div>`;
|
|
343
359
|
|
|
344
360
|
const container = document.createElement('div');
|
|
361
|
+
container.className = 'wrap';
|
|
345
362
|
container.innerHTML = `
|
|
346
363
|
${fullpage ? '' : `<button class="launcher" part="launcher" aria-label="Open chat">${ICON.spark}</button>`}
|
|
347
364
|
<div class="panel${fullpage ? ' fullpage' : ' hidden'}" part="panel" role="${fullpage ? 'region' : 'dialog'}" aria-label="${escapeHtml(resolved.agentName)} chat">
|
|
@@ -407,6 +424,10 @@ export class SmoothAgentChatElement extends HTMLElement {
|
|
|
407
424
|
})();
|
|
408
425
|
});
|
|
409
426
|
|
|
427
|
+
// Full-page mode sizes to the host's box; fall back to the viewport only
|
|
428
|
+
// when the container gives the host no height.
|
|
429
|
+
if (fullpage) this.syncViewportFallback();
|
|
430
|
+
|
|
410
431
|
// Full-page mode connects eagerly (there's no launcher click to trigger it) —
|
|
411
432
|
// but only once any identity gate is cleared.
|
|
412
433
|
if (fullpage && !gating) void this.controller?.connect().catch(() => {});
|
|
@@ -998,6 +1019,25 @@ export class SmoothAgentChatElement extends HTMLElement {
|
|
|
998
1019
|
}
|
|
999
1020
|
}
|
|
1000
1021
|
|
|
1022
|
+
/**
|
|
1023
|
+
* Full-page sizing probe: decide whether the host's container gives it a
|
|
1024
|
+
* real box. With the `.wrap` flex chain hidden, `height: 100%` of a SIZED
|
|
1025
|
+
* container still resolves (clientHeight > 0), while an auto-height parent
|
|
1026
|
+
* (e.g. mounted straight into `<body>`) collapses to ~0. Only the latter
|
|
1027
|
+
* gets `data-viewport-fallback`, whose CSS applies `min-height: 100dvh` —
|
|
1028
|
+
* so an embed inside a fixed-height box never overflows it (the composer
|
|
1029
|
+
* stays visible), and a bare full-page route still fills the viewport.
|
|
1030
|
+
*/
|
|
1031
|
+
private syncViewportFallback(): void {
|
|
1032
|
+
const wrap = this.shadowRoot?.querySelector<HTMLElement>('.wrap');
|
|
1033
|
+
if (!wrap) return;
|
|
1034
|
+
const prev = wrap.style.display;
|
|
1035
|
+
wrap.style.display = 'none';
|
|
1036
|
+
const heightless = this.clientHeight < 8;
|
|
1037
|
+
wrap.style.display = prev;
|
|
1038
|
+
this.toggleAttribute('data-viewport-fallback', heightless);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1001
1041
|
/** Cancel any in-flight reveal loop and clear its state (called on full rebuild). */
|
|
1002
1042
|
private resetReveal(): void {
|
|
1003
1043
|
if (this.rafId && typeof cancelAnimationFrame === 'function') cancelAnimationFrame(this.rafId);
|
package/src/styles.ts
CHANGED
|
@@ -44,11 +44,11 @@ export function buildStyles(theme: ResolvedTheme, mode: ChatWidgetMode = 'popove
|
|
|
44
44
|
${
|
|
45
45
|
mode === 'fullpage'
|
|
46
46
|
? `/* Full-page: fill the host's box (sized by its container, else the viewport). */
|
|
47
|
-
display:
|
|
47
|
+
display: flex;
|
|
48
|
+
flex-direction: column;
|
|
48
49
|
position: relative;
|
|
49
50
|
width: 100%;
|
|
50
|
-
height: 100
|
|
51
|
-
min-height: 100vh;`
|
|
51
|
+
height: 100%;`
|
|
52
52
|
: `/* Popover: float in the bottom-right corner. */
|
|
53
53
|
position: fixed;
|
|
54
54
|
bottom: 24px;
|
|
@@ -58,7 +58,24 @@ export function buildStyles(theme: ResolvedTheme, mode: ChatWidgetMode = 'popove
|
|
|
58
58
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
59
59
|
-webkit-font-smoothing: antialiased;
|
|
60
60
|
}
|
|
61
|
-
|
|
61
|
+
${
|
|
62
|
+
mode === 'fullpage'
|
|
63
|
+
? `
|
|
64
|
+
/* Viewport fallback — the element sets this attribute only when the host's
|
|
65
|
+
container gives it no resolved height (e.g. mounted straight into an
|
|
66
|
+
auto-height <body>). A sized container always wins, so an embed inside a
|
|
67
|
+
fixed-height box never overflows it (composer stays visible). */
|
|
68
|
+
:host([data-viewport-fallback]) { min-height: 100dvh; }
|
|
69
|
+
/* The render wrapper passes the host's box down to the panel via flex. */
|
|
70
|
+
.wrap {
|
|
71
|
+
flex: 1;
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
min-height: 0;
|
|
75
|
+
}
|
|
76
|
+
`
|
|
77
|
+
: ''
|
|
78
|
+
}
|
|
62
79
|
* { box-sizing: border-box; }
|
|
63
80
|
|
|
64
81
|
/* ───────────────────────────── Launcher ───────────────────────────── */
|
|
@@ -140,11 +157,13 @@ export function buildStyles(theme: ResolvedTheme, mode: ChatWidgetMode = 'popove
|
|
|
140
157
|
pointer-events: none;
|
|
141
158
|
background: radial-gradient(120% 100% at 50% 0%, color-mix(in srgb, var(--sac-primary) 22%, transparent), transparent 70%);
|
|
142
159
|
}
|
|
143
|
-
/* Full-page: the panel becomes the whole surface
|
|
160
|
+
/* Full-page: the panel becomes the whole surface — it follows the host's box
|
|
161
|
+
(via the .wrap flex chain), never a hardcoded viewport unit. */
|
|
144
162
|
.panel.fullpage {
|
|
145
163
|
width: 100%;
|
|
146
|
-
|
|
147
|
-
|
|
164
|
+
flex: 1;
|
|
165
|
+
height: auto;
|
|
166
|
+
min-height: 0;
|
|
148
167
|
max-width: none;
|
|
149
168
|
max-height: none;
|
|
150
169
|
border: none;
|
|
@@ -220,7 +239,8 @@ export function buildStyles(theme: ResolvedTheme, mode: ChatWidgetMode = 'popove
|
|
|
220
239
|
}
|
|
221
240
|
.close:hover { background: color-mix(in srgb, var(--sac-text) 12%, transparent); transform: translateY(1px); }
|
|
222
241
|
.close svg { width: 16px; height: 16px; opacity: .8; }
|
|
223
|
-
.powered { margin-left: auto; font-size: 10.5px; letter-spacing: .02em; opacity: .6; }
|
|
242
|
+
.powered { margin-left: auto; font-size: 10.5px; letter-spacing: .02em; opacity: .6; color: inherit; text-decoration: none; }
|
|
243
|
+
.powered:hover { opacity: .85; text-decoration: underline; }
|
|
224
244
|
.header-sep { height: 1px; margin: 0 16px; background: linear-gradient(90deg, transparent, var(--sac-border), transparent); }
|
|
225
245
|
|
|
226
246
|
/* Full-page header: taller, logo-led, no close. */
|
|
@@ -492,6 +512,8 @@ export function buildStyles(theme: ResolvedTheme, mode: ChatWidgetMode = 'popove
|
|
|
492
512
|
color: color-mix(in srgb, var(--sac-text) 38%, transparent);
|
|
493
513
|
}
|
|
494
514
|
.footer b { font-weight: 600; color: color-mix(in srgb, var(--sac-text) 55%, transparent); }
|
|
515
|
+
.footer a { color: inherit; text-decoration: none; }
|
|
516
|
+
.footer a:hover { text-decoration: underline; }
|
|
495
517
|
|
|
496
518
|
/* ─────────────────── Pre-chat identity form ───────────────────────── */
|
|
497
519
|
.prechat { flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 18px; padding: 22px 20px; }
|