@helllo-ai/agent-chat-widget 0.1.6
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 +182 -0
- package/agent-chat.latest.js +345 -0
- package/agent-chat.prod.js +350 -0
- package/agent-chat.staging.js +350 -0
- package/agent-chat.v0.1.0.js +345 -0
- package/agent-chat.v0.1.js +345 -0
- package/agent-chat.v0.js +345 -0
- package/build.js +88 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# @helllo-ai/agent-chat-widget
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/js/%40helllo-ai%2Fagent-chat-widget)
|
|
4
|
+
[](https://unpkg.com/@helllo-ai/agent-chat-widget/)
|
|
5
|
+
[](https://www.jsdelivr.com/package/npm/@helllo-ai/agent-chat-widget)
|
|
6
|
+
|
|
7
|
+
An embeddable chat widget for Helllo AI agents. This widget allows you to easily add AI-powered chat functionality to any website.
|
|
8
|
+
|
|
9
|
+
## 🚀 Quick Start
|
|
10
|
+
|
|
11
|
+
### Via unpkg (CDN)
|
|
12
|
+
|
|
13
|
+
#### Production Environment
|
|
14
|
+
```html
|
|
15
|
+
<script src="https://unpkg.com/@helllo-ai/agent-chat-widget@latest/agent-chat.prod.js"></script>
|
|
16
|
+
<script>
|
|
17
|
+
// Initialize the widget (uses production URLs automatically)
|
|
18
|
+
window.AgentChatWidget.init({
|
|
19
|
+
agentId: 'your-agent-id',
|
|
20
|
+
embedKey: 'your-embed-key',
|
|
21
|
+
primaryColor: '#0f172a',
|
|
22
|
+
backgroundColor: '#ffffff'
|
|
23
|
+
});
|
|
24
|
+
</script>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
#### Staging Environment
|
|
28
|
+
```html
|
|
29
|
+
<script src="https://unpkg.com/@helllo-ai/agent-chat-widget@staging/agent-chat.staging.js"></script>
|
|
30
|
+
<script>
|
|
31
|
+
// Initialize the widget (uses staging URLs automatically)
|
|
32
|
+
window.AgentChatWidget.init({
|
|
33
|
+
agentId: 'your-agent-id',
|
|
34
|
+
embedKey: 'your-embed-key'
|
|
35
|
+
});
|
|
36
|
+
</script>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### Development/Latest
|
|
40
|
+
```html
|
|
41
|
+
<script src="https://unpkg.com/@helllo-ai/agent-chat-widget@latest/agent-chat.latest.js"></script>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Via jsDelivr (Alternative CDN)
|
|
45
|
+
```html
|
|
46
|
+
<!-- Production -->
|
|
47
|
+
<script src="https://cdn.jsdelivr.net/npm/@helllo-ai/agent-chat-widget@latest/agent-chat.prod.js"></script>
|
|
48
|
+
|
|
49
|
+
<!-- Staging -->
|
|
50
|
+
<script src="https://cdn.jsdelivr.net/npm/@helllo-ai/agent-chat-widget@staging/agent-chat.staging.js"></script>
|
|
51
|
+
|
|
52
|
+
<!-- Development -->
|
|
53
|
+
<script src="https://cdn.jsdelivr.net/npm/@helllo-ai/agent-chat-widget@latest/agent-chat.latest.js"></script>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Via npm
|
|
57
|
+
```bash
|
|
58
|
+
npm install @helllo-ai/agent-chat-widget
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
import '@bot-swarm/agent-chat-widget/agent-chat.latest.js';
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 📦 Available Versions
|
|
66
|
+
|
|
67
|
+
| File | Cache Strategy | Use Case |
|
|
68
|
+
|------|---------------|----------|
|
|
69
|
+
| `agent-chat.latest.js` | No cache | Development, always latest |
|
|
70
|
+
| `agent-chat.v0.1.0.js` | 1 year cache | Production (specific version) |
|
|
71
|
+
| `agent-chat.v0.1.js` | 1 month cache | Stay updated within minor version |
|
|
72
|
+
| `agent-chat.v0.js` | 1 week cache | Stay updated within major version |
|
|
73
|
+
|
|
74
|
+
## ⚙️ Configuration
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
window.AgentChatWidget.init({
|
|
78
|
+
agentId: 'your-agent-id', // Required: Your agent ID
|
|
79
|
+
embedKey: 'your-embed-key', // Required: Your embed key
|
|
80
|
+
primaryColor: '#0f172a', // Optional: Primary color
|
|
81
|
+
backgroundColor: '#ffffff', // Optional: Background color
|
|
82
|
+
title: 'Chat with us', // Optional: Widget title
|
|
83
|
+
apiBaseUrl: 'https://api.helllo.ai', // Optional: API base URL
|
|
84
|
+
voiceServiceUrl: 'https://voice.helllo.ai', // Optional: Voice service URL
|
|
85
|
+
wsUrl: null, // Optional: WebSocket URL override
|
|
86
|
+
allowedDomains: ['example.com'] // Optional: Domain restrictions
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 🎯 Widget API
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
// Initialize
|
|
94
|
+
const widget = window.AgentChatWidget.init(config);
|
|
95
|
+
|
|
96
|
+
// Programmatic control
|
|
97
|
+
widget.open(); // Open chat panel
|
|
98
|
+
widget.close(); // Close chat panel
|
|
99
|
+
widget.destroy(); // Remove widget completely
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## 🔧 Development
|
|
103
|
+
|
|
104
|
+
### Building from source
|
|
105
|
+
```bash
|
|
106
|
+
# Install dependencies
|
|
107
|
+
npm install
|
|
108
|
+
|
|
109
|
+
# Build widget files (choose environment)
|
|
110
|
+
npm run build # Development/latest
|
|
111
|
+
npm run build:staging # Staging environment
|
|
112
|
+
npm run build:prod # Production environment
|
|
113
|
+
|
|
114
|
+
# Version bump
|
|
115
|
+
npm run version:patch # or version:minor, version:major
|
|
116
|
+
|
|
117
|
+
# Publish to npm (choose environment)
|
|
118
|
+
npm run publish:public # Production release
|
|
119
|
+
npm run publish:staging # Staging release
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### File Structure
|
|
123
|
+
```
|
|
124
|
+
packages/agent-chat-widget/
|
|
125
|
+
├── agent-chat.latest.js # Latest development version
|
|
126
|
+
├── agent-chat.staging.js # Staging environment version
|
|
127
|
+
├── agent-chat.prod.js # Production environment version
|
|
128
|
+
├── agent-chat.v0.1.0.js # Specific version (production)
|
|
129
|
+
├── agent-chat.v0.1.js # Minor version (auto-updates)
|
|
130
|
+
├── agent-chat.v0.js # Major version (auto-updates)
|
|
131
|
+
├── build.js # Build script
|
|
132
|
+
├── package.json
|
|
133
|
+
└── README.md
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## 📋 CDN URLs
|
|
137
|
+
|
|
138
|
+
### unpkg
|
|
139
|
+
|
|
140
|
+
#### Production Environment
|
|
141
|
+
```
|
|
142
|
+
https://unpkg.com/@helllo-ai/agent-chat-widget@latest/agent-chat.prod.js
|
|
143
|
+
https://unpkg.com/@helllo-ai/agent-chat-widget@0.1.0/agent-chat.v0.1.0.js
|
|
144
|
+
https://unpkg.com/@helllo-ai/agent-chat-widget@0/agent-chat.v0.js
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### Staging Environment
|
|
148
|
+
```
|
|
149
|
+
https://unpkg.com/@helllo-ai/agent-chat-widget@staging/agent-chat.staging.js
|
|
150
|
+
https://unpkg.com/@helllo-ai/agent-chat-widget@0.1.0-staging/agent-chat.v0.1.0.js
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### Development/Latest
|
|
154
|
+
```
|
|
155
|
+
https://unpkg.com/@helllo-ai/agent-chat-widget@latest/agent-chat.latest.js
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### jsDelivr
|
|
159
|
+
|
|
160
|
+
#### Production Environment
|
|
161
|
+
```
|
|
162
|
+
https://cdn.jsdelivr.net/npm/@helllo-ai/agent-chat-widget@latest/agent-chat.prod.js
|
|
163
|
+
https://cdn.jsdelivr.net/npm/@helllo-ai/agent-chat-widget@0.1.0/agent-chat.v0.1.0.js
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### Staging Environment
|
|
167
|
+
```
|
|
168
|
+
https://cdn.jsdelivr.net/npm/@helllo-ai/agent-chat-widget@staging/agent-chat.staging.js
|
|
169
|
+
https://cdn.jsdelivr.net/npm/@helllo-ai/agent-chat-widget@0.1.0-staging/agent-chat.v0.1.0.js
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## 📄 License
|
|
173
|
+
|
|
174
|
+
MIT © [Helllo AI](https://helllo.ai)
|
|
175
|
+
|
|
176
|
+
## 🐛 Issues
|
|
177
|
+
|
|
178
|
+
Report bugs and request features at: [GitHub Issues](https://github.com/helllo-ai/agent-chat-widget/issues)
|
|
179
|
+
|
|
180
|
+
## 📞 Support
|
|
181
|
+
|
|
182
|
+
For support, email support@helllo.ai or visit [helllo.ai](https://helllo.ai)
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
;(function () {
|
|
2
|
+
if (typeof window === 'undefined') return
|
|
3
|
+
if (window.AgentChatWidget) return
|
|
4
|
+
|
|
5
|
+
const VERSION = '0.1.0'
|
|
6
|
+
|
|
7
|
+
function domainAllowed(hostname, allowedList) {
|
|
8
|
+
if (!Array.isArray(allowedList) || allowedList.length === 0) return true
|
|
9
|
+
const host = (hostname || '').toLowerCase()
|
|
10
|
+
return allowedList.some((d) => {
|
|
11
|
+
const domain = (d || '').toLowerCase().trim()
|
|
12
|
+
if (!domain) return false
|
|
13
|
+
return host === domain || host.endsWith('.' + domain)
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function createStyles(primary, background) {
|
|
18
|
+
const safePrimary = primary || '#0f172a'
|
|
19
|
+
const safeBg = background || '#ffffff'
|
|
20
|
+
return `
|
|
21
|
+
:host, .acw * { box-sizing: border-box; }
|
|
22
|
+
.acw-container { position: fixed; right: 16px; bottom: 16px; z-index: 2147483000; font-family: Inter, system-ui, -apple-system, sans-serif; }
|
|
23
|
+
.acw-launcher { background: ${safePrimary}; color: #fff; border: none; border-radius: 999px; padding: 10px 14px; cursor: pointer; display: inline-flex; align-items: center; gap: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.18); font-size: 14px; }
|
|
24
|
+
.acw-launcher:hover { opacity: 0.95; }
|
|
25
|
+
.acw-panel { position: absolute; right: 0; bottom: 56px; width: 360px; max-width: 90vw; height: 520px; max-height: 80vh; background: ${safeBg}; border: 1px solid rgba(0,0,0,0.08); border-radius: 12px; box-shadow: 0 16px 40px rgba(0,0,0,0.22); display: none; flex-direction: column; overflow: hidden; }
|
|
26
|
+
.acw-header { background: ${safePrimary}; color: #fff; padding: 12px 14px; display: flex; align-items: center; justify-content: space-between; }
|
|
27
|
+
.acw-title { font-weight: 600; font-size: 14px; }
|
|
28
|
+
.acw-close { background: transparent; border: none; color: #fff; cursor: pointer; font-size: 16px; }
|
|
29
|
+
.acw-messages { flex: 1; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 8px; }
|
|
30
|
+
.acw-msg { padding: 10px 12px; border-radius: 10px; max-width: 90%; font-size: 14px; line-height: 1.4; white-space: pre-wrap; word-break: break-word; }
|
|
31
|
+
.acw-msg-user { margin-left: auto; background: ${safePrimary}; color: #fff; }
|
|
32
|
+
.acw-msg-bot { margin-right: auto; background: rgba(0,0,0,0.05); color: #111; }
|
|
33
|
+
.acw-input { border-top: 1px solid rgba(0,0,0,0.08); padding: 10px; display: flex; gap: 8px; }
|
|
34
|
+
.acw-input input { flex: 1; padding: 10px 12px; border: 1px solid rgba(0,0,0,0.12); border-radius: 8px; font-size: 14px; }
|
|
35
|
+
.acw-input button { background: ${safePrimary}; color: #fff; border: none; border-radius: 8px; padding: 0 14px; cursor: pointer; font-weight: 600; }
|
|
36
|
+
.acw-status { font-size: 12px; color: #666; padding: 8px 12px; display: flex; align-items: center; gap: 10px; border-bottom: 1px solid rgba(0,0,0,0.06); }
|
|
37
|
+
.acw-dot { width: 8px; height: 8px; border-radius: 999px; box-shadow: 0 0 0 6px rgba(0,0,0,0.04); }
|
|
38
|
+
.acw-dot.connected { background: #22c55e; box-shadow: 0 0 0 6px rgba(34,197,94,0.18); }
|
|
39
|
+
.acw-dot.disconnected { background: #ef4444; box-shadow: 0 0 0 6px rgba(239,68,68,0.18); }
|
|
40
|
+
.acw-disconnect { margin-left: auto; background: #f8fafc; color: #0f172a; border: 1px solid rgba(0,0,0,0.08); border-radius: 6px; padding: 6px 8px; font-size: 12px; cursor: pointer; }
|
|
41
|
+
.acw-disconnect:hover { background: #eef2f7; }
|
|
42
|
+
@media (max-width: 480px) {
|
|
43
|
+
.acw-panel { width: calc(100vw - 24px); height: 70vh; }
|
|
44
|
+
}
|
|
45
|
+
`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createWidget(config) {
|
|
49
|
+
const {
|
|
50
|
+
agentId,
|
|
51
|
+
embedKey,
|
|
52
|
+
primaryColor,
|
|
53
|
+
backgroundColor,
|
|
54
|
+
allowedDomains = [],
|
|
55
|
+
apiBaseUrl,
|
|
56
|
+
voiceServiceUrl,
|
|
57
|
+
wsUrl,
|
|
58
|
+
title = 'Chat with us',
|
|
59
|
+
greeting = null,
|
|
60
|
+
} = config
|
|
61
|
+
|
|
62
|
+
if (!agentId) {
|
|
63
|
+
console.warn('[AgentChatWidget] Missing data-agent-id')
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
if (!embedKey) {
|
|
67
|
+
console.warn('[AgentChatWidget] Missing data-embed-key')
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const hostOk = domainAllowed(window.location.hostname, allowedDomains)
|
|
72
|
+
if (!hostOk) {
|
|
73
|
+
console.warn('[AgentChatWidget] Host not in allowed domains, widget will not load')
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const container = document.createElement('div')
|
|
78
|
+
container.className = 'acw-container'
|
|
79
|
+
const shadow = container.attachShadow({ mode: 'open' })
|
|
80
|
+
const style = document.createElement('style')
|
|
81
|
+
style.textContent = createStyles(primaryColor, backgroundColor)
|
|
82
|
+
shadow.appendChild(style)
|
|
83
|
+
|
|
84
|
+
const panel = document.createElement('div')
|
|
85
|
+
panel.className = 'acw-panel'
|
|
86
|
+
|
|
87
|
+
const launcher = document.createElement('button')
|
|
88
|
+
launcher.className = 'acw-launcher'
|
|
89
|
+
launcher.innerHTML = '<span>Chat</span>'
|
|
90
|
+
|
|
91
|
+
const header = document.createElement('div')
|
|
92
|
+
header.className = 'acw-header'
|
|
93
|
+
const titleEl = document.createElement('div')
|
|
94
|
+
titleEl.className = 'acw-title'
|
|
95
|
+
titleEl.textContent = title
|
|
96
|
+
const headerActions = document.createElement('div')
|
|
97
|
+
headerActions.style.display = 'flex'
|
|
98
|
+
headerActions.style.alignItems = 'center'
|
|
99
|
+
headerActions.style.gap = '8px'
|
|
100
|
+
|
|
101
|
+
const minimizeBtn = document.createElement('button')
|
|
102
|
+
minimizeBtn.className = 'acw-close'
|
|
103
|
+
minimizeBtn.textContent = '–'
|
|
104
|
+
const closeBtn = document.createElement('button')
|
|
105
|
+
closeBtn.className = 'acw-close'
|
|
106
|
+
closeBtn.textContent = '×'
|
|
107
|
+
|
|
108
|
+
headerActions.appendChild(minimizeBtn)
|
|
109
|
+
headerActions.appendChild(closeBtn)
|
|
110
|
+
header.appendChild(titleEl)
|
|
111
|
+
header.appendChild(headerActions)
|
|
112
|
+
|
|
113
|
+
const statusEl = document.createElement('div')
|
|
114
|
+
statusEl.className = 'acw-status'
|
|
115
|
+
const statusDot = document.createElement('div')
|
|
116
|
+
statusDot.className = 'acw-dot disconnected'
|
|
117
|
+
const statusText = document.createElement('span')
|
|
118
|
+
statusText.textContent = 'Disconnected'
|
|
119
|
+
const disconnectBtn = document.createElement('button')
|
|
120
|
+
disconnectBtn.className = 'acw-disconnect'
|
|
121
|
+
disconnectBtn.textContent = 'Connect'
|
|
122
|
+
statusEl.appendChild(statusDot)
|
|
123
|
+
statusEl.appendChild(statusText)
|
|
124
|
+
statusEl.appendChild(disconnectBtn)
|
|
125
|
+
|
|
126
|
+
const messages = document.createElement('div')
|
|
127
|
+
messages.className = 'acw-messages'
|
|
128
|
+
|
|
129
|
+
const inputWrap = document.createElement('div')
|
|
130
|
+
inputWrap.className = 'acw-input'
|
|
131
|
+
const input = document.createElement('input')
|
|
132
|
+
input.type = 'text'
|
|
133
|
+
input.placeholder = 'Type a message...'
|
|
134
|
+
const sendBtn = document.createElement('button')
|
|
135
|
+
sendBtn.textContent = 'Send'
|
|
136
|
+
inputWrap.appendChild(input)
|
|
137
|
+
inputWrap.appendChild(sendBtn)
|
|
138
|
+
|
|
139
|
+
panel.appendChild(header)
|
|
140
|
+
panel.appendChild(statusEl)
|
|
141
|
+
panel.appendChild(messages)
|
|
142
|
+
panel.appendChild(inputWrap)
|
|
143
|
+
shadow.appendChild(panel)
|
|
144
|
+
shadow.appendChild(launcher)
|
|
145
|
+
document.body.appendChild(container)
|
|
146
|
+
|
|
147
|
+
function appendMessage(text, role) {
|
|
148
|
+
const msg = document.createElement('div')
|
|
149
|
+
msg.className = 'acw-msg ' + (role === 'user' ? 'acw-msg-user' : 'acw-msg-bot')
|
|
150
|
+
msg.textContent = text
|
|
151
|
+
messages.appendChild(msg)
|
|
152
|
+
messages.scrollTop = messages.scrollHeight
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// No default greeting; only show messages after connection
|
|
156
|
+
|
|
157
|
+
let ws = null
|
|
158
|
+
let connected = false
|
|
159
|
+
let connecting = false
|
|
160
|
+
|
|
161
|
+
const resolvedWsUrl = (() => {
|
|
162
|
+
if (wsUrl) return wsUrl
|
|
163
|
+
const baseCandidate = voiceServiceUrl || apiBaseUrl || new URL(document.currentScript?.src || window.location.href).origin
|
|
164
|
+
const voiceBase = (baseCandidate || '').replace(/\/$/, '')
|
|
165
|
+
const wsBase = voiceBase.replace(/^http/, 'ws')
|
|
166
|
+
const params = new URLSearchParams()
|
|
167
|
+
params.set('embed_key', embedKey)
|
|
168
|
+
params.set('host', window.location.hostname)
|
|
169
|
+
return `${wsBase}/api/v1/agent-voice/agents/${agentId}/text-chat?${params.toString()}`
|
|
170
|
+
})()
|
|
171
|
+
|
|
172
|
+
// Log resolved URL once for debugging
|
|
173
|
+
try {
|
|
174
|
+
console.info('[AgentChatWidget] Resolved WS URL:', resolvedWsUrl)
|
|
175
|
+
} catch (_) {}
|
|
176
|
+
|
|
177
|
+
function updateStatus(text, isConnected) {
|
|
178
|
+
statusText.textContent = text
|
|
179
|
+
statusDot.classList.remove('connected', 'disconnected')
|
|
180
|
+
statusDot.classList.add(isConnected ? 'connected' : 'disconnected')
|
|
181
|
+
|
|
182
|
+
// Update button text and handler based on connection status
|
|
183
|
+
disconnectBtn.textContent = isConnected ? 'Disconnect' : 'Connect'
|
|
184
|
+
disconnectBtn.onclick = isConnected ? disconnect : connect
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function connect() {
|
|
188
|
+
if (connected || connecting) return
|
|
189
|
+
connecting = true
|
|
190
|
+
const url = resolvedWsUrl
|
|
191
|
+
try {
|
|
192
|
+
ws = new WebSocket(url)
|
|
193
|
+
} catch (err) {
|
|
194
|
+
connecting = false
|
|
195
|
+
updateStatus('Connection error', false)
|
|
196
|
+
console.error('[AgentChatWidget] WS error', err)
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
ws.onopen = () => {
|
|
201
|
+
connected = true
|
|
202
|
+
connecting = false
|
|
203
|
+
updateStatus('Connected', true)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
ws.onmessage = (event) => {
|
|
207
|
+
try {
|
|
208
|
+
const data = JSON.parse(event.data)
|
|
209
|
+
const text = data.text || data.content || data.message || ''
|
|
210
|
+
const role = data.role || 'assistant'
|
|
211
|
+
if (text) appendMessage(text, role === 'user' ? 'user' : 'bot')
|
|
212
|
+
} catch (e) {
|
|
213
|
+
// Fallback to raw text
|
|
214
|
+
if (event.data) appendMessage(String(event.data), 'bot')
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
ws.onerror = (e) => {
|
|
219
|
+
console.error('[AgentChatWidget] WS error', e)
|
|
220
|
+
updateStatus('Connection error', false)
|
|
221
|
+
appendMessage('Connection error. Check WS service and URL.', 'bot')
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
ws.onclose = (ev) => {
|
|
225
|
+
connected = false
|
|
226
|
+
connecting = false
|
|
227
|
+
const reason = ev && (ev.reason || ev.code)
|
|
228
|
+
updateStatus('Disconnected', false)
|
|
229
|
+
if (!ev.wasClean) {
|
|
230
|
+
appendMessage(`Connection closed (${reason || 'unclean'})`, 'bot')
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function disconnect() {
|
|
236
|
+
if (ws) {
|
|
237
|
+
ws.close()
|
|
238
|
+
ws = null
|
|
239
|
+
}
|
|
240
|
+
connected = false
|
|
241
|
+
connecting = false
|
|
242
|
+
updateStatus('Disconnected', false)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function sendMessage() {
|
|
246
|
+
const text = input.value.trim()
|
|
247
|
+
if (!text) return
|
|
248
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
249
|
+
connect()
|
|
250
|
+
setTimeout(sendMessage, 200)
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
appendMessage(text, 'user')
|
|
254
|
+
ws.send(JSON.stringify({ type: 'user_message', text, role: 'user', embed_key: embedKey }))
|
|
255
|
+
input.value = ''
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
launcher.onclick = () => {
|
|
259
|
+
const isOpen = panel.style.display === 'flex'
|
|
260
|
+
if (isOpen) {
|
|
261
|
+
panel.style.display = 'none'
|
|
262
|
+
disconnect()
|
|
263
|
+
} else {
|
|
264
|
+
panel.style.display = 'flex'
|
|
265
|
+
connect()
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
closeBtn.onclick = () => {
|
|
270
|
+
panel.style.display = 'none'
|
|
271
|
+
disconnect()
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
minimizeBtn.onclick = () => {
|
|
275
|
+
panel.style.display = 'none'
|
|
276
|
+
disconnect()
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
sendBtn.onclick = sendMessage
|
|
280
|
+
input.addEventListener('keydown', (e) => {
|
|
281
|
+
if (e.key === 'Enter') {
|
|
282
|
+
e.preventDefault()
|
|
283
|
+
sendMessage()
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
destroy() {
|
|
289
|
+
disconnect()
|
|
290
|
+
container.remove()
|
|
291
|
+
},
|
|
292
|
+
open() {
|
|
293
|
+
panel.style.display = 'flex'
|
|
294
|
+
connect()
|
|
295
|
+
},
|
|
296
|
+
close() {
|
|
297
|
+
panel.style.display = 'none'
|
|
298
|
+
disconnect()
|
|
299
|
+
},
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function buildConfig(userConfig) {
|
|
304
|
+
const scriptEl = document.currentScript || document.querySelector('script[data-agent-id]')
|
|
305
|
+
const ds = (scriptEl && scriptEl.dataset) || {}
|
|
306
|
+
const allowed = (ds.allowedDomains || ds.allowed_domains || '').split(',').map((d) => d.trim()).filter(Boolean)
|
|
307
|
+
return {
|
|
308
|
+
agentId: userConfig.agentId || ds.agentId,
|
|
309
|
+
embedKey: userConfig.embedKey || ds.embedKey || ds.embed_key,
|
|
310
|
+
primaryColor: userConfig.primaryColor || ds.primaryColor || ds.primary_color,
|
|
311
|
+
backgroundColor: userConfig.backgroundColor || ds.backgroundColor || ds.background_color,
|
|
312
|
+
allowedDomains: userConfig.allowedDomains || allowed,
|
|
313
|
+
apiBaseUrl: userConfig.apiBaseUrl || ds.apiBaseUrl || ds.api_base_url,
|
|
314
|
+
voiceServiceUrl: userConfig.voiceServiceUrl || ds.voiceServiceUrl || ds.voice_service_url,
|
|
315
|
+
wsUrl: userConfig.wsUrl || ds.wsUrl || ds.ws_url,
|
|
316
|
+
title: userConfig.title || ds.title,
|
|
317
|
+
greeting: userConfig.greeting || ds.greeting,
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let instance = null
|
|
322
|
+
|
|
323
|
+
function init(userConfig = {}) {
|
|
324
|
+
if (instance) return instance
|
|
325
|
+
const cfg = buildConfig(userConfig)
|
|
326
|
+
instance = createWidget(cfg)
|
|
327
|
+
return instance
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function destroy() {
|
|
331
|
+
if (instance && typeof instance.destroy === 'function') {
|
|
332
|
+
instance.destroy()
|
|
333
|
+
instance = null
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
window.AgentChatWidget = { init, destroy, version: VERSION }
|
|
338
|
+
|
|
339
|
+
// Auto-init if script tag has data-agent-id
|
|
340
|
+
const autoScript = document.currentScript || document.querySelector('script[data-agent-id]')
|
|
341
|
+
if (autoScript && autoScript.dataset && autoScript.dataset.autoInit !== 'false') {
|
|
342
|
+
init()
|
|
343
|
+
}
|
|
344
|
+
})()
|
|
345
|
+
|