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,357 @@
|
|
|
1
|
+
import { checkUrlWhitelist, getWhitelistErrorMessage } from './urlWhitelist';
|
|
2
|
+
import { apiLogger } from './apiLogger';
|
|
3
|
+
export class ChatEngine {
|
|
4
|
+
constructor(config) {
|
|
5
|
+
this.messages = [];
|
|
6
|
+
this.input = '';
|
|
7
|
+
this.isLoading = false;
|
|
8
|
+
this.error = null;
|
|
9
|
+
this.ws = null;
|
|
10
|
+
this.listeners = new Map();
|
|
11
|
+
this.abortController = null;
|
|
12
|
+
this.conversationId = null; // Track conversation ID for context
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.initializeWebSocket();
|
|
15
|
+
}
|
|
16
|
+
// Event emitter pattern for reactivity
|
|
17
|
+
on(event, callback) {
|
|
18
|
+
if (!this.listeners.has(event)) {
|
|
19
|
+
this.listeners.set(event, new Set());
|
|
20
|
+
}
|
|
21
|
+
this.listeners.get(event).add(callback);
|
|
22
|
+
return () => this.off(event, callback);
|
|
23
|
+
}
|
|
24
|
+
off(event, callback) {
|
|
25
|
+
this.listeners.get(event)?.delete(callback);
|
|
26
|
+
}
|
|
27
|
+
emit(event, data) {
|
|
28
|
+
this.listeners.get(event)?.forEach(callback => callback(data));
|
|
29
|
+
}
|
|
30
|
+
// Getters
|
|
31
|
+
getMessages() {
|
|
32
|
+
return [...this.messages];
|
|
33
|
+
}
|
|
34
|
+
getInput() {
|
|
35
|
+
return this.input;
|
|
36
|
+
}
|
|
37
|
+
getIsLoading() {
|
|
38
|
+
return this.isLoading;
|
|
39
|
+
}
|
|
40
|
+
getError() {
|
|
41
|
+
return this.error;
|
|
42
|
+
}
|
|
43
|
+
getConversationId() {
|
|
44
|
+
return this.conversationId;
|
|
45
|
+
}
|
|
46
|
+
// Methods
|
|
47
|
+
setInput(value) {
|
|
48
|
+
this.input = value;
|
|
49
|
+
this.emit('inputChange', value);
|
|
50
|
+
}
|
|
51
|
+
async sendMessage(content) {
|
|
52
|
+
console.log('[ChatEngine] sendMessage called', { content, input: this.input, isLoading: this.isLoading });
|
|
53
|
+
const message = (content || this.input).trim();
|
|
54
|
+
console.log('[ChatEngine] Message after trim:', message);
|
|
55
|
+
if (!message || this.isLoading) {
|
|
56
|
+
console.log('[ChatEngine] Early return - message empty or loading', { message, isLoading: this.isLoading });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Add user message
|
|
60
|
+
const userMessage = {
|
|
61
|
+
id: Date.now().toString(),
|
|
62
|
+
content: message,
|
|
63
|
+
role: 'user',
|
|
64
|
+
timestamp: new Date(),
|
|
65
|
+
};
|
|
66
|
+
this.messages.push(userMessage);
|
|
67
|
+
this.input = '';
|
|
68
|
+
this.emit('inputChange', '');
|
|
69
|
+
this.emit('messagesChange', this.messages);
|
|
70
|
+
this.emit('message', userMessage);
|
|
71
|
+
this.config.onMessage?.(userMessage);
|
|
72
|
+
this.isLoading = true;
|
|
73
|
+
this.error = null;
|
|
74
|
+
this.emit('loadingChange', true);
|
|
75
|
+
this.emit('error', null);
|
|
76
|
+
// Cancel previous request if exists
|
|
77
|
+
if (this.abortController) {
|
|
78
|
+
this.abortController.abort();
|
|
79
|
+
}
|
|
80
|
+
this.abortController = new AbortController();
|
|
81
|
+
try {
|
|
82
|
+
console.log('[ChatEngine] Starting API call process', {
|
|
83
|
+
enableUrlWhitelistCheck: this.config.enableUrlWhitelistCheck,
|
|
84
|
+
apiUrl: this.config.apiUrl,
|
|
85
|
+
enableWebSocket: this.config.enableWebSocket,
|
|
86
|
+
wsReadyState: this.ws?.readyState,
|
|
87
|
+
});
|
|
88
|
+
// Check URL whitelist if enabled (default: true)
|
|
89
|
+
if (this.config.enableUrlWhitelistCheck !== false && this.config.apiUrl) {
|
|
90
|
+
console.log('[ChatEngine] Checking URL whitelist...', this.config.apiUrl);
|
|
91
|
+
try {
|
|
92
|
+
const whitelistCheck = await checkUrlWhitelist(this.config.apiUrl);
|
|
93
|
+
console.log('[ChatEngine] Whitelist check result:', whitelistCheck);
|
|
94
|
+
if (!whitelistCheck.isWhitelisted) {
|
|
95
|
+
const origin = typeof window !== 'undefined' ? window.location.origin : 'unknown';
|
|
96
|
+
const errorMessage = whitelistCheck.error || getWhitelistErrorMessage(origin);
|
|
97
|
+
console.error('[ChatEngine] URL not whitelisted:', errorMessage);
|
|
98
|
+
const error = new Error(errorMessage);
|
|
99
|
+
this.error = error;
|
|
100
|
+
this.emit('error', error);
|
|
101
|
+
this.config.onError?.(error);
|
|
102
|
+
this.isLoading = false;
|
|
103
|
+
this.emit('loadingChange', false);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (whitelistError) {
|
|
108
|
+
console.error('[ChatEngine] Whitelist check failed with error:', whitelistError);
|
|
109
|
+
// Allow the request to proceed if whitelist check fails (backward compatibility)
|
|
110
|
+
console.warn('[ChatEngine] Proceeding despite whitelist check error');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
console.log('[ChatEngine] Skipping URL whitelist check', {
|
|
115
|
+
enableUrlWhitelistCheck: this.config.enableUrlWhitelistCheck,
|
|
116
|
+
hasApiUrl: !!this.config.apiUrl,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// Send via WebSocket if enabled
|
|
120
|
+
if (this.config.enableWebSocket && this.ws?.readyState === WebSocket.OPEN) {
|
|
121
|
+
console.log('[ChatEngine] Sending via WebSocket');
|
|
122
|
+
this.ws.send(JSON.stringify({
|
|
123
|
+
type: 'message',
|
|
124
|
+
content: message,
|
|
125
|
+
role: 'user',
|
|
126
|
+
}));
|
|
127
|
+
// WebSocket responses are handled in onmessage handler
|
|
128
|
+
this.isLoading = false;
|
|
129
|
+
this.emit('loadingChange', false);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Send via HTTP API
|
|
133
|
+
console.log('[ChatEngine] Sending message to backend:', message);
|
|
134
|
+
const response = await this.sendToBackend(message);
|
|
135
|
+
console.log('[ChatEngine] Backend response:', response);
|
|
136
|
+
// Store conversation ID from response to maintain context
|
|
137
|
+
if (response.conversationId) {
|
|
138
|
+
this.conversationId = response.conversationId;
|
|
139
|
+
}
|
|
140
|
+
const assistantMessage = {
|
|
141
|
+
id: response.id || Date.now().toString(),
|
|
142
|
+
content: response.message || response.content || '',
|
|
143
|
+
role: 'assistant',
|
|
144
|
+
timestamp: new Date(response.timestamp || Date.now()),
|
|
145
|
+
metadata: response.metadata,
|
|
146
|
+
};
|
|
147
|
+
this.messages.push(assistantMessage);
|
|
148
|
+
this.emit('messagesChange', this.messages);
|
|
149
|
+
this.emit('message', assistantMessage);
|
|
150
|
+
this.config.onMessage?.(assistantMessage);
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this.error = err instanceof Error ? err : new Error('Failed to send message');
|
|
157
|
+
this.emit('error', this.error);
|
|
158
|
+
this.config.onError?.(this.error);
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
this.isLoading = false;
|
|
162
|
+
this.emit('loadingChange', false);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async sendToBackend(message) {
|
|
166
|
+
if (!this.config.apiUrl) {
|
|
167
|
+
throw new Error('API URL is required');
|
|
168
|
+
}
|
|
169
|
+
const url = `${this.config.apiUrl}/chat`;
|
|
170
|
+
console.log('[ChatEngine] Making API call to:', url);
|
|
171
|
+
const startTime = Date.now();
|
|
172
|
+
const requestBody = {
|
|
173
|
+
message,
|
|
174
|
+
conversationId: this.conversationId || undefined, // Use stored conversation ID for context
|
|
175
|
+
};
|
|
176
|
+
const headers = {
|
|
177
|
+
'Content-Type': 'application/json',
|
|
178
|
+
...this.config.headers,
|
|
179
|
+
};
|
|
180
|
+
if (this.config.apiKey) {
|
|
181
|
+
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
|
182
|
+
}
|
|
183
|
+
// Log request
|
|
184
|
+
const logEntry = {
|
|
185
|
+
timestamp: new Date().toISOString(),
|
|
186
|
+
url,
|
|
187
|
+
method: 'POST',
|
|
188
|
+
requestHeaders: { ...headers },
|
|
189
|
+
requestBody: { ...requestBody },
|
|
190
|
+
};
|
|
191
|
+
// Mask sensitive data in logs
|
|
192
|
+
if (logEntry.requestHeaders && logEntry.requestHeaders['Authorization']) {
|
|
193
|
+
logEntry.requestHeaders['Authorization'] = logEntry.requestHeaders['Authorization'].substring(0, 20) + '...';
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
console.log('[ChatEngine] About to fetch:', {
|
|
197
|
+
url,
|
|
198
|
+
method: 'POST',
|
|
199
|
+
headers,
|
|
200
|
+
body: requestBody,
|
|
201
|
+
});
|
|
202
|
+
const response = await fetch(url, {
|
|
203
|
+
method: 'POST',
|
|
204
|
+
headers,
|
|
205
|
+
body: JSON.stringify(requestBody),
|
|
206
|
+
signal: this.abortController?.signal,
|
|
207
|
+
});
|
|
208
|
+
console.log('[ChatEngine] Fetch completed, status:', response.status);
|
|
209
|
+
const duration = Date.now() - startTime;
|
|
210
|
+
logEntry.duration = duration;
|
|
211
|
+
// Try to get response body (may be JSON or text)
|
|
212
|
+
let responseBody = null;
|
|
213
|
+
const contentType = response.headers.get('content-type');
|
|
214
|
+
try {
|
|
215
|
+
if (contentType && contentType.includes('application/json')) {
|
|
216
|
+
responseBody = await response.json();
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
const text = await response.text();
|
|
220
|
+
try {
|
|
221
|
+
responseBody = JSON.parse(text);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
responseBody = text;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
// Failed to parse response body
|
|
230
|
+
responseBody = null;
|
|
231
|
+
}
|
|
232
|
+
// Log response
|
|
233
|
+
logEntry.responseStatus = response.status;
|
|
234
|
+
logEntry.responseHeaders = Object.fromEntries(response.headers.entries());
|
|
235
|
+
logEntry.responseBody = responseBody;
|
|
236
|
+
if (!response.ok) {
|
|
237
|
+
// Try to extract error message from response
|
|
238
|
+
let errorMessage = `HTTP error! status: ${response.status}`;
|
|
239
|
+
if (response.status === 403) {
|
|
240
|
+
// Likely a whitelist error
|
|
241
|
+
try {
|
|
242
|
+
const errorData = responseBody;
|
|
243
|
+
if (errorData?.error?.message) {
|
|
244
|
+
errorMessage = errorData.error.message;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
const origin = typeof window !== 'undefined' ? window.location.origin : 'unknown';
|
|
248
|
+
errorMessage = getWhitelistErrorMessage(origin);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
const origin = typeof window !== 'undefined' ? window.location.origin : 'unknown';
|
|
253
|
+
errorMessage = getWhitelistErrorMessage(origin);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
else if (responseBody?.error?.message) {
|
|
257
|
+
errorMessage = responseBody.error.message;
|
|
258
|
+
}
|
|
259
|
+
else if (typeof responseBody === 'string') {
|
|
260
|
+
errorMessage = responseBody;
|
|
261
|
+
}
|
|
262
|
+
logEntry.error = errorMessage;
|
|
263
|
+
apiLogger.log(logEntry);
|
|
264
|
+
throw new Error(errorMessage);
|
|
265
|
+
}
|
|
266
|
+
// Log successful response
|
|
267
|
+
apiLogger.log(logEntry);
|
|
268
|
+
return responseBody;
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
const duration = Date.now() - startTime;
|
|
272
|
+
logEntry.duration = duration;
|
|
273
|
+
logEntry.error = err instanceof Error ? err.message : String(err);
|
|
274
|
+
apiLogger.log(logEntry);
|
|
275
|
+
throw err;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
initializeWebSocket() {
|
|
279
|
+
if (this.config.enableWebSocket && this.config.websocketUrl) {
|
|
280
|
+
try {
|
|
281
|
+
this.ws = new WebSocket(this.config.websocketUrl);
|
|
282
|
+
this.ws.onmessage = (event) => {
|
|
283
|
+
try {
|
|
284
|
+
const data = JSON.parse(event.data);
|
|
285
|
+
if (data.type === 'message') {
|
|
286
|
+
const message = {
|
|
287
|
+
id: data.id || Date.now().toString(),
|
|
288
|
+
content: data.content,
|
|
289
|
+
role: data.role || 'assistant',
|
|
290
|
+
timestamp: new Date(data.timestamp || Date.now()),
|
|
291
|
+
metadata: data.metadata,
|
|
292
|
+
};
|
|
293
|
+
this.messages.push(message);
|
|
294
|
+
this.emit('messagesChange', this.messages);
|
|
295
|
+
this.emit('message', message);
|
|
296
|
+
this.config.onMessage?.(message);
|
|
297
|
+
this.isLoading = false;
|
|
298
|
+
this.emit('loadingChange', false);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
const error = err instanceof Error ? err : new Error('Failed to parse WebSocket message');
|
|
303
|
+
this.error = error;
|
|
304
|
+
this.emit('error', error);
|
|
305
|
+
this.config.onError?.(error);
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
this.ws.onerror = () => {
|
|
309
|
+
const error = new Error('WebSocket error');
|
|
310
|
+
this.error = error;
|
|
311
|
+
this.emit('error', error);
|
|
312
|
+
this.config.onError?.(error);
|
|
313
|
+
};
|
|
314
|
+
this.ws.onclose = () => {
|
|
315
|
+
// WebSocket closed
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
catch (err) {
|
|
319
|
+
const error = err instanceof Error ? err : new Error('Failed to initialize WebSocket');
|
|
320
|
+
this.error = error;
|
|
321
|
+
this.emit('error', error);
|
|
322
|
+
this.config.onError?.(error);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
addMessage(message) {
|
|
327
|
+
this.messages.push(message);
|
|
328
|
+
this.emit('messagesChange', this.messages);
|
|
329
|
+
this.emit('message', message);
|
|
330
|
+
this.config.onMessage?.(message);
|
|
331
|
+
}
|
|
332
|
+
clearMessages() {
|
|
333
|
+
this.messages = [];
|
|
334
|
+
this.conversationId = null; // Clear conversation ID when clearing messages
|
|
335
|
+
this.error = null;
|
|
336
|
+
this.emit('messagesChange', this.messages);
|
|
337
|
+
this.emit('error', null);
|
|
338
|
+
}
|
|
339
|
+
retryLastMessage() {
|
|
340
|
+
const lastUserMessage = [...this.messages].reverse().find((m) => m.role === 'user');
|
|
341
|
+
if (lastUserMessage) {
|
|
342
|
+
// Remove the last user message and assistant response
|
|
343
|
+
const index = this.messages.findIndex((m) => m.id === lastUserMessage.id);
|
|
344
|
+
if (index >= 0) {
|
|
345
|
+
this.messages = this.messages.slice(0, index);
|
|
346
|
+
this.emit('messagesChange', this.messages);
|
|
347
|
+
}
|
|
348
|
+
return this.sendMessage(lastUserMessage.content);
|
|
349
|
+
}
|
|
350
|
+
return Promise.resolve();
|
|
351
|
+
}
|
|
352
|
+
destroy() {
|
|
353
|
+
this.ws?.close();
|
|
354
|
+
this.abortController?.abort();
|
|
355
|
+
this.listeners.clear();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Logger - Logs API calls to a file
|
|
3
|
+
*/
|
|
4
|
+
export interface ApiLogEntry {
|
|
5
|
+
timestamp: string;
|
|
6
|
+
url: string;
|
|
7
|
+
method: string;
|
|
8
|
+
requestHeaders?: Record<string, string>;
|
|
9
|
+
requestBody?: any;
|
|
10
|
+
responseStatus?: number;
|
|
11
|
+
responseHeaders?: Record<string, string>;
|
|
12
|
+
responseBody?: any;
|
|
13
|
+
duration?: number;
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
|
16
|
+
declare class ApiLogger {
|
|
17
|
+
private logs;
|
|
18
|
+
private maxLogs;
|
|
19
|
+
private enabled;
|
|
20
|
+
/**
|
|
21
|
+
* Enable or disable logging
|
|
22
|
+
*/
|
|
23
|
+
setEnabled(enabled: boolean): void;
|
|
24
|
+
/**
|
|
25
|
+
* Log an API call
|
|
26
|
+
*/
|
|
27
|
+
log(entry: ApiLogEntry): void;
|
|
28
|
+
/**
|
|
29
|
+
* Get all logs
|
|
30
|
+
*/
|
|
31
|
+
getLogs(): ApiLogEntry[];
|
|
32
|
+
/**
|
|
33
|
+
* Clear all logs
|
|
34
|
+
*/
|
|
35
|
+
clearLogs(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Export logs to a file
|
|
38
|
+
*/
|
|
39
|
+
exportToFile(filename?: string): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Export logs as text file (human-readable format)
|
|
42
|
+
*/
|
|
43
|
+
exportToTextFile(filename?: string): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
export declare const apiLogger: ApiLogger;
|
|
46
|
+
export { ApiLogger };
|
|
47
|
+
//# sourceMappingURL=apiLogger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiLogger.d.ts","sourceRoot":"","sources":["../../../core/src/apiLogger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,YAAY,CAAC,EAAE,GAAG,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,cAAM,SAAS;IACb,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,OAAO,CAAiB;IAEhC;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIlC;;OAEG;IACH,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAkC7B;;OAEG;IACH,OAAO,IAAI,WAAW,EAAE;IAIxB;;OAEG;IACH,SAAS,IAAI,IAAI;IAIjB;;OAEG;IACG,YAAY,CAAC,QAAQ,GAAE,MAA0E,GAAG,OAAO,CAAC,IAAI,CAAC;IAqDvH;;OAEG;IACG,gBAAgB,CAAC,QAAQ,GAAE,MAAyE,GAAG,OAAO,CAAC,IAAI,CAAC;CAqF3H;AAGD,eAAO,MAAM,SAAS,WAAkB,CAAC;AAGzC,OAAO,EAAE,SAAS,EAAE,CAAC"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Logger - Logs API calls to a file
|
|
3
|
+
*/
|
|
4
|
+
class ApiLogger {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.logs = [];
|
|
7
|
+
this.maxLogs = 1000; // Maximum number of logs to keep in memory
|
|
8
|
+
this.enabled = true;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Enable or disable logging
|
|
12
|
+
*/
|
|
13
|
+
setEnabled(enabled) {
|
|
14
|
+
this.enabled = enabled;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Log an API call
|
|
18
|
+
*/
|
|
19
|
+
log(entry) {
|
|
20
|
+
if (!this.enabled)
|
|
21
|
+
return;
|
|
22
|
+
this.logs.push(entry);
|
|
23
|
+
// Keep only the last maxLogs entries
|
|
24
|
+
if (this.logs.length > this.maxLogs) {
|
|
25
|
+
this.logs.shift();
|
|
26
|
+
}
|
|
27
|
+
// Also log to console in development
|
|
28
|
+
// Check for development mode (browser-compatible)
|
|
29
|
+
let isDev = false;
|
|
30
|
+
if (typeof window !== 'undefined') {
|
|
31
|
+
isDev = window.location?.hostname === 'localhost' ||
|
|
32
|
+
window.location?.hostname === '127.0.0.1';
|
|
33
|
+
}
|
|
34
|
+
// Check Node.js process.env if available (using globalThis to avoid TS errors)
|
|
35
|
+
if (!isDev) {
|
|
36
|
+
try {
|
|
37
|
+
const proc = globalThis.process;
|
|
38
|
+
if (proc && proc.env && proc.env.NODE_ENV === 'development') {
|
|
39
|
+
isDev = true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// process not available (browser environment)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (isDev) {
|
|
47
|
+
console.log('[API Logger]', entry);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get all logs
|
|
52
|
+
*/
|
|
53
|
+
getLogs() {
|
|
54
|
+
return [...this.logs];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Clear all logs
|
|
58
|
+
*/
|
|
59
|
+
clearLogs() {
|
|
60
|
+
this.logs = [];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Export logs to a file
|
|
64
|
+
*/
|
|
65
|
+
async exportToFile(filename = `api-logs-${new Date().toISOString().replace(/[:.]/g, '-')}.json`) {
|
|
66
|
+
if (this.logs.length === 0) {
|
|
67
|
+
console.warn('[API Logger] No logs to export');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const logData = {
|
|
71
|
+
exportedAt: new Date().toISOString(),
|
|
72
|
+
totalLogs: this.logs.length,
|
|
73
|
+
logs: this.logs,
|
|
74
|
+
};
|
|
75
|
+
const jsonString = JSON.stringify(logData, null, 2);
|
|
76
|
+
const blob = new Blob([jsonString], { type: 'application/json' });
|
|
77
|
+
// Try to use File System Access API if available (Chrome/Edge)
|
|
78
|
+
if ('showSaveFilePicker' in window) {
|
|
79
|
+
try {
|
|
80
|
+
const fileHandle = await window.showSaveFilePicker({
|
|
81
|
+
suggestedName: filename,
|
|
82
|
+
types: [{
|
|
83
|
+
description: 'JSON files',
|
|
84
|
+
accept: { 'application/json': ['.json'] },
|
|
85
|
+
}],
|
|
86
|
+
});
|
|
87
|
+
const writable = await fileHandle.createWritable();
|
|
88
|
+
await writable.write(blob);
|
|
89
|
+
await writable.close();
|
|
90
|
+
console.log(`[API Logger] Logs saved to file: ${filename}`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
// User cancelled or error occurred, fall back to download
|
|
95
|
+
if (err.name !== 'AbortError') {
|
|
96
|
+
console.warn('[API Logger] File System Access API failed, falling back to download:', err);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
return; // User cancelled
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Fallback: Download file
|
|
104
|
+
const url = URL.createObjectURL(blob);
|
|
105
|
+
const a = document.createElement('a');
|
|
106
|
+
a.href = url;
|
|
107
|
+
a.download = filename;
|
|
108
|
+
document.body.appendChild(a);
|
|
109
|
+
a.click();
|
|
110
|
+
document.body.removeChild(a);
|
|
111
|
+
URL.revokeObjectURL(url);
|
|
112
|
+
console.log(`[API Logger] Logs downloaded as: ${filename}`);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Export logs as text file (human-readable format)
|
|
116
|
+
*/
|
|
117
|
+
async exportToTextFile(filename = `api-logs-${new Date().toISOString().replace(/[:.]/g, '-')}.txt`) {
|
|
118
|
+
if (this.logs.length === 0) {
|
|
119
|
+
console.warn('[API Logger] No logs to export');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
let textContent = `API Call Logs\n`;
|
|
123
|
+
textContent += `Exported at: ${new Date().toISOString()}\n`;
|
|
124
|
+
textContent += `Total logs: ${this.logs.length}\n`;
|
|
125
|
+
textContent += `${'='.repeat(80)}\n\n`;
|
|
126
|
+
this.logs.forEach((log, index) => {
|
|
127
|
+
textContent += `[${index + 1}] ${log.timestamp}\n`;
|
|
128
|
+
textContent += `URL: ${log.method} ${log.url}\n`;
|
|
129
|
+
if (log.requestHeaders) {
|
|
130
|
+
textContent += `Request Headers:\n`;
|
|
131
|
+
Object.entries(log.requestHeaders).forEach(([key, value]) => {
|
|
132
|
+
// Mask sensitive headers
|
|
133
|
+
if (key.toLowerCase() === 'authorization') {
|
|
134
|
+
textContent += ` ${key}: ${value.substring(0, 20)}...\n`;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
textContent += ` ${key}: ${value}\n`;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (log.requestBody) {
|
|
142
|
+
textContent += `Request Body:\n${JSON.stringify(log.requestBody, null, 2)}\n`;
|
|
143
|
+
}
|
|
144
|
+
if (log.error) {
|
|
145
|
+
textContent += `Error: ${log.error}\n`;
|
|
146
|
+
}
|
|
147
|
+
else if (log.responseStatus) {
|
|
148
|
+
textContent += `Response Status: ${log.responseStatus}\n`;
|
|
149
|
+
if (log.responseBody) {
|
|
150
|
+
textContent += `Response Body:\n${JSON.stringify(log.responseBody, null, 2)}\n`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (log.duration !== undefined) {
|
|
154
|
+
textContent += `Duration: ${log.duration}ms\n`;
|
|
155
|
+
}
|
|
156
|
+
textContent += `${'-'.repeat(80)}\n\n`;
|
|
157
|
+
});
|
|
158
|
+
const blob = new Blob([textContent], { type: 'text/plain' });
|
|
159
|
+
// Try to use File System Access API if available
|
|
160
|
+
if ('showSaveFilePicker' in window) {
|
|
161
|
+
try {
|
|
162
|
+
const fileHandle = await window.showSaveFilePicker({
|
|
163
|
+
suggestedName: filename,
|
|
164
|
+
types: [{
|
|
165
|
+
description: 'Text files',
|
|
166
|
+
accept: { 'text/plain': ['.txt'] },
|
|
167
|
+
}],
|
|
168
|
+
});
|
|
169
|
+
const writable = await fileHandle.createWritable();
|
|
170
|
+
await writable.write(blob);
|
|
171
|
+
await writable.close();
|
|
172
|
+
console.log(`[API Logger] Logs saved to file: ${filename}`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
if (err.name !== 'AbortError') {
|
|
177
|
+
console.warn('[API Logger] File System Access API failed, falling back to download:', err);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Fallback: Download file
|
|
185
|
+
const url = URL.createObjectURL(blob);
|
|
186
|
+
const a = document.createElement('a');
|
|
187
|
+
a.href = url;
|
|
188
|
+
a.download = filename;
|
|
189
|
+
document.body.appendChild(a);
|
|
190
|
+
a.click();
|
|
191
|
+
document.body.removeChild(a);
|
|
192
|
+
URL.revokeObjectURL(url);
|
|
193
|
+
console.log(`[API Logger] Logs downloaded as: ${filename}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Singleton instance
|
|
197
|
+
export const apiLogger = new ApiLogger();
|
|
198
|
+
// Export the class for custom instances if needed
|
|
199
|
+
export { ApiLogger };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { ChatEngine } from './ChatEngine';
|
|
2
|
+
export type { Message, ChatConfig, ChatTheme, ChatEventType, ChatEventListener } from './types';
|
|
3
|
+
export { checkUrlWhitelist, getWhitelistErrorMessage } from './urlWhitelist';
|
|
4
|
+
export type { WhitelistCheckResult } from './urlWhitelist';
|
|
5
|
+
export { apiLogger, ApiLogger } from './apiLogger';
|
|
6
|
+
export type { ApiLogEntry } from './apiLogger';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../core/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAChG,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC7E,YAAY,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export interface Message {
|
|
2
|
+
id: string;
|
|
3
|
+
content: string;
|
|
4
|
+
role: 'user' | 'assistant' | 'system';
|
|
5
|
+
timestamp: Date;
|
|
6
|
+
metadata?: Record<string, any>;
|
|
7
|
+
}
|
|
8
|
+
export interface ChatConfig {
|
|
9
|
+
apiUrl?: string;
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
onMessage?: (message: Message) => void;
|
|
13
|
+
onError?: (error: Error) => void;
|
|
14
|
+
enableWebSocket?: boolean;
|
|
15
|
+
websocketUrl?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Enable URL whitelist checking before making API calls
|
|
18
|
+
* @default true
|
|
19
|
+
*/
|
|
20
|
+
enableUrlWhitelistCheck?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface ChatTheme {
|
|
23
|
+
primaryColor?: string;
|
|
24
|
+
secondaryColor?: string;
|
|
25
|
+
backgroundColor?: string;
|
|
26
|
+
textColor?: string;
|
|
27
|
+
userMessageColor?: string;
|
|
28
|
+
assistantMessageColor?: string;
|
|
29
|
+
inputBackgroundColor?: string;
|
|
30
|
+
inputTextColor?: string;
|
|
31
|
+
errorBackgroundColor?: string;
|
|
32
|
+
errorTextColor?: string;
|
|
33
|
+
borderColor?: string;
|
|
34
|
+
fontFamily?: string;
|
|
35
|
+
fontSize?: string;
|
|
36
|
+
fontWeight?: string;
|
|
37
|
+
lineHeight?: string;
|
|
38
|
+
padding?: string;
|
|
39
|
+
messageGap?: string;
|
|
40
|
+
inputPadding?: string;
|
|
41
|
+
messagePadding?: string;
|
|
42
|
+
borderRadius?: string;
|
|
43
|
+
messageBorderRadius?: string;
|
|
44
|
+
inputBorderRadius?: string;
|
|
45
|
+
borderWidth?: string;
|
|
46
|
+
boxShadow?: string;
|
|
47
|
+
messageBoxShadow?: string;
|
|
48
|
+
inputBoxShadow?: string;
|
|
49
|
+
maxHeight?: string;
|
|
50
|
+
maxWidth?: string;
|
|
51
|
+
messageMaxWidth?: string;
|
|
52
|
+
avatarSize?: string;
|
|
53
|
+
transitionDuration?: string;
|
|
54
|
+
animationDuration?: string;
|
|
55
|
+
scrollbarWidth?: string;
|
|
56
|
+
scrollbarColor?: string;
|
|
57
|
+
scrollbarTrackColor?: string;
|
|
58
|
+
customCSS?: string;
|
|
59
|
+
}
|
|
60
|
+
export type ChatEventType = 'messagesChange' | 'inputChange' | 'loadingChange' | 'error' | 'message';
|
|
61
|
+
export type ChatEventListener = (data?: any) => void;
|
|
62
|
+
//# sourceMappingURL=types.d.ts.map
|