@lookalike/widget 1.0.0-beta.7 → 1.2.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/LICENSE +21 -0
- package/README.md +43 -99
- package/dist/README.md +81 -0
- package/dist/example.html +29 -0
- package/dist/lookalike-widget.js +524 -0
- package/dist/lookalike-widget.min.js +8 -0
- package/package.json +35 -48
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/types.d.ts +0 -122
- package/dist/types.d.ts.map +0 -1
- package/dist/umd.d.ts +0 -4
- package/dist/umd.d.ts.map +0 -1
- package/dist/widget.d.ts +0 -66
- package/dist/widget.d.ts.map +0 -1
- package/dist/widget.umd.js +0 -18
- package/dist/widget.umd.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lookalike
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,120 +1,64 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Lookalike Widget Embed
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Self-contained, shadow DOM encapsulated widget for embedding Lookalike conversational AI avatars.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Development
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
## Usage
|
|
12
|
-
|
|
13
|
-
### ES Modules / TypeScript
|
|
14
|
-
|
|
15
|
-
```typescript
|
|
16
|
-
import { LookalikeWidget } from '@lookalike/widget';
|
|
7
|
+
1. Install dependencies:
|
|
8
|
+
```bash
|
|
9
|
+
npm install
|
|
10
|
+
```
|
|
17
11
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
});
|
|
12
|
+
2. Build the widget:
|
|
13
|
+
```bash
|
|
14
|
+
npm run build
|
|
15
|
+
```
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
widget.on('message', (role, content) => console.log(`${role}: ${content}`));
|
|
27
|
-
widget.on('sessionStart', (mode) => console.log(`Session started in ${mode} mode`));
|
|
17
|
+
3. Test locally:
|
|
18
|
+
Open `dist/example.html` in a browser
|
|
28
19
|
|
|
29
|
-
|
|
30
|
-
widget.mount(document.body);
|
|
20
|
+
## Deployment
|
|
31
21
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// Clean up
|
|
37
|
-
widget.destroy();
|
|
22
|
+
### NPM Package
|
|
23
|
+
```bash
|
|
24
|
+
npm publish
|
|
38
25
|
```
|
|
39
26
|
|
|
40
|
-
### CDN
|
|
41
|
-
|
|
27
|
+
### CDN Usage
|
|
28
|
+
After publishing, users can include:
|
|
42
29
|
```html
|
|
43
|
-
<script src="https://unpkg.com/@lookalike/widget/dist/widget.
|
|
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>
|
|
30
|
+
<script src="https://unpkg.com/@lookalike/widget@latest/dist/lookalike-widget.min.js"></script>
|
|
53
31
|
```
|
|
54
32
|
|
|
55
|
-
##
|
|
33
|
+
## Key Features
|
|
56
34
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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 |
|
|
35
|
+
- **No Wix dependencies** - Completely standalone
|
|
36
|
+
- **Shadow DOM** - Complete style encapsulation
|
|
37
|
+
- **Attribute-based config** - Simple HTML attributes
|
|
38
|
+
- **CDN optimized** - Minified and ready for unpkg/jsdelivr
|
|
39
|
+
- **Cross-platform** - Works in any modern browser
|
|
66
40
|
|
|
67
|
-
|
|
41
|
+
## Usage Examples
|
|
68
42
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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');
|
|
43
|
+
### Basic
|
|
44
|
+
```html
|
|
45
|
+
<lookalike-widget uuid="your-uuid"></lookalike-widget>
|
|
112
46
|
```
|
|
113
47
|
|
|
48
|
+
### With Custom Styling
|
|
114
49
|
```html
|
|
115
|
-
<
|
|
50
|
+
<lookalike-widget
|
|
51
|
+
uuid="your-uuid"
|
|
52
|
+
color="#ff6b6b"
|
|
53
|
+
text="true"
|
|
54
|
+
audio="false"
|
|
55
|
+
video="true">
|
|
56
|
+
</lookalike-widget>
|
|
116
57
|
```
|
|
117
58
|
|
|
118
|
-
##
|
|
59
|
+
## Files
|
|
119
60
|
|
|
120
|
-
|
|
61
|
+
- `main-embed.js` - Source code (shadow DOM version)
|
|
62
|
+
- `build.js` - Build script with Terser minification
|
|
63
|
+
- `package.json` - NPM package configuration
|
|
64
|
+
- `dist/` - Built files ready for CDN deployment
|
package/dist/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Lookalike Widget
|
|
2
|
+
|
|
3
|
+
A lightweight, self-contained widget for embedding Lookalike conversational AI avatars into any website.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🚀 **Zero dependencies** - Self-contained web component
|
|
8
|
+
- 🔒 **Shadow DOM encapsulation** - No CSS conflicts
|
|
9
|
+
- 📱 **Responsive design** - Works on desktop and mobile
|
|
10
|
+
- ⚡ **CDN ready** - Optimized for fast loading
|
|
11
|
+
- 🎨 **Customizable** - Colors and interaction modes
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### CDN (Recommended)
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<script src="https://unpkg.com/@lookalike/widget@latest/dist/lookalike-widget.min.js"></script>
|
|
19
|
+
<lookalike-widget uuid="your-uuid-here"></lookalike-widget>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Self-hosted
|
|
23
|
+
|
|
24
|
+
1. Download `lookalike-widget.min.js`
|
|
25
|
+
2. Host it on your server
|
|
26
|
+
3. Include in your HTML:
|
|
27
|
+
|
|
28
|
+
```html
|
|
29
|
+
<script src="/path/to/lookalike-widget.min.js"></script>
|
|
30
|
+
<lookalike-widget uuid="your-uuid-here"></lookalike-widget>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Configuration
|
|
34
|
+
|
|
35
|
+
| Attribute | Default | Description |
|
|
36
|
+
|-----------|---------|-------------|
|
|
37
|
+
| `uuid` | *required* | Your Lookalike storyfile UUID |
|
|
38
|
+
| `color` | `#2563eb` | Primary theme color |
|
|
39
|
+
| `text` | `true` | Enable text chat |
|
|
40
|
+
| `audio` | `true` | Enable voice calls |
|
|
41
|
+
| `video` | `true` | Enable video chat |
|
|
42
|
+
|
|
43
|
+
## Examples
|
|
44
|
+
|
|
45
|
+
### Basic Widget
|
|
46
|
+
```html
|
|
47
|
+
<lookalike-widget uuid="019b4c51-7701-726a-8f59-e89a854682f3"></lookalike-widget>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Custom Theme
|
|
51
|
+
```html
|
|
52
|
+
<lookalike-widget
|
|
53
|
+
uuid="your-uuid"
|
|
54
|
+
color="#ff6b6b">
|
|
55
|
+
</lookalike-widget>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Text-only Mode
|
|
59
|
+
```html
|
|
60
|
+
<lookalike-widget
|
|
61
|
+
uuid="your-uuid"
|
|
62
|
+
audio="false"
|
|
63
|
+
video="false">
|
|
64
|
+
</lookalike-widget>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Browser Support
|
|
68
|
+
|
|
69
|
+
- Chrome 54+
|
|
70
|
+
- Firefox 63+
|
|
71
|
+
- Safari 10.1+
|
|
72
|
+
- Edge 79+
|
|
73
|
+
|
|
74
|
+
## File Sizes
|
|
75
|
+
|
|
76
|
+
- **Minified**: ~11KB
|
|
77
|
+
- **Gzipped**: ~3KB (estimated)
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT License - see LICENSE file for details.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Lookalike Widget Example</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<h1>Lookalike Widget Demo</h1>
|
|
10
|
+
<p>The widget will appear in the bottom-right corner.</p>
|
|
11
|
+
|
|
12
|
+
<!-- Basic usage -->
|
|
13
|
+
<lookalike-widget uuid="019b4c51-7701-726a-8f59-e89a854682f3"></lookalike-widget>
|
|
14
|
+
|
|
15
|
+
<!-- Advanced usage with custom colors and modes -->
|
|
16
|
+
<!--
|
|
17
|
+
<lookalike-widget
|
|
18
|
+
uuid="your-uuid-here"
|
|
19
|
+
color="#ff6b6b"
|
|
20
|
+
text="true"
|
|
21
|
+
audio="false"
|
|
22
|
+
video="true">
|
|
23
|
+
</lookalike-widget>
|
|
24
|
+
-->
|
|
25
|
+
|
|
26
|
+
<!-- Load widget script -->
|
|
27
|
+
<script src="./lookalike-widget.min.js"></script>
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Lookalike Widget v1.1.0
|
|
3
|
+
* https://lookalike.com
|
|
4
|
+
*
|
|
5
|
+
* Copy/paste embed widget for conversational AI avatars
|
|
6
|
+
* Usage: <lookalike-widget uuid="your-uuid"></lookalike-widget>
|
|
7
|
+
*/
|
|
8
|
+
// Lookalike Widget - Standalone Embed Version
|
|
9
|
+
// Self-contained widget for copy/paste embedding (no Wix dependencies)
|
|
10
|
+
// Uses shadow DOM for complete encapsulation
|
|
11
|
+
|
|
12
|
+
const DEFAULT_UUID = '019b4c51-7701-726a-8f59-e89a854682f3';
|
|
13
|
+
|
|
14
|
+
function getBaseUrl() {
|
|
15
|
+
const hostname = window.location.hostname;
|
|
16
|
+
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
|
17
|
+
console.log('🏠 Local development detected');
|
|
18
|
+
return 'http://localhost:3000';
|
|
19
|
+
}
|
|
20
|
+
console.log('🌐 Using production URL');
|
|
21
|
+
return 'https://lookalike.com';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const BASE_URL = getBaseUrl();
|
|
25
|
+
|
|
26
|
+
class LookalikeWidget extends HTMLElement {
|
|
27
|
+
constructor() {
|
|
28
|
+
super();
|
|
29
|
+
this.config = {
|
|
30
|
+
storyfileUuid: null,
|
|
31
|
+
primaryColor: '#2563eb',
|
|
32
|
+
enableText: true,
|
|
33
|
+
enableVideo: true,
|
|
34
|
+
enableAudio: true
|
|
35
|
+
};
|
|
36
|
+
this.currentIframe = null;
|
|
37
|
+
this.shadow = null;
|
|
38
|
+
this.messageHandler = null;
|
|
39
|
+
this.isInWixEmbed = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
connectedCallback() {
|
|
43
|
+
console.log('🚀 Lookalike Widget initializing...');
|
|
44
|
+
|
|
45
|
+
// Detect if we're in a Wix custom code embed (or similar constrained iframe)
|
|
46
|
+
this.detectWixEmbed();
|
|
47
|
+
|
|
48
|
+
// Create shadow DOM for encapsulation
|
|
49
|
+
this.shadow = this.attachShadow({ mode: 'closed' });
|
|
50
|
+
|
|
51
|
+
// Load configuration from attributes
|
|
52
|
+
this.loadConfiguration();
|
|
53
|
+
|
|
54
|
+
// Initialize as live widget
|
|
55
|
+
this.initLive();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
detectWixEmbed() {
|
|
59
|
+
try {
|
|
60
|
+
// Check if we're in an iframe and can't access parent
|
|
61
|
+
const inIframe = window !== window.parent;
|
|
62
|
+
|
|
63
|
+
// Check for Wix-specific indicators
|
|
64
|
+
const hasWixIndicators =
|
|
65
|
+
window.location.hostname.includes('wix.com') ||
|
|
66
|
+
window.location.hostname.includes('wixsite.com') ||
|
|
67
|
+
window.location.search.includes('wix') ||
|
|
68
|
+
document.referrer.includes('wix') ||
|
|
69
|
+
(inIframe && (
|
|
70
|
+
window.innerWidth < 500 || // Typical small embed size
|
|
71
|
+
window.innerHeight < 300
|
|
72
|
+
));
|
|
73
|
+
|
|
74
|
+
this.isInWixEmbed = inIframe && hasWixIndicators;
|
|
75
|
+
|
|
76
|
+
if (this.isInWixEmbed) {
|
|
77
|
+
console.log('🔍 Detected Wix embed environment - using full-screen overlay mode');
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
// If we can't access parent due to cross-origin, assume we're in an embed
|
|
81
|
+
this.isInWixEmbed = true;
|
|
82
|
+
console.log('🔍 Cross-origin iframe detected - using full-screen overlay mode');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
loadConfiguration() {
|
|
87
|
+
// Read configuration from HTML attributes
|
|
88
|
+
if (this.hasAttribute('uuid')) {
|
|
89
|
+
this.config.storyfileUuid = this.getAttribute('uuid');
|
|
90
|
+
}
|
|
91
|
+
if (this.hasAttribute('color')) {
|
|
92
|
+
this.config.primaryColor = this.getAttribute('color');
|
|
93
|
+
}
|
|
94
|
+
if (this.hasAttribute('text')) {
|
|
95
|
+
this.config.enableText = this.getAttribute('text') !== 'false';
|
|
96
|
+
}
|
|
97
|
+
if (this.hasAttribute('video')) {
|
|
98
|
+
this.config.enableVideo = this.getAttribute('video') !== 'false';
|
|
99
|
+
}
|
|
100
|
+
if (this.hasAttribute('audio')) {
|
|
101
|
+
this.config.enableAudio = this.getAttribute('audio') !== 'false';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Use default UUID if none provided
|
|
105
|
+
if (!this.config.storyfileUuid) {
|
|
106
|
+
this.config.storyfileUuid = DEFAULT_UUID;
|
|
107
|
+
console.log('🔧 Using default UUID:', DEFAULT_UUID);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log('📥 Loaded config from attributes:', this.config);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
static get observedAttributes() {
|
|
114
|
+
return ['uuid', 'color', 'text', 'video', 'audio'];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
118
|
+
if (oldValue !== newValue && this.shadow) {
|
|
119
|
+
console.log(`🔄 Attribute changed: ${name} = ${newValue}`);
|
|
120
|
+
|
|
121
|
+
switch (name) {
|
|
122
|
+
case 'uuid':
|
|
123
|
+
this.config.storyfileUuid = newValue;
|
|
124
|
+
break;
|
|
125
|
+
case 'color':
|
|
126
|
+
this.config.primaryColor = newValue;
|
|
127
|
+
break;
|
|
128
|
+
case 'text':
|
|
129
|
+
this.config.enableText = newValue !== 'false';
|
|
130
|
+
break;
|
|
131
|
+
case 'video':
|
|
132
|
+
this.config.enableVideo = newValue !== 'false';
|
|
133
|
+
break;
|
|
134
|
+
case 'audio':
|
|
135
|
+
this.config.enableAudio = newValue !== 'false';
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log(`🔄 Updated config after attribute change:`, this.config);
|
|
140
|
+
|
|
141
|
+
// Reinitialize widget
|
|
142
|
+
if (this.isConnected) {
|
|
143
|
+
this.shadow.innerHTML = '';
|
|
144
|
+
this.initLive();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
initLive() {
|
|
150
|
+
console.log('🚀 Initializing live widget with config:', this.config);
|
|
151
|
+
|
|
152
|
+
if (this.isInWixEmbed) {
|
|
153
|
+
this.initWixEmbedMode();
|
|
154
|
+
} else {
|
|
155
|
+
this.initStandardMode();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.injectShadowContent();
|
|
159
|
+
this.injectIframe();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
initWixEmbedMode() {
|
|
163
|
+
console.log('📱 Initializing Wix embed mode with full-screen overlay');
|
|
164
|
+
|
|
165
|
+
// Make the host element a full-screen transparent overlay
|
|
166
|
+
this.style.cssText = `
|
|
167
|
+
position: fixed;
|
|
168
|
+
top: 0;
|
|
169
|
+
left: 0;
|
|
170
|
+
width: 100vw;
|
|
171
|
+
height: 100vh;
|
|
172
|
+
border: 0;
|
|
173
|
+
z-index: 9999;
|
|
174
|
+
background: transparent;
|
|
175
|
+
pointer-events: none;
|
|
176
|
+
display: block;
|
|
177
|
+
`;
|
|
178
|
+
|
|
179
|
+
// Add collapsed class initially
|
|
180
|
+
this.className = 'collapsed wix-embed';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
initStandardMode() {
|
|
184
|
+
console.log('💻 Initializing standard mode');
|
|
185
|
+
|
|
186
|
+
// Set host element positioning and initial size (original behavior)
|
|
187
|
+
this.style.cssText = `
|
|
188
|
+
position: fixed;
|
|
189
|
+
bottom: 16px;
|
|
190
|
+
right: 16px;
|
|
191
|
+
width: 380px;
|
|
192
|
+
height: 60px;
|
|
193
|
+
max-width: calc(100vw - 32px);
|
|
194
|
+
border: 0;
|
|
195
|
+
border-radius: 16px;
|
|
196
|
+
z-index: 9999;
|
|
197
|
+
box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25);
|
|
198
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
199
|
+
background: transparent;
|
|
200
|
+
display: block;
|
|
201
|
+
`;
|
|
202
|
+
|
|
203
|
+
// Add collapsed class initially
|
|
204
|
+
this.className = 'collapsed';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
injectShadowContent() {
|
|
208
|
+
// Create styles within shadow DOM
|
|
209
|
+
const style = document.createElement('style');
|
|
210
|
+
style.textContent = `
|
|
211
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
|
|
212
|
+
|
|
213
|
+
:host {
|
|
214
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* Standard mode styles */
|
|
218
|
+
:host(.collapsed:not(.wix-embed)) {
|
|
219
|
+
height: 60px !important;
|
|
220
|
+
max-height: 60px !important;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
:host(.expanded:not(.wix-embed)) {
|
|
224
|
+
height: 520px;
|
|
225
|
+
max-height: calc(100vh - 32px);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* Wix embed mode: container for positioned widget */
|
|
229
|
+
.wix-widget-container {
|
|
230
|
+
position: absolute;
|
|
231
|
+
bottom: 16px;
|
|
232
|
+
right: 16px;
|
|
233
|
+
width: 380px;
|
|
234
|
+
height: 60px;
|
|
235
|
+
max-width: calc(100vw - 32px);
|
|
236
|
+
border-radius: 16px;
|
|
237
|
+
box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25);
|
|
238
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
239
|
+
background: transparent;
|
|
240
|
+
pointer-events: auto;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
:host(.wix-embed.collapsed) .wix-widget-container {
|
|
244
|
+
height: 60px;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
:host(.wix-embed.expanded) .wix-widget-container {
|
|
248
|
+
height: 520px;
|
|
249
|
+
max-height: calc(100vh - 32px);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@media (max-width: 639px) {
|
|
253
|
+
:host(.expanded:not(.wix-embed)) {
|
|
254
|
+
width: 100vw;
|
|
255
|
+
height: 100vh;
|
|
256
|
+
max-width: none;
|
|
257
|
+
max-height: none;
|
|
258
|
+
bottom: 0;
|
|
259
|
+
right: 0;
|
|
260
|
+
border-radius: 0;
|
|
261
|
+
box-shadow: none;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
:host(:not(.wix-embed)) {
|
|
265
|
+
width: calc(100vw - 16px);
|
|
266
|
+
right: 8px;
|
|
267
|
+
bottom: 8px;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
:host(.collapsed:not(.wix-embed)) {
|
|
271
|
+
height: 60px !important;
|
|
272
|
+
max-height: 60px !important;
|
|
273
|
+
width: calc(100vw - 16px);
|
|
274
|
+
right: 8px;
|
|
275
|
+
bottom: 8px;
|
|
276
|
+
border-radius: 16px;
|
|
277
|
+
box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* Wix embed mobile styles */
|
|
281
|
+
:host(.wix-embed.expanded) .wix-widget-container {
|
|
282
|
+
width: 100vw;
|
|
283
|
+
height: 100vh;
|
|
284
|
+
max-width: none;
|
|
285
|
+
max-height: none;
|
|
286
|
+
bottom: 0;
|
|
287
|
+
right: 0;
|
|
288
|
+
border-radius: 0;
|
|
289
|
+
box-shadow: none;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.wix-widget-container {
|
|
293
|
+
width: calc(100vw - 16px);
|
|
294
|
+
right: 8px;
|
|
295
|
+
bottom: 8px;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
:host(.wix-embed.collapsed) .wix-widget-container {
|
|
299
|
+
height: 60px !important;
|
|
300
|
+
width: calc(100vw - 16px);
|
|
301
|
+
right: 8px;
|
|
302
|
+
bottom: 8px;
|
|
303
|
+
border-radius: 16px;
|
|
304
|
+
box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.loading {
|
|
309
|
+
background: #f3f4f6;
|
|
310
|
+
display: flex;
|
|
311
|
+
align-items: center;
|
|
312
|
+
justify-content: center;
|
|
313
|
+
width: 100%;
|
|
314
|
+
height: 100%;
|
|
315
|
+
border-radius: inherit;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.loading::after {
|
|
319
|
+
content: 'Loading chat...';
|
|
320
|
+
color: #6b7280;
|
|
321
|
+
font-size: 14px;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.error-container {
|
|
325
|
+
width: 100%;
|
|
326
|
+
height: 100%;
|
|
327
|
+
display: flex;
|
|
328
|
+
align-items: center;
|
|
329
|
+
justify-content: center;
|
|
330
|
+
flex-direction: column;
|
|
331
|
+
background: #f9fafb;
|
|
332
|
+
border-radius: inherit;
|
|
333
|
+
border: 2px dashed #d1d5db;
|
|
334
|
+
color: #6b7280;
|
|
335
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
336
|
+
text-align: center;
|
|
337
|
+
padding: 20px;
|
|
338
|
+
box-sizing: border-box;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.error-icon {
|
|
342
|
+
font-size: 24px;
|
|
343
|
+
margin-bottom: 8px;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.error-title {
|
|
347
|
+
font-size: 14px;
|
|
348
|
+
font-weight: 500;
|
|
349
|
+
margin-bottom: 4px;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.error-message {
|
|
353
|
+
font-size: 12px;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.error-uuid {
|
|
357
|
+
font-size: 11px;
|
|
358
|
+
margin-top: 8px;
|
|
359
|
+
opacity: 0.7;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
iframe {
|
|
363
|
+
width: 100%;
|
|
364
|
+
height: 100%;
|
|
365
|
+
border: 0;
|
|
366
|
+
border-radius: inherit;
|
|
367
|
+
}
|
|
368
|
+
`;
|
|
369
|
+
|
|
370
|
+
this.shadow.appendChild(style);
|
|
371
|
+
|
|
372
|
+
// Create container for iframe
|
|
373
|
+
const container = document.createElement('div');
|
|
374
|
+
|
|
375
|
+
if (this.isInWixEmbed) {
|
|
376
|
+
// In Wix embed mode, create a positioned container within the full-screen overlay
|
|
377
|
+
container.className = 'wix-widget-container';
|
|
378
|
+
} else {
|
|
379
|
+
// In standard mode, container fills the host element
|
|
380
|
+
container.style.cssText = `
|
|
381
|
+
width: 100%;
|
|
382
|
+
height: 100%;
|
|
383
|
+
border-radius: inherit;
|
|
384
|
+
`;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
this.shadow.appendChild(container);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
buildIframeSrc() {
|
|
391
|
+
if (!this.config.storyfileUuid) {
|
|
392
|
+
console.error('❌ No storyfile UUID configured');
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const url = new URL(`/${this.config.storyfileUuid}/chat`, BASE_URL);
|
|
397
|
+
url.searchParams.set('embed', '');
|
|
398
|
+
url.searchParams.set('variant', 'floating');
|
|
399
|
+
url.searchParams.set('anchor', 'bottom-right');
|
|
400
|
+
|
|
401
|
+
// Add enabled modes
|
|
402
|
+
const modes = [];
|
|
403
|
+
if (this.config.enableText) modes.push('text');
|
|
404
|
+
if (this.config.enableAudio) modes.push('audio');
|
|
405
|
+
if (this.config.enableVideo) modes.push('video');
|
|
406
|
+
|
|
407
|
+
if (modes.length > 0 && modes.length < 3) {
|
|
408
|
+
url.searchParams.set('modes', modes.join(','));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Add theme
|
|
412
|
+
if (this.config.primaryColor && this.config.primaryColor !== '#2563eb') {
|
|
413
|
+
url.searchParams.set('theme', `primary:${this.config.primaryColor}`);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
console.log('🔗 Built iframe URL:', url.toString());
|
|
417
|
+
return url.toString();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
injectIframe() {
|
|
421
|
+
const container = this.shadow.querySelector(this.isInWixEmbed ? '.wix-widget-container' : 'div:last-child');
|
|
422
|
+
|
|
423
|
+
// Remove existing iframe if present
|
|
424
|
+
const existingIframe = container.querySelector('iframe');
|
|
425
|
+
if (existingIframe) existingIframe.remove();
|
|
426
|
+
|
|
427
|
+
const iframeSrc = this.buildIframeSrc();
|
|
428
|
+
if (!iframeSrc) {
|
|
429
|
+
this.showConfigurationError(container);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const iframe = document.createElement('iframe');
|
|
434
|
+
iframe.className = 'loading';
|
|
435
|
+
iframe.src = iframeSrc;
|
|
436
|
+
iframe.allow = 'microphone; camera';
|
|
437
|
+
iframe.loading = 'lazy';
|
|
438
|
+
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups allow-presentation');
|
|
439
|
+
|
|
440
|
+
iframe.onerror = () => {
|
|
441
|
+
console.error('❌ Failed to load widget iframe');
|
|
442
|
+
this.showLoadingError(container);
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
iframe.onload = () => {
|
|
446
|
+
console.log('✅ Iframe loaded successfully');
|
|
447
|
+
iframe.classList.remove('loading');
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
this.currentIframe = iframe;
|
|
451
|
+
this.setupMessageHandling(iframe);
|
|
452
|
+
container.appendChild(iframe);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
setupMessageHandling(iframe) {
|
|
456
|
+
// Clean up existing handler
|
|
457
|
+
if (this.messageHandler) {
|
|
458
|
+
window.removeEventListener('message', this.messageHandler);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
this.messageHandler = (e) => {
|
|
462
|
+
if (e.source !== iframe.contentWindow) return;
|
|
463
|
+
|
|
464
|
+
if (e.data?.type === 'lookalike-widget-ready') {
|
|
465
|
+
console.log('🎉 Widget ready, setting up communication');
|
|
466
|
+
|
|
467
|
+
const channel = new MessageChannel();
|
|
468
|
+
channel.port1.onmessage = (ev) => {
|
|
469
|
+
console.log('📏 Received resize message:', ev.data);
|
|
470
|
+
if (ev.data?.type === 'lookalike-widget-resize') {
|
|
471
|
+
const newClass = ev.data.collapsed ? 'collapsed' : 'expanded';
|
|
472
|
+
console.log(`📏 Widget resize: ${this.className} → ${newClass}`);
|
|
473
|
+
this.className = newClass;
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
iframe.contentWindow.postMessage(
|
|
478
|
+
{ type: 'lookalike-init-channel' },
|
|
479
|
+
BASE_URL,
|
|
480
|
+
[channel.port2]
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
window.addEventListener('message', this.messageHandler);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
showConfigurationError(container) {
|
|
489
|
+
container.innerHTML = `
|
|
490
|
+
<div class="error-container">
|
|
491
|
+
<div class="error-icon">⚠️</div>
|
|
492
|
+
<div class="error-title">Configuration Error</div>
|
|
493
|
+
<div class="error-message">No UUID configured. Please add a uuid attribute.</div>
|
|
494
|
+
<div class="error-uuid">Expected: <lookalike-widget uuid="your-uuid"></div>
|
|
495
|
+
</div>
|
|
496
|
+
`;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
showLoadingError(container) {
|
|
500
|
+
container.innerHTML = `
|
|
501
|
+
<div class="error-container">
|
|
502
|
+
<div class="error-icon">⚠️</div>
|
|
503
|
+
<div class="error-title">Widget Loading Error</div>
|
|
504
|
+
<div class="error-message">Check console for details</div>
|
|
505
|
+
<div class="error-uuid">UUID: ${this.config.storyfileUuid}</div>
|
|
506
|
+
</div>
|
|
507
|
+
`;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
disconnectedCallback() {
|
|
511
|
+
if (this.messageHandler) {
|
|
512
|
+
window.removeEventListener('message', this.messageHandler);
|
|
513
|
+
this.messageHandler = null;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Register the custom element
|
|
519
|
+
customElements.define('lookalike-widget', LookalikeWidget);
|
|
520
|
+
|
|
521
|
+
// Export for module systems
|
|
522
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
523
|
+
module.exports = LookalikeWidget;
|
|
524
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Lookalike Widget v1.1.0
|
|
3
|
+
* https://lookalike.com
|
|
4
|
+
*
|
|
5
|
+
* Copy/paste embed widget for conversational AI avatars
|
|
6
|
+
* Usage: <lookalike-widget uuid="your-uuid"></lookalike-widget>
|
|
7
|
+
*/
|
|
8
|
+
const DEFAULT_UUID="019b4c51-7701-726a-8f59-e89a854682f3";function getBaseUrl(){const e=window.location.hostname;return"localhost"===e||"127.0.0.1"===e?(console.log("🏠 Local development detected"),"http://localhost:3000"):(console.log("🌐 Using production URL"),"https://lookalike.com")}const BASE_URL=getBaseUrl();class LookalikeWidget extends HTMLElement{constructor(){super(),this.config={storyfileUuid:null,primaryColor:"#2563eb",enableText:!0,enableVideo:!0,enableAudio:!0},this.currentIframe=null,this.shadow=null,this.messageHandler=null,this.isInWixEmbed=!1}connectedCallback(){console.log("🚀 Lookalike Widget initializing..."),this.detectWixEmbed(),this.shadow=this.attachShadow({mode:"closed"}),this.loadConfiguration(),this.initLive()}detectWixEmbed(){try{const e=window!==window.parent,n=window.location.hostname.includes("wix.com")||window.location.hostname.includes("wixsite.com")||window.location.search.includes("wix")||document.referrer.includes("wix")||e&&(window.innerWidth<500||window.innerHeight<300);this.isInWixEmbed=e&&n,this.isInWixEmbed&&console.log("🔍 Detected Wix embed environment - using full-screen overlay mode")}catch(e){this.isInWixEmbed=!0,console.log("🔍 Cross-origin iframe detected - using full-screen overlay mode")}}loadConfiguration(){this.hasAttribute("uuid")&&(this.config.storyfileUuid=this.getAttribute("uuid")),this.hasAttribute("color")&&(this.config.primaryColor=this.getAttribute("color")),this.hasAttribute("text")&&(this.config.enableText="false"!==this.getAttribute("text")),this.hasAttribute("video")&&(this.config.enableVideo="false"!==this.getAttribute("video")),this.hasAttribute("audio")&&(this.config.enableAudio="false"!==this.getAttribute("audio")),this.config.storyfileUuid||(this.config.storyfileUuid=DEFAULT_UUID,console.log("🔧 Using default UUID:",DEFAULT_UUID)),console.log("📥 Loaded config from attributes:",this.config)}static get observedAttributes(){return["uuid","color","text","video","audio"]}attributeChangedCallback(e,n,i){if(n!==i&&this.shadow){switch(console.log(`🔄 Attribute changed: ${e} = ${i}`),e){case"uuid":this.config.storyfileUuid=i;break;case"color":this.config.primaryColor=i;break;case"text":this.config.enableText="false"!==i;break;case"video":this.config.enableVideo="false"!==i;break;case"audio":this.config.enableAudio="false"!==i}console.log("🔄 Updated config after attribute change:",this.config),this.isConnected&&(this.shadow.innerHTML="",this.initLive())}}initLive(){console.log("🚀 Initializing live widget with config:",this.config),this.isInWixEmbed?this.initWixEmbedMode():this.initStandardMode(),this.injectShadowContent(),this.injectIframe()}initWixEmbedMode(){console.log("📱 Initializing Wix embed mode with full-screen overlay"),this.style.cssText="\n position: fixed;\n top: 0;\n left: 0;\n width: 100vw;\n height: 100vh;\n border: 0;\n z-index: 9999;\n background: transparent;\n pointer-events: none;\n display: block;\n ",this.className="collapsed wix-embed"}initStandardMode(){console.log("💻 Initializing standard mode"),this.style.cssText="\n position: fixed;\n bottom: 16px;\n right: 16px;\n width: 380px;\n height: 60px;\n max-width: calc(100vw - 32px);\n border: 0;\n border-radius: 16px;\n z-index: 9999;\n box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25);\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n background: transparent;\n display: block;\n ",this.className="collapsed"}injectShadowContent(){const e=document.createElement("style");e.textContent="\n @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');\n \n :host {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n }\n\n /* Standard mode styles */\n :host(.collapsed:not(.wix-embed)) {\n height: 60px !important;\n max-height: 60px !important;\n }\n\n :host(.expanded:not(.wix-embed)) {\n height: 520px;\n max-height: calc(100vh - 32px);\n }\n\n /* Wix embed mode: container for positioned widget */\n .wix-widget-container {\n position: absolute;\n bottom: 16px;\n right: 16px;\n width: 380px;\n height: 60px;\n max-width: calc(100vw - 32px);\n border-radius: 16px;\n box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25);\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n background: transparent;\n pointer-events: auto;\n }\n\n :host(.wix-embed.collapsed) .wix-widget-container {\n height: 60px;\n }\n\n :host(.wix-embed.expanded) .wix-widget-container {\n height: 520px;\n max-height: calc(100vh - 32px);\n }\n\n @media (max-width: 639px) {\n :host(.expanded:not(.wix-embed)) {\n width: 100vw;\n height: 100vh;\n max-width: none;\n max-height: none;\n bottom: 0;\n right: 0;\n border-radius: 0;\n box-shadow: none;\n }\n \n :host(:not(.wix-embed)) {\n width: calc(100vw - 16px);\n right: 8px;\n bottom: 8px;\n }\n \n :host(.collapsed:not(.wix-embed)) {\n height: 60px !important;\n max-height: 60px !important;\n width: calc(100vw - 16px);\n right: 8px;\n bottom: 8px;\n border-radius: 16px;\n box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25);\n }\n\n /* Wix embed mobile styles */\n :host(.wix-embed.expanded) .wix-widget-container {\n width: 100vw;\n height: 100vh;\n max-width: none;\n max-height: none;\n bottom: 0;\n right: 0;\n border-radius: 0;\n box-shadow: none;\n }\n \n .wix-widget-container {\n width: calc(100vw - 16px);\n right: 8px;\n bottom: 8px;\n }\n \n :host(.wix-embed.collapsed) .wix-widget-container {\n height: 60px !important;\n width: calc(100vw - 16px);\n right: 8px;\n bottom: 8px;\n border-radius: 16px;\n box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25);\n }\n }\n \n .loading {\n background: #f3f4f6;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n border-radius: inherit;\n }\n \n .loading::after {\n content: 'Loading chat...';\n color: #6b7280;\n font-size: 14px;\n }\n\n .error-container {\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-direction: column;\n background: #f9fafb;\n border-radius: inherit;\n border: 2px dashed #d1d5db;\n color: #6b7280;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n text-align: center;\n padding: 20px;\n box-sizing: border-box;\n }\n\n .error-icon {\n font-size: 24px;\n margin-bottom: 8px;\n }\n\n .error-title {\n font-size: 14px;\n font-weight: 500;\n margin-bottom: 4px;\n }\n\n .error-message {\n font-size: 12px;\n }\n\n .error-uuid {\n font-size: 11px;\n margin-top: 8px;\n opacity: 0.7;\n }\n\n iframe {\n width: 100%;\n height: 100%;\n border: 0;\n border-radius: inherit;\n }\n ",this.shadow.appendChild(e);const n=document.createElement("div");this.isInWixEmbed?n.className="wix-widget-container":n.style.cssText="\n width: 100%;\n height: 100%;\n border-radius: inherit;\n ",this.shadow.appendChild(n)}buildIframeSrc(){if(!this.config.storyfileUuid)return console.error("❌ No storyfile UUID configured"),null;const e=new URL(`/${this.config.storyfileUuid}/chat`,BASE_URL);e.searchParams.set("embed",""),e.searchParams.set("variant","floating"),e.searchParams.set("anchor","bottom-right");const n=[];return this.config.enableText&&n.push("text"),this.config.enableAudio&&n.push("audio"),this.config.enableVideo&&n.push("video"),n.length>0&&n.length<3&&e.searchParams.set("modes",n.join(",")),this.config.primaryColor&&"#2563eb"!==this.config.primaryColor&&e.searchParams.set("theme",`primary:${this.config.primaryColor}`),console.log("🔗 Built iframe URL:",e.toString()),e.toString()}injectIframe(){const e=this.shadow.querySelector(this.isInWixEmbed?".wix-widget-container":"div:last-child"),n=e.querySelector("iframe");n&&n.remove();const i=this.buildIframeSrc();if(!i)return void this.showConfigurationError(e);const t=document.createElement("iframe");t.className="loading",t.src=i,t.allow="microphone; camera",t.loading="lazy",t.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-presentation"),t.onerror=()=>{console.error("❌ Failed to load widget iframe"),this.showLoadingError(e)},t.onload=()=>{console.log("✅ Iframe loaded successfully"),t.classList.remove("loading")},this.currentIframe=t,this.setupMessageHandling(t),e.appendChild(t)}setupMessageHandling(e){this.messageHandler&&window.removeEventListener("message",this.messageHandler),this.messageHandler=n=>{if(n.source===e.contentWindow&&"lookalike-widget-ready"===n.data?.type){console.log("🎉 Widget ready, setting up communication");const n=new MessageChannel;n.port1.onmessage=e=>{if(console.log("📏 Received resize message:",e.data),"lookalike-widget-resize"===e.data?.type){const n=e.data.collapsed?"collapsed":"expanded";console.log(`📏 Widget resize: ${this.className} → ${n}`),this.className=n}},e.contentWindow.postMessage({type:"lookalike-init-channel"},BASE_URL,[n.port2])}},window.addEventListener("message",this.messageHandler)}showConfigurationError(e){e.innerHTML='\n <div class="error-container">\n <div class="error-icon">⚠️</div>\n <div class="error-title">Configuration Error</div>\n <div class="error-message">No UUID configured. Please add a uuid attribute.</div>\n <div class="error-uuid">Expected: <lookalike-widget uuid="your-uuid"></div>\n </div>\n '}showLoadingError(e){e.innerHTML=`\n <div class="error-container">\n <div class="error-icon">⚠️</div>\n <div class="error-title">Widget Loading Error</div>\n <div class="error-message">Check console for details</div>\n <div class="error-uuid">UUID: ${this.config.storyfileUuid}</div>\n </div>\n `}disconnectedCallback(){this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null)}}customElements.define("lookalike-widget",LookalikeWidget),"undefined"!=typeof module&&module.exports&&(module.exports=LookalikeWidget);
|
package/package.json
CHANGED
|
@@ -1,67 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lookalike/widget",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Lookalike conversational AI widget for easy embedding",
|
|
5
|
+
"main": "dist/lookalike-widget.js",
|
|
6
|
+
"module": "dist/lookalike-widget.js",
|
|
7
|
+
"unpkg": "dist/lookalike-widget.min.js",
|
|
8
|
+
"jsdelivr": "dist/lookalike-widget.min.js",
|
|
5
9
|
"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
10
|
"files": [
|
|
19
|
-
"dist"
|
|
11
|
+
"dist/",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
20
14
|
],
|
|
21
15
|
"scripts": {
|
|
22
|
-
"build": "
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"dev": "vite build --watch",
|
|
27
|
-
"test": "node dev-server.js",
|
|
28
|
-
"test:build": "node dev-server.js --build",
|
|
29
|
-
"version:patch": "npm version patch",
|
|
30
|
-
"version:minor": "npm version minor",
|
|
31
|
-
"version:major": "npm version major",
|
|
32
|
-
"version:beta": "npm version prerelease --preid=beta",
|
|
33
|
-
"prepublishOnly": "npm run build:production",
|
|
34
|
-
"publish:beta": "npm publish --tag beta",
|
|
35
|
-
"publish:latest": "npm publish --tag latest"
|
|
36
|
-
},
|
|
37
|
-
"devDependencies": {
|
|
38
|
-
"typescript": "^5.0.0",
|
|
39
|
-
"vite": "^5.0.0",
|
|
40
|
-
"vite-plugin-dts": "^3.0.0"
|
|
16
|
+
"build": "node build.js",
|
|
17
|
+
"prepublishOnly": "npm run build",
|
|
18
|
+
"test": "node -e \"console.log('No tests yet')\"",
|
|
19
|
+
"serve": "npx http-server dist -p 8080 -o"
|
|
41
20
|
},
|
|
42
21
|
"keywords": [
|
|
43
|
-
"
|
|
22
|
+
"ai",
|
|
44
23
|
"widget",
|
|
45
24
|
"chat",
|
|
46
|
-
"ai",
|
|
47
|
-
"conversational",
|
|
48
|
-
"wix",
|
|
49
25
|
"embed",
|
|
50
|
-
"
|
|
26
|
+
"conversational-ai",
|
|
27
|
+
"web-component",
|
|
28
|
+
"lookalike",
|
|
29
|
+
"avatar",
|
|
30
|
+
"shadow-dom"
|
|
51
31
|
],
|
|
52
|
-
"author": "
|
|
32
|
+
"author": "Lookalike <support@lookalike.com>",
|
|
33
|
+
"license": "MIT",
|
|
53
34
|
"repository": {
|
|
54
35
|
"type": "git",
|
|
55
|
-
"url": "https://github.com/
|
|
56
|
-
"directory": "plugins/public/widget"
|
|
36
|
+
"url": "https://github.com/lookalike/widget"
|
|
57
37
|
},
|
|
58
38
|
"homepage": "https://lookalike.com",
|
|
59
39
|
"bugs": {
|
|
60
|
-
"url": "https://github.com/
|
|
40
|
+
"url": "https://github.com/lookalike/widget/issues"
|
|
61
41
|
},
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=14"
|
|
44
|
+
},
|
|
45
|
+
"browserslist": [
|
|
46
|
+
"> 1%",
|
|
47
|
+
"last 2 versions",
|
|
48
|
+
"not dead"
|
|
49
|
+
],
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"terser": "^5.16.0",
|
|
52
|
+
"http-server": "^14.1.1"
|
|
66
53
|
}
|
|
67
|
-
}
|
|
54
|
+
}
|
package/dist/index.d.ts
DELETED
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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/types.d.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
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
|
package/dist/types.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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"}
|
package/dist/umd.d.ts
DELETED
package/dist/umd.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"umd.d.ts","sourceRoot":"","sources":["../src/umd.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAG1C,eAAe,eAAe,CAAA"}
|
package/dist/widget.d.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
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
|
package/dist/widget.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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"}
|
package/dist/widget.umd.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
(function(n,s){typeof exports=="object"&&typeof module<"u"?module.exports=s():typeof define=="function"&&define.amd?define(s):(n=typeof globalThis<"u"?globalThis:n||self,n.LookalikeWidget=s())})(this,function(){"use strict";const n="https://lookalike.com",s={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??n,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 a,o,r,l,h,d,c,m,f,g,p,u,S;if(console.log("[Widget] Received message:",e.data,"from:",e.source===((a=this.iframe)==null?void 0:a.contentWindow)?"iframe":"unknown"),this.iframe&&e.source!==this.iframe.contentWindow){console.log("[Widget] Ignoring message - not from iframe");return}const{type:t,...i}=e.data||{};switch(t){case s.WIDGET_READY:console.log("[Widget] WIDGET_READY event received!"),this.initMessageChannel(),(r=(o=this.events).onReady)==null||r.call(o);break;case s.WIDGET_RESIZE:console.log("[Widget] Received resize event:",{collapsed:i.collapsed,expanded:!i.collapsed}),this.expanded=!i.collapsed,this.updateIframeSize(i.collapsed),(h=(l=this.events).onResize)==null||h.call(l,i.collapsed);break;case s.SESSION_START:(c=(d=this.events).onSessionStart)==null||c.call(d,i.mode);break;case s.SESSION_END:(f=(m=this.events).onSessionEnd)==null||f.call(m);break;case s.MESSAGE:(p=(g=this.events).onMessage)==null||p.call(g,i.role,i.content);break;case s.ERROR:(S=(u=this.events).onError)==null||S.call(u,i.code,i.message);break}}initMessageChannel(){var t;if(!((t=this.iframe)!=null&&t.contentWindow))return;const e=new MessageChannel;this.port=e.port1,this.port.onmessage=i=>{var r,l,h,d,c,m,f,g,p,u;console.log("[Widget] Port message received:",i.data);const{type:a,...o}=i.data||{};switch(a){case s.WIDGET_RESIZE:console.log("[Widget] Received resize event:",{collapsed:o.collapsed,expanded:!o.collapsed}),this.expanded=!o.collapsed,this.updateIframeSize(o.collapsed),(l=(r=this.events).onResize)==null||l.call(r,o.collapsed);break;case s.SESSION_START:(d=(h=this.events).onSessionStart)==null||d.call(h,o.mode);break;case s.SESSION_END:(m=(c=this.events).onSessionEnd)==null||m.call(c);break;case s.MESSAGE:(g=(f=this.events).onMessage)==null||g.call(f,o.role,o.content);break;case s.ERROR:(u=(p=this.events).onError)==null||u.call(p,o.code,o.message);break}},this.iframe.contentWindow.postMessage({type:s.INIT_CHANNEL},"*",[e.port2])}sendCommand(e,t){var a;const i={type:s.COMMAND,action:e,payload:t};this.port?this.port.postMessage(i):(a=this.iframe)!=null&&a.contentWindow&&this.iframe.contentWindow.postMessage(i,"*")}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)")}}return x});
|
|
18
|
-
//# sourceMappingURL=widget.umd.js.map
|
package/dist/widget.umd.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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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"}
|