@smooai/chat-widget 0.9.0 → 0.10.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smooai/chat-widget",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
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
@@ -5,6 +5,8 @@
5
5
  * `<smooth-agent-chat>` element) or programmatically (passing this object to
6
6
  * {@link mountChatWidget} / `element.configure(...)`).
7
7
  */
8
+ import { safeHttpUrl } from './markdown.js';
9
+
8
10
  export interface ChatWidgetTheme {
9
11
  /** Foreground text color for the widget chrome. */
10
12
  text?: string;
@@ -65,6 +67,13 @@ export interface ChatWidgetConfig {
65
67
  agentId: string;
66
68
  /** Display name for the agent (header label). Defaults to "Assistant". */
67
69
  agentName?: string;
70
+ /**
71
+ * Brand logo shown in the full-page header avatar tile; falls back to the
72
+ * Smooth icon. SECURITY: only absolute `http(s)` URLs are honored — any other
73
+ * scheme (`javascript:`/`data:`/…) is ignored, so a hostile config can't
74
+ * inject script.
75
+ */
76
+ logoUrl?: string;
68
77
  /** Optional display name for the user participant. */
69
78
  userName?: string;
70
79
  /** Optional email address for the user participant. */
@@ -127,12 +136,14 @@ export interface ChatWidgetConfig {
127
136
  /** The fully-resolved theme (canonical keys only — aliases are folded in). */
128
137
  export type ResolvedTheme = Required<Omit<ChatWidgetTheme, 'chatBubbleInbound' | 'chatBubbleInboundText' | 'chatBubbleOutbound' | 'chatBubbleOutboundText'>>;
129
138
 
130
- export type ResolvedConfig = Required<Omit<ChatWidgetConfig, 'theme' | 'userName' | 'userEmail' | 'userPhone' | 'authContext'>> & {
139
+ export type ResolvedConfig = Required<Omit<ChatWidgetConfig, 'theme' | 'userName' | 'userEmail' | 'userPhone' | 'authContext' | 'logoUrl'>> & {
131
140
  theme: ResolvedTheme;
132
141
  userName?: string;
133
142
  userEmail?: string;
134
143
  userPhone?: string;
135
144
  authContext?: { userId: string; signature: string; timestamp: number };
145
+ /** Sanitized brand logo URL (`http(s)` only) or `undefined` — see {@link ChatWidgetConfig.logoUrl}. */
146
+ logoUrl?: string;
136
147
  };
137
148
 
138
149
  /** Resolve a partial config against the built-in defaults. */
@@ -150,6 +161,9 @@ export function resolveConfig(config: ChatWidgetConfig): ResolvedConfig {
150
161
  mode: config.mode ?? 'popover',
151
162
  agentId: config.agentId,
152
163
  agentName: config.agentName ?? 'Assistant',
164
+ // Only absolute http(s) URLs survive — anything else (javascript:/data:/
165
+ // relative) is dropped so the header can never render a hostile logo src.
166
+ logoUrl: safeHttpUrl(config.logoUrl) ?? undefined,
153
167
  userName: config.userName,
154
168
  userEmail: config.userEmail,
155
169
  userPhone: config.userPhone,
package/src/element.ts CHANGED
@@ -18,7 +18,7 @@ import { AsYouType, isValidPhoneNumber, parsePhoneNumber } from 'libphonenumber-
18
18
  import type { ChatWidgetConfig, ChatWidgetMode, ChatWidgetTheme } from './config.js';
19
19
  import { needsUserInfo, resolveConfig } from './config.js';
20
20
  import { type ChatMessage, type Citation, type ConnectionStatus, ConversationController, type IdentityRestore, type Interrupt } from './conversation.js';
21
- import { SMOOTH_LOGO_SVG } from './logo.js';
21
+ import { SMOOTH_ICON_SVG } from './logo.js';
22
22
  import { cleanCitationSnippet, escapeHtml, renderMarkdown, safeHttpUrl } from './markdown.js';
23
23
  import { buildStyles } from './styles.js';
24
24
 
@@ -49,7 +49,7 @@ function phoneToE164(value: string): string | null {
49
49
  }
50
50
  }
51
51
 
52
- const OBSERVED = ['endpoint', 'agent-id', 'agent-name', 'placeholder', 'greeting', 'start-open', 'mode'] as const;
52
+ const OBSERVED = ['endpoint', 'agent-id', 'agent-name', 'logo-url', 'placeholder', 'greeting', 'start-open', 'mode'] as const;
53
53
 
54
54
  /**
55
55
  * Inline SVG icons (static, trusted strings — never interpolated with user data).
@@ -195,6 +195,7 @@ export class SmoothAgentChatElement extends HTMLElement {
195
195
  mode,
196
196
  agentId,
197
197
  agentName: this.overrides.agentName ?? this.getAttribute('agent-name') ?? undefined,
198
+ logoUrl: this.overrides.logoUrl ?? this.getAttribute('logo-url') ?? undefined,
198
199
  userName: this.overrides.userName,
199
200
  userEmail: this.overrides.userEmail,
200
201
  userPhone: this.overrides.userPhone,
@@ -264,13 +265,19 @@ export class SmoothAgentChatElement extends HTMLElement {
264
265
  const style = document.createElement('style');
265
266
  style.textContent = buildStyles(resolved.theme, resolved.mode);
266
267
 
267
- // Header: in full-page mode lead with the Smooth logo in the avatar tile
268
+ // Header: in full-page mode lead with the brand logo in the avatar tile
268
269
  // and a subtle "powered by" tag; in popover mode show a brand-colored
269
- // monogram avatar + a compact close (collapse) button.
270
+ // monogram avatar + a compact close (collapse) button. The logo defaults
271
+ // to the square Smooth icon, but a host page can override it with
272
+ // `logoUrl` (already sanitized to http(s)-only by resolveConfig; escaped
273
+ // here so it can't break out of the src attribute).
270
274
  const monogram = escapeHtml((resolved.agentName.trim().charAt(0) || 'A').toUpperCase());
275
+ const headerLogo = resolved.logoUrl
276
+ ? `<img src="${escapeHtml(resolved.logoUrl)}" alt="" class="logo-img" />`
277
+ : SMOOTH_ICON_SVG;
271
278
  const header = fullpage
272
279
  ? `<div class="header">
273
- <div class="avatar"><span class="logo-wrap">${SMOOTH_LOGO_SVG}</span></div>
280
+ <div class="avatar"><span class="logo-wrap">${headerLogo}</span></div>
274
281
  <div class="meta">
275
282
  <span class="title">${escapeHtml(resolved.agentName)}</span>
276
283
  <span class="status"><span class="dot off"></span><span class="status-text"></span></span>
package/src/logo.ts CHANGED
@@ -1,9 +1,14 @@
1
1
  /**
2
- * The Smooth logo, inlined as an SVG string so the full-page header can render
3
- * it without a separate network fetch (the IIFE bundle is self-contained).
2
+ * The Smooth logo + icon, inlined as SVG strings so the full-page header can
3
+ * render them without a separate network fetch (the IIFE bundle is
4
+ * self-contained).
4
5
  *
5
- * GENERATED from `assets/smooth-logo.svg` — do not edit by hand. Regenerate with:
6
- * node -e ... (see the commit that added this file)
6
+ * GENERATED from `assets/smooth-logo.svg` / `assets/smooth-icon.svg` — do not
7
+ * edit by hand. Regenerate with:
8
+ * node -e 'const fs=require("fs");process.stdout.write(JSON.stringify(fs.readFileSync("assets/smooth-icon.svg","utf8")))'
7
9
  */
8
10
  /* eslint-disable */
9
11
  export const SMOOTH_LOGO_SVG = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<svg id=\"Layer_1\" data-name=\"Layer 1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 550 135\">\n <defs>\n <style>\n .cls-1 {\n fill: url(#linear-gradient-3);\n }\n\n .cls-2 {\n fill: url(#linear-gradient-2);\n }\n\n .cls-3 {\n fill: url(#linear-gradient);\n fill-rule: evenodd;\n }\n </style>\n <linearGradient id=\"linear-gradient\" x1=\"115.59\" y1=\"112.81\" x2=\"25.08\" y2=\"22.3\" gradientUnits=\"userSpaceOnUse\">\n <stop offset=\".3\" stop-color=\"#f49f0a\"/>\n <stop offset=\".79\" stop-color=\"#fb7a4d\"/>\n <stop offset=\"1\" stop-color=\"#ff6b6c\"/>\n </linearGradient>\n <linearGradient id=\"linear-gradient-2\" x1=\"360.91\" y1=\"152.01\" x2=\"202.32\" y2=\"-6.59\" xlink:href=\"#linear-gradient\"/>\n <linearGradient id=\"linear-gradient-3\" x1=\"443.91\" y1=\"30.15\" x2=\"531.36\" y2=\"117.59\" gradientUnits=\"userSpaceOnUse\">\n <stop offset=\".43\" stop-color=\"#00a6a6\"/>\n <stop offset=\"1\" stop-color=\"#1238dd\"/>\n </linearGradient>\n </defs>\n <path class=\"cls-3\" d=\"M48.28,14.96c-12.39,5.21-22.54,14.64-28.65,26.61-6.12,11.97-7.8,25.72-4.77,38.81,3.04,13.09,10.6,24.69,21.36,32.75,10.76,8.06,24.02,12.05,37.44,11.28,13.42-.77,26.13-6.26,35.9-15.5,9.76-9.24,15.95-21.63,17.46-34.99,1.51-13.36-1.74-26.82-9.19-38.01-1.07-1.61-.64-3.78.97-4.85,1.61-1.07,3.78-.64,4.85.97,8.36,12.56,12.02,27.68,10.32,42.67-1.7,15-8.64,28.91-19.61,39.28-10.96,10.37-25.24,16.54-40.31,17.4-15.07.87-29.96-3.62-42.04-12.66-12.08-9.05-20.58-22.07-23.99-36.77-3.41-14.7-1.51-30.14,5.35-43.58,6.87-13.44,18.26-24.02,32.17-29.87,13.91-5.85,29.44-6.6,43.85-2.11,1.85.57,2.88,2.54,2.3,4.38-.57,1.85-2.54,2.88-4.38,2.3-12.83-4-26.67-3.33-39.06,1.88ZM111.39,19.75c0,2.07-1.68,3.75-3.75,3.75s-3.75-1.68-3.75-3.75,1.68-3.75,3.75-3.75,3.75,1.68,3.75,3.75ZM64.64,59.93c0,1.91,2.39,2.56,7.69,3.88,3.89.97,6.6,2.18,8.15,3.63,1.53,1.45,2.29,3.53,2.29,6.25,0,3.57-1.03,6.26-3.11,8.08-2.07,1.82-5.09,2.73-9.09,2.73h-9.6c-1.97,0-3.57-1.6-3.59-3.57-.01-1.99,1.6-3.61,3.59-3.61h9.41c3.15-.12,4.79-.95,4.91-2.47,0-1.3-1.03-2.21-3.07-2.73-6.91-1.72-11.11-3.44-12.6-5.15-1.48-1.71-2.23-3.77-2.23-6.19,0-6.59,3.2-9.85,9.59-9.8h10.77c1.99,0,3.6,1.61,3.6,3.59s-1.61,3.59-3.6,3.59h-9.69c-1.83,0-3.43.06-3.43,1.77Z\"/>\n <path class=\"cls-2\" d=\"M205.52,48.44h-8.86c-.44-3.75-2.23-6.65-5.38-8.72-3.16-2.07-7.03-3.1-11.6-3.1h0c-3.35,0-6.27.54-8.78,1.62-2.49,1.09-4.44,2.59-5.84,4.48-1.39,1.89-2.08,4.05-2.08,6.46h0c0,2.01.49,3.75,1.46,5.2.97,1.44,2.22,2.63,3.74,3.58,1.53.95,3.13,1.72,4.8,2.32,1.68.6,3.22,1.09,4.62,1.46h0l7.68,2.06c1.97.52,4.17,1.23,6.6,2.14,2.43.92,4.75,2.16,6.98,3.72,2.23,1.56,4.07,3.56,5.52,6,1.45,2.44,2.18,5.43,2.18,8.98h0c0,4.08-1.07,7.77-3.2,11.08-2.12,3.29-5.22,5.91-9.3,7.86-4.08,1.95-9.02,2.92-14.82,2.92h0c-5.43,0-10.11-.87-14.06-2.62-3.95-1.75-7.05-4.19-9.3-7.32-2.25-3.12-3.53-6.75-3.84-10.88h9.46c.25,2.85,1.22,5.21,2.9,7.06,1.69,1.87,3.83,3.25,6.42,4.14,2.6.89,5.41,1.34,8.42,1.34h0c3.49,0,6.63-.57,9.4-1.72,2.79-1.13,4.99-2.73,6.62-4.8,1.63-2.05,2.44-4.46,2.44-7.22h0c0-2.51-.7-4.55-2.1-6.12-1.41-1.57-3.26-2.85-5.54-3.84-2.29-.99-4.77-1.85-7.44-2.58h0l-9.3-2.66c-5.91-1.71-10.59-4.13-14.04-7.28-3.44-3.16-5.16-7.29-5.16-12.38h0c0-4.23,1.15-7.93,3.46-11.1,2.29-3.16,5.39-5.62,9.3-7.38,3.91-1.76,8.27-2.64,13.08-2.64h0c4.88,0,9.21.87,13,2.6,3.8,1.73,6.81,4.11,9.04,7.12,2.23,3,3.4,6.41,3.52,10.22h0ZM229.16,105.18h-8.72v-56.74h8.42v8.86h.74c1.19-3.03,3.1-5.38,5.74-7.06,2.63-1.69,5.79-2.54,9.48-2.54h0c3.75,0,6.87.85,9.36,2.54,2.51,1.68,4.46,4.03,5.86,7.06h.58c1.45-2.92,3.63-5.25,6.54-7,2.91-1.73,6.39-2.6,10.46-2.6h0c5.07,0,9.21,1.58,12.44,4.74,3.23,3.17,4.84,8.09,4.84,14.76h0v37.98h-8.72v-37.98c0-4.19-1.14-7.18-3.42-8.98-2.29-1.79-4.99-2.68-8.1-2.68h0c-3.99,0-7.07,1.2-9.26,3.6-2.2,2.4-3.3,5.43-3.3,9.1h0v36.94h-8.86v-38.86c0-3.23-1.05-5.83-3.14-7.82-2.09-1.97-4.79-2.96-8.08-2.96h0c-2.27,0-4.38.6-6.34,1.8-1.96,1.21-3.53,2.88-4.72,5-1.2,2.13-1.8,4.59-1.8,7.38h0v35.46ZM333.9,106.36h0c-5.12,0-9.61-1.22-13.46-3.66-3.85-2.44-6.86-5.85-9.02-10.24-2.15-4.37-3.22-9.49-3.22-15.36h0c0-5.91,1.07-11.07,3.22-15.48,2.16-4.4,5.17-7.82,9.02-10.26,3.85-2.44,8.34-3.66,13.46-3.66h0c5.12,0,9.61,1.22,13.46,3.66,3.85,2.44,6.86,5.86,9.02,10.26,2.15,4.41,3.22,9.57,3.22,15.48h0c0,5.87-1.07,10.99-3.22,15.36-2.16,4.39-5.17,7.8-9.02,10.24-3.85,2.44-8.34,3.66-13.46,3.66ZM333.9,98.52h0c3.89,0,7.09-.99,9.6-2.98,2.52-2,4.38-4.63,5.58-7.88,1.21-3.25,1.82-6.77,1.82-10.56h0c0-3.79-.61-7.32-1.82-10.6-1.2-3.27-3.06-5.91-5.58-7.94-2.51-2.01-5.71-3.02-9.6-3.02h0c-3.89,0-7.09,1.01-9.6,3.02-2.51,2.03-4.37,4.67-5.58,7.94-1.2,3.28-1.8,6.81-1.8,10.6h0c0,3.79.6,7.31,1.8,10.56,1.21,3.25,3.07,5.88,5.58,7.88,2.51,1.99,5.71,2.98,9.6,2.98ZM395.94,106.36h0c-5.12,0-9.61-1.22-13.46-3.66-3.85-2.44-6.85-5.85-9-10.24-2.16-4.37-3.24-9.49-3.24-15.36h0c0-5.91,1.08-11.07,3.24-15.48,2.15-4.4,5.15-7.82,9-10.26,3.85-2.44,8.34-3.66,13.46-3.66h0c5.12,0,9.61,1.22,13.46,3.66,3.85,2.44,6.86,5.86,9.02,10.26,2.16,4.41,3.24,9.57,3.24,15.48h0c0,5.87-1.08,10.99-3.24,15.36-2.16,4.39-5.17,7.8-9.02,10.24-3.85,2.44-8.34,3.66-13.46,3.66ZM395.94,98.52h0c3.89,0,7.09-.99,9.6-2.98,2.52-2,4.38-4.63,5.58-7.88,1.21-3.25,1.82-6.77,1.82-10.56h0c0-3.79-.61-7.32-1.82-10.6-1.2-3.27-3.06-5.91-5.58-7.94-2.51-2.01-5.71-3.02-9.6-3.02h0c-3.88,0-7.08,1.01-9.6,3.02-2.51,2.03-4.37,4.67-5.58,7.94-1.2,3.28-1.8,6.81-1.8,10.6h0c0,3.79.6,7.31,1.8,10.56,1.21,3.25,3.07,5.88,5.58,7.88,2.52,1.99,5.72,2.98,9.6,2.98Z\"/>\n <path class=\"cls-1\" d=\"M467.88,48.02v13.28h-35.79v-13.28h35.79ZM439.68,34.38h17.89v53.42c0,1.5.36,2.62,1.08,3.36.72.74,1.88,1.1,3.49,1.1.62,0,1.48-.07,2.59-.21,1.11-.14,1.91-.27,2.38-.41l2.31,13.02c-2.02.58-3.97.97-5.84,1.18-1.88.21-3.66.31-5.33.31-6.08,0-10.7-1.43-13.84-4.28-3.15-2.85-4.72-7.01-4.72-12.48v-55.01ZM506.59,72.63v32.71h-17.89V28.95h17.53v33.53h-1.13c1.4-4.55,3.6-8.21,6.59-11,2.99-2.79,7.01-4.18,12.07-4.18,4,0,7.48.89,10.46,2.67,2.97,1.78,5.28,4.29,6.92,7.54,1.64,3.25,2.46,7.02,2.46,11.33v36.5h-17.89v-33.02c0-3.21-.82-5.73-2.46-7.56-1.64-1.83-3.93-2.74-6.87-2.74-1.92,0-3.62.42-5.1,1.26-1.49.84-2.64,2.04-3.46,3.61-.82,1.57-1.23,3.49-1.23,5.74Z\"/>\n</svg>";
12
+
13
+ /** The square Smooth icon (the stylized `th` glyph) — used as the default full-page header avatar. */
14
+ export const SMOOTH_ICON_SVG = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<svg id=\"Layer_1\" data-name=\"Layer 1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 150 150\">\n <defs>\n <style>\n .cls-1 {\n fill: url(#linear-gradient);\n }\n </style>\n <linearGradient id=\"linear-gradient\" x1=\"31.06\" y1=\"37.6\" x2=\"118.5\" y2=\"125.04\" gradientUnits=\"userSpaceOnUse\">\n <stop offset=\".43\" stop-color=\"#00a6a6\"/>\n <stop offset=\"1\" stop-color=\"#1238dd\"/>\n </linearGradient>\n </defs>\n <path class=\"cls-1\" d=\"M55.03,55.47v13.28H19.24v-13.28h35.79ZM26.83,41.83h17.89v53.42c0,1.5.36,2.62,1.08,3.36.72.74,1.88,1.1,3.49,1.1.62,0,1.48-.07,2.59-.21,1.11-.14,1.91-.27,2.38-.41l2.31,13.02c-2.02.58-3.97.97-5.84,1.18-1.88.21-3.66.31-5.33.31-6.08,0-10.7-1.43-13.84-4.28-3.15-2.85-4.72-7.01-4.72-12.48v-55.01ZM93.74,80.08v32.71h-17.89V36.39h17.53v33.53h-1.13c1.4-4.55,3.6-8.21,6.59-11,2.99-2.79,7.01-4.18,12.07-4.18,4,0,7.48.89,10.46,2.67,2.97,1.78,5.28,4.29,6.92,7.54,1.64,3.25,2.46,7.02,2.46,11.33v36.5h-17.89v-33.02c0-3.21-.82-5.73-2.46-7.56-1.64-1.83-3.93-2.74-6.87-2.74-1.92,0-3.62.42-5.1,1.26-1.49.84-2.64,2.04-3.46,3.61-.82,1.57-1.23,3.49-1.23,5.74Z\"/>\n</svg>";
package/src/styles.ts CHANGED
@@ -176,8 +176,9 @@ export function buildStyles(theme: ResolvedTheme, mode: ChatWidgetMode = 'popove
176
176
  0 1px 0 rgba(255, 255, 255, .25) inset;
177
177
  }
178
178
  .avatar svg { width: 22px; height: 22px; }
179
- .avatar .logo-wrap { display: flex; }
179
+ .avatar .logo-wrap { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; }
180
180
  .avatar .logo { height: 22px; width: auto; display: block; }
181
+ .avatar .logo-img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; display: block; border-radius: 9px; }
181
182
  .meta { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 2px; }
182
183
  .title { font-weight: 650; font-size: 15.5px; letter-spacing: -.01em; line-height: 1.1; }
183
184
  .status {
@@ -226,6 +227,7 @@ export function buildStyles(theme: ResolvedTheme, mode: ChatWidgetMode = 'popove
226
227
  .panel.fullpage .header { padding: 18px 22px; }
227
228
  .panel.fullpage .avatar { width: 44px; height: 44px; }
228
229
  .panel.fullpage .avatar .logo { height: 26px; }
230
+ .panel.fullpage .avatar svg { width: 28px; height: 28px; }
229
231
 
230
232
  /* ────────────────────────────── Messages ──────────────────────────── */
231
233
  .messages {