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.
Files changed (62) hide show
  1. package/README.md +309 -0
  2. package/browser/index.ts +15 -0
  3. package/dist/adapters/react/ChatUI.d.ts +32 -0
  4. package/dist/adapters/react/ChatUI.d.ts.map +1 -0
  5. package/dist/adapters/react/ChatUI.js +170 -0
  6. package/dist/adapters/react/index.d.ts +5 -0
  7. package/dist/adapters/react/index.d.ts.map +1 -0
  8. package/dist/adapters/react/index.js +2 -0
  9. package/dist/adapters/react/useChat.d.ts +14 -0
  10. package/dist/adapters/react/useChat.d.ts.map +1 -0
  11. package/dist/adapters/react/useChat.js +64 -0
  12. package/dist/adapters/solid/createChat.d.ts +13 -0
  13. package/dist/adapters/solid/createChat.d.ts.map +1 -0
  14. package/dist/adapters/solid/createChat.js +34 -0
  15. package/dist/adapters/solid/index.d.ts +3 -0
  16. package/dist/adapters/solid/index.d.ts.map +1 -0
  17. package/dist/adapters/solid/index.js +1 -0
  18. package/dist/adapters/vanilla/index.d.ts +31 -0
  19. package/dist/adapters/vanilla/index.d.ts.map +1 -0
  20. package/dist/adapters/vanilla/index.js +346 -0
  21. package/dist/browser/Archive.zip +0 -0
  22. package/dist/browser/cdn-example.html +177 -0
  23. package/dist/browser/chatbot-ui.css +508 -0
  24. package/dist/browser/chatbot-ui.js +878 -0
  25. package/dist/browser/chatbot-ui.js.map +7 -0
  26. package/dist/browser/chatbot-ui.min.js +71 -0
  27. package/dist/browser/chatbot.html +100 -0
  28. package/dist/core/src/ChatEngine.d.ts +30 -0
  29. package/dist/core/src/ChatEngine.d.ts.map +1 -0
  30. package/dist/core/src/ChatEngine.js +357 -0
  31. package/dist/core/src/apiLogger.d.ts +47 -0
  32. package/dist/core/src/apiLogger.d.ts.map +1 -0
  33. package/dist/core/src/apiLogger.js +199 -0
  34. package/dist/core/src/index.d.ts +7 -0
  35. package/dist/core/src/index.d.ts.map +1 -0
  36. package/dist/core/src/index.js +3 -0
  37. package/dist/core/src/types.d.ts +62 -0
  38. package/dist/core/src/types.d.ts.map +1 -0
  39. package/dist/core/src/types.js +1 -0
  40. package/dist/core/src/urlWhitelist.d.ts +19 -0
  41. package/dist/core/src/urlWhitelist.d.ts.map +1 -0
  42. package/dist/core/src/urlWhitelist.js +66 -0
  43. package/dist/src/ChatUI.stories.d.ts +37 -0
  44. package/dist/src/ChatUI.stories.d.ts.map +1 -0
  45. package/dist/src/ChatUI.stories.js +65 -0
  46. package/dist/src/ChatUIThemes.stories.d.ts +28 -0
  47. package/dist/src/ChatUIThemes.stories.d.ts.map +1 -0
  48. package/dist/src/ChatUIThemes.stories.js +109 -0
  49. package/dist/src/ThemeProperties.stories.d.ts +92 -0
  50. package/dist/src/ThemeProperties.stories.d.ts.map +1 -0
  51. package/dist/src/ThemeProperties.stories.js +195 -0
  52. package/dist/src/UseChat.stories.d.ts +21 -0
  53. package/dist/src/UseChat.stories.d.ts.map +1 -0
  54. package/dist/src/UseChat.stories.js +66 -0
  55. package/dist/src/VanillaAdapter.stories.d.ts +39 -0
  56. package/dist/src/VanillaAdapter.stories.d.ts.map +1 -0
  57. package/dist/src/VanillaAdapter.stories.js +78 -0
  58. package/dist/src/index.d.ts +9 -0
  59. package/dist/src/index.d.ts.map +1 -0
  60. package/dist/src/index.js +8 -0
  61. package/package.json +117 -0
  62. 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,3 @@
1
+ export { ChatEngine } from './ChatEngine';
2
+ export { checkUrlWhitelist, getWhitelistErrorMessage } from './urlWhitelist';
3
+ export { apiLogger, ApiLogger } from './apiLogger';
@@ -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