@lookalike/widget 1.0.0-beta.1

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 ADDED
@@ -0,0 +1,120 @@
1
+ # @lookalike/widget
2
+
3
+ Embed Lookalike conversational AI widgets in your website.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @lookalike/widget
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### ES Modules / TypeScript
14
+
15
+ ```typescript
16
+ import { LookalikeWidget } from '@lookalike/widget';
17
+
18
+ const widget = new LookalikeWidget({
19
+ handle: 'john-doe',
20
+ variant: 'floating',
21
+ theme: { primaryColor: '#3b82f6' }
22
+ });
23
+
24
+ // Register event handlers
25
+ widget.on('ready', () => console.log('Widget ready'));
26
+ widget.on('message', (role, content) => console.log(`${role}: ${content}`));
27
+ widget.on('sessionStart', (mode) => console.log(`Session started in ${mode} mode`));
28
+
29
+ // Mount to DOM
30
+ widget.mount(document.body);
31
+
32
+ // Control the widget
33
+ widget.expand();
34
+ widget.collapse();
35
+
36
+ // Clean up
37
+ widget.destroy();
38
+ ```
39
+
40
+ ### CDN / Script Tag
41
+
42
+ ```html
43
+ <script src="https://unpkg.com/@lookalike/widget/dist/widget.umd.js"></script>
44
+ <script>
45
+ const widget = new Lookalike.Widget({
46
+ handle: 'john-doe',
47
+ variant: 'floating'
48
+ });
49
+
50
+ widget.on('ready', () => console.log('Ready!'));
51
+ widget.mount(document.body);
52
+ </script>
53
+ ```
54
+
55
+ ## Configuration
56
+
57
+ | Option | Type | Default | Description |
58
+ |--------|------|---------|-------------|
59
+ | `handle` | `string` | Required | The Lookalike handle to load |
60
+ | `variant` | `'floating' \| 'inline'` | `'floating'` | Widget display variant |
61
+ | `anchor` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'` | `'bottom-right'` | Position for floating variant |
62
+ | `collapsible` | `boolean` | `false` | For inline variant: allow collapse |
63
+ | `modes` | `('text' \| 'audio' \| 'video')[]` | `['text', 'audio', 'video']` | Allowed chat modes |
64
+ | `theme` | `object` | `undefined` | Theme customization |
65
+ | `baseUrl` | `string` | `'https://lookalike.com'` | Base URL for widget |
66
+
67
+ ### Theme Options
68
+
69
+ ```typescript
70
+ {
71
+ theme: {
72
+ primaryColor: '#3b82f6', // Header background color
73
+ accentColor: '#ffffff', // Text/icon color
74
+ borderRadius: '16px' // Corner radius
75
+ }
76
+ }
77
+ ```
78
+
79
+ ## Events
80
+
81
+ | Event | Arguments | Description |
82
+ |-------|-----------|-------------|
83
+ | `ready` | - | Widget has loaded |
84
+ | `resize` | `collapsed: boolean` | Widget collapsed/expanded |
85
+ | `sessionStart` | `mode: string` | Chat session started |
86
+ | `sessionEnd` | - | Chat session ended |
87
+ | `message` | `role: string, content: string` | New message received |
88
+ | `error` | `code: string, message: string` | Error occurred |
89
+
90
+ ## Methods
91
+
92
+ | Method | Description |
93
+ |--------|-------------|
94
+ | `mount(container)` | Mount widget to DOM element or selector |
95
+ | `expand()` | Expand the widget |
96
+ | `collapse()` | Collapse the widget |
97
+ | `destroy()` | Remove widget and clean up |
98
+ | `isExpanded()` | Check if widget is expanded |
99
+
100
+ ## Inline Variant
101
+
102
+ For embedding the widget inline in your page:
103
+
104
+ ```typescript
105
+ const widget = new LookalikeWidget({
106
+ handle: 'john-doe',
107
+ variant: 'inline',
108
+ collapsible: true // Optional: allow collapse
109
+ });
110
+
111
+ widget.mount('#chat-container');
112
+ ```
113
+
114
+ ```html
115
+ <div id="chat-container" style="width: 400px; height: 600px;"></div>
116
+ ```
117
+
118
+ ## License
119
+
120
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const v="https://your-app-name.up.railway.app",a={WIDGET_READY:"lookalike-widget-ready",WIDGET_RESIZE:"lookalike-widget-resize",SESSION_START:"lookalike-session-start",SESSION_END:"lookalike-session-end",MESSAGE:"lookalike-message",ERROR:"lookalike-error",INIT_CHANNEL:"lookalike-init-channel",COMMAND:"lookalike-command"},S={EXPAND:"expand",COLLAPSE:"collapse"};class E{constructor(e){this.events={},this.iframe=null,this.container=null,this.port=null,this.expanded=!1,this.mounted=!1,this.messageHandler=null,this.config={handle:e.handle,variant:e.variant??"floating",anchor:e.anchor??"bottom-right",collapsible:e.collapsible??!1,modes:e.modes??["text","audio","video"],baseUrl:e.baseUrl??v,theme:e.theme}}on(e,t){return this.events[e]=t,this}off(e){return delete this.events[e],this}mount(e){if(this.mounted){console.warn("LookalikeWidget: Already mounted");return}if(typeof e=="string"){const t=document.querySelector(e);if(!t)throw new Error(`LookalikeWidget: Container "${e}" not found`);this.container=t}else this.container=e;this.iframe=document.createElement("iframe"),this.iframe.src=this.buildIframeUrl(),this.iframe.style.cssText=this.getIframeStyles(),this.iframe.setAttribute("allow","camera; microphone; autoplay"),this.iframe.setAttribute("allowfullscreen",""),this.iframe.onload=()=>{console.log("[Widget] Iframe loaded successfully")},this.iframe.onerror=t=>{console.error("[Widget] Iframe error:",t)},this.messageHandler=this.handleMessage.bind(this),window.addEventListener("message",this.messageHandler),console.log("[Widget] Message listener added"),this.container.appendChild(this.iframe),this.mounted=!0,console.log("[Widget] Iframe mounted, URL:",this.iframe.src)}expand(){this.sendCommand(S.EXPAND)}collapse(){this.sendCommand(S.COLLAPSE)}destroy(){this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.port&&(this.port.close(),this.port=null),this.iframe&&this.container&&(this.container.removeChild(this.iframe),this.iframe=null),this.container=null,this.mounted=!1,this.expanded=!1}isExpanded(){return this.expanded}buildIframeUrl(){const e=new URL(`/${this.config.handle}/chat`,this.config.baseUrl);if(e.searchParams.set("embed",""),e.searchParams.set("variant",this.config.variant),this.config.variant==="floating"&&e.searchParams.set("anchor",this.config.anchor),this.config.variant==="inline"&&this.config.collapsible&&e.searchParams.set("collapsible",""),e.searchParams.set("modes",this.config.modes.join(",")),this.config.theme){const t=[];this.config.theme.primaryColor&&t.push(`primary:${this.config.theme.primaryColor}`),this.config.theme.accentColor&&t.push(`accent:${this.config.theme.accentColor}`),this.config.theme.borderRadius&&t.push(`radius:${this.config.theme.borderRadius}`),t.length>0&&e.searchParams.set("theme",t.join(","))}return e.toString()}getIframeStyles(){if(this.config.variant==="floating"){const e=this.config.anchor;return`
2
+ position: fixed;
3
+ ${{"bottom-right":"bottom: 20px; right: 20px;","bottom-left":"bottom: 20px; left: 20px;","top-right":"top: 20px; right: 20px;","top-left":"top: 20px; left: 20px;"}[e]}
4
+ width: 380px;
5
+ height: 72px;
6
+ max-height: 72px;
7
+ border: none;
8
+ border-radius: 16px;
9
+ z-index: 9999;
10
+ transition: all 0.3s ease;
11
+ `.replace(/\s+/g," ").trim()}return`
12
+ width: 100%;
13
+ height: 100%;
14
+ min-height: 400px;
15
+ border: none;
16
+ border-radius: 16px;
17
+ `.replace(/\s+/g," ").trim()}handleMessage(e){var o,i,n,r,l,h,d,c,m,g,f,p,u;if(console.log("[Widget] Received message:",e.data,"from:",e.source===((o=this.iframe)==null?void 0:o.contentWindow)?"iframe":"unknown"),this.iframe&&e.source!==this.iframe.contentWindow){console.log("[Widget] Ignoring message - not from iframe");return}const{type:t,...s}=e.data||{};switch(t){case a.WIDGET_READY:console.log("[Widget] WIDGET_READY event received!"),this.initMessageChannel(),(n=(i=this.events).onReady)==null||n.call(i);break;case a.WIDGET_RESIZE:console.log("[Widget] Received resize event:",{collapsed:s.collapsed,expanded:!s.collapsed}),this.expanded=!s.collapsed,this.updateIframeSize(s.collapsed),(l=(r=this.events).onResize)==null||l.call(r,s.collapsed);break;case a.SESSION_START:(d=(h=this.events).onSessionStart)==null||d.call(h,s.mode);break;case a.SESSION_END:(m=(c=this.events).onSessionEnd)==null||m.call(c);break;case a.MESSAGE:(f=(g=this.events).onMessage)==null||f.call(g,s.role,s.content);break;case a.ERROR:(u=(p=this.events).onError)==null||u.call(p,s.code,s.message);break}}initMessageChannel(){var t;if(!((t=this.iframe)!=null&&t.contentWindow))return;const e=new MessageChannel;this.port=e.port1,this.port.onmessage=s=>{var n,r,l,h,d,c,m,g,f,p;console.log("[Widget] Port message received:",s.data);const{type:o,...i}=s.data||{};switch(o){case a.WIDGET_RESIZE:console.log("[Widget] Received resize event:",{collapsed:i.collapsed,expanded:!i.collapsed}),this.expanded=!i.collapsed,this.updateIframeSize(i.collapsed),(r=(n=this.events).onResize)==null||r.call(n,i.collapsed);break;case a.SESSION_START:(h=(l=this.events).onSessionStart)==null||h.call(l,i.mode);break;case a.SESSION_END:(c=(d=this.events).onSessionEnd)==null||c.call(d);break;case a.MESSAGE:(g=(m=this.events).onMessage)==null||g.call(m,i.role,i.content);break;case a.ERROR:(p=(f=this.events).onError)==null||p.call(f,i.code,i.message);break}},this.iframe.contentWindow.postMessage({type:a.INIT_CHANNEL},"*",[e.port2])}sendCommand(e,t){var o;const s={type:a.COMMAND,action:e,payload:t};this.port?this.port.postMessage(s):(o=this.iframe)!=null&&o.contentWindow&&this.iframe.contentWindow.postMessage(s,"*")}updateIframeSize(e){if(console.log("[Widget] updateIframeSize called:",{collapsed:e,variant:this.config.variant,hasIframe:!!this.iframe}),!this.iframe||this.config.variant!=="floating"){console.log("[Widget] updateIframeSize skipped - no iframe or not floating variant");return}e?(console.log("[Widget] Setting collapsed size: 380x72"),this.iframe.style.width="380px",this.iframe.style.height="72px",this.iframe.style.maxHeight="72px"):(console.log("[Widget] Setting expanded size: 380x520"),this.iframe.style.width="380px",this.iframe.style.height="520px",this.iframe.style.maxHeight="calc(100vh - 40px)")}}exports.LookalikeWidget=E;exports.Widget=E;
18
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/widget.ts"],"sourcesContent":["import type {\r\n LookalikeWidgetConfig,\r\n LookalikeWidgetEvents,\r\n LookalikeWidgetInstance,\r\n ChatMode,\r\n} from './types'\r\n\r\n// Use production URL if defined, otherwise fallback to lookalike.com\r\nconst DEFAULT_BASE_URL = typeof __PRODUCTION_BASE_URL__ !== 'undefined' \r\n ? __PRODUCTION_BASE_URL__ \r\n : 'https://lookalike.com'\r\n\r\n// Event type constants\r\nconst EVENTS = {\r\n WIDGET_READY: 'lookalike-widget-ready',\r\n WIDGET_RESIZE: 'lookalike-widget-resize',\r\n SESSION_START: 'lookalike-session-start',\r\n SESSION_END: 'lookalike-session-end',\r\n MESSAGE: 'lookalike-message',\r\n ERROR: 'lookalike-error',\r\n INIT_CHANNEL: 'lookalike-init-channel',\r\n COMMAND: 'lookalike-command',\r\n} as const\r\n\r\n// Command constants\r\nconst COMMANDS = {\r\n EXPAND: 'expand',\r\n COLLAPSE: 'collapse',\r\n} as const\r\n\r\n/**\r\n * Lookalike Widget - Embed conversational AI in your website\r\n *\r\n * @example\r\n * ```javascript\r\n * const widget = new LookalikeWidget({\r\n * handle: 'john-doe',\r\n * variant: 'floating',\r\n * theme: { primaryColor: '#3b82f6' }\r\n * });\r\n *\r\n * widget.on('ready', () => console.log('Widget ready'));\r\n * widget.on('message', (role, content) => console.log(`${role}: ${content}`));\r\n *\r\n * widget.mount(document.body);\r\n * ```\r\n */\r\nexport class LookalikeWidget implements LookalikeWidgetInstance {\r\n private config: Required<Omit<LookalikeWidgetConfig, 'theme'>> & { theme?: LookalikeWidgetConfig['theme'] }\r\n private events: LookalikeWidgetEvents = {}\r\n private iframe: HTMLIFrameElement | null = null\r\n private container: HTMLElement | null = null\r\n private port: MessagePort | null = null\r\n private expanded = false\r\n private mounted = false\r\n private messageHandler: ((e: MessageEvent) => void) | null = null\r\n\r\n constructor(config: LookalikeWidgetConfig) {\r\n this.config = {\r\n handle: config.handle,\r\n variant: config.variant ?? 'floating',\r\n anchor: config.anchor ?? 'bottom-right',\r\n collapsible: config.collapsible ?? false,\r\n modes: config.modes ?? ['text', 'audio', 'video'],\r\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\r\n theme: config.theme,\r\n }\r\n }\r\n\r\n /**\r\n * Register an event handler\r\n */\r\n on<K extends keyof LookalikeWidgetEvents>(\r\n event: K,\r\n handler: NonNullable<LookalikeWidgetEvents[K]>\r\n ): this {\r\n this.events[event] = handler as LookalikeWidgetEvents[K]\r\n return this\r\n }\r\n\r\n /**\r\n * Remove an event handler\r\n */\r\n off<K extends keyof LookalikeWidgetEvents>(event: K): this {\r\n delete this.events[event]\r\n return this\r\n }\r\n\r\n /**\r\n * Mount the widget to a container element\r\n */\r\n mount(container: string | HTMLElement): void {\r\n if (this.mounted) {\r\n console.warn('LookalikeWidget: Already mounted')\r\n return\r\n }\r\n\r\n // Resolve container\r\n if (typeof container === 'string') {\r\n const el = document.querySelector(container)\r\n if (!el) {\r\n throw new Error(`LookalikeWidget: Container \"${container}\" not found`)\r\n }\r\n this.container = el as HTMLElement\r\n } else {\r\n this.container = container\r\n }\r\n\r\n // Create iframe\r\n this.iframe = document.createElement('iframe')\r\n this.iframe.src = this.buildIframeUrl()\r\n this.iframe.style.cssText = this.getIframeStyles()\r\n this.iframe.setAttribute('allow', 'camera; microphone; autoplay')\r\n this.iframe.setAttribute('allowfullscreen', '')\r\n \r\n // Add load event listener for debugging\r\n this.iframe.onload = () => {\r\n console.log('[Widget] Iframe loaded successfully')\r\n }\r\n this.iframe.onerror = (error) => {\r\n console.error('[Widget] Iframe error:', error)\r\n }\r\n\r\n // Set up message listener for handshake\r\n this.messageHandler = this.handleMessage.bind(this)\r\n window.addEventListener('message', this.messageHandler)\r\n console.log('[Widget] Message listener added')\r\n\r\n // Append iframe\r\n this.container.appendChild(this.iframe)\r\n this.mounted = true\r\n console.log('[Widget] Iframe mounted, URL:', this.iframe.src)\r\n }\r\n\r\n /**\r\n * Expand the widget\r\n */\r\n expand(): void {\r\n this.sendCommand(COMMANDS.EXPAND)\r\n }\r\n\r\n /**\r\n * Collapse the widget\r\n */\r\n collapse(): void {\r\n this.sendCommand(COMMANDS.COLLAPSE)\r\n }\r\n\r\n /**\r\n * Destroy the widget and clean up\r\n */\r\n destroy(): void {\r\n if (this.messageHandler) {\r\n window.removeEventListener('message', this.messageHandler)\r\n this.messageHandler = null\r\n }\r\n\r\n if (this.port) {\r\n this.port.close()\r\n this.port = null\r\n }\r\n\r\n if (this.iframe && this.container) {\r\n this.container.removeChild(this.iframe)\r\n this.iframe = null\r\n }\r\n\r\n this.container = null\r\n this.mounted = false\r\n this.expanded = false\r\n }\r\n\r\n /**\r\n * Check if widget is currently expanded\r\n */\r\n isExpanded(): boolean {\r\n return this.expanded\r\n }\r\n\r\n private buildIframeUrl(): string {\r\n const url = new URL(`/${this.config.handle}/chat`, this.config.baseUrl)\r\n\r\n // Add embed flag\r\n url.searchParams.set('embed', '')\r\n\r\n // Add variant\r\n url.searchParams.set('variant', this.config.variant)\r\n\r\n // Add anchor for floating\r\n if (this.config.variant === 'floating') {\r\n url.searchParams.set('anchor', this.config.anchor)\r\n }\r\n\r\n // Add collapsible for inline\r\n if (this.config.variant === 'inline' && this.config.collapsible) {\r\n url.searchParams.set('collapsible', '')\r\n }\r\n\r\n // Add modes\r\n url.searchParams.set('modes', this.config.modes.join(','))\r\n\r\n // Add theme\r\n if (this.config.theme) {\r\n const themeParts: string[] = []\r\n if (this.config.theme.primaryColor) {\r\n themeParts.push(`primary:${this.config.theme.primaryColor}`)\r\n }\r\n if (this.config.theme.accentColor) {\r\n themeParts.push(`accent:${this.config.theme.accentColor}`)\r\n }\r\n if (this.config.theme.borderRadius) {\r\n themeParts.push(`radius:${this.config.theme.borderRadius}`)\r\n }\r\n if (themeParts.length > 0) {\r\n url.searchParams.set('theme', themeParts.join(','))\r\n }\r\n }\r\n\r\n return url.toString()\r\n }\r\n\r\n private getIframeStyles(): string {\r\n if (this.config.variant === 'floating') {\r\n // Floating: positioned in corner, needs to resize based on collapsed state\r\n const anchor = this.config.anchor\r\n const position = {\r\n 'bottom-right': 'bottom: 20px; right: 20px;',\r\n 'bottom-left': 'bottom: 20px; left: 20px;',\r\n 'top-right': 'top: 20px; right: 20px;',\r\n 'top-left': 'top: 20px; left: 20px;',\r\n }[anchor]\r\n\r\n return `\r\n position: fixed;\r\n ${position}\r\n width: 380px;\r\n height: 72px;\r\n max-height: 72px;\r\n border: none;\r\n border-radius: 16px;\r\n z-index: 9999;\r\n transition: all 0.3s ease;\r\n `.replace(/\\s+/g, ' ').trim()\r\n }\r\n\r\n // Inline: fills container\r\n return `\r\n width: 100%;\r\n height: 100%;\r\n min-height: 400px;\r\n border: none;\r\n border-radius: 16px;\r\n `.replace(/\\s+/g, ' ').trim()\r\n }\r\n\r\n private handleMessage(e: MessageEvent): void {\r\n console.log('[Widget] Received message:', e.data, 'from:', e.source === this.iframe?.contentWindow ? 'iframe' : 'unknown')\r\n // Only accept messages from our iframe\r\n if (this.iframe && e.source !== this.iframe.contentWindow) {\r\n console.log('[Widget] Ignoring message - not from iframe')\r\n return\r\n }\r\n\r\n const { type, ...payload } = e.data || {}\r\n\r\n switch (type) {\r\n case EVENTS.WIDGET_READY:\r\n console.log('[Widget] WIDGET_READY event received!')\r\n this.initMessageChannel()\r\n this.events.onReady?.()\r\n break\r\n\r\n case EVENTS.WIDGET_RESIZE:\r\n console.log('[Widget] Received resize event:', { collapsed: payload.collapsed, expanded: !payload.collapsed })\r\n this.expanded = !payload.collapsed\r\n this.updateIframeSize(payload.collapsed)\r\n this.events.onResize?.(payload.collapsed)\r\n break\r\n\r\n case EVENTS.SESSION_START:\r\n this.events.onSessionStart?.(payload.mode as ChatMode)\r\n break\r\n\r\n case EVENTS.SESSION_END:\r\n this.events.onSessionEnd?.()\r\n break\r\n\r\n case EVENTS.MESSAGE:\r\n this.events.onMessage?.(payload.role, payload.content)\r\n break\r\n\r\n case EVENTS.ERROR:\r\n this.events.onError?.(payload.code, payload.message)\r\n break\r\n }\r\n }\r\n\r\n private initMessageChannel(): void {\r\n if (!this.iframe?.contentWindow) return\r\n\r\n // Create a MessageChannel for secure communication\r\n const channel = new MessageChannel()\r\n this.port = channel.port1\r\n\r\n // Set up port message handler\r\n this.port.onmessage = (e) => {\r\n console.log('[Widget] Port message received:', e.data)\r\n // For MessageChannel, we don't need to check source since the channel is secure\r\n const { type, ...payload } = e.data || {}\r\n \r\n switch (type) {\r\n case EVENTS.WIDGET_RESIZE:\r\n console.log('[Widget] Received resize event:', { collapsed: payload.collapsed, expanded: !payload.collapsed })\r\n this.expanded = !payload.collapsed\r\n this.updateIframeSize(payload.collapsed)\r\n this.events.onResize?.(payload.collapsed)\r\n break\r\n\r\n case EVENTS.SESSION_START:\r\n this.events.onSessionStart?.(payload.mode as ChatMode)\r\n break\r\n\r\n case EVENTS.SESSION_END:\r\n this.events.onSessionEnd?.()\r\n break\r\n\r\n case EVENTS.MESSAGE:\r\n this.events.onMessage?.(payload.role, payload.content)\r\n break\r\n\r\n case EVENTS.ERROR:\r\n this.events.onError?.(payload.code, payload.message)\r\n break\r\n }\r\n }\r\n\r\n // Send port2 to iframe\r\n this.iframe.contentWindow.postMessage(\r\n { type: EVENTS.INIT_CHANNEL },\r\n '*',\r\n [channel.port2]\r\n )\r\n }\r\n\r\n private sendCommand(action: string, payload?: unknown): void {\r\n const message = { type: EVENTS.COMMAND, action, payload }\r\n\r\n if (this.port) {\r\n this.port.postMessage(message)\r\n } else if (this.iframe?.contentWindow) {\r\n this.iframe.contentWindow.postMessage(message, '*')\r\n }\r\n }\r\n\r\n private updateIframeSize(collapsed: boolean): void {\r\n console.log('[Widget] updateIframeSize called:', { collapsed, variant: this.config.variant, hasIframe: !!this.iframe })\r\n if (!this.iframe || this.config.variant !== 'floating') {\r\n console.log('[Widget] updateIframeSize skipped - no iframe or not floating variant')\r\n return\r\n }\r\n\r\n if (collapsed) {\r\n // Match the online widget's collapsed height more closely\r\n console.log('[Widget] Setting collapsed size: 380x72')\r\n this.iframe.style.width = '380px'\r\n this.iframe.style.height = '72px'\r\n this.iframe.style.maxHeight = '72px'\r\n } else {\r\n // Match the online widget's expanded dimensions\r\n console.log('[Widget] Setting expanded size: 380x520')\r\n this.iframe.style.width = '380px'\r\n this.iframe.style.height = '520px'\r\n this.iframe.style.maxHeight = 'calc(100vh - 40px)'\r\n }\r\n }\r\n}\r\n\r\n// Also export as Widget for convenience with UMD\r\nexport { LookalikeWidget as Widget }\r\n"],"names":["DEFAULT_BASE_URL","EVENTS","COMMANDS","LookalikeWidget","config","event","handler","container","el","error","url","themeParts","anchor","_a","type","payload","_c","_b","_e","_d","_g","_f","_i","_h","_k","_j","_m","_l","channel","e","action","message","collapsed"],"mappings":"gFAQA,MAAMA,EACF,uCAIEC,EAAS,CACb,aAAc,yBACd,cAAe,0BACf,cAAe,0BACf,YAAa,wBACb,QAAS,oBACT,MAAO,kBACP,aAAc,yBACd,QAAS,mBACX,EAGMC,EAAW,CACf,OAAQ,SACR,SAAU,UACZ,EAmBO,MAAMC,CAAmD,CAU9D,YAAYC,EAA+B,CAR3C,KAAQ,OAAgC,CAAA,EACxC,KAAQ,OAAmC,KAC3C,KAAQ,UAAgC,KACxC,KAAQ,KAA2B,KACnC,KAAQ,SAAW,GACnB,KAAQ,QAAU,GAClB,KAAQ,eAAqD,KAG3D,KAAK,OAAS,CACZ,OAAQA,EAAO,OACf,QAASA,EAAO,SAAW,WAC3B,OAAQA,EAAO,QAAU,eACzB,YAAaA,EAAO,aAAe,GACnC,MAAOA,EAAO,OAAS,CAAC,OAAQ,QAAS,OAAO,EAChD,QAASA,EAAO,SAAWJ,EAC3B,MAAOI,EAAO,KAAA,CAElB,CAKA,GACEC,EACAC,EACM,CACN,YAAK,OAAOD,CAAK,EAAIC,EACd,IACT,CAKA,IAA2CD,EAAgB,CACzD,cAAO,KAAK,OAAOA,CAAK,EACjB,IACT,CAKA,MAAME,EAAuC,CAC3C,GAAI,KAAK,QAAS,CAChB,QAAQ,KAAK,kCAAkC,EAC/C,MACF,CAGA,GAAI,OAAOA,GAAc,SAAU,CACjC,MAAMC,EAAK,SAAS,cAAcD,CAAS,EAC3C,GAAI,CAACC,EACH,MAAM,IAAI,MAAM,+BAA+BD,CAAS,aAAa,EAEvE,KAAK,UAAYC,CACnB,MACE,KAAK,UAAYD,EAInB,KAAK,OAAS,SAAS,cAAc,QAAQ,EAC7C,KAAK,OAAO,IAAM,KAAK,eAAA,EACvB,KAAK,OAAO,MAAM,QAAU,KAAK,gBAAA,EACjC,KAAK,OAAO,aAAa,QAAS,8BAA8B,EAChE,KAAK,OAAO,aAAa,kBAAmB,EAAE,EAG9C,KAAK,OAAO,OAAS,IAAM,CACzB,QAAQ,IAAI,qCAAqC,CACnD,EACA,KAAK,OAAO,QAAWE,GAAU,CAC/B,QAAQ,MAAM,yBAA0BA,CAAK,CAC/C,EAGA,KAAK,eAAiB,KAAK,cAAc,KAAK,IAAI,EAClD,OAAO,iBAAiB,UAAW,KAAK,cAAc,EACtD,QAAQ,IAAI,iCAAiC,EAG7C,KAAK,UAAU,YAAY,KAAK,MAAM,EACtC,KAAK,QAAU,GACf,QAAQ,IAAI,gCAAiC,KAAK,OAAO,GAAG,CAC9D,CAKA,QAAe,CACb,KAAK,YAAYP,EAAS,MAAM,CAClC,CAKA,UAAiB,CACf,KAAK,YAAYA,EAAS,QAAQ,CACpC,CAKA,SAAgB,CACV,KAAK,iBACP,OAAO,oBAAoB,UAAW,KAAK,cAAc,EACzD,KAAK,eAAiB,MAGpB,KAAK,OACP,KAAK,KAAK,MAAA,EACV,KAAK,KAAO,MAGV,KAAK,QAAU,KAAK,YACtB,KAAK,UAAU,YAAY,KAAK,MAAM,EACtC,KAAK,OAAS,MAGhB,KAAK,UAAY,KACjB,KAAK,QAAU,GACf,KAAK,SAAW,EAClB,CAKA,YAAsB,CACpB,OAAO,KAAK,QACd,CAEQ,gBAAyB,CAC/B,MAAMQ,EAAM,IAAI,IAAI,IAAI,KAAK,OAAO,MAAM,QAAS,KAAK,OAAO,OAAO,EAsBtE,GAnBAA,EAAI,aAAa,IAAI,QAAS,EAAE,EAGhCA,EAAI,aAAa,IAAI,UAAW,KAAK,OAAO,OAAO,EAG/C,KAAK,OAAO,UAAY,YAC1BA,EAAI,aAAa,IAAI,SAAU,KAAK,OAAO,MAAM,EAI/C,KAAK,OAAO,UAAY,UAAY,KAAK,OAAO,aAClDA,EAAI,aAAa,IAAI,cAAe,EAAE,EAIxCA,EAAI,aAAa,IAAI,QAAS,KAAK,OAAO,MAAM,KAAK,GAAG,CAAC,EAGrD,KAAK,OAAO,MAAO,CACrB,MAAMC,EAAuB,CAAA,EACzB,KAAK,OAAO,MAAM,cACpBA,EAAW,KAAK,WAAW,KAAK,OAAO,MAAM,YAAY,EAAE,EAEzD,KAAK,OAAO,MAAM,aACpBA,EAAW,KAAK,UAAU,KAAK,OAAO,MAAM,WAAW,EAAE,EAEvD,KAAK,OAAO,MAAM,cACpBA,EAAW,KAAK,UAAU,KAAK,OAAO,MAAM,YAAY,EAAE,EAExDA,EAAW,OAAS,GACtBD,EAAI,aAAa,IAAI,QAASC,EAAW,KAAK,GAAG,CAAC,CAEtD,CAEA,OAAOD,EAAI,SAAA,CACb,CAEQ,iBAA0B,CAChC,GAAI,KAAK,OAAO,UAAY,WAAY,CAEtC,MAAME,EAAS,KAAK,OAAO,OAQ3B,MAAO;AAAA;AAAA,UAPU,CACf,eAAgB,6BAChB,cAAe,4BACf,YAAa,0BACb,WAAY,wBAAA,EACZA,CAAM,CAII;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAQV,QAAQ,OAAQ,GAAG,EAAE,KAAA,CACzB,CAGA,MAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAML,QAAQ,OAAQ,GAAG,EAAE,KAAA,CACzB,CAEQ,cAAc,EAAuB,+BAG3C,GAFA,QAAQ,IAAI,6BAA8B,EAAE,KAAM,QAAS,EAAE,WAAWC,EAAA,KAAK,SAAL,YAAAA,EAAa,eAAgB,SAAW,SAAS,EAErH,KAAK,QAAU,EAAE,SAAW,KAAK,OAAO,cAAe,CACzD,QAAQ,IAAI,6CAA6C,EACzD,MACF,CAEA,KAAM,CAAE,KAAAC,EAAM,GAAGC,GAAY,EAAE,MAAQ,CAAA,EAEvC,OAAQD,EAAA,CACN,KAAKb,EAAO,aACV,QAAQ,IAAI,uCAAuC,EACnD,KAAK,mBAAA,GACLe,GAAAC,EAAA,KAAK,QAAO,UAAZ,MAAAD,EAAA,KAAAC,GACA,MAEF,KAAKhB,EAAO,cACV,QAAQ,IAAI,kCAAmC,CAAE,UAAWc,EAAQ,UAAW,SAAU,CAACA,EAAQ,UAAW,EAC7G,KAAK,SAAW,CAACA,EAAQ,UACzB,KAAK,iBAAiBA,EAAQ,SAAS,GACvCG,GAAAC,EAAA,KAAK,QAAO,WAAZ,MAAAD,EAAA,KAAAC,EAAuBJ,EAAQ,WAC/B,MAEF,KAAKd,EAAO,eACVmB,GAAAC,EAAA,KAAK,QAAO,iBAAZ,MAAAD,EAAA,KAAAC,EAA6BN,EAAQ,MACrC,MAEF,KAAKd,EAAO,aACVqB,GAAAC,EAAA,KAAK,QAAO,eAAZ,MAAAD,EAAA,KAAAC,GACA,MAEF,KAAKtB,EAAO,SACVuB,GAAAC,EAAA,KAAK,QAAO,YAAZ,MAAAD,EAAA,KAAAC,EAAwBV,EAAQ,KAAMA,EAAQ,SAC9C,MAEF,KAAKd,EAAO,OACVyB,GAAAC,EAAA,KAAK,QAAO,UAAZ,MAAAD,EAAA,KAAAC,EAAsBZ,EAAQ,KAAMA,EAAQ,SAC5C,KAAA,CAEN,CAEQ,oBAA2B,OACjC,GAAI,GAACF,EAAA,KAAK,SAAL,MAAAA,EAAa,eAAe,OAGjC,MAAMe,EAAU,IAAI,eACpB,KAAK,KAAOA,EAAQ,MAGpB,KAAK,KAAK,UAAaC,GAAM,yBAC3B,QAAQ,IAAI,kCAAmCA,EAAE,IAAI,EAErD,KAAM,CAAE,KAAAf,EAAM,GAAGC,GAAYc,EAAE,MAAQ,CAAA,EAEvC,OAAQf,EAAA,CACN,KAAKb,EAAO,cACV,QAAQ,IAAI,kCAAmC,CAAE,UAAWc,EAAQ,UAAW,SAAU,CAACA,EAAQ,UAAW,EAC7G,KAAK,SAAW,CAACA,EAAQ,UACzB,KAAK,iBAAiBA,EAAQ,SAAS,GACvCE,GAAAJ,EAAA,KAAK,QAAO,WAAZ,MAAAI,EAAA,KAAAJ,EAAuBE,EAAQ,WAC/B,MAEF,KAAKd,EAAO,eACVkB,GAAAH,EAAA,KAAK,QAAO,iBAAZ,MAAAG,EAAA,KAAAH,EAA6BD,EAAQ,MACrC,MAEF,KAAKd,EAAO,aACVoB,GAAAH,EAAA,KAAK,QAAO,eAAZ,MAAAG,EAAA,KAAAH,GACA,MAEF,KAAKjB,EAAO,SACVsB,GAAAH,EAAA,KAAK,QAAO,YAAZ,MAAAG,EAAA,KAAAH,EAAwBL,EAAQ,KAAMA,EAAQ,SAC9C,MAEF,KAAKd,EAAO,OACVwB,GAAAH,EAAA,KAAK,QAAO,UAAZ,MAAAG,EAAA,KAAAH,EAAsBP,EAAQ,KAAMA,EAAQ,SAC5C,KAAA,CAEN,EAGA,KAAK,OAAO,cAAc,YACxB,CAAE,KAAMd,EAAO,YAAA,EACf,IACA,CAAC2B,EAAQ,KAAK,CAAA,CAElB,CAEQ,YAAYE,EAAgBf,EAAyB,OAC3D,MAAMgB,EAAU,CAAE,KAAM9B,EAAO,QAAS,OAAA6B,EAAQ,QAAAf,CAAA,EAE5C,KAAK,KACP,KAAK,KAAK,YAAYgB,CAAO,GACpBlB,EAAA,KAAK,SAAL,MAAAA,EAAa,eACtB,KAAK,OAAO,cAAc,YAAYkB,EAAS,GAAG,CAEtD,CAEQ,iBAAiBC,EAA0B,CAEjD,GADA,QAAQ,IAAI,oCAAqC,CAAE,UAAAA,EAAW,QAAS,KAAK,OAAO,QAAS,UAAW,CAAC,CAAC,KAAK,OAAQ,EAClH,CAAC,KAAK,QAAU,KAAK,OAAO,UAAY,WAAY,CACtD,QAAQ,IAAI,uEAAuE,EACnF,MACF,CAEIA,GAEF,QAAQ,IAAI,yCAAyC,EACrD,KAAK,OAAO,MAAM,MAAQ,QAC1B,KAAK,OAAO,MAAM,OAAS,OAC3B,KAAK,OAAO,MAAM,UAAY,SAG9B,QAAQ,IAAI,yCAAyC,EACrD,KAAK,OAAO,MAAM,MAAQ,QAC1B,KAAK,OAAO,MAAM,OAAS,QAC3B,KAAK,OAAO,MAAM,UAAY,qBAElC,CACF"}
@@ -0,0 +1,3 @@
1
+ export { LookalikeWidget, Widget } from './widget';
2
+ export type { LookalikeWidgetConfig, LookalikeWidgetEvents, LookalikeWidgetInstance, LookalikeTheme, WidgetVariant, WidgetAnchor, ChatMode, } from './types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAClD,YAAY,EACV,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EACvB,cAAc,EACd,aAAa,EACb,YAAY,EACZ,QAAQ,GACT,MAAM,SAAS,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,195 @@
1
+ const S = "https://your-app-name.up.railway.app", a = {
2
+ WIDGET_READY: "lookalike-widget-ready",
3
+ WIDGET_RESIZE: "lookalike-widget-resize",
4
+ SESSION_START: "lookalike-session-start",
5
+ SESSION_END: "lookalike-session-end",
6
+ MESSAGE: "lookalike-message",
7
+ ERROR: "lookalike-error",
8
+ INIT_CHANNEL: "lookalike-init-channel",
9
+ COMMAND: "lookalike-command"
10
+ }, E = {
11
+ EXPAND: "expand",
12
+ COLLAPSE: "collapse"
13
+ };
14
+ class v {
15
+ constructor(e) {
16
+ this.events = {}, this.iframe = null, this.container = null, this.port = null, this.expanded = !1, this.mounted = !1, this.messageHandler = null, this.config = {
17
+ handle: e.handle,
18
+ variant: e.variant ?? "floating",
19
+ anchor: e.anchor ?? "bottom-right",
20
+ collapsible: e.collapsible ?? !1,
21
+ modes: e.modes ?? ["text", "audio", "video"],
22
+ baseUrl: e.baseUrl ?? S,
23
+ theme: e.theme
24
+ };
25
+ }
26
+ /**
27
+ * Register an event handler
28
+ */
29
+ on(e, t) {
30
+ return this.events[e] = t, this;
31
+ }
32
+ /**
33
+ * Remove an event handler
34
+ */
35
+ off(e) {
36
+ return delete this.events[e], this;
37
+ }
38
+ /**
39
+ * Mount the widget to a container element
40
+ */
41
+ mount(e) {
42
+ if (this.mounted) {
43
+ console.warn("LookalikeWidget: Already mounted");
44
+ return;
45
+ }
46
+ if (typeof e == "string") {
47
+ const t = document.querySelector(e);
48
+ if (!t)
49
+ throw new Error(`LookalikeWidget: Container "${e}" not found`);
50
+ this.container = t;
51
+ } else
52
+ this.container = e;
53
+ this.iframe = document.createElement("iframe"), this.iframe.src = this.buildIframeUrl(), this.iframe.style.cssText = this.getIframeStyles(), this.iframe.setAttribute("allow", "camera; microphone; autoplay"), this.iframe.setAttribute("allowfullscreen", ""), this.iframe.onload = () => {
54
+ console.log("[Widget] Iframe loaded successfully");
55
+ }, this.iframe.onerror = (t) => {
56
+ console.error("[Widget] Iframe error:", t);
57
+ }, this.messageHandler = this.handleMessage.bind(this), window.addEventListener("message", this.messageHandler), console.log("[Widget] Message listener added"), this.container.appendChild(this.iframe), this.mounted = !0, console.log("[Widget] Iframe mounted, URL:", this.iframe.src);
58
+ }
59
+ /**
60
+ * Expand the widget
61
+ */
62
+ expand() {
63
+ this.sendCommand(E.EXPAND);
64
+ }
65
+ /**
66
+ * Collapse the widget
67
+ */
68
+ collapse() {
69
+ this.sendCommand(E.COLLAPSE);
70
+ }
71
+ /**
72
+ * Destroy the widget and clean up
73
+ */
74
+ destroy() {
75
+ this.messageHandler && (window.removeEventListener("message", this.messageHandler), this.messageHandler = null), this.port && (this.port.close(), this.port = null), this.iframe && this.container && (this.container.removeChild(this.iframe), this.iframe = null), this.container = null, this.mounted = !1, this.expanded = !1;
76
+ }
77
+ /**
78
+ * Check if widget is currently expanded
79
+ */
80
+ isExpanded() {
81
+ return this.expanded;
82
+ }
83
+ buildIframeUrl() {
84
+ const e = new URL(`/${this.config.handle}/chat`, this.config.baseUrl);
85
+ if (e.searchParams.set("embed", ""), e.searchParams.set("variant", this.config.variant), this.config.variant === "floating" && e.searchParams.set("anchor", this.config.anchor), this.config.variant === "inline" && this.config.collapsible && e.searchParams.set("collapsible", ""), e.searchParams.set("modes", this.config.modes.join(",")), this.config.theme) {
86
+ const t = [];
87
+ this.config.theme.primaryColor && t.push(`primary:${this.config.theme.primaryColor}`), this.config.theme.accentColor && t.push(`accent:${this.config.theme.accentColor}`), this.config.theme.borderRadius && t.push(`radius:${this.config.theme.borderRadius}`), t.length > 0 && e.searchParams.set("theme", t.join(","));
88
+ }
89
+ return e.toString();
90
+ }
91
+ getIframeStyles() {
92
+ if (this.config.variant === "floating") {
93
+ const e = this.config.anchor;
94
+ return `
95
+ position: fixed;
96
+ ${{
97
+ "bottom-right": "bottom: 20px; right: 20px;",
98
+ "bottom-left": "bottom: 20px; left: 20px;",
99
+ "top-right": "top: 20px; right: 20px;",
100
+ "top-left": "top: 20px; left: 20px;"
101
+ }[e]}
102
+ width: 380px;
103
+ height: 72px;
104
+ max-height: 72px;
105
+ border: none;
106
+ border-radius: 16px;
107
+ z-index: 9999;
108
+ transition: all 0.3s ease;
109
+ `.replace(/\s+/g, " ").trim();
110
+ }
111
+ return `
112
+ width: 100%;
113
+ height: 100%;
114
+ min-height: 400px;
115
+ border: none;
116
+ border-radius: 16px;
117
+ `.replace(/\s+/g, " ").trim();
118
+ }
119
+ handleMessage(e) {
120
+ var o, i, n, r, l, h, d, c, m, g, f, p, u;
121
+ if (console.log("[Widget] Received message:", e.data, "from:", e.source === ((o = this.iframe) == null ? void 0 : o.contentWindow) ? "iframe" : "unknown"), this.iframe && e.source !== this.iframe.contentWindow) {
122
+ console.log("[Widget] Ignoring message - not from iframe");
123
+ return;
124
+ }
125
+ const { type: t, ...s } = e.data || {};
126
+ switch (t) {
127
+ case a.WIDGET_READY:
128
+ console.log("[Widget] WIDGET_READY event received!"), this.initMessageChannel(), (n = (i = this.events).onReady) == null || n.call(i);
129
+ break;
130
+ case a.WIDGET_RESIZE:
131
+ console.log("[Widget] Received resize event:", { collapsed: s.collapsed, expanded: !s.collapsed }), this.expanded = !s.collapsed, this.updateIframeSize(s.collapsed), (l = (r = this.events).onResize) == null || l.call(r, s.collapsed);
132
+ break;
133
+ case a.SESSION_START:
134
+ (d = (h = this.events).onSessionStart) == null || d.call(h, s.mode);
135
+ break;
136
+ case a.SESSION_END:
137
+ (m = (c = this.events).onSessionEnd) == null || m.call(c);
138
+ break;
139
+ case a.MESSAGE:
140
+ (f = (g = this.events).onMessage) == null || f.call(g, s.role, s.content);
141
+ break;
142
+ case a.ERROR:
143
+ (u = (p = this.events).onError) == null || u.call(p, s.code, s.message);
144
+ break;
145
+ }
146
+ }
147
+ initMessageChannel() {
148
+ var t;
149
+ if (!((t = this.iframe) != null && t.contentWindow)) return;
150
+ const e = new MessageChannel();
151
+ this.port = e.port1, this.port.onmessage = (s) => {
152
+ var n, r, l, h, d, c, m, g, f, p;
153
+ console.log("[Widget] Port message received:", s.data);
154
+ const { type: o, ...i } = s.data || {};
155
+ switch (o) {
156
+ case a.WIDGET_RESIZE:
157
+ console.log("[Widget] Received resize event:", { collapsed: i.collapsed, expanded: !i.collapsed }), this.expanded = !i.collapsed, this.updateIframeSize(i.collapsed), (r = (n = this.events).onResize) == null || r.call(n, i.collapsed);
158
+ break;
159
+ case a.SESSION_START:
160
+ (h = (l = this.events).onSessionStart) == null || h.call(l, i.mode);
161
+ break;
162
+ case a.SESSION_END:
163
+ (c = (d = this.events).onSessionEnd) == null || c.call(d);
164
+ break;
165
+ case a.MESSAGE:
166
+ (g = (m = this.events).onMessage) == null || g.call(m, i.role, i.content);
167
+ break;
168
+ case a.ERROR:
169
+ (p = (f = this.events).onError) == null || p.call(f, i.code, i.message);
170
+ break;
171
+ }
172
+ }, this.iframe.contentWindow.postMessage(
173
+ { type: a.INIT_CHANNEL },
174
+ "*",
175
+ [e.port2]
176
+ );
177
+ }
178
+ sendCommand(e, t) {
179
+ var o;
180
+ const s = { type: a.COMMAND, action: e, payload: t };
181
+ this.port ? this.port.postMessage(s) : (o = this.iframe) != null && o.contentWindow && this.iframe.contentWindow.postMessage(s, "*");
182
+ }
183
+ updateIframeSize(e) {
184
+ if (console.log("[Widget] updateIframeSize called:", { collapsed: e, variant: this.config.variant, hasIframe: !!this.iframe }), !this.iframe || this.config.variant !== "floating") {
185
+ console.log("[Widget] updateIframeSize skipped - no iframe or not floating variant");
186
+ return;
187
+ }
188
+ e ? (console.log("[Widget] Setting collapsed size: 380x72"), this.iframe.style.width = "380px", this.iframe.style.height = "72px", this.iframe.style.maxHeight = "72px") : (console.log("[Widget] Setting expanded size: 380x520"), this.iframe.style.width = "380px", this.iframe.style.height = "520px", this.iframe.style.maxHeight = "calc(100vh - 40px)");
189
+ }
190
+ }
191
+ export {
192
+ v as LookalikeWidget,
193
+ v as Widget
194
+ };
195
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/widget.ts"],"sourcesContent":["import type {\r\n LookalikeWidgetConfig,\r\n LookalikeWidgetEvents,\r\n LookalikeWidgetInstance,\r\n ChatMode,\r\n} from './types'\r\n\r\n// Use production URL if defined, otherwise fallback to lookalike.com\r\nconst DEFAULT_BASE_URL = typeof __PRODUCTION_BASE_URL__ !== 'undefined' \r\n ? __PRODUCTION_BASE_URL__ \r\n : 'https://lookalike.com'\r\n\r\n// Event type constants\r\nconst EVENTS = {\r\n WIDGET_READY: 'lookalike-widget-ready',\r\n WIDGET_RESIZE: 'lookalike-widget-resize',\r\n SESSION_START: 'lookalike-session-start',\r\n SESSION_END: 'lookalike-session-end',\r\n MESSAGE: 'lookalike-message',\r\n ERROR: 'lookalike-error',\r\n INIT_CHANNEL: 'lookalike-init-channel',\r\n COMMAND: 'lookalike-command',\r\n} as const\r\n\r\n// Command constants\r\nconst COMMANDS = {\r\n EXPAND: 'expand',\r\n COLLAPSE: 'collapse',\r\n} as const\r\n\r\n/**\r\n * Lookalike Widget - Embed conversational AI in your website\r\n *\r\n * @example\r\n * ```javascript\r\n * const widget = new LookalikeWidget({\r\n * handle: 'john-doe',\r\n * variant: 'floating',\r\n * theme: { primaryColor: '#3b82f6' }\r\n * });\r\n *\r\n * widget.on('ready', () => console.log('Widget ready'));\r\n * widget.on('message', (role, content) => console.log(`${role}: ${content}`));\r\n *\r\n * widget.mount(document.body);\r\n * ```\r\n */\r\nexport class LookalikeWidget implements LookalikeWidgetInstance {\r\n private config: Required<Omit<LookalikeWidgetConfig, 'theme'>> & { theme?: LookalikeWidgetConfig['theme'] }\r\n private events: LookalikeWidgetEvents = {}\r\n private iframe: HTMLIFrameElement | null = null\r\n private container: HTMLElement | null = null\r\n private port: MessagePort | null = null\r\n private expanded = false\r\n private mounted = false\r\n private messageHandler: ((e: MessageEvent) => void) | null = null\r\n\r\n constructor(config: LookalikeWidgetConfig) {\r\n this.config = {\r\n handle: config.handle,\r\n variant: config.variant ?? 'floating',\r\n anchor: config.anchor ?? 'bottom-right',\r\n collapsible: config.collapsible ?? false,\r\n modes: config.modes ?? ['text', 'audio', 'video'],\r\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\r\n theme: config.theme,\r\n }\r\n }\r\n\r\n /**\r\n * Register an event handler\r\n */\r\n on<K extends keyof LookalikeWidgetEvents>(\r\n event: K,\r\n handler: NonNullable<LookalikeWidgetEvents[K]>\r\n ): this {\r\n this.events[event] = handler as LookalikeWidgetEvents[K]\r\n return this\r\n }\r\n\r\n /**\r\n * Remove an event handler\r\n */\r\n off<K extends keyof LookalikeWidgetEvents>(event: K): this {\r\n delete this.events[event]\r\n return this\r\n }\r\n\r\n /**\r\n * Mount the widget to a container element\r\n */\r\n mount(container: string | HTMLElement): void {\r\n if (this.mounted) {\r\n console.warn('LookalikeWidget: Already mounted')\r\n return\r\n }\r\n\r\n // Resolve container\r\n if (typeof container === 'string') {\r\n const el = document.querySelector(container)\r\n if (!el) {\r\n throw new Error(`LookalikeWidget: Container \"${container}\" not found`)\r\n }\r\n this.container = el as HTMLElement\r\n } else {\r\n this.container = container\r\n }\r\n\r\n // Create iframe\r\n this.iframe = document.createElement('iframe')\r\n this.iframe.src = this.buildIframeUrl()\r\n this.iframe.style.cssText = this.getIframeStyles()\r\n this.iframe.setAttribute('allow', 'camera; microphone; autoplay')\r\n this.iframe.setAttribute('allowfullscreen', '')\r\n \r\n // Add load event listener for debugging\r\n this.iframe.onload = () => {\r\n console.log('[Widget] Iframe loaded successfully')\r\n }\r\n this.iframe.onerror = (error) => {\r\n console.error('[Widget] Iframe error:', error)\r\n }\r\n\r\n // Set up message listener for handshake\r\n this.messageHandler = this.handleMessage.bind(this)\r\n window.addEventListener('message', this.messageHandler)\r\n console.log('[Widget] Message listener added')\r\n\r\n // Append iframe\r\n this.container.appendChild(this.iframe)\r\n this.mounted = true\r\n console.log('[Widget] Iframe mounted, URL:', this.iframe.src)\r\n }\r\n\r\n /**\r\n * Expand the widget\r\n */\r\n expand(): void {\r\n this.sendCommand(COMMANDS.EXPAND)\r\n }\r\n\r\n /**\r\n * Collapse the widget\r\n */\r\n collapse(): void {\r\n this.sendCommand(COMMANDS.COLLAPSE)\r\n }\r\n\r\n /**\r\n * Destroy the widget and clean up\r\n */\r\n destroy(): void {\r\n if (this.messageHandler) {\r\n window.removeEventListener('message', this.messageHandler)\r\n this.messageHandler = null\r\n }\r\n\r\n if (this.port) {\r\n this.port.close()\r\n this.port = null\r\n }\r\n\r\n if (this.iframe && this.container) {\r\n this.container.removeChild(this.iframe)\r\n this.iframe = null\r\n }\r\n\r\n this.container = null\r\n this.mounted = false\r\n this.expanded = false\r\n }\r\n\r\n /**\r\n * Check if widget is currently expanded\r\n */\r\n isExpanded(): boolean {\r\n return this.expanded\r\n }\r\n\r\n private buildIframeUrl(): string {\r\n const url = new URL(`/${this.config.handle}/chat`, this.config.baseUrl)\r\n\r\n // Add embed flag\r\n url.searchParams.set('embed', '')\r\n\r\n // Add variant\r\n url.searchParams.set('variant', this.config.variant)\r\n\r\n // Add anchor for floating\r\n if (this.config.variant === 'floating') {\r\n url.searchParams.set('anchor', this.config.anchor)\r\n }\r\n\r\n // Add collapsible for inline\r\n if (this.config.variant === 'inline' && this.config.collapsible) {\r\n url.searchParams.set('collapsible', '')\r\n }\r\n\r\n // Add modes\r\n url.searchParams.set('modes', this.config.modes.join(','))\r\n\r\n // Add theme\r\n if (this.config.theme) {\r\n const themeParts: string[] = []\r\n if (this.config.theme.primaryColor) {\r\n themeParts.push(`primary:${this.config.theme.primaryColor}`)\r\n }\r\n if (this.config.theme.accentColor) {\r\n themeParts.push(`accent:${this.config.theme.accentColor}`)\r\n }\r\n if (this.config.theme.borderRadius) {\r\n themeParts.push(`radius:${this.config.theme.borderRadius}`)\r\n }\r\n if (themeParts.length > 0) {\r\n url.searchParams.set('theme', themeParts.join(','))\r\n }\r\n }\r\n\r\n return url.toString()\r\n }\r\n\r\n private getIframeStyles(): string {\r\n if (this.config.variant === 'floating') {\r\n // Floating: positioned in corner, needs to resize based on collapsed state\r\n const anchor = this.config.anchor\r\n const position = {\r\n 'bottom-right': 'bottom: 20px; right: 20px;',\r\n 'bottom-left': 'bottom: 20px; left: 20px;',\r\n 'top-right': 'top: 20px; right: 20px;',\r\n 'top-left': 'top: 20px; left: 20px;',\r\n }[anchor]\r\n\r\n return `\r\n position: fixed;\r\n ${position}\r\n width: 380px;\r\n height: 72px;\r\n max-height: 72px;\r\n border: none;\r\n border-radius: 16px;\r\n z-index: 9999;\r\n transition: all 0.3s ease;\r\n `.replace(/\\s+/g, ' ').trim()\r\n }\r\n\r\n // Inline: fills container\r\n return `\r\n width: 100%;\r\n height: 100%;\r\n min-height: 400px;\r\n border: none;\r\n border-radius: 16px;\r\n `.replace(/\\s+/g, ' ').trim()\r\n }\r\n\r\n private handleMessage(e: MessageEvent): void {\r\n console.log('[Widget] Received message:', e.data, 'from:', e.source === this.iframe?.contentWindow ? 'iframe' : 'unknown')\r\n // Only accept messages from our iframe\r\n if (this.iframe && e.source !== this.iframe.contentWindow) {\r\n console.log('[Widget] Ignoring message - not from iframe')\r\n return\r\n }\r\n\r\n const { type, ...payload } = e.data || {}\r\n\r\n switch (type) {\r\n case EVENTS.WIDGET_READY:\r\n console.log('[Widget] WIDGET_READY event received!')\r\n this.initMessageChannel()\r\n this.events.onReady?.()\r\n break\r\n\r\n case EVENTS.WIDGET_RESIZE:\r\n console.log('[Widget] Received resize event:', { collapsed: payload.collapsed, expanded: !payload.collapsed })\r\n this.expanded = !payload.collapsed\r\n this.updateIframeSize(payload.collapsed)\r\n this.events.onResize?.(payload.collapsed)\r\n break\r\n\r\n case EVENTS.SESSION_START:\r\n this.events.onSessionStart?.(payload.mode as ChatMode)\r\n break\r\n\r\n case EVENTS.SESSION_END:\r\n this.events.onSessionEnd?.()\r\n break\r\n\r\n case EVENTS.MESSAGE:\r\n this.events.onMessage?.(payload.role, payload.content)\r\n break\r\n\r\n case EVENTS.ERROR:\r\n this.events.onError?.(payload.code, payload.message)\r\n break\r\n }\r\n }\r\n\r\n private initMessageChannel(): void {\r\n if (!this.iframe?.contentWindow) return\r\n\r\n // Create a MessageChannel for secure communication\r\n const channel = new MessageChannel()\r\n this.port = channel.port1\r\n\r\n // Set up port message handler\r\n this.port.onmessage = (e) => {\r\n console.log('[Widget] Port message received:', e.data)\r\n // For MessageChannel, we don't need to check source since the channel is secure\r\n const { type, ...payload } = e.data || {}\r\n \r\n switch (type) {\r\n case EVENTS.WIDGET_RESIZE:\r\n console.log('[Widget] Received resize event:', { collapsed: payload.collapsed, expanded: !payload.collapsed })\r\n this.expanded = !payload.collapsed\r\n this.updateIframeSize(payload.collapsed)\r\n this.events.onResize?.(payload.collapsed)\r\n break\r\n\r\n case EVENTS.SESSION_START:\r\n this.events.onSessionStart?.(payload.mode as ChatMode)\r\n break\r\n\r\n case EVENTS.SESSION_END:\r\n this.events.onSessionEnd?.()\r\n break\r\n\r\n case EVENTS.MESSAGE:\r\n this.events.onMessage?.(payload.role, payload.content)\r\n break\r\n\r\n case EVENTS.ERROR:\r\n this.events.onError?.(payload.code, payload.message)\r\n break\r\n }\r\n }\r\n\r\n // Send port2 to iframe\r\n this.iframe.contentWindow.postMessage(\r\n { type: EVENTS.INIT_CHANNEL },\r\n '*',\r\n [channel.port2]\r\n )\r\n }\r\n\r\n private sendCommand(action: string, payload?: unknown): void {\r\n const message = { type: EVENTS.COMMAND, action, payload }\r\n\r\n if (this.port) {\r\n this.port.postMessage(message)\r\n } else if (this.iframe?.contentWindow) {\r\n this.iframe.contentWindow.postMessage(message, '*')\r\n }\r\n }\r\n\r\n private updateIframeSize(collapsed: boolean): void {\r\n console.log('[Widget] updateIframeSize called:', { collapsed, variant: this.config.variant, hasIframe: !!this.iframe })\r\n if (!this.iframe || this.config.variant !== 'floating') {\r\n console.log('[Widget] updateIframeSize skipped - no iframe or not floating variant')\r\n return\r\n }\r\n\r\n if (collapsed) {\r\n // Match the online widget's collapsed height more closely\r\n console.log('[Widget] Setting collapsed size: 380x72')\r\n this.iframe.style.width = '380px'\r\n this.iframe.style.height = '72px'\r\n this.iframe.style.maxHeight = '72px'\r\n } else {\r\n // Match the online widget's expanded dimensions\r\n console.log('[Widget] Setting expanded size: 380x520')\r\n this.iframe.style.width = '380px'\r\n this.iframe.style.height = '520px'\r\n this.iframe.style.maxHeight = 'calc(100vh - 40px)'\r\n }\r\n }\r\n}\r\n\r\n// Also export as Widget for convenience with UMD\r\nexport { LookalikeWidget as Widget }\r\n"],"names":["DEFAULT_BASE_URL","EVENTS","COMMANDS","LookalikeWidget","config","event","handler","container","el","error","url","themeParts","anchor","_a","_b","_c","_d","_e","_f","_g","_h","_i","_j","_k","_l","_m","type","payload","channel","e","action","message","collapsed"],"mappings":"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"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Widget display variant
3
+ */
4
+ export type WidgetVariant = 'floating' | 'inline';
5
+ /**
6
+ * Anchor position for floating widget
7
+ */
8
+ export type WidgetAnchor = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
9
+ /**
10
+ * Chat mode
11
+ */
12
+ export type ChatMode = 'text' | 'audio' | 'video';
13
+ /**
14
+ * Theme customization options
15
+ */
16
+ export interface LookalikeTheme {
17
+ /**
18
+ * Primary background color (e.g., '#3b82f6')
19
+ */
20
+ primaryColor?: string;
21
+ /**
22
+ * Accent/text color (e.g., '#ffffff')
23
+ */
24
+ accentColor?: string;
25
+ /**
26
+ * Border radius (e.g., '16px' or '1rem')
27
+ */
28
+ borderRadius?: string;
29
+ }
30
+ /**
31
+ * Widget configuration options
32
+ */
33
+ export interface LookalikeWidgetConfig {
34
+ /**
35
+ * The Lookalike handle (username) to load
36
+ */
37
+ handle: string;
38
+ /**
39
+ * Widget display variant. 'floating' allows collapse/expand, 'inline' is embedded.
40
+ * @default 'floating'
41
+ */
42
+ variant?: WidgetVariant;
43
+ /**
44
+ * Anchor position for floating variant
45
+ * @default 'bottom-right'
46
+ */
47
+ anchor?: WidgetAnchor;
48
+ /**
49
+ * For inline variant: whether the widget can be collapsed
50
+ * @default false
51
+ */
52
+ collapsible?: boolean;
53
+ /**
54
+ * Allowed chat modes
55
+ * @default ['text', 'audio', 'video']
56
+ */
57
+ modes?: ChatMode[];
58
+ /**
59
+ * Theme customization
60
+ */
61
+ theme?: LookalikeTheme;
62
+ /**
63
+ * Base URL for the widget iframe
64
+ * @default 'https://lookalike.com'
65
+ */
66
+ baseUrl?: string;
67
+ }
68
+ /**
69
+ * Event handlers for widget events
70
+ */
71
+ export interface LookalikeWidgetEvents {
72
+ /**
73
+ * Called when the widget is ready
74
+ */
75
+ onReady?: () => void;
76
+ /**
77
+ * Called when the widget resizes (collapse/expand)
78
+ */
79
+ onResize?: (collapsed: boolean) => void;
80
+ /**
81
+ * Called when a chat session starts
82
+ */
83
+ onSessionStart?: (mode: ChatMode) => void;
84
+ /**
85
+ * Called when a chat session ends
86
+ */
87
+ onSessionEnd?: () => void;
88
+ /**
89
+ * Called when a new message is received
90
+ */
91
+ onMessage?: (role: 'user' | 'assistant', content: string) => void;
92
+ /**
93
+ * Called when an error occurs
94
+ */
95
+ onError?: (code: string, message: string) => void;
96
+ }
97
+ /**
98
+ * Widget instance interface for controlling the widget
99
+ */
100
+ export interface LookalikeWidgetInstance {
101
+ /**
102
+ * Mount the widget to a container element
103
+ */
104
+ mount(container: string | HTMLElement): void;
105
+ /**
106
+ * Expand the widget (show full chat)
107
+ */
108
+ expand(): void;
109
+ /**
110
+ * Collapse the widget (minimize)
111
+ */
112
+ collapse(): void;
113
+ /**
114
+ * Destroy the widget and clean up
115
+ */
116
+ destroy(): void;
117
+ /**
118
+ * Check if widget is currently expanded
119
+ */
120
+ isExpanded(): boolean;
121
+ }
122
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,QAAQ,CAAA;AAEjD;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,UAAU,CAAA;AAEpF;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAA;AAEjD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;OAGG;IACH,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAA;IAClB;;OAEG;IACH,KAAK,CAAC,EAAE,cAAc,CAAA;IACtB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAA;IACvC;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAA;IACzC;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;IACzB;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACjE;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAClD;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAAA;IAC5C;;OAEG;IACH,MAAM,IAAI,IAAI,CAAA;IACd;;OAEG;IACH,QAAQ,IAAI,IAAI,CAAA;IAChB;;OAEG;IACH,OAAO,IAAI,IAAI,CAAA;IACf;;OAEG;IACH,UAAU,IAAI,OAAO,CAAA;CACtB"}
@@ -0,0 +1,66 @@
1
+ import { LookalikeWidgetConfig, LookalikeWidgetEvents, LookalikeWidgetInstance } from './types';
2
+
3
+ /**
4
+ * Lookalike Widget - Embed conversational AI in your website
5
+ *
6
+ * @example
7
+ * ```javascript
8
+ * const widget = new LookalikeWidget({
9
+ * handle: 'john-doe',
10
+ * variant: 'floating',
11
+ * theme: { primaryColor: '#3b82f6' }
12
+ * });
13
+ *
14
+ * widget.on('ready', () => console.log('Widget ready'));
15
+ * widget.on('message', (role, content) => console.log(`${role}: ${content}`));
16
+ *
17
+ * widget.mount(document.body);
18
+ * ```
19
+ */
20
+ export declare class LookalikeWidget implements LookalikeWidgetInstance {
21
+ private config;
22
+ private events;
23
+ private iframe;
24
+ private container;
25
+ private port;
26
+ private expanded;
27
+ private mounted;
28
+ private messageHandler;
29
+ constructor(config: LookalikeWidgetConfig);
30
+ /**
31
+ * Register an event handler
32
+ */
33
+ on<K extends keyof LookalikeWidgetEvents>(event: K, handler: NonNullable<LookalikeWidgetEvents[K]>): this;
34
+ /**
35
+ * Remove an event handler
36
+ */
37
+ off<K extends keyof LookalikeWidgetEvents>(event: K): this;
38
+ /**
39
+ * Mount the widget to a container element
40
+ */
41
+ mount(container: string | HTMLElement): void;
42
+ /**
43
+ * Expand the widget
44
+ */
45
+ expand(): void;
46
+ /**
47
+ * Collapse the widget
48
+ */
49
+ collapse(): void;
50
+ /**
51
+ * Destroy the widget and clean up
52
+ */
53
+ destroy(): void;
54
+ /**
55
+ * Check if widget is currently expanded
56
+ */
57
+ isExpanded(): boolean;
58
+ private buildIframeUrl;
59
+ private getIframeStyles;
60
+ private handleMessage;
61
+ private initMessageChannel;
62
+ private sendCommand;
63
+ private updateIframeSize;
64
+ }
65
+ export { LookalikeWidget as Widget };
66
+ //# sourceMappingURL=widget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.d.ts","sourceRoot":"","sources":["../src/widget.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EAExB,MAAM,SAAS,CAAA;AAyBhB;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,eAAgB,YAAW,uBAAuB;IAC7D,OAAO,CAAC,MAAM,CAA6F;IAC3G,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,IAAI,CAA2B;IACvC,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,cAAc,CAA2C;gBAErD,MAAM,EAAE,qBAAqB;IAYzC;;OAEG;IACH,EAAE,CAAC,CAAC,SAAS,MAAM,qBAAqB,EACtC,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,WAAW,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,GAC7C,IAAI;IAKP;;OAEG;IACH,GAAG,CAAC,CAAC,SAAS,MAAM,qBAAqB,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAK1D;;OAEG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IA2C5C;;OAEG;IACH,MAAM,IAAI,IAAI;IAId;;OAEG;IACH,QAAQ,IAAI,IAAI;IAIhB;;OAEG;IACH,OAAO,IAAI,IAAI;IAqBf;;OAEG;IACH,UAAU,IAAI,OAAO;IAIrB,OAAO,CAAC,cAAc;IA0CtB,OAAO,CAAC,eAAe;IAkCvB,OAAO,CAAC,aAAa;IA0CrB,OAAO,CAAC,kBAAkB;IA+C1B,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,gBAAgB;CAqBzB;AAGD,OAAO,EAAE,eAAe,IAAI,MAAM,EAAE,CAAA"}
@@ -0,0 +1,18 @@
1
+ (function(a,r){typeof exports=="object"&&typeof module<"u"?r(exports):typeof define=="function"&&define.amd?define(["exports"],r):(a=typeof globalThis<"u"?globalThis:a||self,r(a.LookalikeWidget={}))})(this,function(a){"use strict";const r="https://your-app-name.up.railway.app",i={WIDGET_READY:"lookalike-widget-ready",WIDGET_RESIZE:"lookalike-widget-resize",SESSION_START:"lookalike-session-start",SESSION_END:"lookalike-session-end",MESSAGE:"lookalike-message",ERROR:"lookalike-error",INIT_CHANNEL:"lookalike-init-channel",COMMAND:"lookalike-command"},E={EXPAND:"expand",COLLAPSE:"collapse"};class x{constructor(e){this.events={},this.iframe=null,this.container=null,this.port=null,this.expanded=!1,this.mounted=!1,this.messageHandler=null,this.config={handle:e.handle,variant:e.variant??"floating",anchor:e.anchor??"bottom-right",collapsible:e.collapsible??!1,modes:e.modes??["text","audio","video"],baseUrl:e.baseUrl??r,theme:e.theme}}on(e,t){return this.events[e]=t,this}off(e){return delete this.events[e],this}mount(e){if(this.mounted){console.warn("LookalikeWidget: Already mounted");return}if(typeof e=="string"){const t=document.querySelector(e);if(!t)throw new Error(`LookalikeWidget: Container "${e}" not found`);this.container=t}else this.container=e;this.iframe=document.createElement("iframe"),this.iframe.src=this.buildIframeUrl(),this.iframe.style.cssText=this.getIframeStyles(),this.iframe.setAttribute("allow","camera; microphone; autoplay"),this.iframe.setAttribute("allowfullscreen",""),this.iframe.onload=()=>{console.log("[Widget] Iframe loaded successfully")},this.iframe.onerror=t=>{console.error("[Widget] Iframe error:",t)},this.messageHandler=this.handleMessage.bind(this),window.addEventListener("message",this.messageHandler),console.log("[Widget] Message listener added"),this.container.appendChild(this.iframe),this.mounted=!0,console.log("[Widget] Iframe mounted, URL:",this.iframe.src)}expand(){this.sendCommand(E.EXPAND)}collapse(){this.sendCommand(E.COLLAPSE)}destroy(){this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.port&&(this.port.close(),this.port=null),this.iframe&&this.container&&(this.container.removeChild(this.iframe),this.iframe=null),this.container=null,this.mounted=!1,this.expanded=!1}isExpanded(){return this.expanded}buildIframeUrl(){const e=new URL(`/${this.config.handle}/chat`,this.config.baseUrl);if(e.searchParams.set("embed",""),e.searchParams.set("variant",this.config.variant),this.config.variant==="floating"&&e.searchParams.set("anchor",this.config.anchor),this.config.variant==="inline"&&this.config.collapsible&&e.searchParams.set("collapsible",""),e.searchParams.set("modes",this.config.modes.join(",")),this.config.theme){const t=[];this.config.theme.primaryColor&&t.push(`primary:${this.config.theme.primaryColor}`),this.config.theme.accentColor&&t.push(`accent:${this.config.theme.accentColor}`),this.config.theme.borderRadius&&t.push(`radius:${this.config.theme.borderRadius}`),t.length>0&&e.searchParams.set("theme",t.join(","))}return e.toString()}getIframeStyles(){if(this.config.variant==="floating"){const e=this.config.anchor;return`
2
+ position: fixed;
3
+ ${{"bottom-right":"bottom: 20px; right: 20px;","bottom-left":"bottom: 20px; left: 20px;","top-right":"top: 20px; right: 20px;","top-left":"top: 20px; left: 20px;"}[e]}
4
+ width: 380px;
5
+ height: 72px;
6
+ max-height: 72px;
7
+ border: none;
8
+ border-radius: 16px;
9
+ z-index: 9999;
10
+ transition: all 0.3s ease;
11
+ `.replace(/\s+/g," ").trim()}return`
12
+ width: 100%;
13
+ height: 100%;
14
+ min-height: 400px;
15
+ border: none;
16
+ border-radius: 16px;
17
+ `.replace(/\s+/g," ").trim()}handleMessage(e){var n,o,l,h,d,c,m,f,g,p,u,S,v;if(console.log("[Widget] Received message:",e.data,"from:",e.source===((n=this.iframe)==null?void 0:n.contentWindow)?"iframe":"unknown"),this.iframe&&e.source!==this.iframe.contentWindow){console.log("[Widget] Ignoring message - not from iframe");return}const{type:t,...s}=e.data||{};switch(t){case i.WIDGET_READY:console.log("[Widget] WIDGET_READY event received!"),this.initMessageChannel(),(l=(o=this.events).onReady)==null||l.call(o);break;case i.WIDGET_RESIZE:console.log("[Widget] Received resize event:",{collapsed:s.collapsed,expanded:!s.collapsed}),this.expanded=!s.collapsed,this.updateIframeSize(s.collapsed),(d=(h=this.events).onResize)==null||d.call(h,s.collapsed);break;case i.SESSION_START:(m=(c=this.events).onSessionStart)==null||m.call(c,s.mode);break;case i.SESSION_END:(g=(f=this.events).onSessionEnd)==null||g.call(f);break;case i.MESSAGE:(u=(p=this.events).onMessage)==null||u.call(p,s.role,s.content);break;case i.ERROR:(v=(S=this.events).onError)==null||v.call(S,s.code,s.message);break}}initMessageChannel(){var t;if(!((t=this.iframe)!=null&&t.contentWindow))return;const e=new MessageChannel;this.port=e.port1,this.port.onmessage=s=>{var l,h,d,c,m,f,g,p,u,S;console.log("[Widget] Port message received:",s.data);const{type:n,...o}=s.data||{};switch(n){case i.WIDGET_RESIZE:console.log("[Widget] Received resize event:",{collapsed:o.collapsed,expanded:!o.collapsed}),this.expanded=!o.collapsed,this.updateIframeSize(o.collapsed),(h=(l=this.events).onResize)==null||h.call(l,o.collapsed);break;case i.SESSION_START:(c=(d=this.events).onSessionStart)==null||c.call(d,o.mode);break;case i.SESSION_END:(f=(m=this.events).onSessionEnd)==null||f.call(m);break;case i.MESSAGE:(p=(g=this.events).onMessage)==null||p.call(g,o.role,o.content);break;case i.ERROR:(S=(u=this.events).onError)==null||S.call(u,o.code,o.message);break}},this.iframe.contentWindow.postMessage({type:i.INIT_CHANNEL},"*",[e.port2])}sendCommand(e,t){var n;const s={type:i.COMMAND,action:e,payload:t};this.port?this.port.postMessage(s):(n=this.iframe)!=null&&n.contentWindow&&this.iframe.contentWindow.postMessage(s,"*")}updateIframeSize(e){if(console.log("[Widget] updateIframeSize called:",{collapsed:e,variant:this.config.variant,hasIframe:!!this.iframe}),!this.iframe||this.config.variant!=="floating"){console.log("[Widget] updateIframeSize skipped - no iframe or not floating variant");return}e?(console.log("[Widget] Setting collapsed size: 380x72"),this.iframe.style.width="380px",this.iframe.style.height="72px",this.iframe.style.maxHeight="72px"):(console.log("[Widget] Setting expanded size: 380x520"),this.iframe.style.width="380px",this.iframe.style.height="520px",this.iframe.style.maxHeight="calc(100vh - 40px)")}}a.LookalikeWidget=x,a.Widget=x,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})});
18
+ //# sourceMappingURL=widget.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.umd.js","sources":["../src/widget.ts"],"sourcesContent":["import type {\r\n LookalikeWidgetConfig,\r\n LookalikeWidgetEvents,\r\n LookalikeWidgetInstance,\r\n ChatMode,\r\n} from './types'\r\n\r\n// Use production URL if defined, otherwise fallback to lookalike.com\r\nconst DEFAULT_BASE_URL = typeof __PRODUCTION_BASE_URL__ !== 'undefined' \r\n ? __PRODUCTION_BASE_URL__ \r\n : 'https://lookalike.com'\r\n\r\n// Event type constants\r\nconst EVENTS = {\r\n WIDGET_READY: 'lookalike-widget-ready',\r\n WIDGET_RESIZE: 'lookalike-widget-resize',\r\n SESSION_START: 'lookalike-session-start',\r\n SESSION_END: 'lookalike-session-end',\r\n MESSAGE: 'lookalike-message',\r\n ERROR: 'lookalike-error',\r\n INIT_CHANNEL: 'lookalike-init-channel',\r\n COMMAND: 'lookalike-command',\r\n} as const\r\n\r\n// Command constants\r\nconst COMMANDS = {\r\n EXPAND: 'expand',\r\n COLLAPSE: 'collapse',\r\n} as const\r\n\r\n/**\r\n * Lookalike Widget - Embed conversational AI in your website\r\n *\r\n * @example\r\n * ```javascript\r\n * const widget = new LookalikeWidget({\r\n * handle: 'john-doe',\r\n * variant: 'floating',\r\n * theme: { primaryColor: '#3b82f6' }\r\n * });\r\n *\r\n * widget.on('ready', () => console.log('Widget ready'));\r\n * widget.on('message', (role, content) => console.log(`${role}: ${content}`));\r\n *\r\n * widget.mount(document.body);\r\n * ```\r\n */\r\nexport class LookalikeWidget implements LookalikeWidgetInstance {\r\n private config: Required<Omit<LookalikeWidgetConfig, 'theme'>> & { theme?: LookalikeWidgetConfig['theme'] }\r\n private events: LookalikeWidgetEvents = {}\r\n private iframe: HTMLIFrameElement | null = null\r\n private container: HTMLElement | null = null\r\n private port: MessagePort | null = null\r\n private expanded = false\r\n private mounted = false\r\n private messageHandler: ((e: MessageEvent) => void) | null = null\r\n\r\n constructor(config: LookalikeWidgetConfig) {\r\n this.config = {\r\n handle: config.handle,\r\n variant: config.variant ?? 'floating',\r\n anchor: config.anchor ?? 'bottom-right',\r\n collapsible: config.collapsible ?? false,\r\n modes: config.modes ?? ['text', 'audio', 'video'],\r\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\r\n theme: config.theme,\r\n }\r\n }\r\n\r\n /**\r\n * Register an event handler\r\n */\r\n on<K extends keyof LookalikeWidgetEvents>(\r\n event: K,\r\n handler: NonNullable<LookalikeWidgetEvents[K]>\r\n ): this {\r\n this.events[event] = handler as LookalikeWidgetEvents[K]\r\n return this\r\n }\r\n\r\n /**\r\n * Remove an event handler\r\n */\r\n off<K extends keyof LookalikeWidgetEvents>(event: K): this {\r\n delete this.events[event]\r\n return this\r\n }\r\n\r\n /**\r\n * Mount the widget to a container element\r\n */\r\n mount(container: string | HTMLElement): void {\r\n if (this.mounted) {\r\n console.warn('LookalikeWidget: Already mounted')\r\n return\r\n }\r\n\r\n // Resolve container\r\n if (typeof container === 'string') {\r\n const el = document.querySelector(container)\r\n if (!el) {\r\n throw new Error(`LookalikeWidget: Container \"${container}\" not found`)\r\n }\r\n this.container = el as HTMLElement\r\n } else {\r\n this.container = container\r\n }\r\n\r\n // Create iframe\r\n this.iframe = document.createElement('iframe')\r\n this.iframe.src = this.buildIframeUrl()\r\n this.iframe.style.cssText = this.getIframeStyles()\r\n this.iframe.setAttribute('allow', 'camera; microphone; autoplay')\r\n this.iframe.setAttribute('allowfullscreen', '')\r\n \r\n // Add load event listener for debugging\r\n this.iframe.onload = () => {\r\n console.log('[Widget] Iframe loaded successfully')\r\n }\r\n this.iframe.onerror = (error) => {\r\n console.error('[Widget] Iframe error:', error)\r\n }\r\n\r\n // Set up message listener for handshake\r\n this.messageHandler = this.handleMessage.bind(this)\r\n window.addEventListener('message', this.messageHandler)\r\n console.log('[Widget] Message listener added')\r\n\r\n // Append iframe\r\n this.container.appendChild(this.iframe)\r\n this.mounted = true\r\n console.log('[Widget] Iframe mounted, URL:', this.iframe.src)\r\n }\r\n\r\n /**\r\n * Expand the widget\r\n */\r\n expand(): void {\r\n this.sendCommand(COMMANDS.EXPAND)\r\n }\r\n\r\n /**\r\n * Collapse the widget\r\n */\r\n collapse(): void {\r\n this.sendCommand(COMMANDS.COLLAPSE)\r\n }\r\n\r\n /**\r\n * Destroy the widget and clean up\r\n */\r\n destroy(): void {\r\n if (this.messageHandler) {\r\n window.removeEventListener('message', this.messageHandler)\r\n this.messageHandler = null\r\n }\r\n\r\n if (this.port) {\r\n this.port.close()\r\n this.port = null\r\n }\r\n\r\n if (this.iframe && this.container) {\r\n this.container.removeChild(this.iframe)\r\n this.iframe = null\r\n }\r\n\r\n this.container = null\r\n this.mounted = false\r\n this.expanded = false\r\n }\r\n\r\n /**\r\n * Check if widget is currently expanded\r\n */\r\n isExpanded(): boolean {\r\n return this.expanded\r\n }\r\n\r\n private buildIframeUrl(): string {\r\n const url = new URL(`/${this.config.handle}/chat`, this.config.baseUrl)\r\n\r\n // Add embed flag\r\n url.searchParams.set('embed', '')\r\n\r\n // Add variant\r\n url.searchParams.set('variant', this.config.variant)\r\n\r\n // Add anchor for floating\r\n if (this.config.variant === 'floating') {\r\n url.searchParams.set('anchor', this.config.anchor)\r\n }\r\n\r\n // Add collapsible for inline\r\n if (this.config.variant === 'inline' && this.config.collapsible) {\r\n url.searchParams.set('collapsible', '')\r\n }\r\n\r\n // Add modes\r\n url.searchParams.set('modes', this.config.modes.join(','))\r\n\r\n // Add theme\r\n if (this.config.theme) {\r\n const themeParts: string[] = []\r\n if (this.config.theme.primaryColor) {\r\n themeParts.push(`primary:${this.config.theme.primaryColor}`)\r\n }\r\n if (this.config.theme.accentColor) {\r\n themeParts.push(`accent:${this.config.theme.accentColor}`)\r\n }\r\n if (this.config.theme.borderRadius) {\r\n themeParts.push(`radius:${this.config.theme.borderRadius}`)\r\n }\r\n if (themeParts.length > 0) {\r\n url.searchParams.set('theme', themeParts.join(','))\r\n }\r\n }\r\n\r\n return url.toString()\r\n }\r\n\r\n private getIframeStyles(): string {\r\n if (this.config.variant === 'floating') {\r\n // Floating: positioned in corner, needs to resize based on collapsed state\r\n const anchor = this.config.anchor\r\n const position = {\r\n 'bottom-right': 'bottom: 20px; right: 20px;',\r\n 'bottom-left': 'bottom: 20px; left: 20px;',\r\n 'top-right': 'top: 20px; right: 20px;',\r\n 'top-left': 'top: 20px; left: 20px;',\r\n }[anchor]\r\n\r\n return `\r\n position: fixed;\r\n ${position}\r\n width: 380px;\r\n height: 72px;\r\n max-height: 72px;\r\n border: none;\r\n border-radius: 16px;\r\n z-index: 9999;\r\n transition: all 0.3s ease;\r\n `.replace(/\\s+/g, ' ').trim()\r\n }\r\n\r\n // Inline: fills container\r\n return `\r\n width: 100%;\r\n height: 100%;\r\n min-height: 400px;\r\n border: none;\r\n border-radius: 16px;\r\n `.replace(/\\s+/g, ' ').trim()\r\n }\r\n\r\n private handleMessage(e: MessageEvent): void {\r\n console.log('[Widget] Received message:', e.data, 'from:', e.source === this.iframe?.contentWindow ? 'iframe' : 'unknown')\r\n // Only accept messages from our iframe\r\n if (this.iframe && e.source !== this.iframe.contentWindow) {\r\n console.log('[Widget] Ignoring message - not from iframe')\r\n return\r\n }\r\n\r\n const { type, ...payload } = e.data || {}\r\n\r\n switch (type) {\r\n case EVENTS.WIDGET_READY:\r\n console.log('[Widget] WIDGET_READY event received!')\r\n this.initMessageChannel()\r\n this.events.onReady?.()\r\n break\r\n\r\n case EVENTS.WIDGET_RESIZE:\r\n console.log('[Widget] Received resize event:', { collapsed: payload.collapsed, expanded: !payload.collapsed })\r\n this.expanded = !payload.collapsed\r\n this.updateIframeSize(payload.collapsed)\r\n this.events.onResize?.(payload.collapsed)\r\n break\r\n\r\n case EVENTS.SESSION_START:\r\n this.events.onSessionStart?.(payload.mode as ChatMode)\r\n break\r\n\r\n case EVENTS.SESSION_END:\r\n this.events.onSessionEnd?.()\r\n break\r\n\r\n case EVENTS.MESSAGE:\r\n this.events.onMessage?.(payload.role, payload.content)\r\n break\r\n\r\n case EVENTS.ERROR:\r\n this.events.onError?.(payload.code, payload.message)\r\n break\r\n }\r\n }\r\n\r\n private initMessageChannel(): void {\r\n if (!this.iframe?.contentWindow) return\r\n\r\n // Create a MessageChannel for secure communication\r\n const channel = new MessageChannel()\r\n this.port = channel.port1\r\n\r\n // Set up port message handler\r\n this.port.onmessage = (e) => {\r\n console.log('[Widget] Port message received:', e.data)\r\n // For MessageChannel, we don't need to check source since the channel is secure\r\n const { type, ...payload } = e.data || {}\r\n \r\n switch (type) {\r\n case EVENTS.WIDGET_RESIZE:\r\n console.log('[Widget] Received resize event:', { collapsed: payload.collapsed, expanded: !payload.collapsed })\r\n this.expanded = !payload.collapsed\r\n this.updateIframeSize(payload.collapsed)\r\n this.events.onResize?.(payload.collapsed)\r\n break\r\n\r\n case EVENTS.SESSION_START:\r\n this.events.onSessionStart?.(payload.mode as ChatMode)\r\n break\r\n\r\n case EVENTS.SESSION_END:\r\n this.events.onSessionEnd?.()\r\n break\r\n\r\n case EVENTS.MESSAGE:\r\n this.events.onMessage?.(payload.role, payload.content)\r\n break\r\n\r\n case EVENTS.ERROR:\r\n this.events.onError?.(payload.code, payload.message)\r\n break\r\n }\r\n }\r\n\r\n // Send port2 to iframe\r\n this.iframe.contentWindow.postMessage(\r\n { type: EVENTS.INIT_CHANNEL },\r\n '*',\r\n [channel.port2]\r\n )\r\n }\r\n\r\n private sendCommand(action: string, payload?: unknown): void {\r\n const message = { type: EVENTS.COMMAND, action, payload }\r\n\r\n if (this.port) {\r\n this.port.postMessage(message)\r\n } else if (this.iframe?.contentWindow) {\r\n this.iframe.contentWindow.postMessage(message, '*')\r\n }\r\n }\r\n\r\n private updateIframeSize(collapsed: boolean): void {\r\n console.log('[Widget] updateIframeSize called:', { collapsed, variant: this.config.variant, hasIframe: !!this.iframe })\r\n if (!this.iframe || this.config.variant !== 'floating') {\r\n console.log('[Widget] updateIframeSize skipped - no iframe or not floating variant')\r\n return\r\n }\r\n\r\n if (collapsed) {\r\n // Match the online widget's collapsed height more closely\r\n console.log('[Widget] Setting collapsed size: 380x72')\r\n this.iframe.style.width = '380px'\r\n this.iframe.style.height = '72px'\r\n this.iframe.style.maxHeight = '72px'\r\n } else {\r\n // Match the online widget's expanded dimensions\r\n console.log('[Widget] Setting expanded size: 380x520')\r\n this.iframe.style.width = '380px'\r\n this.iframe.style.height = '520px'\r\n this.iframe.style.maxHeight = 'calc(100vh - 40px)'\r\n }\r\n }\r\n}\r\n\r\n// Also export as Widget for convenience with UMD\r\nexport { LookalikeWidget as Widget }\r\n"],"names":["DEFAULT_BASE_URL","EVENTS","COMMANDS","LookalikeWidget","config","event","handler","container","el","error","url","themeParts","anchor","_a","type","payload","_c","_b","_e","_d","_g","_f","_i","_h","_k","_j","_m","_l","channel","e","action","message","collapsed"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@lookalike/widget",
3
+ "version": "1.0.0-beta.1",
4
+ "description": "Embed Lookalike conversational AI widgets in your website with Wix integration support",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "unpkg": "dist/widget.umd.js",
10
+ "jsdelivr": "dist/widget.umd.js",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "scripts": {
22
+ "build": "vite build",
23
+ "build:production": "vite build --config vite.config.production.ts",
24
+ "dev": "vite build --watch",
25
+ "test": "node dev-server.js",
26
+ "test:build": "node dev-server.js --build",
27
+ "version:patch": "npm version patch",
28
+ "version:minor": "npm version minor",
29
+ "version:major": "npm version major",
30
+ "version:beta": "npm version prerelease --preid=beta",
31
+ "prepublishOnly": "npm run build:production",
32
+ "publish:beta": "npm publish --tag beta",
33
+ "publish:latest": "npm publish --tag latest"
34
+ },
35
+ "devDependencies": {
36
+ "typescript": "^5.0.0",
37
+ "vite": "^5.0.0",
38
+ "vite-plugin-dts": "^3.0.0"
39
+ },
40
+ "keywords": [
41
+ "lookalike",
42
+ "widget",
43
+ "chat",
44
+ "ai",
45
+ "conversational",
46
+ "wix",
47
+ "embed",
48
+ "iframe"
49
+ ],
50
+ "author": "Storyfile LLC",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/Storyfilellc/lookalike.git",
54
+ "directory": "plugins/public/widget"
55
+ },
56
+ "homepage": "https://lookalike.com",
57
+ "bugs": {
58
+ "url": "https://github.com/Storyfilellc/lookalike/issues"
59
+ },
60
+ "license": "MIT",
61
+ "publishConfig": {
62
+ "access": "public",
63
+ "registry": "https://registry.npmjs.org/"
64
+ }
65
+ }