acecoderz-chat-ui 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 +309 -0
- package/browser/index.ts +15 -0
- package/dist/adapters/react/ChatUI.d.ts +32 -0
- package/dist/adapters/react/ChatUI.d.ts.map +1 -0
- package/dist/adapters/react/ChatUI.js +170 -0
- package/dist/adapters/react/index.d.ts +5 -0
- package/dist/adapters/react/index.d.ts.map +1 -0
- package/dist/adapters/react/index.js +2 -0
- package/dist/adapters/react/useChat.d.ts +14 -0
- package/dist/adapters/react/useChat.d.ts.map +1 -0
- package/dist/adapters/react/useChat.js +64 -0
- package/dist/adapters/solid/createChat.d.ts +13 -0
- package/dist/adapters/solid/createChat.d.ts.map +1 -0
- package/dist/adapters/solid/createChat.js +34 -0
- package/dist/adapters/solid/index.d.ts +3 -0
- package/dist/adapters/solid/index.d.ts.map +1 -0
- package/dist/adapters/solid/index.js +1 -0
- package/dist/adapters/vanilla/index.d.ts +31 -0
- package/dist/adapters/vanilla/index.d.ts.map +1 -0
- package/dist/adapters/vanilla/index.js +346 -0
- package/dist/browser/Archive.zip +0 -0
- package/dist/browser/cdn-example.html +177 -0
- package/dist/browser/chatbot-ui.css +508 -0
- package/dist/browser/chatbot-ui.js +878 -0
- package/dist/browser/chatbot-ui.js.map +7 -0
- package/dist/browser/chatbot-ui.min.js +71 -0
- package/dist/browser/chatbot.html +100 -0
- package/dist/core/src/ChatEngine.d.ts +30 -0
- package/dist/core/src/ChatEngine.d.ts.map +1 -0
- package/dist/core/src/ChatEngine.js +357 -0
- package/dist/core/src/apiLogger.d.ts +47 -0
- package/dist/core/src/apiLogger.d.ts.map +1 -0
- package/dist/core/src/apiLogger.js +199 -0
- package/dist/core/src/index.d.ts +7 -0
- package/dist/core/src/index.d.ts.map +1 -0
- package/dist/core/src/index.js +3 -0
- package/dist/core/src/types.d.ts +62 -0
- package/dist/core/src/types.d.ts.map +1 -0
- package/dist/core/src/types.js +1 -0
- package/dist/core/src/urlWhitelist.d.ts +19 -0
- package/dist/core/src/urlWhitelist.d.ts.map +1 -0
- package/dist/core/src/urlWhitelist.js +66 -0
- package/dist/src/ChatUI.stories.d.ts +37 -0
- package/dist/src/ChatUI.stories.d.ts.map +1 -0
- package/dist/src/ChatUI.stories.js +65 -0
- package/dist/src/ChatUIThemes.stories.d.ts +28 -0
- package/dist/src/ChatUIThemes.stories.d.ts.map +1 -0
- package/dist/src/ChatUIThemes.stories.js +109 -0
- package/dist/src/ThemeProperties.stories.d.ts +92 -0
- package/dist/src/ThemeProperties.stories.d.ts.map +1 -0
- package/dist/src/ThemeProperties.stories.js +195 -0
- package/dist/src/UseChat.stories.d.ts +21 -0
- package/dist/src/UseChat.stories.d.ts.map +1 -0
- package/dist/src/UseChat.stories.js +66 -0
- package/dist/src/VanillaAdapter.stories.d.ts +39 -0
- package/dist/src/VanillaAdapter.stories.d.ts.map +1 -0
- package/dist/src/VanillaAdapter.stories.js +78 -0
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +8 -0
- package/package.json +117 -0
- package/styles/chat.css +508 -0
|
@@ -0,0 +1,878 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
(() => {
|
|
3
|
+
// core/src/urlWhitelist.ts
|
|
4
|
+
async function checkUrlWhitelist(apiUrl) {
|
|
5
|
+
try {
|
|
6
|
+
if (typeof window === "undefined" || !window.location) {
|
|
7
|
+
return { isWhitelisted: true };
|
|
8
|
+
}
|
|
9
|
+
const currentOrigin = window.location.origin;
|
|
10
|
+
if (!currentOrigin) {
|
|
11
|
+
return { isWhitelisted: true };
|
|
12
|
+
}
|
|
13
|
+
const checkUrl = `${apiUrl}/url-whitelist/check?url=${encodeURIComponent(currentOrigin)}`;
|
|
14
|
+
const response = await fetch(checkUrl, {
|
|
15
|
+
method: "GET",
|
|
16
|
+
headers: {
|
|
17
|
+
"Content-Type": "application/json"
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
if (response.status === 404) {
|
|
22
|
+
return { isWhitelisted: true };
|
|
23
|
+
}
|
|
24
|
+
const errorData = await response.json().catch(() => ({ message: "Unknown error" }));
|
|
25
|
+
return {
|
|
26
|
+
isWhitelisted: false,
|
|
27
|
+
error: errorData.error?.message || errorData.message || "Failed to check URL whitelist"
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const data = await response.json();
|
|
31
|
+
const result = data.data || data;
|
|
32
|
+
if (result.isWhitelisted) {
|
|
33
|
+
return { isWhitelisted: true };
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
isWhitelisted: false,
|
|
37
|
+
error: `This chatbot is not authorized for ${currentOrigin}. Please contact the administrator.`
|
|
38
|
+
};
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.warn("URL whitelist check failed:", error);
|
|
41
|
+
return { isWhitelisted: true };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function getWhitelistErrorMessage(origin) {
|
|
45
|
+
return `This chatbot is not authorized to work on ${origin}. Please contact the administrator to add this URL to the whitelist.`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// core/src/apiLogger.ts
|
|
49
|
+
var ApiLogger = class {
|
|
50
|
+
constructor() {
|
|
51
|
+
this.logs = [];
|
|
52
|
+
this.maxLogs = 1e3;
|
|
53
|
+
// Maximum number of logs to keep in memory
|
|
54
|
+
this.enabled = true;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Enable or disable logging
|
|
58
|
+
*/
|
|
59
|
+
setEnabled(enabled) {
|
|
60
|
+
this.enabled = enabled;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Log an API call
|
|
64
|
+
*/
|
|
65
|
+
log(entry) {
|
|
66
|
+
if (!this.enabled) return;
|
|
67
|
+
this.logs.push(entry);
|
|
68
|
+
if (this.logs.length > this.maxLogs) {
|
|
69
|
+
this.logs.shift();
|
|
70
|
+
}
|
|
71
|
+
let isDev = false;
|
|
72
|
+
if (typeof window !== "undefined") {
|
|
73
|
+
isDev = window.location?.hostname === "localhost" || window.location?.hostname === "127.0.0.1";
|
|
74
|
+
}
|
|
75
|
+
if (!isDev) {
|
|
76
|
+
try {
|
|
77
|
+
const proc = globalThis.process;
|
|
78
|
+
if (proc && proc.env && proc.env.NODE_ENV === "development") {
|
|
79
|
+
isDev = true;
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (isDev) {
|
|
85
|
+
console.log("[API Logger]", entry);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get all logs
|
|
90
|
+
*/
|
|
91
|
+
getLogs() {
|
|
92
|
+
return [...this.logs];
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Clear all logs
|
|
96
|
+
*/
|
|
97
|
+
clearLogs() {
|
|
98
|
+
this.logs = [];
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Export logs to a file
|
|
102
|
+
*/
|
|
103
|
+
async exportToFile(filename = `api-logs-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`) {
|
|
104
|
+
if (this.logs.length === 0) {
|
|
105
|
+
console.warn("[API Logger] No logs to export");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const logData = {
|
|
109
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
110
|
+
totalLogs: this.logs.length,
|
|
111
|
+
logs: this.logs
|
|
112
|
+
};
|
|
113
|
+
const jsonString = JSON.stringify(logData, null, 2);
|
|
114
|
+
const blob = new Blob([jsonString], { type: "application/json" });
|
|
115
|
+
if ("showSaveFilePicker" in window) {
|
|
116
|
+
try {
|
|
117
|
+
const fileHandle = await window.showSaveFilePicker({
|
|
118
|
+
suggestedName: filename,
|
|
119
|
+
types: [{
|
|
120
|
+
description: "JSON files",
|
|
121
|
+
accept: { "application/json": [".json"] }
|
|
122
|
+
}]
|
|
123
|
+
});
|
|
124
|
+
const writable = await fileHandle.createWritable();
|
|
125
|
+
await writable.write(blob);
|
|
126
|
+
await writable.close();
|
|
127
|
+
console.log(`[API Logger] Logs saved to file: ${filename}`);
|
|
128
|
+
return;
|
|
129
|
+
} catch (err) {
|
|
130
|
+
if (err.name !== "AbortError") {
|
|
131
|
+
console.warn("[API Logger] File System Access API failed, falling back to download:", err);
|
|
132
|
+
} else {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const url = URL.createObjectURL(blob);
|
|
138
|
+
const a = document.createElement("a");
|
|
139
|
+
a.href = url;
|
|
140
|
+
a.download = filename;
|
|
141
|
+
document.body.appendChild(a);
|
|
142
|
+
a.click();
|
|
143
|
+
document.body.removeChild(a);
|
|
144
|
+
URL.revokeObjectURL(url);
|
|
145
|
+
console.log(`[API Logger] Logs downloaded as: ${filename}`);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Export logs as text file (human-readable format)
|
|
149
|
+
*/
|
|
150
|
+
async exportToTextFile(filename = `api-logs-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.txt`) {
|
|
151
|
+
if (this.logs.length === 0) {
|
|
152
|
+
console.warn("[API Logger] No logs to export");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
let textContent = `API Call Logs
|
|
156
|
+
`;
|
|
157
|
+
textContent += `Exported at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
158
|
+
`;
|
|
159
|
+
textContent += `Total logs: ${this.logs.length}
|
|
160
|
+
`;
|
|
161
|
+
textContent += `${"=".repeat(80)}
|
|
162
|
+
|
|
163
|
+
`;
|
|
164
|
+
this.logs.forEach((log, index) => {
|
|
165
|
+
textContent += `[${index + 1}] ${log.timestamp}
|
|
166
|
+
`;
|
|
167
|
+
textContent += `URL: ${log.method} ${log.url}
|
|
168
|
+
`;
|
|
169
|
+
if (log.requestHeaders) {
|
|
170
|
+
textContent += `Request Headers:
|
|
171
|
+
`;
|
|
172
|
+
Object.entries(log.requestHeaders).forEach(([key, value]) => {
|
|
173
|
+
if (key.toLowerCase() === "authorization") {
|
|
174
|
+
textContent += ` ${key}: ${value.substring(0, 20)}...
|
|
175
|
+
`;
|
|
176
|
+
} else {
|
|
177
|
+
textContent += ` ${key}: ${value}
|
|
178
|
+
`;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (log.requestBody) {
|
|
183
|
+
textContent += `Request Body:
|
|
184
|
+
${JSON.stringify(log.requestBody, null, 2)}
|
|
185
|
+
`;
|
|
186
|
+
}
|
|
187
|
+
if (log.error) {
|
|
188
|
+
textContent += `Error: ${log.error}
|
|
189
|
+
`;
|
|
190
|
+
} else if (log.responseStatus) {
|
|
191
|
+
textContent += `Response Status: ${log.responseStatus}
|
|
192
|
+
`;
|
|
193
|
+
if (log.responseBody) {
|
|
194
|
+
textContent += `Response Body:
|
|
195
|
+
${JSON.stringify(log.responseBody, null, 2)}
|
|
196
|
+
`;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (log.duration !== void 0) {
|
|
200
|
+
textContent += `Duration: ${log.duration}ms
|
|
201
|
+
`;
|
|
202
|
+
}
|
|
203
|
+
textContent += `${"-".repeat(80)}
|
|
204
|
+
|
|
205
|
+
`;
|
|
206
|
+
});
|
|
207
|
+
const blob = new Blob([textContent], { type: "text/plain" });
|
|
208
|
+
if ("showSaveFilePicker" in window) {
|
|
209
|
+
try {
|
|
210
|
+
const fileHandle = await window.showSaveFilePicker({
|
|
211
|
+
suggestedName: filename,
|
|
212
|
+
types: [{
|
|
213
|
+
description: "Text files",
|
|
214
|
+
accept: { "text/plain": [".txt"] }
|
|
215
|
+
}]
|
|
216
|
+
});
|
|
217
|
+
const writable = await fileHandle.createWritable();
|
|
218
|
+
await writable.write(blob);
|
|
219
|
+
await writable.close();
|
|
220
|
+
console.log(`[API Logger] Logs saved to file: ${filename}`);
|
|
221
|
+
return;
|
|
222
|
+
} catch (err) {
|
|
223
|
+
if (err.name !== "AbortError") {
|
|
224
|
+
console.warn("[API Logger] File System Access API failed, falling back to download:", err);
|
|
225
|
+
} else {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const url = URL.createObjectURL(blob);
|
|
231
|
+
const a = document.createElement("a");
|
|
232
|
+
a.href = url;
|
|
233
|
+
a.download = filename;
|
|
234
|
+
document.body.appendChild(a);
|
|
235
|
+
a.click();
|
|
236
|
+
document.body.removeChild(a);
|
|
237
|
+
URL.revokeObjectURL(url);
|
|
238
|
+
console.log(`[API Logger] Logs downloaded as: ${filename}`);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
var apiLogger = new ApiLogger();
|
|
242
|
+
|
|
243
|
+
// core/src/ChatEngine.ts
|
|
244
|
+
var ChatEngine = class {
|
|
245
|
+
// Track conversation ID for context
|
|
246
|
+
constructor(config) {
|
|
247
|
+
this.messages = [];
|
|
248
|
+
this.input = "";
|
|
249
|
+
this.isLoading = false;
|
|
250
|
+
this.error = null;
|
|
251
|
+
this.ws = null;
|
|
252
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
253
|
+
this.abortController = null;
|
|
254
|
+
this.conversationId = null;
|
|
255
|
+
this.config = config;
|
|
256
|
+
this.initializeWebSocket();
|
|
257
|
+
}
|
|
258
|
+
// Event emitter pattern for reactivity
|
|
259
|
+
on(event, callback) {
|
|
260
|
+
if (!this.listeners.has(event)) {
|
|
261
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
262
|
+
}
|
|
263
|
+
this.listeners.get(event).add(callback);
|
|
264
|
+
return () => this.off(event, callback);
|
|
265
|
+
}
|
|
266
|
+
off(event, callback) {
|
|
267
|
+
this.listeners.get(event)?.delete(callback);
|
|
268
|
+
}
|
|
269
|
+
emit(event, data) {
|
|
270
|
+
this.listeners.get(event)?.forEach((callback) => callback(data));
|
|
271
|
+
}
|
|
272
|
+
// Getters
|
|
273
|
+
getMessages() {
|
|
274
|
+
return [...this.messages];
|
|
275
|
+
}
|
|
276
|
+
getInput() {
|
|
277
|
+
return this.input;
|
|
278
|
+
}
|
|
279
|
+
getIsLoading() {
|
|
280
|
+
return this.isLoading;
|
|
281
|
+
}
|
|
282
|
+
getError() {
|
|
283
|
+
return this.error;
|
|
284
|
+
}
|
|
285
|
+
getConversationId() {
|
|
286
|
+
return this.conversationId;
|
|
287
|
+
}
|
|
288
|
+
// Methods
|
|
289
|
+
setInput(value) {
|
|
290
|
+
this.input = value;
|
|
291
|
+
this.emit("inputChange", value);
|
|
292
|
+
}
|
|
293
|
+
async sendMessage(content) {
|
|
294
|
+
console.log("[ChatEngine] sendMessage called", { content, input: this.input, isLoading: this.isLoading });
|
|
295
|
+
const message = (content || this.input).trim();
|
|
296
|
+
console.log("[ChatEngine] Message after trim:", message);
|
|
297
|
+
if (!message || this.isLoading) {
|
|
298
|
+
console.log("[ChatEngine] Early return - message empty or loading", { message, isLoading: this.isLoading });
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const userMessage = {
|
|
302
|
+
id: Date.now().toString(),
|
|
303
|
+
content: message,
|
|
304
|
+
role: "user",
|
|
305
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
306
|
+
};
|
|
307
|
+
this.messages.push(userMessage);
|
|
308
|
+
this.input = "";
|
|
309
|
+
this.emit("inputChange", "");
|
|
310
|
+
this.emit("messagesChange", this.messages);
|
|
311
|
+
this.emit("message", userMessage);
|
|
312
|
+
this.config.onMessage?.(userMessage);
|
|
313
|
+
this.isLoading = true;
|
|
314
|
+
this.error = null;
|
|
315
|
+
this.emit("loadingChange", true);
|
|
316
|
+
this.emit("error", null);
|
|
317
|
+
if (this.abortController) {
|
|
318
|
+
this.abortController.abort();
|
|
319
|
+
}
|
|
320
|
+
this.abortController = new AbortController();
|
|
321
|
+
try {
|
|
322
|
+
console.log("[ChatEngine] Starting API call process", {
|
|
323
|
+
enableUrlWhitelistCheck: this.config.enableUrlWhitelistCheck,
|
|
324
|
+
apiUrl: this.config.apiUrl,
|
|
325
|
+
enableWebSocket: this.config.enableWebSocket,
|
|
326
|
+
wsReadyState: this.ws?.readyState
|
|
327
|
+
});
|
|
328
|
+
if (this.config.enableUrlWhitelistCheck !== false && this.config.apiUrl) {
|
|
329
|
+
console.log("[ChatEngine] Checking URL whitelist...", this.config.apiUrl);
|
|
330
|
+
try {
|
|
331
|
+
const whitelistCheck = await checkUrlWhitelist(this.config.apiUrl);
|
|
332
|
+
console.log("[ChatEngine] Whitelist check result:", whitelistCheck);
|
|
333
|
+
if (!whitelistCheck.isWhitelisted) {
|
|
334
|
+
const origin = typeof window !== "undefined" ? window.location.origin : "unknown";
|
|
335
|
+
const errorMessage = whitelistCheck.error || getWhitelistErrorMessage(origin);
|
|
336
|
+
console.error("[ChatEngine] URL not whitelisted:", errorMessage);
|
|
337
|
+
const error = new Error(errorMessage);
|
|
338
|
+
this.error = error;
|
|
339
|
+
this.emit("error", error);
|
|
340
|
+
this.config.onError?.(error);
|
|
341
|
+
this.isLoading = false;
|
|
342
|
+
this.emit("loadingChange", false);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
} catch (whitelistError) {
|
|
346
|
+
console.error("[ChatEngine] Whitelist check failed with error:", whitelistError);
|
|
347
|
+
console.warn("[ChatEngine] Proceeding despite whitelist check error");
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
console.log("[ChatEngine] Skipping URL whitelist check", {
|
|
351
|
+
enableUrlWhitelistCheck: this.config.enableUrlWhitelistCheck,
|
|
352
|
+
hasApiUrl: !!this.config.apiUrl
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
if (this.config.enableWebSocket && this.ws?.readyState === WebSocket.OPEN) {
|
|
356
|
+
console.log("[ChatEngine] Sending via WebSocket");
|
|
357
|
+
this.ws.send(JSON.stringify({
|
|
358
|
+
type: "message",
|
|
359
|
+
content: message,
|
|
360
|
+
role: "user"
|
|
361
|
+
}));
|
|
362
|
+
this.isLoading = false;
|
|
363
|
+
this.emit("loadingChange", false);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
console.log("[ChatEngine] Sending message to backend:", message);
|
|
367
|
+
const response = await this.sendToBackend(message);
|
|
368
|
+
console.log("[ChatEngine] Backend response:", response);
|
|
369
|
+
if (response.conversationId) {
|
|
370
|
+
this.conversationId = response.conversationId;
|
|
371
|
+
}
|
|
372
|
+
const assistantMessage = {
|
|
373
|
+
id: response.id || Date.now().toString(),
|
|
374
|
+
content: response.message || response.content || "",
|
|
375
|
+
role: "assistant",
|
|
376
|
+
timestamp: new Date(response.timestamp || Date.now()),
|
|
377
|
+
metadata: response.metadata
|
|
378
|
+
};
|
|
379
|
+
this.messages.push(assistantMessage);
|
|
380
|
+
this.emit("messagesChange", this.messages);
|
|
381
|
+
this.emit("message", assistantMessage);
|
|
382
|
+
this.config.onMessage?.(assistantMessage);
|
|
383
|
+
} catch (err) {
|
|
384
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
this.error = err instanceof Error ? err : new Error("Failed to send message");
|
|
388
|
+
this.emit("error", this.error);
|
|
389
|
+
this.config.onError?.(this.error);
|
|
390
|
+
} finally {
|
|
391
|
+
this.isLoading = false;
|
|
392
|
+
this.emit("loadingChange", false);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
async sendToBackend(message) {
|
|
396
|
+
if (!this.config.apiUrl) {
|
|
397
|
+
throw new Error("API URL is required");
|
|
398
|
+
}
|
|
399
|
+
const url = `${this.config.apiUrl}/chat`;
|
|
400
|
+
console.log("[ChatEngine] Making API call to:", url);
|
|
401
|
+
const startTime = Date.now();
|
|
402
|
+
const requestBody = {
|
|
403
|
+
message,
|
|
404
|
+
conversationId: this.conversationId || void 0
|
|
405
|
+
// Use stored conversation ID for context
|
|
406
|
+
};
|
|
407
|
+
const headers = {
|
|
408
|
+
"Content-Type": "application/json",
|
|
409
|
+
...this.config.headers
|
|
410
|
+
};
|
|
411
|
+
if (this.config.apiKey) {
|
|
412
|
+
headers["Authorization"] = `Bearer ${this.config.apiKey}`;
|
|
413
|
+
}
|
|
414
|
+
const logEntry = {
|
|
415
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
416
|
+
url,
|
|
417
|
+
method: "POST",
|
|
418
|
+
requestHeaders: { ...headers },
|
|
419
|
+
requestBody: { ...requestBody }
|
|
420
|
+
};
|
|
421
|
+
if (logEntry.requestHeaders && logEntry.requestHeaders["Authorization"]) {
|
|
422
|
+
logEntry.requestHeaders["Authorization"] = logEntry.requestHeaders["Authorization"].substring(0, 20) + "...";
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
console.log("[ChatEngine] About to fetch:", {
|
|
426
|
+
url,
|
|
427
|
+
method: "POST",
|
|
428
|
+
headers,
|
|
429
|
+
body: requestBody
|
|
430
|
+
});
|
|
431
|
+
const response = await fetch(url, {
|
|
432
|
+
method: "POST",
|
|
433
|
+
headers,
|
|
434
|
+
body: JSON.stringify(requestBody),
|
|
435
|
+
signal: this.abortController?.signal
|
|
436
|
+
});
|
|
437
|
+
console.log("[ChatEngine] Fetch completed, status:", response.status);
|
|
438
|
+
const duration = Date.now() - startTime;
|
|
439
|
+
logEntry.duration = duration;
|
|
440
|
+
let responseBody = null;
|
|
441
|
+
const contentType = response.headers.get("content-type");
|
|
442
|
+
try {
|
|
443
|
+
if (contentType && contentType.includes("application/json")) {
|
|
444
|
+
responseBody = await response.json();
|
|
445
|
+
} else {
|
|
446
|
+
const text = await response.text();
|
|
447
|
+
try {
|
|
448
|
+
responseBody = JSON.parse(text);
|
|
449
|
+
} catch {
|
|
450
|
+
responseBody = text;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
} catch (err) {
|
|
454
|
+
responseBody = null;
|
|
455
|
+
}
|
|
456
|
+
logEntry.responseStatus = response.status;
|
|
457
|
+
logEntry.responseHeaders = Object.fromEntries(response.headers.entries());
|
|
458
|
+
logEntry.responseBody = responseBody;
|
|
459
|
+
if (!response.ok) {
|
|
460
|
+
let errorMessage = `HTTP error! status: ${response.status}`;
|
|
461
|
+
if (response.status === 403) {
|
|
462
|
+
try {
|
|
463
|
+
const errorData = responseBody;
|
|
464
|
+
if (errorData?.error?.message) {
|
|
465
|
+
errorMessage = errorData.error.message;
|
|
466
|
+
} else {
|
|
467
|
+
const origin = typeof window !== "undefined" ? window.location.origin : "unknown";
|
|
468
|
+
errorMessage = getWhitelistErrorMessage(origin);
|
|
469
|
+
}
|
|
470
|
+
} catch {
|
|
471
|
+
const origin = typeof window !== "undefined" ? window.location.origin : "unknown";
|
|
472
|
+
errorMessage = getWhitelistErrorMessage(origin);
|
|
473
|
+
}
|
|
474
|
+
} else if (responseBody?.error?.message) {
|
|
475
|
+
errorMessage = responseBody.error.message;
|
|
476
|
+
} else if (typeof responseBody === "string") {
|
|
477
|
+
errorMessage = responseBody;
|
|
478
|
+
}
|
|
479
|
+
logEntry.error = errorMessage;
|
|
480
|
+
apiLogger.log(logEntry);
|
|
481
|
+
throw new Error(errorMessage);
|
|
482
|
+
}
|
|
483
|
+
apiLogger.log(logEntry);
|
|
484
|
+
return responseBody;
|
|
485
|
+
} catch (err) {
|
|
486
|
+
const duration = Date.now() - startTime;
|
|
487
|
+
logEntry.duration = duration;
|
|
488
|
+
logEntry.error = err instanceof Error ? err.message : String(err);
|
|
489
|
+
apiLogger.log(logEntry);
|
|
490
|
+
throw err;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
initializeWebSocket() {
|
|
494
|
+
if (this.config.enableWebSocket && this.config.websocketUrl) {
|
|
495
|
+
try {
|
|
496
|
+
this.ws = new WebSocket(this.config.websocketUrl);
|
|
497
|
+
this.ws.onmessage = (event) => {
|
|
498
|
+
try {
|
|
499
|
+
const data = JSON.parse(event.data);
|
|
500
|
+
if (data.type === "message") {
|
|
501
|
+
const message = {
|
|
502
|
+
id: data.id || Date.now().toString(),
|
|
503
|
+
content: data.content,
|
|
504
|
+
role: data.role || "assistant",
|
|
505
|
+
timestamp: new Date(data.timestamp || Date.now()),
|
|
506
|
+
metadata: data.metadata
|
|
507
|
+
};
|
|
508
|
+
this.messages.push(message);
|
|
509
|
+
this.emit("messagesChange", this.messages);
|
|
510
|
+
this.emit("message", message);
|
|
511
|
+
this.config.onMessage?.(message);
|
|
512
|
+
this.isLoading = false;
|
|
513
|
+
this.emit("loadingChange", false);
|
|
514
|
+
}
|
|
515
|
+
} catch (err) {
|
|
516
|
+
const error = err instanceof Error ? err : new Error("Failed to parse WebSocket message");
|
|
517
|
+
this.error = error;
|
|
518
|
+
this.emit("error", error);
|
|
519
|
+
this.config.onError?.(error);
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
this.ws.onerror = () => {
|
|
523
|
+
const error = new Error("WebSocket error");
|
|
524
|
+
this.error = error;
|
|
525
|
+
this.emit("error", error);
|
|
526
|
+
this.config.onError?.(error);
|
|
527
|
+
};
|
|
528
|
+
this.ws.onclose = () => {
|
|
529
|
+
};
|
|
530
|
+
} catch (err) {
|
|
531
|
+
const error = err instanceof Error ? err : new Error("Failed to initialize WebSocket");
|
|
532
|
+
this.error = error;
|
|
533
|
+
this.emit("error", error);
|
|
534
|
+
this.config.onError?.(error);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
addMessage(message) {
|
|
539
|
+
this.messages.push(message);
|
|
540
|
+
this.emit("messagesChange", this.messages);
|
|
541
|
+
this.emit("message", message);
|
|
542
|
+
this.config.onMessage?.(message);
|
|
543
|
+
}
|
|
544
|
+
clearMessages() {
|
|
545
|
+
this.messages = [];
|
|
546
|
+
this.conversationId = null;
|
|
547
|
+
this.error = null;
|
|
548
|
+
this.emit("messagesChange", this.messages);
|
|
549
|
+
this.emit("error", null);
|
|
550
|
+
}
|
|
551
|
+
retryLastMessage() {
|
|
552
|
+
const lastUserMessage = [...this.messages].reverse().find((m) => m.role === "user");
|
|
553
|
+
if (lastUserMessage) {
|
|
554
|
+
const index = this.messages.findIndex((m) => m.id === lastUserMessage.id);
|
|
555
|
+
if (index >= 0) {
|
|
556
|
+
this.messages = this.messages.slice(0, index);
|
|
557
|
+
this.emit("messagesChange", this.messages);
|
|
558
|
+
}
|
|
559
|
+
return this.sendMessage(lastUserMessage.content);
|
|
560
|
+
}
|
|
561
|
+
return Promise.resolve();
|
|
562
|
+
}
|
|
563
|
+
destroy() {
|
|
564
|
+
this.ws?.close();
|
|
565
|
+
this.abortController?.abort();
|
|
566
|
+
this.listeners.clear();
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
// adapters/vanilla/index.ts
|
|
571
|
+
function escapeHtml(text) {
|
|
572
|
+
const div = document.createElement("div");
|
|
573
|
+
div.textContent = text;
|
|
574
|
+
return div.innerHTML;
|
|
575
|
+
}
|
|
576
|
+
function formatText(text, enableMarkdown) {
|
|
577
|
+
if (!enableMarkdown) {
|
|
578
|
+
return escapeHtml(text);
|
|
579
|
+
}
|
|
580
|
+
let formatted = escapeHtml(text);
|
|
581
|
+
formatted = formatted.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
|
|
582
|
+
formatted = formatted.replace(/__(.+?)__/g, "<strong>$1</strong>");
|
|
583
|
+
formatted = formatted.replace(/\*(.+?)\*/g, "<em>$1</em>");
|
|
584
|
+
formatted = formatted.replace(/_(.+?)_/g, "<em>$1</em>");
|
|
585
|
+
formatted = formatted.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
586
|
+
formatted = formatted.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
587
|
+
formatted = formatted.replace(/\n/g, "<br>");
|
|
588
|
+
return formatted;
|
|
589
|
+
}
|
|
590
|
+
function getContainerElement(container) {
|
|
591
|
+
if (typeof container === "string") {
|
|
592
|
+
const element = document.querySelector(container);
|
|
593
|
+
if (!element) {
|
|
594
|
+
throw new Error(`Chatbot container not found: ${container}`);
|
|
595
|
+
}
|
|
596
|
+
if (!(element instanceof HTMLElement)) {
|
|
597
|
+
throw new Error(`Chatbot container is not an HTMLElement: ${container}`);
|
|
598
|
+
}
|
|
599
|
+
return element;
|
|
600
|
+
}
|
|
601
|
+
return container;
|
|
602
|
+
}
|
|
603
|
+
function createChatUI(options) {
|
|
604
|
+
const {
|
|
605
|
+
config,
|
|
606
|
+
theme = {},
|
|
607
|
+
container,
|
|
608
|
+
placeholder = "Type your message...",
|
|
609
|
+
showTimestamp = true,
|
|
610
|
+
showAvatar = true,
|
|
611
|
+
userAvatar,
|
|
612
|
+
assistantAvatar,
|
|
613
|
+
maxHeight = "600px",
|
|
614
|
+
disabled = false,
|
|
615
|
+
autoScroll = true,
|
|
616
|
+
enableMarkdown = true,
|
|
617
|
+
showClearButton = true,
|
|
618
|
+
emptyStateMessage = "Start a conversation...",
|
|
619
|
+
loadingMessage = "Thinking...",
|
|
620
|
+
customMessageRenderer,
|
|
621
|
+
onInit
|
|
622
|
+
} = options;
|
|
623
|
+
const containerElement = getContainerElement(container);
|
|
624
|
+
const engine = new ChatEngine(config);
|
|
625
|
+
let messagesContainer = null;
|
|
626
|
+
let inputElement = null;
|
|
627
|
+
let formElement = null;
|
|
628
|
+
let sendButtonElement = null;
|
|
629
|
+
let clearButtonElement = null;
|
|
630
|
+
let isUpdatingInputProgrammatically = false;
|
|
631
|
+
const applyTheme = () => {
|
|
632
|
+
const root = containerElement;
|
|
633
|
+
root.style.setProperty("--chat-primary-color", theme.primaryColor || "#3b82f6");
|
|
634
|
+
root.style.setProperty("--chat-secondary-color", theme.secondaryColor || "#64748b");
|
|
635
|
+
root.style.setProperty("--chat-background-color", theme.backgroundColor || "#ffffff");
|
|
636
|
+
root.style.setProperty("--chat-text-color", theme.textColor || "#1e293b");
|
|
637
|
+
root.style.setProperty("--chat-user-message-color", theme.userMessageColor || "#3b82f6");
|
|
638
|
+
root.style.setProperty("--chat-assistant-message-color", theme.assistantMessageColor || "#f1f5f9");
|
|
639
|
+
root.style.setProperty("--chat-input-background-color", theme.inputBackgroundColor || "#f8fafc");
|
|
640
|
+
root.style.setProperty("--chat-input-text-color", theme.inputTextColor || "#1e293b");
|
|
641
|
+
root.style.setProperty("--chat-border-radius", theme.borderRadius || "0.5rem");
|
|
642
|
+
root.style.setProperty("--chat-font-family", theme.fontFamily || "system-ui, sans-serif");
|
|
643
|
+
root.style.setProperty("--chat-font-size", theme.fontSize || "1rem");
|
|
644
|
+
root.style.setProperty("--chat-max-height", maxHeight);
|
|
645
|
+
};
|
|
646
|
+
const scrollToBottom = () => {
|
|
647
|
+
if (messagesContainer && autoScroll) {
|
|
648
|
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
const formatTimestamp = (date) => {
|
|
652
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
653
|
+
hour: "2-digit",
|
|
654
|
+
minute: "2-digit"
|
|
655
|
+
}).format(date);
|
|
656
|
+
};
|
|
657
|
+
const renderAvatar = (role) => {
|
|
658
|
+
if (!showAvatar) return "";
|
|
659
|
+
const avatar = role === "user" ? userAvatar : assistantAvatar;
|
|
660
|
+
if (avatar) {
|
|
661
|
+
return `<div class="chat-avatar"><img src="${escapeHtml(avatar)}" alt="${role}" /></div>`;
|
|
662
|
+
}
|
|
663
|
+
return `<div class="chat-avatar">${role === "user" ? "U" : "A"}</div>`;
|
|
664
|
+
};
|
|
665
|
+
const renderMessage = (message) => {
|
|
666
|
+
if (customMessageRenderer) {
|
|
667
|
+
return customMessageRenderer(message);
|
|
668
|
+
}
|
|
669
|
+
const isUser = message.role === "user";
|
|
670
|
+
const isSystem = message.role === "system";
|
|
671
|
+
const role = isUser ? "user" : "assistant";
|
|
672
|
+
const avatarHtml = !isSystem ? renderAvatar(role) : "";
|
|
673
|
+
const messageContent = enableMarkdown ? `<div class="chat-markdown">${formatText(message.content, enableMarkdown)}</div>` : `<div class="chat-message-text">${formatText(message.content, false)}</div>`;
|
|
674
|
+
return `
|
|
675
|
+
<div class="chat-message ${isUser ? "chat-message-user" : isSystem ? "chat-message-system" : "chat-message-assistant"}" data-message-id="${escapeHtml(message.id)}">
|
|
676
|
+
${!isUser && !isSystem ? avatarHtml : ""}
|
|
677
|
+
<div class="chat-message-content ${isUser ? "chat-message-content-user" : isSystem ? "chat-message-content-system" : "chat-message-content-assistant"}">
|
|
678
|
+
${messageContent}
|
|
679
|
+
${showTimestamp ? `<div class="chat-message-timestamp">${formatTimestamp(message.timestamp)}</div>` : ""}
|
|
680
|
+
</div>
|
|
681
|
+
${isUser ? avatarHtml : ""}
|
|
682
|
+
</div>
|
|
683
|
+
`;
|
|
684
|
+
};
|
|
685
|
+
const render = () => {
|
|
686
|
+
const messages = engine.getMessages();
|
|
687
|
+
const input = engine.getInput();
|
|
688
|
+
const isLoading = engine.getIsLoading();
|
|
689
|
+
const error = engine.getError();
|
|
690
|
+
const wasInputFocused = inputElement && document.activeElement === inputElement;
|
|
691
|
+
const inputCursorPosition = inputElement ? inputElement.selectionStart || 0 : 0;
|
|
692
|
+
applyTheme();
|
|
693
|
+
containerElement.className = `chat-container ${containerElement.className || ""}`.trim();
|
|
694
|
+
containerElement.setAttribute("data-chat-ui", "true");
|
|
695
|
+
containerElement.innerHTML = `
|
|
696
|
+
<div class="chat-messages-container">
|
|
697
|
+
${messages.length === 0 ? `<div class="chat-empty-state">${escapeHtml(emptyStateMessage)}</div>` : ""}
|
|
698
|
+
${messages.map(renderMessage).join("")}
|
|
699
|
+
${isLoading ? `
|
|
700
|
+
<div class="chat-message chat-message-assistant chat-message-loading">
|
|
701
|
+
${renderAvatar("assistant")}
|
|
702
|
+
<div class="chat-message-content chat-message-content-assistant">
|
|
703
|
+
<div class="chat-message-text">${escapeHtml(loadingMessage)}</div>
|
|
704
|
+
</div>
|
|
705
|
+
</div>
|
|
706
|
+
` : ""}
|
|
707
|
+
</div>
|
|
708
|
+
${error ? `<div class="chat-error">
|
|
709
|
+
<div class="chat-error-message">Error: ${escapeHtml(error.message)}</div>
|
|
710
|
+
<button class="chat-retry-button" type="button">Retry</button>
|
|
711
|
+
</div>` : ""}
|
|
712
|
+
${showClearButton && messages.length > 0 ? `
|
|
713
|
+
<div class="chat-actions">
|
|
714
|
+
<button class="chat-clear-button" type="button" title="Clear conversation">Clear</button>
|
|
715
|
+
</div>
|
|
716
|
+
` : ""}
|
|
717
|
+
<div class="chat-input-container">
|
|
718
|
+
<form class="chat-input-form">
|
|
719
|
+
<input
|
|
720
|
+
type="text"
|
|
721
|
+
class="chat-input"
|
|
722
|
+
value="${escapeHtml(input)}"
|
|
723
|
+
placeholder="${escapeHtml(placeholder)}"
|
|
724
|
+
${disabled || isLoading ? "disabled" : ""}
|
|
725
|
+
aria-label="Chat input"
|
|
726
|
+
/>
|
|
727
|
+
<button
|
|
728
|
+
type="submit"
|
|
729
|
+
class="chat-send-button"
|
|
730
|
+
${disabled || isLoading || !input.trim() ? "disabled" : ""}
|
|
731
|
+
aria-label="Send message"
|
|
732
|
+
>
|
|
733
|
+
Send
|
|
734
|
+
</button>
|
|
735
|
+
</form>
|
|
736
|
+
</div>
|
|
737
|
+
`;
|
|
738
|
+
messagesContainer = containerElement.querySelector(".chat-messages-container");
|
|
739
|
+
inputElement = containerElement.querySelector(".chat-input");
|
|
740
|
+
formElement = containerElement.querySelector(".chat-input-form");
|
|
741
|
+
sendButtonElement = containerElement.querySelector(".chat-send-button");
|
|
742
|
+
clearButtonElement = containerElement.querySelector(".chat-clear-button");
|
|
743
|
+
const retryButton = containerElement.querySelector(".chat-retry-button");
|
|
744
|
+
if (wasInputFocused && inputElement) {
|
|
745
|
+
requestAnimationFrame(() => {
|
|
746
|
+
if (inputElement) {
|
|
747
|
+
inputElement.focus();
|
|
748
|
+
const newCursorPosition = Math.min(inputCursorPosition, inputElement.value.length);
|
|
749
|
+
inputElement.setSelectionRange(newCursorPosition, newCursorPosition);
|
|
750
|
+
}
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
if (formElement) {
|
|
754
|
+
formElement.addEventListener("submit", (e) => {
|
|
755
|
+
e.preventDefault();
|
|
756
|
+
if (!disabled && !isLoading && input.trim()) {
|
|
757
|
+
engine.sendMessage();
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
if (inputElement) {
|
|
762
|
+
inputElement.addEventListener("input", (e) => {
|
|
763
|
+
if (isUpdatingInputProgrammatically) {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
const target = e.target;
|
|
767
|
+
engine.setInput(target.value);
|
|
768
|
+
});
|
|
769
|
+
inputElement.addEventListener("keydown", (e) => {
|
|
770
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
771
|
+
e.preventDefault();
|
|
772
|
+
if (!disabled && !isLoading && input.trim()) {
|
|
773
|
+
engine.sendMessage();
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
if (clearButtonElement) {
|
|
779
|
+
clearButtonElement.addEventListener("click", () => {
|
|
780
|
+
if (confirm("Are you sure you want to clear the conversation?")) {
|
|
781
|
+
engine.clearMessages();
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
if (retryButton) {
|
|
786
|
+
retryButton.addEventListener("click", () => {
|
|
787
|
+
engine.retryLastMessage();
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
setTimeout(() => {
|
|
791
|
+
scrollToBottom();
|
|
792
|
+
}, 0);
|
|
793
|
+
};
|
|
794
|
+
engine.on("messagesChange", () => {
|
|
795
|
+
render();
|
|
796
|
+
setTimeout(() => {
|
|
797
|
+
scrollToBottom();
|
|
798
|
+
}, 100);
|
|
799
|
+
});
|
|
800
|
+
const updateSendButtonState = () => {
|
|
801
|
+
if (sendButtonElement) {
|
|
802
|
+
const input = engine.getInput();
|
|
803
|
+
const isLoading = engine.getIsLoading();
|
|
804
|
+
const shouldDisable = disabled || isLoading || !input.trim();
|
|
805
|
+
sendButtonElement.disabled = shouldDisable;
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
engine.on("inputChange", (value) => {
|
|
809
|
+
if (inputElement && inputElement.isConnected) {
|
|
810
|
+
const wasFocused = document.activeElement === inputElement;
|
|
811
|
+
const cursorPosition = inputElement.selectionStart || 0;
|
|
812
|
+
isUpdatingInputProgrammatically = true;
|
|
813
|
+
inputElement.value = value || "";
|
|
814
|
+
isUpdatingInputProgrammatically = false;
|
|
815
|
+
updateSendButtonState();
|
|
816
|
+
if (wasFocused) {
|
|
817
|
+
requestAnimationFrame(() => {
|
|
818
|
+
if (inputElement) {
|
|
819
|
+
inputElement.focus();
|
|
820
|
+
const newCursorPosition = Math.min(cursorPosition, inputElement.value.length);
|
|
821
|
+
inputElement.setSelectionRange(newCursorPosition, newCursorPosition);
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
} else {
|
|
826
|
+
render();
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
engine.on("loadingChange", () => {
|
|
830
|
+
updateSendButtonState();
|
|
831
|
+
render();
|
|
832
|
+
setTimeout(() => {
|
|
833
|
+
scrollToBottom();
|
|
834
|
+
}, 100);
|
|
835
|
+
});
|
|
836
|
+
engine.on("error", render);
|
|
837
|
+
render();
|
|
838
|
+
const instance = {
|
|
839
|
+
engine,
|
|
840
|
+
render,
|
|
841
|
+
destroy: () => {
|
|
842
|
+
engine.destroy();
|
|
843
|
+
containerElement.innerHTML = "";
|
|
844
|
+
messagesContainer = null;
|
|
845
|
+
inputElement = null;
|
|
846
|
+
formElement = null;
|
|
847
|
+
sendButtonElement = null;
|
|
848
|
+
clearButtonElement = null;
|
|
849
|
+
},
|
|
850
|
+
clear: () => {
|
|
851
|
+
engine.clearMessages();
|
|
852
|
+
},
|
|
853
|
+
scrollToBottom,
|
|
854
|
+
getContainer: () => containerElement
|
|
855
|
+
};
|
|
856
|
+
if (onInit) {
|
|
857
|
+
onInit(instance);
|
|
858
|
+
}
|
|
859
|
+
return instance;
|
|
860
|
+
}
|
|
861
|
+
if (typeof window !== "undefined") {
|
|
862
|
+
window.ChatbotUI = {
|
|
863
|
+
create: createChatUI,
|
|
864
|
+
init: (containerId, options) => {
|
|
865
|
+
const container = typeof containerId === "string" ? document.getElementById(containerId) || document.querySelector(containerId) : containerId;
|
|
866
|
+
if (!container) {
|
|
867
|
+
console.error(`Chatbot container not found: ${containerId}`);
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
return createChatUI({
|
|
871
|
+
container,
|
|
872
|
+
...options
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
})();
|
|
878
|
+
//# sourceMappingURL=chatbot-ui.js.map
|