@qelos/web-sdk 3.11.10 → 4.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 +59 -0
- package/dist/chat-widget.d.ts +68 -0
- package/dist/chat-widget.js +442 -0
- package/dist/chat-widget.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/widget-cdn.d.ts +8 -0
- package/dist/widget-cdn.js +63 -0
- package/dist/widget-cdn.js.map +1 -0
- package/package.json +1 -1
- package/src/chat-widget.ts +508 -0
- package/src/index.ts +3 -0
- package/src/widget-cdn.ts +66 -0
package/README.md
CHANGED
|
@@ -24,4 +24,63 @@ fetch('/api/you-own-api', {
|
|
|
24
24
|
})
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
## Embeddable AI Chat Widget
|
|
28
|
+
|
|
29
|
+
Drop a Qelos AI agent chat into any web page.
|
|
30
|
+
|
|
31
|
+
### Single script tag (CDN)
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<script src="https://cdn.qelos.io/widget.js"
|
|
35
|
+
data-agent-id="665a1b2c3d4e5f6a7b8c9d0e"
|
|
36
|
+
data-app-url="https://my.qelos.io"
|
|
37
|
+
data-api-token="qelos_pk_..."
|
|
38
|
+
data-position="bottom-right"
|
|
39
|
+
data-theme="light"
|
|
40
|
+
data-header-text="Need help?"
|
|
41
|
+
data-initial-message="Hi! How can I help today?">
|
|
42
|
+
</script>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The CDN bundle (`dist/widget-cdn.js`) reads its configuration from `data-*` attributes on its own `<script>` tag and auto-mounts a chat bubble. It also exposes `window.QelosChatWidget` for programmatic control.
|
|
46
|
+
|
|
47
|
+
### Programmatic
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { QelosChatWidget } from '@qelos/web-sdk';
|
|
51
|
+
|
|
52
|
+
QelosChatWidget.init({
|
|
53
|
+
agentId: '665a1b2c3d4e5f6a7b8c9d0e',
|
|
54
|
+
appUrl: 'https://my.qelos.io',
|
|
55
|
+
apiToken: 'qelos_pk_...',
|
|
56
|
+
position: 'bottom-right',
|
|
57
|
+
theme: 'light',
|
|
58
|
+
headerText: 'Need help?',
|
|
59
|
+
initialMessage: 'Hi! How can I help today?',
|
|
60
|
+
recordThread: true,
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Options
|
|
65
|
+
|
|
66
|
+
| Option | Default | Description |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `agentId` | _required_ | The Qelos AI agent (integration) ID |
|
|
69
|
+
| `appUrl` | _required_ | Base URL of the Qelos instance |
|
|
70
|
+
| `apiToken` | — | Public API token (`qelos_pk_...`). Provide this **or** `accessToken` |
|
|
71
|
+
| `accessToken` | — | Bearer access token for end-user-bound chats |
|
|
72
|
+
| `position` | `"bottom-right"` | `"bottom-right"`, `"bottom-left"`, `"top-right"`, `"top-left"` |
|
|
73
|
+
| `theme` | `"light"` | `"light"` or `"dark"` |
|
|
74
|
+
| `accentColor` | `"#409eff"` | Brand accent color (any CSS color) |
|
|
75
|
+
| `headerText` | `"AI Assistant"` | Title shown in the panel header |
|
|
76
|
+
| `initialMessage` | — | First assistant message shown when the panel opens |
|
|
77
|
+
| `inputPlaceholder` | `"Type your message…"` | Placeholder for the input box |
|
|
78
|
+
| `recordThread` | `false` | Persist the conversation as a Qelos thread |
|
|
79
|
+
| `threadId` | — | Resume an existing thread |
|
|
80
|
+
| `autoOpen` | `false` | Open the panel automatically on load |
|
|
81
|
+
| `chatContext` | — | Object sent as `context` in every chat request |
|
|
82
|
+
| `container` | `document.body` | Element to mount the widget into |
|
|
83
|
+
|
|
84
|
+
The widget is rendered inside a Shadow DOM, so it does not pick up or leak host page styles.
|
|
85
|
+
|
|
27
86
|
Enjoy!
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export interface QelosChatWidgetOptions {
|
|
2
|
+
/** Qelos AI agent (integration) ID to chat with */
|
|
3
|
+
agentId: string;
|
|
4
|
+
/** Base URL of the Qelos instance, e.g. "https://my.qelos.io" */
|
|
5
|
+
appUrl: string;
|
|
6
|
+
/** Public API token used as `x-api-key` (qelos_pk_...) */
|
|
7
|
+
apiToken?: string;
|
|
8
|
+
/** Bearer access token for end-user-bound chats */
|
|
9
|
+
accessToken?: string;
|
|
10
|
+
/** Where to dock the launcher: "bottom-right" (default), "bottom-left", "top-right", "top-left" */
|
|
11
|
+
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
12
|
+
/** Color theme for the widget */
|
|
13
|
+
theme?: 'light' | 'dark';
|
|
14
|
+
/** First assistant message shown when the chat panel opens */
|
|
15
|
+
initialMessage?: string;
|
|
16
|
+
/** Header text inside the chat panel */
|
|
17
|
+
headerText?: string;
|
|
18
|
+
/** Placeholder text in the input box */
|
|
19
|
+
inputPlaceholder?: string;
|
|
20
|
+
/** Persist conversation state on the backend with a thread */
|
|
21
|
+
recordThread?: boolean;
|
|
22
|
+
/** Existing thread ID to resume */
|
|
23
|
+
threadId?: string;
|
|
24
|
+
/** Container to mount into. Defaults to `document.body`. */
|
|
25
|
+
container?: HTMLElement;
|
|
26
|
+
/** Open the panel automatically on load */
|
|
27
|
+
autoOpen?: boolean;
|
|
28
|
+
/** Brand accent color override (CSS color) */
|
|
29
|
+
accentColor?: string;
|
|
30
|
+
/** Optional context object sent with every chat request */
|
|
31
|
+
chatContext?: Record<string, string | number | boolean | undefined | null>;
|
|
32
|
+
}
|
|
33
|
+
export declare class QelosChatWidget {
|
|
34
|
+
private options;
|
|
35
|
+
private host;
|
|
36
|
+
private shadow;
|
|
37
|
+
private launcher;
|
|
38
|
+
private panel;
|
|
39
|
+
private messagesEl;
|
|
40
|
+
private inputEl;
|
|
41
|
+
private sendBtn;
|
|
42
|
+
private statusEl;
|
|
43
|
+
private messages;
|
|
44
|
+
private threadId?;
|
|
45
|
+
private isOpen;
|
|
46
|
+
private isSending;
|
|
47
|
+
private destroyed;
|
|
48
|
+
constructor(options: QelosChatWidgetOptions);
|
|
49
|
+
/** Static helper for the documented `QelosChatWidget.init({...})` pattern. */
|
|
50
|
+
static init(options: QelosChatWidgetOptions): QelosChatWidget;
|
|
51
|
+
open(): void;
|
|
52
|
+
close(): void;
|
|
53
|
+
toggle(): void;
|
|
54
|
+
destroy(): void;
|
|
55
|
+
private render;
|
|
56
|
+
private bindEvents;
|
|
57
|
+
private renderMessages;
|
|
58
|
+
private appendStreamingChunk;
|
|
59
|
+
private scrollToBottom;
|
|
60
|
+
private setStatus;
|
|
61
|
+
private buildHeaders;
|
|
62
|
+
private buildUrl;
|
|
63
|
+
private ensureThread;
|
|
64
|
+
private send;
|
|
65
|
+
private consumeStream;
|
|
66
|
+
private handleSSEEvent;
|
|
67
|
+
}
|
|
68
|
+
export default QelosChatWidget;
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
const POSITION_STYLES = {
|
|
2
|
+
'bottom-right': 'inset-block-end:24px;inset-inline-end:24px;',
|
|
3
|
+
'bottom-left': 'inset-block-end:24px;inset-inline-start:24px;',
|
|
4
|
+
'top-right': 'inset-block-start:24px;inset-inline-end:24px;',
|
|
5
|
+
'top-left': 'inset-block-start:24px;inset-inline-start:24px;',
|
|
6
|
+
};
|
|
7
|
+
function escapeHtml(text) {
|
|
8
|
+
return text
|
|
9
|
+
.replace(/&/g, '&')
|
|
10
|
+
.replace(/</g, '<')
|
|
11
|
+
.replace(/>/g, '>')
|
|
12
|
+
.replace(/"/g, '"')
|
|
13
|
+
.replace(/'/g, ''');
|
|
14
|
+
}
|
|
15
|
+
function renderInlineMarkdown(text) {
|
|
16
|
+
let html = escapeHtml(text);
|
|
17
|
+
html = html.replace(/```([\s\S]*?)```/g, (_, code) => `<pre><code>${code}</code></pre>`);
|
|
18
|
+
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
19
|
+
html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
20
|
+
html = html.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
|
21
|
+
html = html.replace(/\[([^\]]+)\]\((https?:[^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
22
|
+
html = html.replace(/\n/g, '<br>');
|
|
23
|
+
return html;
|
|
24
|
+
}
|
|
25
|
+
export class QelosChatWidget {
|
|
26
|
+
options;
|
|
27
|
+
host;
|
|
28
|
+
shadow;
|
|
29
|
+
launcher;
|
|
30
|
+
panel;
|
|
31
|
+
messagesEl;
|
|
32
|
+
inputEl;
|
|
33
|
+
sendBtn;
|
|
34
|
+
statusEl;
|
|
35
|
+
messages = [];
|
|
36
|
+
threadId;
|
|
37
|
+
isOpen = false;
|
|
38
|
+
isSending = false;
|
|
39
|
+
destroyed = false;
|
|
40
|
+
constructor(options) {
|
|
41
|
+
if (!options || !options.agentId || !options.appUrl) {
|
|
42
|
+
throw new Error('QelosChatWidget: "agentId" and "appUrl" are required.');
|
|
43
|
+
}
|
|
44
|
+
if (!options.apiToken && !options.accessToken) {
|
|
45
|
+
throw new Error('QelosChatWidget: provide either "apiToken" or "accessToken".');
|
|
46
|
+
}
|
|
47
|
+
this.options = {
|
|
48
|
+
position: 'bottom-right',
|
|
49
|
+
theme: 'light',
|
|
50
|
+
headerText: 'AI Assistant',
|
|
51
|
+
inputPlaceholder: 'Type your message…',
|
|
52
|
+
autoOpen: false,
|
|
53
|
+
recordThread: false,
|
|
54
|
+
...options,
|
|
55
|
+
};
|
|
56
|
+
this.threadId = this.options.threadId;
|
|
57
|
+
this.host = document.createElement('div');
|
|
58
|
+
this.host.setAttribute('data-qelos-chat-widget', '');
|
|
59
|
+
this.shadow = this.host.attachShadow({ mode: 'open' });
|
|
60
|
+
(this.options.container || document.body).appendChild(this.host);
|
|
61
|
+
this.render();
|
|
62
|
+
this.bindEvents();
|
|
63
|
+
if (this.options.initialMessage) {
|
|
64
|
+
this.messages.push({ role: 'assistant', content: this.options.initialMessage });
|
|
65
|
+
this.renderMessages();
|
|
66
|
+
}
|
|
67
|
+
if (this.options.autoOpen) {
|
|
68
|
+
this.open();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Static helper for the documented `QelosChatWidget.init({...})` pattern. */
|
|
72
|
+
static init(options) {
|
|
73
|
+
return new QelosChatWidget(options);
|
|
74
|
+
}
|
|
75
|
+
open() {
|
|
76
|
+
if (this.destroyed)
|
|
77
|
+
return;
|
|
78
|
+
this.isOpen = true;
|
|
79
|
+
this.panel.setAttribute('data-open', 'true');
|
|
80
|
+
this.launcher.setAttribute('aria-expanded', 'true');
|
|
81
|
+
setTimeout(() => this.inputEl.focus(), 50);
|
|
82
|
+
}
|
|
83
|
+
close() {
|
|
84
|
+
if (this.destroyed)
|
|
85
|
+
return;
|
|
86
|
+
this.isOpen = false;
|
|
87
|
+
this.panel.removeAttribute('data-open');
|
|
88
|
+
this.launcher.setAttribute('aria-expanded', 'false');
|
|
89
|
+
}
|
|
90
|
+
toggle() {
|
|
91
|
+
this.isOpen ? this.close() : this.open();
|
|
92
|
+
}
|
|
93
|
+
destroy() {
|
|
94
|
+
this.destroyed = true;
|
|
95
|
+
this.host.remove();
|
|
96
|
+
}
|
|
97
|
+
render() {
|
|
98
|
+
const { position, theme, headerText, inputPlaceholder, accentColor } = this.options;
|
|
99
|
+
const accent = accentColor || '#409eff';
|
|
100
|
+
const positionStyle = POSITION_STYLES[position];
|
|
101
|
+
const style = document.createElement('style');
|
|
102
|
+
style.textContent = `
|
|
103
|
+
:host { all: initial; }
|
|
104
|
+
.root {
|
|
105
|
+
position: fixed;
|
|
106
|
+
${positionStyle}
|
|
107
|
+
z-index: 2147483000;
|
|
108
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
109
|
+
--ql-accent: ${accent};
|
|
110
|
+
--ql-bg: ${theme === 'dark' ? '#1f2230' : '#ffffff'};
|
|
111
|
+
--ql-bg-soft: ${theme === 'dark' ? '#272a3a' : '#f6f8fb'};
|
|
112
|
+
--ql-text: ${theme === 'dark' ? '#e6e8f1' : '#0f172a'};
|
|
113
|
+
--ql-text-soft: ${theme === 'dark' ? '#9aa1bd' : '#5a6478'};
|
|
114
|
+
--ql-border: ${theme === 'dark' ? '#34384d' : 'rgba(15,23,42,0.08)'};
|
|
115
|
+
--ql-user-bg: ${theme === 'dark' ? '#3a4063' : 'rgba(64,158,255,0.12)'};
|
|
116
|
+
--ql-assistant-bg: ${theme === 'dark' ? '#272a3a' : '#ffffff'};
|
|
117
|
+
}
|
|
118
|
+
button { font: inherit; cursor: pointer; }
|
|
119
|
+
.launcher {
|
|
120
|
+
width: 56px; height: 56px;
|
|
121
|
+
border-radius: 50%;
|
|
122
|
+
border: none;
|
|
123
|
+
background: var(--ql-accent);
|
|
124
|
+
color: #fff;
|
|
125
|
+
box-shadow: 0 10px 28px rgba(15,23,42,0.22);
|
|
126
|
+
display: flex; align-items: center; justify-content: center;
|
|
127
|
+
transition: transform 0.18s ease, box-shadow 0.18s ease;
|
|
128
|
+
}
|
|
129
|
+
.launcher:hover { transform: translateY(-2px); box-shadow: 0 14px 32px rgba(15,23,42,0.28); }
|
|
130
|
+
.launcher:active { transform: translateY(0); }
|
|
131
|
+
.launcher svg { width: 26px; height: 26px; }
|
|
132
|
+
.panel {
|
|
133
|
+
position: absolute;
|
|
134
|
+
${position.includes('bottom') ? 'inset-block-end: 72px;' : 'inset-block-start: 72px;'}
|
|
135
|
+
${position.includes('right') ? 'inset-inline-end: 0;' : 'inset-inline-start: 0;'}
|
|
136
|
+
width: min(380px, calc(100vw - 32px));
|
|
137
|
+
height: min(560px, calc(100vh - 120px));
|
|
138
|
+
background: var(--ql-bg);
|
|
139
|
+
color: var(--ql-text);
|
|
140
|
+
border: 1px solid var(--ql-border);
|
|
141
|
+
border-radius: 16px;
|
|
142
|
+
box-shadow: 0 24px 60px rgba(15,23,42,0.22);
|
|
143
|
+
display: none;
|
|
144
|
+
flex-direction: column;
|
|
145
|
+
overflow: hidden;
|
|
146
|
+
transform-origin: ${position.includes('bottom') ? 'bottom' : 'top'} ${position.includes('right') ? 'right' : 'left'};
|
|
147
|
+
animation: pop 0.18s ease;
|
|
148
|
+
}
|
|
149
|
+
.panel[data-open="true"] { display: flex; }
|
|
150
|
+
@keyframes pop {
|
|
151
|
+
from { opacity: 0; transform: scale(0.94); }
|
|
152
|
+
to { opacity: 1; transform: scale(1); }
|
|
153
|
+
}
|
|
154
|
+
header {
|
|
155
|
+
display: flex; align-items: center; gap: 10px;
|
|
156
|
+
padding: 14px 16px;
|
|
157
|
+
background: var(--ql-accent);
|
|
158
|
+
color: #fff;
|
|
159
|
+
}
|
|
160
|
+
header .title { font-weight: 600; font-size: 0.95rem; flex: 1; }
|
|
161
|
+
header button {
|
|
162
|
+
background: transparent; border: none; color: inherit;
|
|
163
|
+
width: 28px; height: 28px; border-radius: 50%;
|
|
164
|
+
display: flex; align-items: center; justify-content: center;
|
|
165
|
+
opacity: 0.85;
|
|
166
|
+
}
|
|
167
|
+
header button:hover { background: rgba(255,255,255,0.18); opacity: 1; }
|
|
168
|
+
.messages {
|
|
169
|
+
flex: 1; overflow-y: auto;
|
|
170
|
+
padding: 14px;
|
|
171
|
+
display: flex; flex-direction: column; gap: 8px;
|
|
172
|
+
background: var(--ql-bg-soft);
|
|
173
|
+
}
|
|
174
|
+
.msg { max-width: 85%; padding: 8px 12px; border-radius: 12px; line-height: 1.45; font-size: 0.92rem; word-wrap: break-word; white-space: pre-wrap; }
|
|
175
|
+
.msg.user { align-self: flex-end; background: var(--ql-user-bg); color: var(--ql-text); border-bottom-right-radius: 4px; }
|
|
176
|
+
.msg.assistant { align-self: flex-start; background: var(--ql-assistant-bg); color: var(--ql-text); border: 1px solid var(--ql-border); border-bottom-left-radius: 4px; }
|
|
177
|
+
.msg code { background: rgba(0,0,0,0.08); padding: 0 4px; border-radius: 3px; font-family: ui-monospace, Menlo, monospace; font-size: 0.85em; }
|
|
178
|
+
.msg pre { background: rgba(0,0,0,0.08); padding: 8px; border-radius: 6px; overflow-x: auto; }
|
|
179
|
+
.msg pre code { background: none; padding: 0; }
|
|
180
|
+
.msg a { color: var(--ql-accent); text-decoration: none; }
|
|
181
|
+
.msg a:hover { text-decoration: underline; }
|
|
182
|
+
.typing { display: inline-flex; gap: 4px; align-items: center; padding: 8px 12px; }
|
|
183
|
+
.typing span { width: 6px; height: 6px; background: var(--ql-text-soft); border-radius: 50%; opacity: 0.4; animation: blink 1.4s infinite; }
|
|
184
|
+
.typing span:nth-child(2) { animation-delay: 0.2s; }
|
|
185
|
+
.typing span:nth-child(3) { animation-delay: 0.4s; }
|
|
186
|
+
@keyframes blink { 0%,80%,100% { opacity: 0.3; } 40% { opacity: 1; } }
|
|
187
|
+
.status { padding: 0 14px; font-size: 0.78rem; color: var(--ql-text-soft); min-height: 18px; }
|
|
188
|
+
.status.error { color: #d24c4c; }
|
|
189
|
+
form {
|
|
190
|
+
display: flex; gap: 8px; padding: 10px 12px;
|
|
191
|
+
border-top: 1px solid var(--ql-border);
|
|
192
|
+
background: var(--ql-bg);
|
|
193
|
+
}
|
|
194
|
+
textarea {
|
|
195
|
+
flex: 1; resize: none; border: 1px solid var(--ql-border); background: var(--ql-bg-soft);
|
|
196
|
+
color: var(--ql-text); border-radius: 10px; padding: 8px 10px;
|
|
197
|
+
font: inherit; font-size: 0.92rem; max-height: 120px; min-height: 38px;
|
|
198
|
+
outline: none;
|
|
199
|
+
}
|
|
200
|
+
textarea:focus { border-color: var(--ql-accent); box-shadow: 0 0 0 2px rgba(64,158,255,0.18); }
|
|
201
|
+
.send {
|
|
202
|
+
background: var(--ql-accent); color: #fff;
|
|
203
|
+
border: none; border-radius: 10px;
|
|
204
|
+
width: 40px; display: flex; align-items: center; justify-content: center;
|
|
205
|
+
}
|
|
206
|
+
.send:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
207
|
+
.send svg { width: 18px; height: 18px; }
|
|
208
|
+
`;
|
|
209
|
+
const root = document.createElement('div');
|
|
210
|
+
root.className = 'root';
|
|
211
|
+
root.innerHTML = `
|
|
212
|
+
<button class="launcher" type="button" aria-label="Open chat" aria-expanded="false">
|
|
213
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
214
|
+
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
|
|
215
|
+
</svg>
|
|
216
|
+
</button>
|
|
217
|
+
<div class="panel" role="dialog" aria-label="Chat">
|
|
218
|
+
<header>
|
|
219
|
+
<div class="title"></div>
|
|
220
|
+
<button type="button" class="close" aria-label="Close chat">
|
|
221
|
+
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
222
|
+
</button>
|
|
223
|
+
</header>
|
|
224
|
+
<div class="messages" role="log" aria-live="polite"></div>
|
|
225
|
+
<div class="status"></div>
|
|
226
|
+
<form>
|
|
227
|
+
<textarea rows="1" autocomplete="off"></textarea>
|
|
228
|
+
<button class="send" type="submit" aria-label="Send">
|
|
229
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
|
|
230
|
+
</button>
|
|
231
|
+
</form>
|
|
232
|
+
</div>
|
|
233
|
+
`;
|
|
234
|
+
this.shadow.appendChild(style);
|
|
235
|
+
this.shadow.appendChild(root);
|
|
236
|
+
this.launcher = root.querySelector('.launcher');
|
|
237
|
+
this.panel = root.querySelector('.panel');
|
|
238
|
+
this.messagesEl = root.querySelector('.messages');
|
|
239
|
+
this.inputEl = root.querySelector('textarea');
|
|
240
|
+
this.sendBtn = root.querySelector('.send');
|
|
241
|
+
this.statusEl = root.querySelector('.status');
|
|
242
|
+
root.querySelector('.title').textContent = headerText;
|
|
243
|
+
this.inputEl.placeholder = inputPlaceholder;
|
|
244
|
+
}
|
|
245
|
+
bindEvents() {
|
|
246
|
+
this.launcher.addEventListener('click', () => this.toggle());
|
|
247
|
+
this.shadow.querySelector('header .close').addEventListener('click', () => this.close());
|
|
248
|
+
const form = this.shadow.querySelector('form');
|
|
249
|
+
form.addEventListener('submit', (e) => {
|
|
250
|
+
e.preventDefault();
|
|
251
|
+
this.send();
|
|
252
|
+
});
|
|
253
|
+
this.inputEl.addEventListener('keydown', (e) => {
|
|
254
|
+
if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
|
|
255
|
+
e.preventDefault();
|
|
256
|
+
this.send();
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
this.inputEl.addEventListener('input', () => {
|
|
260
|
+
this.inputEl.style.height = 'auto';
|
|
261
|
+
this.inputEl.style.height = Math.min(this.inputEl.scrollHeight, 120) + 'px';
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
renderMessages() {
|
|
265
|
+
this.messagesEl.innerHTML = '';
|
|
266
|
+
for (const msg of this.messages) {
|
|
267
|
+
const el = document.createElement('div');
|
|
268
|
+
el.className = `msg ${msg.role}`;
|
|
269
|
+
el.innerHTML = renderInlineMarkdown(msg.content);
|
|
270
|
+
this.messagesEl.appendChild(el);
|
|
271
|
+
}
|
|
272
|
+
this.scrollToBottom();
|
|
273
|
+
}
|
|
274
|
+
appendStreamingChunk(chunk) {
|
|
275
|
+
const last = this.messages[this.messages.length - 1];
|
|
276
|
+
if (!last || last.role !== 'assistant' || !last.streaming)
|
|
277
|
+
return;
|
|
278
|
+
last.content += chunk;
|
|
279
|
+
const lastEl = this.messagesEl.lastElementChild;
|
|
280
|
+
if (lastEl) {
|
|
281
|
+
lastEl.innerHTML = renderInlineMarkdown(last.content);
|
|
282
|
+
this.scrollToBottom();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
scrollToBottom() {
|
|
286
|
+
this.messagesEl.scrollTop = this.messagesEl.scrollHeight;
|
|
287
|
+
}
|
|
288
|
+
setStatus(text, isError = false) {
|
|
289
|
+
this.statusEl.textContent = text;
|
|
290
|
+
this.statusEl.classList.toggle('error', isError);
|
|
291
|
+
}
|
|
292
|
+
buildHeaders() {
|
|
293
|
+
const headers = {
|
|
294
|
+
'content-type': 'application/json',
|
|
295
|
+
accept: 'text/event-stream',
|
|
296
|
+
};
|
|
297
|
+
if (this.options.apiToken) {
|
|
298
|
+
headers['x-api-key'] = this.options.apiToken;
|
|
299
|
+
}
|
|
300
|
+
else if (this.options.accessToken) {
|
|
301
|
+
headers.authorization = 'Bearer ' + this.options.accessToken;
|
|
302
|
+
}
|
|
303
|
+
return headers;
|
|
304
|
+
}
|
|
305
|
+
buildUrl(path) {
|
|
306
|
+
const base = this.options.appUrl.replace(/\/+$/, '');
|
|
307
|
+
return base + path;
|
|
308
|
+
}
|
|
309
|
+
async ensureThread() {
|
|
310
|
+
if (!this.options.recordThread || this.threadId)
|
|
311
|
+
return this.threadId;
|
|
312
|
+
const headers = this.buildHeaders();
|
|
313
|
+
delete headers.accept;
|
|
314
|
+
const res = await fetch(this.buildUrl('/api/ai/threads'), {
|
|
315
|
+
method: 'POST',
|
|
316
|
+
headers,
|
|
317
|
+
body: JSON.stringify({ integration: this.options.agentId }),
|
|
318
|
+
});
|
|
319
|
+
if (!res.ok)
|
|
320
|
+
throw new Error(`Failed to create thread: HTTP ${res.status}`);
|
|
321
|
+
const thread = await res.json();
|
|
322
|
+
this.threadId = thread._id;
|
|
323
|
+
return this.threadId;
|
|
324
|
+
}
|
|
325
|
+
async send() {
|
|
326
|
+
if (this.destroyed || this.isSending)
|
|
327
|
+
return;
|
|
328
|
+
const text = this.inputEl.value.trim();
|
|
329
|
+
if (!text)
|
|
330
|
+
return;
|
|
331
|
+
this.isSending = true;
|
|
332
|
+
this.sendBtn.disabled = true;
|
|
333
|
+
this.inputEl.disabled = true;
|
|
334
|
+
this.inputEl.value = '';
|
|
335
|
+
this.inputEl.style.height = 'auto';
|
|
336
|
+
this.setStatus('');
|
|
337
|
+
this.messages.push({ role: 'user', content: text });
|
|
338
|
+
this.messages.push({ role: 'assistant', content: '', streaming: true });
|
|
339
|
+
this.renderMessages();
|
|
340
|
+
this.setStatus('AI is typing…');
|
|
341
|
+
try {
|
|
342
|
+
const threadId = await this.ensureThread();
|
|
343
|
+
const path = threadId
|
|
344
|
+
? `/api/ai/${this.options.agentId}/chat-completion/${threadId}?stream=true`
|
|
345
|
+
: `/api/ai/${this.options.agentId}/chat-completion?stream=true`;
|
|
346
|
+
const payload = {
|
|
347
|
+
messages: this.messages
|
|
348
|
+
.filter(m => m.role === 'user' || (m.role === 'assistant' && !m.streaming))
|
|
349
|
+
.map(m => ({ role: m.role, content: m.content })),
|
|
350
|
+
stream: true,
|
|
351
|
+
};
|
|
352
|
+
if (this.options.chatContext)
|
|
353
|
+
payload.context = this.options.chatContext;
|
|
354
|
+
const res = await fetch(this.buildUrl(path), {
|
|
355
|
+
method: 'POST',
|
|
356
|
+
headers: this.buildHeaders(),
|
|
357
|
+
body: JSON.stringify(payload),
|
|
358
|
+
});
|
|
359
|
+
if (!res.ok || !res.body) {
|
|
360
|
+
throw new Error(`HTTP ${res.status}: ${res.statusText || 'Request failed'}`);
|
|
361
|
+
}
|
|
362
|
+
await this.consumeStream(res.body);
|
|
363
|
+
const last = this.messages[this.messages.length - 1];
|
|
364
|
+
if (last)
|
|
365
|
+
last.streaming = false;
|
|
366
|
+
this.setStatus('');
|
|
367
|
+
}
|
|
368
|
+
catch (err) {
|
|
369
|
+
const last = this.messages[this.messages.length - 1];
|
|
370
|
+
if (last && last.streaming) {
|
|
371
|
+
last.streaming = false;
|
|
372
|
+
if (!last.content)
|
|
373
|
+
last.content = '_Sorry, I could not get a response. Please try again._';
|
|
374
|
+
this.renderMessages();
|
|
375
|
+
}
|
|
376
|
+
this.setStatus(err instanceof Error ? err.message : 'Something went wrong', true);
|
|
377
|
+
}
|
|
378
|
+
finally {
|
|
379
|
+
this.isSending = false;
|
|
380
|
+
this.sendBtn.disabled = false;
|
|
381
|
+
this.inputEl.disabled = false;
|
|
382
|
+
this.inputEl.focus();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
async consumeStream(stream) {
|
|
386
|
+
const reader = stream.getReader();
|
|
387
|
+
const decoder = new TextDecoder();
|
|
388
|
+
let buffer = '';
|
|
389
|
+
try {
|
|
390
|
+
while (true) {
|
|
391
|
+
const { done, value } = await reader.read();
|
|
392
|
+
if (done)
|
|
393
|
+
break;
|
|
394
|
+
buffer += decoder.decode(value, { stream: true });
|
|
395
|
+
const lines = buffer.split(/\r?\n/);
|
|
396
|
+
buffer = lines.pop() || '';
|
|
397
|
+
for (const line of lines) {
|
|
398
|
+
if (!line.startsWith('data: '))
|
|
399
|
+
continue;
|
|
400
|
+
const data = line.slice(6);
|
|
401
|
+
if (data === '[DONE]')
|
|
402
|
+
return;
|
|
403
|
+
let parsed;
|
|
404
|
+
try {
|
|
405
|
+
parsed = JSON.parse(data);
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
this.handleSSEEvent(parsed);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
finally {
|
|
415
|
+
reader.releaseLock();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
handleSSEEvent(data) {
|
|
419
|
+
if (!data || typeof data !== 'object')
|
|
420
|
+
return;
|
|
421
|
+
if (data.type === 'connection_established')
|
|
422
|
+
return;
|
|
423
|
+
if (data.type === 'continuing_conversation') {
|
|
424
|
+
const last = this.messages[this.messages.length - 1];
|
|
425
|
+
if (last && last.streaming) {
|
|
426
|
+
last.content = '';
|
|
427
|
+
this.renderMessages();
|
|
428
|
+
}
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (data.type === 'chunk' || data.type === 'followup_chunk') {
|
|
432
|
+
if (typeof data.content === 'string' && data.content) {
|
|
433
|
+
this.appendStreamingChunk(data.content);
|
|
434
|
+
}
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if (data.type === 'done')
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
export default QelosChatWidget;
|
|
442
|
+
//# sourceMappingURL=chat-widget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-widget.js","sourceRoot":"","sources":["../src/chat-widget.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -2,5 +2,6 @@ import loadStyles from './shared-style';
|
|
|
2
2
|
import { getCode, authorize, unAuthorize } from './auth';
|
|
3
3
|
export const code = getCode();
|
|
4
4
|
export { authorize, unAuthorize };
|
|
5
|
+
export { QelosChatWidget } from './chat-widget';
|
|
5
6
|
loadStyles();
|
|
6
7
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAC,MAAM,QAAQ,CAAC;AAEvD,MAAM,CAAC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;AAE9B,OAAO,EACL,SAAS,EACT,WAAW,EACZ,CAAA;AAED,UAAU,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAC,MAAM,QAAQ,CAAC;AAEvD,MAAM,CAAC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;AAE9B,OAAO,EACL,SAAS,EACT,WAAW,EACZ,CAAA;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGhD,UAAU,EAAE,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { QelosChatWidget } from './chat-widget';
|
|
2
|
+
function getCurrentScript() {
|
|
3
|
+
if (document.currentScript && document.currentScript instanceof HTMLScriptElement) {
|
|
4
|
+
return document.currentScript;
|
|
5
|
+
}
|
|
6
|
+
const scripts = document.getElementsByTagName('script');
|
|
7
|
+
for (let i = scripts.length - 1; i >= 0; i--) {
|
|
8
|
+
if (scripts[i].dataset && scripts[i].dataset.agentId)
|
|
9
|
+
return scripts[i];
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
function readOptionsFromScript(script) {
|
|
14
|
+
const ds = script.dataset;
|
|
15
|
+
if (!ds.agentId || !ds.appUrl)
|
|
16
|
+
return null;
|
|
17
|
+
const opts = {
|
|
18
|
+
agentId: ds.agentId,
|
|
19
|
+
appUrl: ds.appUrl,
|
|
20
|
+
apiToken: ds.apiToken,
|
|
21
|
+
accessToken: ds.accessToken,
|
|
22
|
+
headerText: ds.headerText,
|
|
23
|
+
initialMessage: ds.initialMessage,
|
|
24
|
+
inputPlaceholder: ds.inputPlaceholder,
|
|
25
|
+
accentColor: ds.accentColor,
|
|
26
|
+
threadId: ds.threadId,
|
|
27
|
+
autoOpen: ds.autoOpen === 'true',
|
|
28
|
+
recordThread: ds.recordThread === 'true',
|
|
29
|
+
};
|
|
30
|
+
if (ds.position)
|
|
31
|
+
opts.position = ds.position;
|
|
32
|
+
if (ds.theme)
|
|
33
|
+
opts.theme = ds.theme;
|
|
34
|
+
return opts;
|
|
35
|
+
}
|
|
36
|
+
function autoInit() {
|
|
37
|
+
const script = getCurrentScript();
|
|
38
|
+
if (!script)
|
|
39
|
+
return;
|
|
40
|
+
const options = readOptionsFromScript(script);
|
|
41
|
+
if (!options)
|
|
42
|
+
return;
|
|
43
|
+
const start = () => {
|
|
44
|
+
try {
|
|
45
|
+
QelosChatWidget.init(options);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
console.error('[QelosChatWidget]', err);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
if (document.readyState === 'loading') {
|
|
52
|
+
document.addEventListener('DOMContentLoaded', start, { once: true });
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
start();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (typeof window !== 'undefined') {
|
|
59
|
+
window.QelosChatWidget = QelosChatWidget;
|
|
60
|
+
autoInit();
|
|
61
|
+
}
|
|
62
|
+
export { QelosChatWidget };
|
|
63
|
+
//# sourceMappingURL=widget-cdn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget-cdn.js","sourceRoot":"","sources":["../src/widget-cdn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAA0B,MAAM,eAAe,CAAC;AAQxE,SAAS,gBAAgB;IACvB,IAAI,QAAQ,CAAC,aAAa,IAAI,QAAQ,CAAC,aAAa,YAAY,iBAAiB,EAAE;QACjF,OAAO,QAAQ,CAAC,aAAa,CAAC;KAC/B;IACD,MAAM,OAAO,GAAG,QAAQ,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACxD,KAAK,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE;QAC5C,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO;YAAE,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC;KACzE;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,qBAAqB,CAAC,MAAyB;IACtD,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,CAAC,OAAO,IAAI,CAAC,EAAE,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAC3C,MAAM,IAAI,GAA2B;QACnC,OAAO,EAAE,EAAE,CAAC,OAAO;QACnB,MAAM,EAAE,EAAE,CAAC,MAAM;QACjB,QAAQ,EAAE,EAAE,CAAC,QAAQ;QACrB,WAAW,EAAE,EAAE,CAAC,WAAW;QAC3B,UAAU,EAAE,EAAE,CAAC,UAAU;QACzB,cAAc,EAAE,EAAE,CAAC,cAAc;QACjC,gBAAgB,EAAE,EAAE,CAAC,gBAAgB;QACrC,WAAW,EAAE,EAAE,CAAC,WAAW;QAC3B,QAAQ,EAAE,EAAE,CAAC,QAAQ;QACrB,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,MAAM;QAChC,YAAY,EAAE,EAAE,CAAC,YAAY,KAAK,MAAM;KACzC,CAAC;IACF,IAAI,EAAE,CAAC,QAAQ;QAAE,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC,QAA8C,CAAC;IACnF,IAAI,EAAE,CAAC,KAAK;QAAE,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC,KAAwC,CAAC;IACvE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,QAAQ;IACf,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,IAAI,CAAC,MAAM;QAAE,OAAO;IACpB,MAAM,OAAO,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9C,IAAI,CAAC,OAAO;QAAE,OAAO;IACrB,MAAM,KAAK,GAAG,GAAG,EAAE;QACjB,IAAI;YACF,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;SAC/B;QAAC,OAAO,GAAG,EAAE;YACZ,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC;SACzC;IACH,CAAC,CAAC;IACF,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS,EAAE;QACrC,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;KACtE;SAAM;QACL,KAAK,EAAE,CAAC;KACT;AACH,CAAC;AAED,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE;IACjC,MAAM,CAAC,eAAe,GAAG,eAAe,CAAC;IACzC,QAAQ,EAAE,CAAC;CACZ;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
export interface QelosChatWidgetOptions {
|
|
2
|
+
/** Qelos AI agent (integration) ID to chat with */
|
|
3
|
+
agentId: string;
|
|
4
|
+
/** Base URL of the Qelos instance, e.g. "https://my.qelos.io" */
|
|
5
|
+
appUrl: string;
|
|
6
|
+
/** Public API token used as `x-api-key` (qelos_pk_...) */
|
|
7
|
+
apiToken?: string;
|
|
8
|
+
/** Bearer access token for end-user-bound chats */
|
|
9
|
+
accessToken?: string;
|
|
10
|
+
/** Where to dock the launcher: "bottom-right" (default), "bottom-left", "top-right", "top-left" */
|
|
11
|
+
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
12
|
+
/** Color theme for the widget */
|
|
13
|
+
theme?: 'light' | 'dark';
|
|
14
|
+
/** First assistant message shown when the chat panel opens */
|
|
15
|
+
initialMessage?: string;
|
|
16
|
+
/** Header text inside the chat panel */
|
|
17
|
+
headerText?: string;
|
|
18
|
+
/** Placeholder text in the input box */
|
|
19
|
+
inputPlaceholder?: string;
|
|
20
|
+
/** Persist conversation state on the backend with a thread */
|
|
21
|
+
recordThread?: boolean;
|
|
22
|
+
/** Existing thread ID to resume */
|
|
23
|
+
threadId?: string;
|
|
24
|
+
/** Container to mount into. Defaults to `document.body`. */
|
|
25
|
+
container?: HTMLElement;
|
|
26
|
+
/** Open the panel automatically on load */
|
|
27
|
+
autoOpen?: boolean;
|
|
28
|
+
/** Brand accent color override (CSS color) */
|
|
29
|
+
accentColor?: string;
|
|
30
|
+
/** Optional context object sent with every chat request */
|
|
31
|
+
chatContext?: Record<string, string | number | boolean | undefined | null>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface InternalMessage {
|
|
35
|
+
role: 'user' | 'assistant';
|
|
36
|
+
content: string;
|
|
37
|
+
streaming?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const POSITION_STYLES: Record<NonNullable<QelosChatWidgetOptions['position']>, string> = {
|
|
41
|
+
'bottom-right': 'inset-block-end:24px;inset-inline-end:24px;',
|
|
42
|
+
'bottom-left': 'inset-block-end:24px;inset-inline-start:24px;',
|
|
43
|
+
'top-right': 'inset-block-start:24px;inset-inline-end:24px;',
|
|
44
|
+
'top-left': 'inset-block-start:24px;inset-inline-start:24px;',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function escapeHtml(text: string): string {
|
|
48
|
+
return text
|
|
49
|
+
.replace(/&/g, '&')
|
|
50
|
+
.replace(/</g, '<')
|
|
51
|
+
.replace(/>/g, '>')
|
|
52
|
+
.replace(/"/g, '"')
|
|
53
|
+
.replace(/'/g, ''');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function renderInlineMarkdown(text: string): string {
|
|
57
|
+
let html = escapeHtml(text);
|
|
58
|
+
html = html.replace(/```([\s\S]*?)```/g, (_, code) => `<pre><code>${code}</code></pre>`);
|
|
59
|
+
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
60
|
+
html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
61
|
+
html = html.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
|
62
|
+
html = html.replace(/\[([^\]]+)\]\((https?:[^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
63
|
+
html = html.replace(/\n/g, '<br>');
|
|
64
|
+
return html;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export class QelosChatWidget {
|
|
68
|
+
private options: Required<Pick<QelosChatWidgetOptions,
|
|
69
|
+
'position' | 'theme' | 'headerText' | 'inputPlaceholder' | 'autoOpen' | 'recordThread'
|
|
70
|
+
>> & QelosChatWidgetOptions;
|
|
71
|
+
|
|
72
|
+
private host: HTMLElement;
|
|
73
|
+
private shadow: ShadowRoot;
|
|
74
|
+
private launcher!: HTMLButtonElement;
|
|
75
|
+
private panel!: HTMLDivElement;
|
|
76
|
+
private messagesEl!: HTMLDivElement;
|
|
77
|
+
private inputEl!: HTMLTextAreaElement;
|
|
78
|
+
private sendBtn!: HTMLButtonElement;
|
|
79
|
+
private statusEl!: HTMLDivElement;
|
|
80
|
+
|
|
81
|
+
private messages: InternalMessage[] = [];
|
|
82
|
+
private threadId?: string;
|
|
83
|
+
private isOpen = false;
|
|
84
|
+
private isSending = false;
|
|
85
|
+
private destroyed = false;
|
|
86
|
+
|
|
87
|
+
constructor(options: QelosChatWidgetOptions) {
|
|
88
|
+
if (!options || !options.agentId || !options.appUrl) {
|
|
89
|
+
throw new Error('QelosChatWidget: "agentId" and "appUrl" are required.');
|
|
90
|
+
}
|
|
91
|
+
if (!options.apiToken && !options.accessToken) {
|
|
92
|
+
throw new Error('QelosChatWidget: provide either "apiToken" or "accessToken".');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.options = {
|
|
96
|
+
position: 'bottom-right',
|
|
97
|
+
theme: 'light',
|
|
98
|
+
headerText: 'AI Assistant',
|
|
99
|
+
inputPlaceholder: 'Type your message…',
|
|
100
|
+
autoOpen: false,
|
|
101
|
+
recordThread: false,
|
|
102
|
+
...options,
|
|
103
|
+
};
|
|
104
|
+
this.threadId = this.options.threadId;
|
|
105
|
+
|
|
106
|
+
this.host = document.createElement('div');
|
|
107
|
+
this.host.setAttribute('data-qelos-chat-widget', '');
|
|
108
|
+
this.shadow = this.host.attachShadow({ mode: 'open' });
|
|
109
|
+
|
|
110
|
+
(this.options.container || document.body).appendChild(this.host);
|
|
111
|
+
|
|
112
|
+
this.render();
|
|
113
|
+
this.bindEvents();
|
|
114
|
+
|
|
115
|
+
if (this.options.initialMessage) {
|
|
116
|
+
this.messages.push({ role: 'assistant', content: this.options.initialMessage });
|
|
117
|
+
this.renderMessages();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (this.options.autoOpen) {
|
|
121
|
+
this.open();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Static helper for the documented `QelosChatWidget.init({...})` pattern. */
|
|
126
|
+
static init(options: QelosChatWidgetOptions): QelosChatWidget {
|
|
127
|
+
return new QelosChatWidget(options);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
open(): void {
|
|
131
|
+
if (this.destroyed) return;
|
|
132
|
+
this.isOpen = true;
|
|
133
|
+
this.panel.setAttribute('data-open', 'true');
|
|
134
|
+
this.launcher.setAttribute('aria-expanded', 'true');
|
|
135
|
+
setTimeout(() => this.inputEl.focus(), 50);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
close(): void {
|
|
139
|
+
if (this.destroyed) return;
|
|
140
|
+
this.isOpen = false;
|
|
141
|
+
this.panel.removeAttribute('data-open');
|
|
142
|
+
this.launcher.setAttribute('aria-expanded', 'false');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
toggle(): void {
|
|
146
|
+
this.isOpen ? this.close() : this.open();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
destroy(): void {
|
|
150
|
+
this.destroyed = true;
|
|
151
|
+
this.host.remove();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private render(): void {
|
|
155
|
+
const { position, theme, headerText, inputPlaceholder, accentColor } = this.options;
|
|
156
|
+
const accent = accentColor || '#409eff';
|
|
157
|
+
const positionStyle = POSITION_STYLES[position];
|
|
158
|
+
|
|
159
|
+
const style = document.createElement('style');
|
|
160
|
+
style.textContent = `
|
|
161
|
+
:host { all: initial; }
|
|
162
|
+
.root {
|
|
163
|
+
position: fixed;
|
|
164
|
+
${positionStyle}
|
|
165
|
+
z-index: 2147483000;
|
|
166
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
167
|
+
--ql-accent: ${accent};
|
|
168
|
+
--ql-bg: ${theme === 'dark' ? '#1f2230' : '#ffffff'};
|
|
169
|
+
--ql-bg-soft: ${theme === 'dark' ? '#272a3a' : '#f6f8fb'};
|
|
170
|
+
--ql-text: ${theme === 'dark' ? '#e6e8f1' : '#0f172a'};
|
|
171
|
+
--ql-text-soft: ${theme === 'dark' ? '#9aa1bd' : '#5a6478'};
|
|
172
|
+
--ql-border: ${theme === 'dark' ? '#34384d' : 'rgba(15,23,42,0.08)'};
|
|
173
|
+
--ql-user-bg: ${theme === 'dark' ? '#3a4063' : 'rgba(64,158,255,0.12)'};
|
|
174
|
+
--ql-assistant-bg: ${theme === 'dark' ? '#272a3a' : '#ffffff'};
|
|
175
|
+
}
|
|
176
|
+
button { font: inherit; cursor: pointer; }
|
|
177
|
+
.launcher {
|
|
178
|
+
width: 56px; height: 56px;
|
|
179
|
+
border-radius: 50%;
|
|
180
|
+
border: none;
|
|
181
|
+
background: var(--ql-accent);
|
|
182
|
+
color: #fff;
|
|
183
|
+
box-shadow: 0 10px 28px rgba(15,23,42,0.22);
|
|
184
|
+
display: flex; align-items: center; justify-content: center;
|
|
185
|
+
transition: transform 0.18s ease, box-shadow 0.18s ease;
|
|
186
|
+
}
|
|
187
|
+
.launcher:hover { transform: translateY(-2px); box-shadow: 0 14px 32px rgba(15,23,42,0.28); }
|
|
188
|
+
.launcher:active { transform: translateY(0); }
|
|
189
|
+
.launcher svg { width: 26px; height: 26px; }
|
|
190
|
+
.panel {
|
|
191
|
+
position: absolute;
|
|
192
|
+
${position.includes('bottom') ? 'inset-block-end: 72px;' : 'inset-block-start: 72px;'}
|
|
193
|
+
${position.includes('right') ? 'inset-inline-end: 0;' : 'inset-inline-start: 0;'}
|
|
194
|
+
width: min(380px, calc(100vw - 32px));
|
|
195
|
+
height: min(560px, calc(100vh - 120px));
|
|
196
|
+
background: var(--ql-bg);
|
|
197
|
+
color: var(--ql-text);
|
|
198
|
+
border: 1px solid var(--ql-border);
|
|
199
|
+
border-radius: 16px;
|
|
200
|
+
box-shadow: 0 24px 60px rgba(15,23,42,0.22);
|
|
201
|
+
display: none;
|
|
202
|
+
flex-direction: column;
|
|
203
|
+
overflow: hidden;
|
|
204
|
+
transform-origin: ${position.includes('bottom') ? 'bottom' : 'top'} ${position.includes('right') ? 'right' : 'left'};
|
|
205
|
+
animation: pop 0.18s ease;
|
|
206
|
+
}
|
|
207
|
+
.panel[data-open="true"] { display: flex; }
|
|
208
|
+
@keyframes pop {
|
|
209
|
+
from { opacity: 0; transform: scale(0.94); }
|
|
210
|
+
to { opacity: 1; transform: scale(1); }
|
|
211
|
+
}
|
|
212
|
+
header {
|
|
213
|
+
display: flex; align-items: center; gap: 10px;
|
|
214
|
+
padding: 14px 16px;
|
|
215
|
+
background: var(--ql-accent);
|
|
216
|
+
color: #fff;
|
|
217
|
+
}
|
|
218
|
+
header .title { font-weight: 600; font-size: 0.95rem; flex: 1; }
|
|
219
|
+
header button {
|
|
220
|
+
background: transparent; border: none; color: inherit;
|
|
221
|
+
width: 28px; height: 28px; border-radius: 50%;
|
|
222
|
+
display: flex; align-items: center; justify-content: center;
|
|
223
|
+
opacity: 0.85;
|
|
224
|
+
}
|
|
225
|
+
header button:hover { background: rgba(255,255,255,0.18); opacity: 1; }
|
|
226
|
+
.messages {
|
|
227
|
+
flex: 1; overflow-y: auto;
|
|
228
|
+
padding: 14px;
|
|
229
|
+
display: flex; flex-direction: column; gap: 8px;
|
|
230
|
+
background: var(--ql-bg-soft);
|
|
231
|
+
}
|
|
232
|
+
.msg { max-width: 85%; padding: 8px 12px; border-radius: 12px; line-height: 1.45; font-size: 0.92rem; word-wrap: break-word; white-space: pre-wrap; }
|
|
233
|
+
.msg.user { align-self: flex-end; background: var(--ql-user-bg); color: var(--ql-text); border-bottom-right-radius: 4px; }
|
|
234
|
+
.msg.assistant { align-self: flex-start; background: var(--ql-assistant-bg); color: var(--ql-text); border: 1px solid var(--ql-border); border-bottom-left-radius: 4px; }
|
|
235
|
+
.msg code { background: rgba(0,0,0,0.08); padding: 0 4px; border-radius: 3px; font-family: ui-monospace, Menlo, monospace; font-size: 0.85em; }
|
|
236
|
+
.msg pre { background: rgba(0,0,0,0.08); padding: 8px; border-radius: 6px; overflow-x: auto; }
|
|
237
|
+
.msg pre code { background: none; padding: 0; }
|
|
238
|
+
.msg a { color: var(--ql-accent); text-decoration: none; }
|
|
239
|
+
.msg a:hover { text-decoration: underline; }
|
|
240
|
+
.typing { display: inline-flex; gap: 4px; align-items: center; padding: 8px 12px; }
|
|
241
|
+
.typing span { width: 6px; height: 6px; background: var(--ql-text-soft); border-radius: 50%; opacity: 0.4; animation: blink 1.4s infinite; }
|
|
242
|
+
.typing span:nth-child(2) { animation-delay: 0.2s; }
|
|
243
|
+
.typing span:nth-child(3) { animation-delay: 0.4s; }
|
|
244
|
+
@keyframes blink { 0%,80%,100% { opacity: 0.3; } 40% { opacity: 1; } }
|
|
245
|
+
.status { padding: 0 14px; font-size: 0.78rem; color: var(--ql-text-soft); min-height: 18px; }
|
|
246
|
+
.status.error { color: #d24c4c; }
|
|
247
|
+
form {
|
|
248
|
+
display: flex; gap: 8px; padding: 10px 12px;
|
|
249
|
+
border-top: 1px solid var(--ql-border);
|
|
250
|
+
background: var(--ql-bg);
|
|
251
|
+
}
|
|
252
|
+
textarea {
|
|
253
|
+
flex: 1; resize: none; border: 1px solid var(--ql-border); background: var(--ql-bg-soft);
|
|
254
|
+
color: var(--ql-text); border-radius: 10px; padding: 8px 10px;
|
|
255
|
+
font: inherit; font-size: 0.92rem; max-height: 120px; min-height: 38px;
|
|
256
|
+
outline: none;
|
|
257
|
+
}
|
|
258
|
+
textarea:focus { border-color: var(--ql-accent); box-shadow: 0 0 0 2px rgba(64,158,255,0.18); }
|
|
259
|
+
.send {
|
|
260
|
+
background: var(--ql-accent); color: #fff;
|
|
261
|
+
border: none; border-radius: 10px;
|
|
262
|
+
width: 40px; display: flex; align-items: center; justify-content: center;
|
|
263
|
+
}
|
|
264
|
+
.send:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
265
|
+
.send svg { width: 18px; height: 18px; }
|
|
266
|
+
`;
|
|
267
|
+
|
|
268
|
+
const root = document.createElement('div');
|
|
269
|
+
root.className = 'root';
|
|
270
|
+
|
|
271
|
+
root.innerHTML = `
|
|
272
|
+
<button class="launcher" type="button" aria-label="Open chat" aria-expanded="false">
|
|
273
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
274
|
+
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
|
|
275
|
+
</svg>
|
|
276
|
+
</button>
|
|
277
|
+
<div class="panel" role="dialog" aria-label="Chat">
|
|
278
|
+
<header>
|
|
279
|
+
<div class="title"></div>
|
|
280
|
+
<button type="button" class="close" aria-label="Close chat">
|
|
281
|
+
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
282
|
+
</button>
|
|
283
|
+
</header>
|
|
284
|
+
<div class="messages" role="log" aria-live="polite"></div>
|
|
285
|
+
<div class="status"></div>
|
|
286
|
+
<form>
|
|
287
|
+
<textarea rows="1" autocomplete="off"></textarea>
|
|
288
|
+
<button class="send" type="submit" aria-label="Send">
|
|
289
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
|
|
290
|
+
</button>
|
|
291
|
+
</form>
|
|
292
|
+
</div>
|
|
293
|
+
`;
|
|
294
|
+
|
|
295
|
+
this.shadow.appendChild(style);
|
|
296
|
+
this.shadow.appendChild(root);
|
|
297
|
+
|
|
298
|
+
this.launcher = root.querySelector('.launcher') as HTMLButtonElement;
|
|
299
|
+
this.panel = root.querySelector('.panel') as HTMLDivElement;
|
|
300
|
+
this.messagesEl = root.querySelector('.messages') as HTMLDivElement;
|
|
301
|
+
this.inputEl = root.querySelector('textarea') as HTMLTextAreaElement;
|
|
302
|
+
this.sendBtn = root.querySelector('.send') as HTMLButtonElement;
|
|
303
|
+
this.statusEl = root.querySelector('.status') as HTMLDivElement;
|
|
304
|
+
|
|
305
|
+
(root.querySelector('.title') as HTMLElement).textContent = headerText;
|
|
306
|
+
this.inputEl.placeholder = inputPlaceholder;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private bindEvents(): void {
|
|
310
|
+
this.launcher.addEventListener('click', () => this.toggle());
|
|
311
|
+
(this.shadow.querySelector('header .close') as HTMLButtonElement).addEventListener('click', () => this.close());
|
|
312
|
+
|
|
313
|
+
const form = this.shadow.querySelector('form') as HTMLFormElement;
|
|
314
|
+
form.addEventListener('submit', (e) => {
|
|
315
|
+
e.preventDefault();
|
|
316
|
+
this.send();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
this.inputEl.addEventListener('keydown', (e) => {
|
|
320
|
+
if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
|
|
321
|
+
e.preventDefault();
|
|
322
|
+
this.send();
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
this.inputEl.addEventListener('input', () => {
|
|
327
|
+
this.inputEl.style.height = 'auto';
|
|
328
|
+
this.inputEl.style.height = Math.min(this.inputEl.scrollHeight, 120) + 'px';
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private renderMessages(): void {
|
|
333
|
+
this.messagesEl.innerHTML = '';
|
|
334
|
+
for (const msg of this.messages) {
|
|
335
|
+
const el = document.createElement('div');
|
|
336
|
+
el.className = `msg ${msg.role}`;
|
|
337
|
+
el.innerHTML = renderInlineMarkdown(msg.content);
|
|
338
|
+
this.messagesEl.appendChild(el);
|
|
339
|
+
}
|
|
340
|
+
this.scrollToBottom();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
private appendStreamingChunk(chunk: string): void {
|
|
344
|
+
const last = this.messages[this.messages.length - 1];
|
|
345
|
+
if (!last || last.role !== 'assistant' || !last.streaming) return;
|
|
346
|
+
last.content += chunk;
|
|
347
|
+
const lastEl = this.messagesEl.lastElementChild as HTMLElement | null;
|
|
348
|
+
if (lastEl) {
|
|
349
|
+
lastEl.innerHTML = renderInlineMarkdown(last.content);
|
|
350
|
+
this.scrollToBottom();
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
private scrollToBottom(): void {
|
|
355
|
+
this.messagesEl.scrollTop = this.messagesEl.scrollHeight;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private setStatus(text: string, isError = false): void {
|
|
359
|
+
this.statusEl.textContent = text;
|
|
360
|
+
this.statusEl.classList.toggle('error', isError);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private buildHeaders(): Record<string, string> {
|
|
364
|
+
const headers: Record<string, string> = {
|
|
365
|
+
'content-type': 'application/json',
|
|
366
|
+
accept: 'text/event-stream',
|
|
367
|
+
};
|
|
368
|
+
if (this.options.apiToken) {
|
|
369
|
+
headers['x-api-key'] = this.options.apiToken;
|
|
370
|
+
} else if (this.options.accessToken) {
|
|
371
|
+
headers.authorization = 'Bearer ' + this.options.accessToken;
|
|
372
|
+
}
|
|
373
|
+
return headers;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private buildUrl(path: string): string {
|
|
377
|
+
const base = this.options.appUrl.replace(/\/+$/, '');
|
|
378
|
+
return base + path;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
private async ensureThread(): Promise<string | undefined> {
|
|
382
|
+
if (!this.options.recordThread || this.threadId) return this.threadId;
|
|
383
|
+
const headers = this.buildHeaders();
|
|
384
|
+
delete headers.accept;
|
|
385
|
+
const res = await fetch(this.buildUrl('/api/ai/threads'), {
|
|
386
|
+
method: 'POST',
|
|
387
|
+
headers,
|
|
388
|
+
body: JSON.stringify({ integration: this.options.agentId }),
|
|
389
|
+
});
|
|
390
|
+
if (!res.ok) throw new Error(`Failed to create thread: HTTP ${res.status}`);
|
|
391
|
+
const thread = await res.json();
|
|
392
|
+
this.threadId = thread._id;
|
|
393
|
+
return this.threadId;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
private async send(): Promise<void> {
|
|
397
|
+
if (this.destroyed || this.isSending) return;
|
|
398
|
+
const text = this.inputEl.value.trim();
|
|
399
|
+
if (!text) return;
|
|
400
|
+
|
|
401
|
+
this.isSending = true;
|
|
402
|
+
this.sendBtn.disabled = true;
|
|
403
|
+
this.inputEl.disabled = true;
|
|
404
|
+
this.inputEl.value = '';
|
|
405
|
+
this.inputEl.style.height = 'auto';
|
|
406
|
+
this.setStatus('');
|
|
407
|
+
|
|
408
|
+
this.messages.push({ role: 'user', content: text });
|
|
409
|
+
this.messages.push({ role: 'assistant', content: '', streaming: true });
|
|
410
|
+
this.renderMessages();
|
|
411
|
+
this.setStatus('AI is typing…');
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
const threadId = await this.ensureThread();
|
|
415
|
+
const path = threadId
|
|
416
|
+
? `/api/ai/${this.options.agentId}/chat-completion/${threadId}?stream=true`
|
|
417
|
+
: `/api/ai/${this.options.agentId}/chat-completion?stream=true`;
|
|
418
|
+
|
|
419
|
+
const payload: any = {
|
|
420
|
+
messages: this.messages
|
|
421
|
+
.filter(m => m.role === 'user' || (m.role === 'assistant' && !m.streaming))
|
|
422
|
+
.map(m => ({ role: m.role, content: m.content })),
|
|
423
|
+
stream: true,
|
|
424
|
+
};
|
|
425
|
+
if (this.options.chatContext) payload.context = this.options.chatContext;
|
|
426
|
+
|
|
427
|
+
const res = await fetch(this.buildUrl(path), {
|
|
428
|
+
method: 'POST',
|
|
429
|
+
headers: this.buildHeaders(),
|
|
430
|
+
body: JSON.stringify(payload),
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
if (!res.ok || !res.body) {
|
|
434
|
+
throw new Error(`HTTP ${res.status}: ${res.statusText || 'Request failed'}`);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
await this.consumeStream(res.body);
|
|
438
|
+
|
|
439
|
+
const last = this.messages[this.messages.length - 1];
|
|
440
|
+
if (last) last.streaming = false;
|
|
441
|
+
this.setStatus('');
|
|
442
|
+
} catch (err) {
|
|
443
|
+
const last = this.messages[this.messages.length - 1];
|
|
444
|
+
if (last && last.streaming) {
|
|
445
|
+
last.streaming = false;
|
|
446
|
+
if (!last.content) last.content = '_Sorry, I could not get a response. Please try again._';
|
|
447
|
+
this.renderMessages();
|
|
448
|
+
}
|
|
449
|
+
this.setStatus(err instanceof Error ? err.message : 'Something went wrong', true);
|
|
450
|
+
} finally {
|
|
451
|
+
this.isSending = false;
|
|
452
|
+
this.sendBtn.disabled = false;
|
|
453
|
+
this.inputEl.disabled = false;
|
|
454
|
+
this.inputEl.focus();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
private async consumeStream(stream: ReadableStream<Uint8Array>): Promise<void> {
|
|
459
|
+
const reader = stream.getReader();
|
|
460
|
+
const decoder = new TextDecoder();
|
|
461
|
+
let buffer = '';
|
|
462
|
+
try {
|
|
463
|
+
while (true) {
|
|
464
|
+
const { done, value } = await reader.read();
|
|
465
|
+
if (done) break;
|
|
466
|
+
buffer += decoder.decode(value, { stream: true });
|
|
467
|
+
const lines = buffer.split(/\r?\n/);
|
|
468
|
+
buffer = lines.pop() || '';
|
|
469
|
+
for (const line of lines) {
|
|
470
|
+
if (!line.startsWith('data: ')) continue;
|
|
471
|
+
const data = line.slice(6);
|
|
472
|
+
if (data === '[DONE]') return;
|
|
473
|
+
let parsed: any;
|
|
474
|
+
try {
|
|
475
|
+
parsed = JSON.parse(data);
|
|
476
|
+
} catch {
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
this.handleSSEEvent(parsed);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
} finally {
|
|
483
|
+
reader.releaseLock();
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
private handleSSEEvent(data: any): void {
|
|
488
|
+
if (!data || typeof data !== 'object') return;
|
|
489
|
+
if (data.type === 'connection_established') return;
|
|
490
|
+
if (data.type === 'continuing_conversation') {
|
|
491
|
+
const last = this.messages[this.messages.length - 1];
|
|
492
|
+
if (last && last.streaming) {
|
|
493
|
+
last.content = '';
|
|
494
|
+
this.renderMessages();
|
|
495
|
+
}
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
if (data.type === 'chunk' || data.type === 'followup_chunk') {
|
|
499
|
+
if (typeof data.content === 'string' && data.content) {
|
|
500
|
+
this.appendStreamingChunk(data.content);
|
|
501
|
+
}
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
if (data.type === 'done') return;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
export default QelosChatWidget;
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { QelosChatWidget, QelosChatWidgetOptions } from './chat-widget';
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
interface Window {
|
|
5
|
+
QelosChatWidget?: typeof QelosChatWidget;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getCurrentScript(): HTMLScriptElement | null {
|
|
10
|
+
if (document.currentScript && document.currentScript instanceof HTMLScriptElement) {
|
|
11
|
+
return document.currentScript;
|
|
12
|
+
}
|
|
13
|
+
const scripts = document.getElementsByTagName('script');
|
|
14
|
+
for (let i = scripts.length - 1; i >= 0; i--) {
|
|
15
|
+
if (scripts[i].dataset && scripts[i].dataset.agentId) return scripts[i];
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function readOptionsFromScript(script: HTMLScriptElement): QelosChatWidgetOptions | null {
|
|
21
|
+
const ds = script.dataset;
|
|
22
|
+
if (!ds.agentId || !ds.appUrl) return null;
|
|
23
|
+
const opts: QelosChatWidgetOptions = {
|
|
24
|
+
agentId: ds.agentId,
|
|
25
|
+
appUrl: ds.appUrl,
|
|
26
|
+
apiToken: ds.apiToken,
|
|
27
|
+
accessToken: ds.accessToken,
|
|
28
|
+
headerText: ds.headerText,
|
|
29
|
+
initialMessage: ds.initialMessage,
|
|
30
|
+
inputPlaceholder: ds.inputPlaceholder,
|
|
31
|
+
accentColor: ds.accentColor,
|
|
32
|
+
threadId: ds.threadId,
|
|
33
|
+
autoOpen: ds.autoOpen === 'true',
|
|
34
|
+
recordThread: ds.recordThread === 'true',
|
|
35
|
+
};
|
|
36
|
+
if (ds.position) opts.position = ds.position as QelosChatWidgetOptions['position'];
|
|
37
|
+
if (ds.theme) opts.theme = ds.theme as QelosChatWidgetOptions['theme'];
|
|
38
|
+
return opts;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function autoInit(): void {
|
|
42
|
+
const script = getCurrentScript();
|
|
43
|
+
if (!script) return;
|
|
44
|
+
const options = readOptionsFromScript(script);
|
|
45
|
+
if (!options) return;
|
|
46
|
+
const start = () => {
|
|
47
|
+
try {
|
|
48
|
+
QelosChatWidget.init(options);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error('[QelosChatWidget]', err);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
if (document.readyState === 'loading') {
|
|
54
|
+
document.addEventListener('DOMContentLoaded', start, { once: true });
|
|
55
|
+
} else {
|
|
56
|
+
start();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (typeof window !== 'undefined') {
|
|
61
|
+
window.QelosChatWidget = QelosChatWidget;
|
|
62
|
+
autoInit();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { QelosChatWidget };
|
|
66
|
+
export type { QelosChatWidgetOptions } from './chat-widget';
|