convai-voice-widget 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +150 -0
- package/dist/widget.js +327 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# AvinAI ConvAI Widget Embed
|
|
2
|
+
|
|
3
|
+
Embeddable voice conversation widget for AvinAI agents. Add AI-powered voice conversations to any website with just 2 lines of code.
|
|
4
|
+
|
|
5
|
+
## đ Quick Start
|
|
6
|
+
|
|
7
|
+
Add the widget to your website:
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<!-- Step 1: Add the widget element with your agent ID -->
|
|
11
|
+
<avin-convai id="YOUR_AGENT_ID"></avin-convai>
|
|
12
|
+
|
|
13
|
+
<!-- Step 2: Load the widget script -->
|
|
14
|
+
<script src="https://unpkg.com/@avinai/convai-widget-embed" async type="text/javascript"></script>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
That's it! The voice assistant will appear as a floating button on your website.
|
|
18
|
+
|
|
19
|
+
## đ Features
|
|
20
|
+
|
|
21
|
+
- â
**Easy Integration** - Just 2 lines of code
|
|
22
|
+
- â
**Voice Conversations** - WebRTC-powered real-time voice
|
|
23
|
+
- â
**Customizable** - Multiple configuration options
|
|
24
|
+
- â
**Responsive** - Works on desktop and mobile
|
|
25
|
+
- â
**Zero Dependencies** - Self-contained web component
|
|
26
|
+
|
|
27
|
+
## đ¨ Configuration
|
|
28
|
+
|
|
29
|
+
Customize the widget with attributes:
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<avin-convai
|
|
33
|
+
id="YOUR_AGENT_ID"
|
|
34
|
+
position="bottom-right"
|
|
35
|
+
primary-color="#6366f1"
|
|
36
|
+
server-url="https://your-server.com"
|
|
37
|
+
></avin-convai>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Available Attributes
|
|
41
|
+
|
|
42
|
+
| Attribute | Type | Default | Description |
|
|
43
|
+
|-----------|------|---------|-------------|
|
|
44
|
+
| `id` | string | *required* | Your AvinAI agent ID |
|
|
45
|
+
| `position` | string | `bottom-right` | Widget position: `bottom-right`, `bottom-left`, `top-right`, `top-left` |
|
|
46
|
+
| `primary-color` | string | `#6366f1` | Primary color for the widget (hex format) |
|
|
47
|
+
| `server-url` | string | auto | Custom server URL for API calls |
|
|
48
|
+
|
|
49
|
+
## đ Examples
|
|
50
|
+
|
|
51
|
+
### Basic Usage
|
|
52
|
+
```html
|
|
53
|
+
<avin-convai id="63c510f4-b98a-435c-ad22-aa1e042d1d92"></avin-convai>
|
|
54
|
+
<script src="https://unpkg.com/@avinai/convai-widget-embed" async type="text/javascript"></script>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Custom Position and Color
|
|
58
|
+
```html
|
|
59
|
+
<avin-convai
|
|
60
|
+
id="63c510f4-b98a-435c-ad22-aa1e042d1d92"
|
|
61
|
+
position="bottom-left"
|
|
62
|
+
primary-color="#10b981"
|
|
63
|
+
></avin-convai>
|
|
64
|
+
<script src="https://unpkg.com/@avinai/convai-widget-embed" async type="text/javascript"></script>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### With Custom Server
|
|
68
|
+
```html
|
|
69
|
+
<avin-convai
|
|
70
|
+
id="63c510f4-b98a-435c-ad22-aa1e042d1d92"
|
|
71
|
+
server-url="https://your-custom-server.com"
|
|
72
|
+
></avin-convai>
|
|
73
|
+
<script src="https://unpkg.com/@avinai/convai-widget-embed" async type="text/javascript"></script>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## đ§ Advanced Usage
|
|
77
|
+
|
|
78
|
+
### Using Specific Version
|
|
79
|
+
```html
|
|
80
|
+
<script src="https://unpkg.com/@avinai/convai-widget-embed@1.0.0" async type="text/javascript"></script>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Self-Hosting
|
|
84
|
+
Download and host the widget yourself:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm install @avinai/convai-widget-embed
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Then include from your own server:
|
|
91
|
+
```html
|
|
92
|
+
<script src="/path/to/widget.js" type="module"></script>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## đ Browser Support
|
|
96
|
+
|
|
97
|
+
- Chrome / Edge (latest)
|
|
98
|
+
- Firefox (latest)
|
|
99
|
+
- Safari (latest)
|
|
100
|
+
- Mobile browsers (iOS Safari, Chrome Android)
|
|
101
|
+
|
|
102
|
+
**Requirements:**
|
|
103
|
+
- WebRTC support
|
|
104
|
+
- Microphone access
|
|
105
|
+
|
|
106
|
+
## đą Mobile Support
|
|
107
|
+
|
|
108
|
+
The widget is fully responsive and works on mobile devices. Users will be prompted to grant microphone permission on first use.
|
|
109
|
+
|
|
110
|
+
## đ Security & Privacy
|
|
111
|
+
|
|
112
|
+
- HTTPS required for microphone access
|
|
113
|
+
- User must explicitly grant microphone permission
|
|
114
|
+
- All voice data is transmitted via secure WebRTC
|
|
115
|
+
- No data is stored by the widget
|
|
116
|
+
|
|
117
|
+
## đ ī¸ Development
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# Clone the repository
|
|
121
|
+
git clone https://github.com/Alok7268/hr-dashboard.git
|
|
122
|
+
cd sdk
|
|
123
|
+
|
|
124
|
+
# Install dependencies
|
|
125
|
+
npm install
|
|
126
|
+
|
|
127
|
+
# Start dev server
|
|
128
|
+
npm run dev
|
|
129
|
+
|
|
130
|
+
# Build for production
|
|
131
|
+
npm run build
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## đ License
|
|
135
|
+
|
|
136
|
+
MIT
|
|
137
|
+
|
|
138
|
+
## đ¤ Support
|
|
139
|
+
|
|
140
|
+
For support, please visit [AvinAI Dashboard](https://your-dashboard.com) or contact support@avinai.com
|
|
141
|
+
|
|
142
|
+
## đ Links
|
|
143
|
+
|
|
144
|
+
- [Documentation](https://docs.avinai.com)
|
|
145
|
+
- [Dashboard](https://your-dashboard.com)
|
|
146
|
+
- [GitHub](https://github.com/Alok7268/hr-dashboard)
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
Made with â¤ī¸ by AvinAI
|
package/dist/widget.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
(function(){"use strict";const c={phone:`
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
3
|
+
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/>
|
|
4
|
+
</svg>
|
|
5
|
+
`,close:`
|
|
6
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
7
|
+
<line x1="18" y1="6" x2="6" y2="18"/>
|
|
8
|
+
<line x1="6" y1="6" x2="18" y2="18"/>
|
|
9
|
+
</svg>
|
|
10
|
+
`,mic:`
|
|
11
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
12
|
+
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3z"/>
|
|
13
|
+
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
|
|
14
|
+
<line x1="12" y1="19" x2="12" y2="22"/>
|
|
15
|
+
</svg>
|
|
16
|
+
`,micOff:`
|
|
17
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
18
|
+
<line x1="1" y1="1" x2="23" y2="23"/>
|
|
19
|
+
<path d="M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6"/>
|
|
20
|
+
<path d="M17 16.95A7 7 0 0 1 5 12v-2m14 0v2a7 7 0 0 1-.11 1.23"/>
|
|
21
|
+
<line x1="12" y1="19" x2="12" y2="23"/>
|
|
22
|
+
</svg>
|
|
23
|
+
`,waves:`
|
|
24
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
25
|
+
<path d="M2 6c.6.5 1.2 1 2.5 1C7 7 7 5 9.5 5c2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"/>
|
|
26
|
+
<path d="M2 12c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"/>
|
|
27
|
+
<path d="M2 18c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"/>
|
|
28
|
+
</svg>
|
|
29
|
+
`},p=`
|
|
30
|
+
* {
|
|
31
|
+
box-sizing: border-box;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
:host {
|
|
35
|
+
--primary: #6366f1;
|
|
36
|
+
--primary-hover: #4f46e5;
|
|
37
|
+
--bg: #ffffff;
|
|
38
|
+
--text: #1f2937;
|
|
39
|
+
--text-muted: #6b7280;
|
|
40
|
+
--border: #e5e7eb;
|
|
41
|
+
--shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
|
42
|
+
--shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.15);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.widget-fab {
|
|
46
|
+
position: fixed;
|
|
47
|
+
z-index: 9999;
|
|
48
|
+
width: 64px;
|
|
49
|
+
height: 64px;
|
|
50
|
+
border-radius: 50%;
|
|
51
|
+
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-hover) 100%);
|
|
52
|
+
border: none;
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
justify-content: center;
|
|
57
|
+
box-shadow: var(--shadow);
|
|
58
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
59
|
+
animation: fadeInScale 0.4s ease-out;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.widget-fab:hover {
|
|
63
|
+
transform: scale(1.1);
|
|
64
|
+
box-shadow: var(--shadow-lg);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.widget-fab:active {
|
|
68
|
+
transform: scale(0.95);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.widget-fab svg {
|
|
72
|
+
width: 28px;
|
|
73
|
+
height: 28px;
|
|
74
|
+
color: white;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.widget-panel {
|
|
78
|
+
position: fixed;
|
|
79
|
+
z-index: 9998;
|
|
80
|
+
background: var(--bg);
|
|
81
|
+
border-radius: 20px;
|
|
82
|
+
box-shadow: var(--shadow-lg);
|
|
83
|
+
overflow: hidden;
|
|
84
|
+
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
85
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.widget-panel.hidden {
|
|
89
|
+
opacity: 0;
|
|
90
|
+
pointer-events: none;
|
|
91
|
+
transform: scale(0.8);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.widget-header {
|
|
95
|
+
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-hover) 100%);
|
|
96
|
+
color: white;
|
|
97
|
+
padding: 20px;
|
|
98
|
+
display: flex;
|
|
99
|
+
justify-content: space-between;
|
|
100
|
+
align-items: center;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.widget-title {
|
|
104
|
+
font-size: 18px;
|
|
105
|
+
font-weight: 600;
|
|
106
|
+
margin: 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.close-btn {
|
|
110
|
+
background: rgba(255, 255, 255, 0.2);
|
|
111
|
+
border: none;
|
|
112
|
+
width: 32px;
|
|
113
|
+
height: 32px;
|
|
114
|
+
border-radius: 50%;
|
|
115
|
+
display: flex;
|
|
116
|
+
align-items: center;
|
|
117
|
+
justify-content: center;
|
|
118
|
+
cursor: pointer;
|
|
119
|
+
transition: background 0.2s;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.close-btn:hover {
|
|
123
|
+
background: rgba(255, 255, 255, 0.3);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.close-btn svg {
|
|
127
|
+
width: 18px;
|
|
128
|
+
height: 18px;
|
|
129
|
+
color: white;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.widget-body {
|
|
133
|
+
padding: 24px;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.status-indicator {
|
|
137
|
+
display: flex;
|
|
138
|
+
align-items: center;
|
|
139
|
+
gap: 12px;
|
|
140
|
+
padding: 16px;
|
|
141
|
+
background: #f9fafb;
|
|
142
|
+
border-radius: 12px;
|
|
143
|
+
margin-bottom: 20px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.status-dot {
|
|
147
|
+
width: 10px;
|
|
148
|
+
height: 10px;
|
|
149
|
+
border-radius: 50%;
|
|
150
|
+
background: #10b981;
|
|
151
|
+
animation: pulse 2s infinite;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.status-text {
|
|
155
|
+
font-size: 14px;
|
|
156
|
+
color: var(--text-muted);
|
|
157
|
+
margin: 0;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.transcript-box {
|
|
161
|
+
min-height: 100px;
|
|
162
|
+
max-height: 200px;
|
|
163
|
+
overflow-y: auto;
|
|
164
|
+
background: #f9fafb;
|
|
165
|
+
border: 1px solid var(--border);
|
|
166
|
+
border-radius: 12px;
|
|
167
|
+
padding: 16px;
|
|
168
|
+
margin-bottom: 20px;
|
|
169
|
+
font-size: 14px;
|
|
170
|
+
line-height: 1.6;
|
|
171
|
+
color: var(--text);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.transcript-box::-webkit-scrollbar {
|
|
175
|
+
width: 6px;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.transcript-box::-webkit-scrollbar-thumb {
|
|
179
|
+
background: #cbd5e1;
|
|
180
|
+
border-radius: 3px;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.controls {
|
|
184
|
+
display: flex;
|
|
185
|
+
gap: 12px;
|
|
186
|
+
justify-content: center;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.control-btn {
|
|
190
|
+
flex: 1;
|
|
191
|
+
padding: 14px 20px;
|
|
192
|
+
border: none;
|
|
193
|
+
border-radius: 12px;
|
|
194
|
+
font-size: 14px;
|
|
195
|
+
font-weight: 600;
|
|
196
|
+
cursor: pointer;
|
|
197
|
+
transition: all 0.2s;
|
|
198
|
+
display: flex;
|
|
199
|
+
align-items: center;
|
|
200
|
+
justify-content: center;
|
|
201
|
+
gap: 8px;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.control-btn svg {
|
|
205
|
+
width: 18px;
|
|
206
|
+
height: 18px;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.control-btn.primary {
|
|
210
|
+
background: var(--primary);
|
|
211
|
+
color: white;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.control-btn.primary:hover {
|
|
215
|
+
background: var(--primary-hover);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.control-btn.danger {
|
|
219
|
+
background: #ef4444;
|
|
220
|
+
color: white;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.control-btn.danger:hover {
|
|
224
|
+
background: #dc2626;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.control-btn.secondary {
|
|
228
|
+
background: #f3f4f6;
|
|
229
|
+
color: var(--text);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.control-btn.secondary:hover {
|
|
233
|
+
background: #e5e7eb;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.control-btn:disabled {
|
|
237
|
+
opacity: 0.5;
|
|
238
|
+
cursor: not-allowed;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.wave-animation {
|
|
242
|
+
display: inline-block;
|
|
243
|
+
animation: wave 1.5s ease-in-out infinite;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
@keyframes fadeInScale {
|
|
247
|
+
from {
|
|
248
|
+
opacity: 0;
|
|
249
|
+
transform: scale(0.8);
|
|
250
|
+
}
|
|
251
|
+
to {
|
|
252
|
+
opacity: 1;
|
|
253
|
+
transform: scale(1);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
@keyframes pulse {
|
|
258
|
+
0%, 100% {
|
|
259
|
+
opacity: 1;
|
|
260
|
+
}
|
|
261
|
+
50% {
|
|
262
|
+
opacity: 0.5;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
@keyframes wave {
|
|
267
|
+
0%, 100% {
|
|
268
|
+
transform: translateY(0);
|
|
269
|
+
}
|
|
270
|
+
50% {
|
|
271
|
+
transform: translateY(-4px);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@media (max-width: 640px) {
|
|
276
|
+
.widget-panel {
|
|
277
|
+
width: 100vw !important;
|
|
278
|
+
height: 100vh !important;
|
|
279
|
+
top: 0 !important;
|
|
280
|
+
left: 0 !important;
|
|
281
|
+
right: 0 !important;
|
|
282
|
+
bottom: 0 !important;
|
|
283
|
+
border-radius: 0;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
`;class g{constructor(t){if(!t.serverUrl)throw new Error("serverUrl is required");if(!t.agentId)throw new Error("agentId is required");this.serverUrl=t.serverUrl.replace(/\/$/,""),this.agentId=t.agentId,this.callId=t.callId||this.generateCallId(),this.onConnected=t.onConnected||(()=>{}),this.onDisconnected=t.onDisconnected||(()=>{}),this.onError=t.onError||(o=>console.error("[WebRTC]",o)),this.onTranscription=t.onTranscription||(()=>{}),this.pc=null,this.dataChannel=null,this.audioElement=null,this.connected=!1}async connect(){console.log("đĩ [WebRTC] Starting connection...");try{this.pc=new RTCPeerConnection({iceServers:[{urls:"stun:stun.l.google.com:19302"}]}),this.pc.addTransceiver("video",{direction:"recvonly"});const t=await navigator.mediaDevices.getUserMedia({audio:{echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0,sampleRate:{ideal:16e3},channelCount:1}});console.log("đ¤ [WebRTC] Microphone access granted"),t.getTracks().forEach(n=>{this.pc.addTrack(n,t)}),this.dataChannel=this.pc.createDataChannel("control"),this.dataChannel.onopen=()=>console.log("đĄ [WebRTC] DataChannel opened"),this.dataChannel.onmessage=n=>this.handleControlEvent(JSON.parse(n.data)),this.dataChannel.onerror=n=>console.error("â [WebRTC] DataChannel error:",n),this.pc.ontrack=n=>{const r=n.track,l=n.streams[0];console.log(`đĨ [WebRTC] Received ${r.kind} track`),r.kind==="audio"&&(console.log("đ [WebRTC] Setting up audio playback"),this.audioElement=new Audio,this.audioElement.srcObject=l,this.audioElement.play().catch(v=>console.warn("Audio autoplay blocked:",v)),this.audioElement.onended=()=>{this.sendEvent("playedStream")})},this.pc.onconnectionstatechange=()=>{console.log("đ [WebRTC] State:",this.pc.connectionState),this.pc.connectionState==="connected"?(this.connected=!0,this.onConnected()):(this.pc.connectionState==="failed"||this.pc.connectionState==="disconnected"||this.pc.connectionState==="closed")&&(this.connected=!1,this.onDisconnected())};const o=await this.pc.createOffer();await this.pc.setLocalDescription(o),console.log("đ [WebRTC] Created offer, waiting for ICE gathering..."),await this.waitForIceGathering(),console.log("đ§ [WebRTC] ICE gathering complete"),console.log("đ¤ [WebRTC] Sending offer via HTTP...");const e=await fetch(`${this.serverUrl}/webrtc?agent=${this.agentId}_${this.callId}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({offer:this.pc.localDescription,agentId:this.agentId,callId:this.callId})});if(!e.ok){const n=await e.json();throw new Error(n.error||`HTTP ${e.status}`)}const{answer:a}=await e.json();console.log("đĨ [WebRTC] Received answer from server"),await this.pc.setRemoteDescription(a),console.log("â
[WebRTC] Connection established!")}catch(t){throw console.error("â [WebRTC] Connection failed:",t),this.onError(t.message||t),t}}waitForIceGathering(){return new Promise(t=>{if(this.pc.iceGatheringState==="complete")t();else{const o=()=>{this.pc.iceGatheringState==="complete"&&(this.pc.removeEventListener("icegatheringstatechange",o),t())};this.pc.addEventListener("icegatheringstatechange",o),setTimeout(()=>{this.pc.removeEventListener("icegatheringstatechange",o),console.warn("â ī¸ [WebRTC] ICE gathering timeout, proceeding anyway"),t()},5e3)}})}sendEvent(t,o={}){var e;((e=this.dataChannel)==null?void 0:e.readyState)==="open"&&this.dataChannel.send(JSON.stringify({event:t,...o}))}handleControlEvent(t){switch(t.event){case"clearAudio":console.log("đ [WebRTC] Interrupt: stopping audio"),this.audioElement&&(this.audioElement.pause(),this.audioElement.currentTime=0);break;case"transcription":console.log("đ [WebRTC] Transcription:",t.text),this.onTranscription(t.text,t.isFinal);break;case"mark":console.log("đˇī¸ [WebRTC] Mark:",t.name);break;default:console.log("âšī¸ [WebRTC] Unknown event:",t.event)}}disconnect(){console.log("đ´ [WebRTC] Disconnecting..."),this.audioElement&&(this.audioElement.pause(),this.audioElement.srcObject=null),this.dataChannel&&this.dataChannel.close(),this.pc&&this.pc.close(),this.connected=!1,this.onDisconnected()}generateCallId(){return"web_"+Date.now()+"_"+Math.random().toString(36).substr(2,8)}}class d extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this.isPanelOpen=!1,this.callActive=!1,this.isMuted=!1,this.transcript="",this.client=null}connectedCallback(){console.log("[ConvAI Widget] connectedCallback triggered");const t=this.getAttribute("agent-id")||this.getAttribute("id"),o=this.getAttribute("position")||"bottom-right",e=this.getAttribute("primary-color")||"#6366f1",a=this.getAttribute("server-url")||"https://api.travelr.club";if(console.log("[ConvAI Widget] Configuration:",{agentId:t,position:o,primaryColor:e,serverUrl:a}),!t){console.error("[ConvAI Widget] agent-id attribute is required");return}this.agentId=t,this.serverUrl=a,console.log("[ConvAI Widget] Rendering widget..."),this.render(o,e),console.log("[ConvAI Widget] Attaching event listeners..."),this.attachEventListeners(),console.log("[ConvAI Widget] Widget initialization complete!")}render(t,o){const e={"bottom-right":{bottom:"24px",right:"24px"},"bottom-left":{bottom:"24px",left:"24px"},"top-right":{top:"24px",right:"24px"},"top-left":{top:"24px",left:"24px"}},a=e[t]||e["bottom-right"],n=p.replace("--primary: #6366f1",`--primary: ${o}`);this.shadowRoot.innerHTML=`
|
|
287
|
+
<style>${n}</style>
|
|
288
|
+
|
|
289
|
+
<!-- Floating Action Button -->
|
|
290
|
+
<button class="widget-fab" style="${Object.entries(a).map(([r,l])=>`${r}: ${l}`).join("; ")}">
|
|
291
|
+
${c.phone}
|
|
292
|
+
</button>
|
|
293
|
+
|
|
294
|
+
<!-- Voice Panel (hidden by default) -->
|
|
295
|
+
<div class="widget-panel hidden" style="
|
|
296
|
+
width: 380px;
|
|
297
|
+
height: 480px;
|
|
298
|
+
${t.includes("bottom")?"bottom: 100px":"top: 100px"};
|
|
299
|
+
${t.includes("right")?"right: 24px":"left: 24px"};
|
|
300
|
+
">
|
|
301
|
+
<div class="widget-header">
|
|
302
|
+
<h3 class="widget-title">Voice Assistant</h3>
|
|
303
|
+
<button class="close-btn" id="close-panel">
|
|
304
|
+
${c.close}
|
|
305
|
+
</button>
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
<div class="widget-body">
|
|
309
|
+
<div class="status-indicator">
|
|
310
|
+
<div class="status-dot" id="status-dot" style="background: #ef4444;"></div>
|
|
311
|
+
<p class="status-text" id="status-text">Click Start to begin conversation</p>
|
|
312
|
+
</div>
|
|
313
|
+
|
|
314
|
+
<div class="transcript-box" id="transcript">
|
|
315
|
+
<p style="color: #9ca3af; font-style: italic;">Transcript will appear here...</p>
|
|
316
|
+
</div>
|
|
317
|
+
|
|
318
|
+
<div class="controls" id="controls">
|
|
319
|
+
<button class="control-btn primary" id="start-btn">
|
|
320
|
+
${c.mic}
|
|
321
|
+
<span>Start Call</span>
|
|
322
|
+
</button>
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
`}attachEventListeners(){const t=this.shadowRoot.querySelector(".widget-fab"),o=this.shadowRoot.querySelector(".widget-panel"),e=this.shadowRoot.querySelector("#close-panel"),a=this.shadowRoot.querySelector("#start-btn");t.addEventListener("click",()=>{this.isPanelOpen=!this.isPanelOpen,o.classList.toggle("hidden",!this.isPanelOpen)}),e.addEventListener("click",()=>{this.isPanelOpen=!1,o.classList.add("hidden")}),a.addEventListener("click",()=>{this.callActive?this.stopCall():this.startCall()})}async startCall(){const t=this.shadowRoot.querySelector("#status-dot"),o=this.shadowRoot.querySelector("#status-text"),e=this.shadowRoot.querySelector("#start-btn"),a=this.shadowRoot.querySelector("#transcript");try{o.textContent="Connecting...",t.style.background="#f59e0b",e.disabled=!0,this.client=new g({serverUrl:this.serverUrl,agentId:this.agentId,onConnected:()=>{this.callActive=!0,o.textContent="Connected - Listening...",t.style.background="#10b981",e.innerHTML=`${c.close}<span>End Call</span>`,e.className="control-btn danger",e.disabled=!1},onDisconnected:()=>{this.stopCall()},onError:n=>{o.textContent=`Error: ${n}`,t.style.background="#ef4444",e.disabled=!1},onTranscription:(n,r)=>{r&&(this.transcript+=(this.transcript?`
|
|
327
|
+
`:"")+n,a.innerHTML=this.transcript||'<p style="color: #9ca3af; font-style: italic;">Transcript will appear here...</p>',a.scrollTop=a.scrollHeight)}}),await this.client.connect()}catch(n){console.error("[ConvAI Widget] Connection failed:",n),o.textContent="Connection failed. Please try again.",t.style.background="#ef4444",e.disabled=!1}}stopCall(){this.client&&(this.client.disconnect(),this.client=null);const t=this.shadowRoot.querySelector("#status-dot"),o=this.shadowRoot.querySelector("#status-text"),e=this.shadowRoot.querySelector("#start-btn");this.callActive=!1,o.textContent="Click Start to begin conversation",t.style.background="#ef4444",e.innerHTML=`${c.mic}<span>Start Call</span>`,e.className="control-btn primary",e.disabled=!1}disconnectedCallback(){this.client&&this.client.disconnect()}}const i=document.currentScript,h=i==null?void 0:i.getAttribute("data-agent-id"),u=(i==null?void 0:i.getAttribute("data-position"))||"bottom-right",b=(i==null?void 0:i.getAttribute("data-primary-color"))||"#6366f1",f=(i==null?void 0:i.getAttribute("data-server-url"))||"https://api.travelr.club";if(customElements.get("avin-convai")||customElements.define("avin-convai",d),h){const s=document.createElement("avin-convai");s.setAttribute("agent-id",h),s.setAttribute("position",u),s.setAttribute("primary-color",b),s.setAttribute("server-url",f),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{document.body.appendChild(s)}):document.body.appendChild(s)}window.AvinConvAI=d})();
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "convai-voice-widget",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Embeddable voice conversation widget for AvinAI agents",
|
|
5
|
+
"main": "dist/widget.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "vite",
|
|
12
|
+
"build": "vite build",
|
|
13
|
+
"preview": "vite preview",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"voice",
|
|
18
|
+
"ai",
|
|
19
|
+
"widget",
|
|
20
|
+
"webrtc",
|
|
21
|
+
"conversation",
|
|
22
|
+
"embed",
|
|
23
|
+
"avinai"
|
|
24
|
+
],
|
|
25
|
+
"author": "AvinAI",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/Alok7268/hr-dashboard"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"vite": "^5.4.2"
|
|
33
|
+
}
|
|
34
|
+
}
|