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,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../core/src/urlWhitelist.ts", "../../core/src/apiLogger.ts", "../../core/src/ChatEngine.ts", "../../adapters/vanilla/index.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * URL Whitelist Utility\n * Checks if the current origin is whitelisted before making API calls\n */\n\nexport interface WhitelistCheckResult {\n isWhitelisted: boolean;\n error?: string;\n}\n\n/**\n * Check if the current origin is whitelisted\n * @param apiUrl - The backend API URL\n * @returns Promise with whitelist check result\n */\nexport async function checkUrlWhitelist(apiUrl: string): Promise<WhitelistCheckResult> {\n try {\n // Check if we're in a browser environment\n if (typeof window === 'undefined' || !window.location) {\n // Not in browser environment (e.g., Node.js, SSR), allow the request\n return { isWhitelisted: true };\n }\n\n // Get current origin\n const currentOrigin = window.location.origin;\n \n if (!currentOrigin) {\n // If we can't determine origin (e.g., file:// protocol), allow it\n // This is useful for local development and testing\n return { isWhitelisted: true };\n }\n\n // Call the public whitelist check endpoint\n const checkUrl = `${apiUrl}/url-whitelist/check?url=${encodeURIComponent(currentOrigin)}`;\n \n const response = await fetch(checkUrl, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n // If the endpoint doesn't exist or returns an error, allow the request\n // This ensures backward compatibility\n if (response.status === 404) {\n return { isWhitelisted: true };\n }\n \n const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));\n return {\n isWhitelisted: false,\n error: errorData.error?.message || errorData.message || 'Failed to check URL whitelist',\n };\n }\n\n const data = await response.json();\n const result = data.data || data;\n\n if (result.isWhitelisted) {\n return { isWhitelisted: true };\n }\n\n return {\n isWhitelisted: false,\n error: `This chatbot is not authorized for ${currentOrigin}. Please contact the administrator.`,\n };\n } catch (error) {\n // If there's a network error or the endpoint is unavailable,\n // allow the request (for backward compatibility and offline scenarios)\n console.warn('URL whitelist check failed:', error);\n return { isWhitelisted: true };\n }\n}\n\n/**\n * Get a user-friendly error message for whitelist failures\n */\nexport function getWhitelistErrorMessage(origin: string): string {\n return `This chatbot is not authorized to work on ${origin}. Please contact the administrator to add this URL to the whitelist.`;\n}\n", "/**\n * API Logger - Logs API calls to a file\n */\n\nexport interface ApiLogEntry {\n timestamp: string;\n url: string;\n method: string;\n requestHeaders?: Record<string, string>;\n requestBody?: any;\n responseStatus?: number;\n responseHeaders?: Record<string, string>;\n responseBody?: any;\n duration?: number;\n error?: string;\n}\n\nclass ApiLogger {\n private logs: ApiLogEntry[] = [];\n private maxLogs: number = 1000; // Maximum number of logs to keep in memory\n private enabled: boolean = true;\n\n /**\n * Enable or disable logging\n */\n setEnabled(enabled: boolean): void {\n this.enabled = enabled;\n }\n\n /**\n * Log an API call\n */\n log(entry: ApiLogEntry): void {\n if (!this.enabled) return;\n\n this.logs.push(entry);\n\n // Keep only the last maxLogs entries\n if (this.logs.length > this.maxLogs) {\n this.logs.shift();\n }\n\n // Also log to console in development\n // Check for development mode (browser-compatible)\n let isDev = false;\n if (typeof window !== 'undefined') {\n isDev = window.location?.hostname === 'localhost' || \n window.location?.hostname === '127.0.0.1';\n }\n // Check Node.js process.env if available (using globalThis to avoid TS errors)\n if (!isDev) {\n try {\n const proc = (globalThis as any).process;\n if (proc && proc.env && proc.env.NODE_ENV === 'development') {\n isDev = true;\n }\n } catch {\n // process not available (browser environment)\n }\n }\n \n if (isDev) {\n console.log('[API Logger]', entry);\n }\n }\n\n /**\n * Get all logs\n */\n getLogs(): ApiLogEntry[] {\n return [...this.logs];\n }\n\n /**\n * Clear all logs\n */\n clearLogs(): void {\n this.logs = [];\n }\n\n /**\n * Export logs to a file\n */\n async exportToFile(filename: string = `api-logs-${new Date().toISOString().replace(/[:.]/g, '-')}.json`): Promise<void> {\n if (this.logs.length === 0) {\n console.warn('[API Logger] No logs to export');\n return;\n }\n\n const logData = {\n exportedAt: new Date().toISOString(),\n totalLogs: this.logs.length,\n logs: this.logs,\n };\n\n const jsonString = JSON.stringify(logData, null, 2);\n const blob = new Blob([jsonString], { type: 'application/json' });\n\n // Try to use File System Access API if available (Chrome/Edge)\n if ('showSaveFilePicker' in window) {\n try {\n const fileHandle = await (window as any).showSaveFilePicker({\n suggestedName: filename,\n types: [{\n description: 'JSON files',\n accept: { 'application/json': ['.json'] },\n }],\n });\n\n const writable = await fileHandle.createWritable();\n await writable.write(blob);\n await writable.close();\n console.log(`[API Logger] Logs saved to file: ${filename}`);\n return;\n } catch (err: any) {\n // User cancelled or error occurred, fall back to download\n if (err.name !== 'AbortError') {\n console.warn('[API Logger] File System Access API failed, falling back to download:', err);\n } else {\n return; // User cancelled\n }\n }\n }\n\n // Fallback: Download file\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n console.log(`[API Logger] Logs downloaded as: ${filename}`);\n }\n\n /**\n * Export logs as text file (human-readable format)\n */\n async exportToTextFile(filename: string = `api-logs-${new Date().toISOString().replace(/[:.]/g, '-')}.txt`): Promise<void> {\n if (this.logs.length === 0) {\n console.warn('[API Logger] No logs to export');\n return;\n }\n\n let textContent = `API Call Logs\\n`;\n textContent += `Exported at: ${new Date().toISOString()}\\n`;\n textContent += `Total logs: ${this.logs.length}\\n`;\n textContent += `${'='.repeat(80)}\\n\\n`;\n\n this.logs.forEach((log, index) => {\n textContent += `[${index + 1}] ${log.timestamp}\\n`;\n textContent += `URL: ${log.method} ${log.url}\\n`;\n \n if (log.requestHeaders) {\n textContent += `Request Headers:\\n`;\n Object.entries(log.requestHeaders).forEach(([key, value]) => {\n // Mask sensitive headers\n if (key.toLowerCase() === 'authorization') {\n textContent += ` ${key}: ${value.substring(0, 20)}...\\n`;\n } else {\n textContent += ` ${key}: ${value}\\n`;\n }\n });\n }\n\n if (log.requestBody) {\n textContent += `Request Body:\\n${JSON.stringify(log.requestBody, null, 2)}\\n`;\n }\n\n if (log.error) {\n textContent += `Error: ${log.error}\\n`;\n } else if (log.responseStatus) {\n textContent += `Response Status: ${log.responseStatus}\\n`;\n if (log.responseBody) {\n textContent += `Response Body:\\n${JSON.stringify(log.responseBody, null, 2)}\\n`;\n }\n }\n\n if (log.duration !== undefined) {\n textContent += `Duration: ${log.duration}ms\\n`;\n }\n\n textContent += `${'-'.repeat(80)}\\n\\n`;\n });\n\n const blob = new Blob([textContent], { type: 'text/plain' });\n\n // Try to use File System Access API if available\n if ('showSaveFilePicker' in window) {\n try {\n const fileHandle = await (window as any).showSaveFilePicker({\n suggestedName: filename,\n types: [{\n description: 'Text files',\n accept: { 'text/plain': ['.txt'] },\n }],\n });\n\n const writable = await fileHandle.createWritable();\n await writable.write(blob);\n await writable.close();\n console.log(`[API Logger] Logs saved to file: ${filename}`);\n return;\n } catch (err: any) {\n if (err.name !== 'AbortError') {\n console.warn('[API Logger] File System Access API failed, falling back to download:', err);\n } else {\n return;\n }\n }\n }\n\n // Fallback: Download file\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n console.log(`[API Logger] Logs downloaded as: ${filename}`);\n }\n}\n\n// Singleton instance\nexport const apiLogger = new ApiLogger();\n\n// Export the class for custom instances if needed\nexport { ApiLogger };\n", "import type { Message, ChatConfig, ChatEventType, ChatEventListener } from './types';\nimport { checkUrlWhitelist, getWhitelistErrorMessage } from './urlWhitelist';\nimport { apiLogger, type ApiLogEntry } from './apiLogger';\n\nexport class ChatEngine {\n private messages: Message[] = [];\n private input: string = '';\n private isLoading: boolean = false;\n private error: Error | null = null;\n private ws: WebSocket | null = null;\n private config: ChatConfig;\n private listeners: Map<ChatEventType, Set<ChatEventListener>> = new Map();\n private abortController: AbortController | null = null;\n private conversationId: string | null = null; // Track conversation ID for context\n\n constructor(config: ChatConfig) {\n this.config = config;\n this.initializeWebSocket();\n }\n\n // Event emitter pattern for reactivity\n on(event: ChatEventType, callback: ChatEventListener): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(callback);\n return () => this.off(event, callback);\n }\n\n off(event: ChatEventType, callback: ChatEventListener): void {\n this.listeners.get(event)?.delete(callback);\n }\n\n private emit(event: ChatEventType, data?: any): void {\n this.listeners.get(event)?.forEach(callback => callback(data));\n }\n\n // Getters\n getMessages(): Message[] {\n return [...this.messages];\n }\n\n getInput(): string {\n return this.input;\n }\n\n getIsLoading(): boolean {\n return this.isLoading;\n }\n\n getError(): Error | null {\n return this.error;\n }\n\n getConversationId(): string | null {\n return this.conversationId;\n }\n\n // Methods\n setInput(value: string): void {\n this.input = value;\n this.emit('inputChange', value);\n }\n\n async sendMessage(content?: string): Promise<void> {\n console.log('[ChatEngine] sendMessage called', { content, input: this.input, isLoading: this.isLoading });\n const message = (content || this.input).trim();\n console.log('[ChatEngine] Message after trim:', message);\n if (!message || this.isLoading) {\n console.log('[ChatEngine] Early return - message empty or loading', { message, isLoading: this.isLoading });\n return;\n }\n\n // Add user message\n const userMessage: Message = {\n id: Date.now().toString(),\n content: message,\n role: 'user',\n timestamp: new Date(),\n };\n \n this.messages.push(userMessage);\n this.input = '';\n this.emit('inputChange', '');\n this.emit('messagesChange', this.messages);\n this.emit('message', userMessage);\n this.config.onMessage?.(userMessage);\n\n this.isLoading = true;\n this.error = null;\n this.emit('loadingChange', true);\n this.emit('error', null);\n\n // Cancel previous request if exists\n if (this.abortController) {\n this.abortController.abort();\n }\n this.abortController = new AbortController();\n\n try {\n console.log('[ChatEngine] Starting API call process', {\n enableUrlWhitelistCheck: this.config.enableUrlWhitelistCheck,\n apiUrl: this.config.apiUrl,\n enableWebSocket: this.config.enableWebSocket,\n wsReadyState: this.ws?.readyState,\n });\n \n // Check URL whitelist if enabled (default: true)\n if (this.config.enableUrlWhitelistCheck !== false && this.config.apiUrl) {\n console.log('[ChatEngine] Checking URL whitelist...', this.config.apiUrl);\n try {\n const whitelistCheck = await checkUrlWhitelist(this.config.apiUrl);\n console.log('[ChatEngine] Whitelist check result:', whitelistCheck);\n \n if (!whitelistCheck.isWhitelisted) {\n const origin = typeof window !== 'undefined' ? window.location.origin : 'unknown';\n const errorMessage = whitelistCheck.error || getWhitelistErrorMessage(origin);\n console.error('[ChatEngine] URL not whitelisted:', errorMessage);\n const error = new Error(errorMessage);\n this.error = error;\n this.emit('error', error);\n this.config.onError?.(error);\n this.isLoading = false;\n this.emit('loadingChange', false);\n return;\n }\n } catch (whitelistError) {\n console.error('[ChatEngine] Whitelist check failed with error:', whitelistError);\n // Allow the request to proceed if whitelist check fails (backward compatibility)\n console.warn('[ChatEngine] Proceeding despite whitelist check error');\n }\n } else {\n console.log('[ChatEngine] Skipping URL whitelist check', {\n enableUrlWhitelistCheck: this.config.enableUrlWhitelistCheck,\n hasApiUrl: !!this.config.apiUrl,\n });\n }\n\n // Send via WebSocket if enabled\n if (this.config.enableWebSocket && this.ws?.readyState === WebSocket.OPEN) {\n console.log('[ChatEngine] Sending via WebSocket');\n this.ws.send(JSON.stringify({\n type: 'message',\n content: message,\n role: 'user',\n }));\n // WebSocket responses are handled in onmessage handler\n this.isLoading = false;\n this.emit('loadingChange', false);\n return;\n }\n\n // Send via HTTP API\n console.log('[ChatEngine] Sending message to backend:', message);\n const response = await this.sendToBackend(message);\n console.log('[ChatEngine] Backend response:', response);\n \n // Store conversation ID from response to maintain context\n if (response.conversationId) {\n this.conversationId = response.conversationId;\n }\n \n const assistantMessage: Message = {\n id: response.id || Date.now().toString(),\n content: response.message || response.content || '',\n role: 'assistant',\n timestamp: new Date(response.timestamp || Date.now()),\n metadata: response.metadata,\n };\n\n this.messages.push(assistantMessage);\n this.emit('messagesChange', this.messages);\n this.emit('message', assistantMessage);\n this.config.onMessage?.(assistantMessage);\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') {\n return;\n }\n this.error = err instanceof Error ? err : new Error('Failed to send message');\n this.emit('error', this.error);\n this.config.onError?.(this.error);\n } finally {\n this.isLoading = false;\n this.emit('loadingChange', false);\n }\n }\n\n private async sendToBackend(message: string): Promise<any> {\n if (!this.config.apiUrl) {\n throw new Error('API URL is required');\n }\n\n const url = `${this.config.apiUrl}/chat`;\n console.log('[ChatEngine] Making API call to:', url);\n const startTime = Date.now();\n const requestBody = {\n message,\n conversationId: this.conversationId || undefined, // Use stored conversation ID for context\n };\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...this.config.headers,\n };\n\n if (this.config.apiKey) {\n headers['Authorization'] = `Bearer ${this.config.apiKey}`;\n }\n\n // Log request\n const logEntry: ApiLogEntry = {\n timestamp: new Date().toISOString(),\n url,\n method: 'POST',\n requestHeaders: { ...headers },\n requestBody: { ...requestBody },\n };\n\n // Mask sensitive data in logs\n if (logEntry.requestHeaders && logEntry.requestHeaders['Authorization']) {\n logEntry.requestHeaders['Authorization'] = logEntry.requestHeaders['Authorization'].substring(0, 20) + '...';\n }\n\n try {\n console.log('[ChatEngine] About to fetch:', {\n url,\n method: 'POST',\n headers,\n body: requestBody,\n });\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(requestBody),\n signal: this.abortController?.signal,\n });\n console.log('[ChatEngine] Fetch completed, status:', response.status);\n\n const duration = Date.now() - startTime;\n logEntry.duration = duration;\n\n // Try to get response body (may be JSON or text)\n let responseBody: any = null;\n const contentType = response.headers.get('content-type');\n \n try {\n if (contentType && contentType.includes('application/json')) {\n responseBody = await response.json();\n } else {\n const text = await response.text();\n try {\n responseBody = JSON.parse(text);\n } catch {\n responseBody = text;\n }\n }\n } catch (err) {\n // Failed to parse response body\n responseBody = null;\n }\n\n // Log response\n logEntry.responseStatus = response.status;\n logEntry.responseHeaders = Object.fromEntries(response.headers.entries());\n logEntry.responseBody = responseBody;\n\n if (!response.ok) {\n // Try to extract error message from response\n let errorMessage = `HTTP error! status: ${response.status}`;\n \n if (response.status === 403) {\n // Likely a whitelist error\n try {\n const errorData = responseBody;\n if (errorData?.error?.message) {\n errorMessage = errorData.error.message;\n } else {\n const origin = typeof window !== 'undefined' ? window.location.origin : 'unknown';\n errorMessage = getWhitelistErrorMessage(origin);\n }\n } catch {\n const origin = typeof window !== 'undefined' ? window.location.origin : 'unknown';\n errorMessage = getWhitelistErrorMessage(origin);\n }\n } else if (responseBody?.error?.message) {\n errorMessage = responseBody.error.message;\n } else if (typeof responseBody === 'string') {\n errorMessage = responseBody;\n }\n \n logEntry.error = errorMessage;\n apiLogger.log(logEntry);\n throw new Error(errorMessage);\n }\n\n // Log successful response\n apiLogger.log(logEntry);\n return responseBody;\n } catch (err) {\n const duration = Date.now() - startTime;\n logEntry.duration = duration;\n logEntry.error = err instanceof Error ? err.message : String(err);\n apiLogger.log(logEntry);\n throw err;\n }\n }\n\n private initializeWebSocket(): void {\n if (this.config.enableWebSocket && this.config.websocketUrl) {\n try {\n this.ws = new WebSocket(this.config.websocketUrl);\n \n this.ws.onmessage = (event) => {\n try {\n const data = JSON.parse(event.data);\n if (data.type === 'message') {\n const message: Message = {\n id: data.id || Date.now().toString(),\n content: data.content,\n role: data.role || 'assistant',\n timestamp: new Date(data.timestamp || Date.now()),\n metadata: data.metadata,\n };\n this.messages.push(message);\n this.emit('messagesChange', this.messages);\n this.emit('message', message);\n this.config.onMessage?.(message);\n this.isLoading = false;\n this.emit('loadingChange', false);\n }\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to parse WebSocket message');\n this.error = error;\n this.emit('error', error);\n this.config.onError?.(error);\n }\n };\n\n this.ws.onerror = () => {\n const error = new Error('WebSocket error');\n this.error = error;\n this.emit('error', error);\n this.config.onError?.(error);\n };\n\n this.ws.onclose = () => {\n // WebSocket closed\n };\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to initialize WebSocket');\n this.error = error;\n this.emit('error', error);\n this.config.onError?.(error);\n }\n }\n }\n\n addMessage(message: Message): void {\n this.messages.push(message);\n this.emit('messagesChange', this.messages);\n this.emit('message', message);\n this.config.onMessage?.(message);\n }\n\n clearMessages(): void {\n this.messages = [];\n this.conversationId = null; // Clear conversation ID when clearing messages\n this.error = null;\n this.emit('messagesChange', this.messages);\n this.emit('error', null);\n }\n\n retryLastMessage(): Promise<void> {\n const lastUserMessage = [...this.messages].reverse().find((m) => m.role === 'user');\n if (lastUserMessage) {\n // Remove the last user message and assistant response\n const index = this.messages.findIndex((m) => m.id === lastUserMessage.id);\n if (index >= 0) {\n this.messages = this.messages.slice(0, index);\n this.emit('messagesChange', this.messages);\n }\n return this.sendMessage(lastUserMessage.content);\n }\n return Promise.resolve();\n }\n\n destroy(): void {\n this.ws?.close();\n this.abortController?.abort();\n this.listeners.clear();\n }\n}\n\n", "import { ChatEngine } from '../../core/src/ChatEngine';\nimport type { ChatConfig, Message, ChatTheme } from '../../core/src/types';\n\nexport interface ChatUIOptions {\n config: ChatConfig;\n theme?: ChatTheme;\n container: HTMLElement | string; // Accept selector string or element\n placeholder?: string;\n showTimestamp?: boolean;\n showAvatar?: boolean;\n userAvatar?: string;\n assistantAvatar?: string;\n maxHeight?: string;\n disabled?: boolean;\n autoScroll?: boolean; // Auto-scroll to bottom on new messages\n enableMarkdown?: boolean; // Enable markdown rendering\n showClearButton?: boolean; // Show clear conversation button\n emptyStateMessage?: string; // Custom empty state message\n loadingMessage?: string; // Custom loading message\n customMessageRenderer?: (message: Message) => string; // Custom message renderer\n onInit?: (instance: ChatUIInstance) => void; // Callback after initialization\n}\n\nexport interface ChatUIInstance {\n engine: ChatEngine;\n render: () => void;\n destroy: () => void;\n clear: () => void; // Clear conversation\n scrollToBottom: () => void; // Scroll to bottom manually\n getContainer: () => HTMLElement; // Get container element\n}\n\n/**\n * Escape HTML to prevent XSS\n */\nfunction escapeHtml(text: string): string {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n}\n\n/**\n * Simple markdown-like text formatting (basic support)\n */\nfunction formatText(text: string, enableMarkdown: boolean): string {\n if (!enableMarkdown) {\n return escapeHtml(text);\n }\n\n // Basic markdown support\n let formatted = escapeHtml(text);\n \n // Bold: **text** or __text__\n formatted = formatted.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');\n formatted = formatted.replace(/__(.+?)__/g, '<strong>$1</strong>');\n \n // Italic: *text* or _text_\n formatted = formatted.replace(/\\*(.+?)\\*/g, '<em>$1</em>');\n formatted = formatted.replace(/_(.+?)_/g, '<em>$1</em>');\n \n // Code: `code`\n formatted = formatted.replace(/`([^`]+)`/g, '<code>$1</code>');\n \n // Links: [text](url)\n formatted = formatted.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href=\"$2\" target=\"_blank\" rel=\"noopener noreferrer\">$1</a>');\n \n // Line breaks\n formatted = formatted.replace(/\\n/g, '<br>');\n \n return formatted;\n}\n\n/**\n * Get container element from selector or element\n */\nfunction getContainerElement(container: HTMLElement | string): HTMLElement {\n if (typeof container === 'string') {\n const element = document.querySelector(container);\n if (!element) {\n throw new Error(`Chatbot container not found: ${container}`);\n }\n if (!(element instanceof HTMLElement)) {\n throw new Error(`Chatbot container is not an HTMLElement: ${container}`);\n }\n return element;\n }\n return container;\n}\n\nexport function createChatUI(options: ChatUIOptions): ChatUIInstance {\n const {\n config,\n theme = {},\n container,\n placeholder = 'Type your message...',\n showTimestamp = true,\n showAvatar = true,\n userAvatar,\n assistantAvatar,\n maxHeight = '600px',\n disabled = false,\n autoScroll = true,\n enableMarkdown = true,\n showClearButton = true,\n emptyStateMessage = 'Start a conversation...',\n loadingMessage = 'Thinking...',\n customMessageRenderer,\n onInit,\n } = options;\n\n // Get container element\n const containerElement = getContainerElement(container);\n const engine = new ChatEngine(config);\n\n // Store references for cleanup\n let messagesContainer: HTMLElement | null = null;\n let inputElement: HTMLInputElement | null = null;\n let formElement: HTMLFormElement | null = null;\n let sendButtonElement: HTMLButtonElement | null = null;\n let clearButtonElement: HTMLButtonElement | null = null;\n let isUpdatingInputProgrammatically = false; // Flag to prevent input event loop\n\n // Apply CSS variables for theming\n const applyTheme = () => {\n const root = containerElement;\n root.style.setProperty('--chat-primary-color', theme.primaryColor || '#3b82f6');\n root.style.setProperty('--chat-secondary-color', theme.secondaryColor || '#64748b');\n root.style.setProperty('--chat-background-color', theme.backgroundColor || '#ffffff');\n root.style.setProperty('--chat-text-color', theme.textColor || '#1e293b');\n root.style.setProperty('--chat-user-message-color', theme.userMessageColor || '#3b82f6');\n root.style.setProperty('--chat-assistant-message-color', theme.assistantMessageColor || '#f1f5f9');\n root.style.setProperty('--chat-input-background-color', theme.inputBackgroundColor || '#f8fafc');\n root.style.setProperty('--chat-input-text-color', theme.inputTextColor || '#1e293b');\n root.style.setProperty('--chat-border-radius', theme.borderRadius || '0.5rem');\n root.style.setProperty('--chat-font-family', theme.fontFamily || 'system-ui, sans-serif');\n root.style.setProperty('--chat-font-size', theme.fontSize || '1rem');\n root.style.setProperty('--chat-max-height', maxHeight);\n };\n\n // Scroll to bottom helper\n const scrollToBottom = () => {\n if (messagesContainer && autoScroll) {\n messagesContainer.scrollTop = messagesContainer.scrollHeight;\n }\n };\n\n const formatTimestamp = (date: Date) => {\n return new Intl.DateTimeFormat('en-US', {\n hour: '2-digit',\n minute: '2-digit',\n }).format(date);\n };\n\n const renderAvatar = (role: 'user' | 'assistant'): string => {\n if (!showAvatar) return '';\n \n const avatar = role === 'user' ? userAvatar : assistantAvatar;\n \n if (avatar) {\n return `<div class=\"chat-avatar\"><img src=\"${escapeHtml(avatar)}\" alt=\"${role}\" /></div>`;\n }\n \n return `<div class=\"chat-avatar\">${role === 'user' ? 'U' : 'A'}</div>`;\n };\n\n const renderMessage = (message: Message): string => {\n // Use custom renderer if provided\n if (customMessageRenderer) {\n return customMessageRenderer(message);\n }\n\n const isUser = message.role === 'user';\n const isSystem = message.role === 'system';\n const role = isUser ? 'user' : 'assistant';\n const avatarHtml = !isSystem ? renderAvatar(role) : '';\n \n const messageContent = enableMarkdown \n ? `<div class=\"chat-markdown\">${formatText(message.content, enableMarkdown)}</div>`\n : `<div class=\"chat-message-text\">${formatText(message.content, false)}</div>`;\n \n return `\n <div class=\"chat-message ${isUser ? 'chat-message-user' : isSystem ? 'chat-message-system' : 'chat-message-assistant'}\" data-message-id=\"${escapeHtml(message.id)}\">\n ${!isUser && !isSystem ? avatarHtml : ''}\n <div class=\"chat-message-content ${isUser ? 'chat-message-content-user' : isSystem ? 'chat-message-content-system' : 'chat-message-content-assistant'}\">\n ${messageContent}\n ${showTimestamp ? `<div class=\"chat-message-timestamp\">${formatTimestamp(message.timestamp)}</div>` : ''}\n </div>\n ${isUser ? avatarHtml : ''}\n </div>\n `;\n };\n\n const render = () => {\n const messages = engine.getMessages();\n const input = engine.getInput();\n const isLoading = engine.getIsLoading();\n const error = engine.getError();\n \n // Preserve input focus and cursor position before re-render\n const wasInputFocused = inputElement && document.activeElement === inputElement;\n const inputCursorPosition = inputElement ? inputElement.selectionStart || 0 : 0;\n \n applyTheme();\n \n containerElement.className = `chat-container ${containerElement.className || ''}`.trim();\n containerElement.setAttribute('data-chat-ui', 'true');\n \n containerElement.innerHTML = `\n <div class=\"chat-messages-container\">\n ${messages.length === 0 ? `<div class=\"chat-empty-state\">${escapeHtml(emptyStateMessage)}</div>` : ''}\n ${messages.map(renderMessage).join('')}\n ${isLoading ? `\n <div class=\"chat-message chat-message-assistant chat-message-loading\">\n ${renderAvatar('assistant')}\n <div class=\"chat-message-content chat-message-content-assistant\">\n <div class=\"chat-message-text\">${escapeHtml(loadingMessage)}</div>\n </div>\n </div>\n ` : ''}\n </div>\n ${error ? `<div class=\"chat-error\">\n <div class=\"chat-error-message\">Error: ${escapeHtml(error.message)}</div>\n <button class=\"chat-retry-button\" type=\"button\">Retry</button>\n </div>` : ''}\n ${showClearButton && messages.length > 0 ? `\n <div class=\"chat-actions\">\n <button class=\"chat-clear-button\" type=\"button\" title=\"Clear conversation\">Clear</button>\n </div>\n ` : ''}\n <div class=\"chat-input-container\">\n <form class=\"chat-input-form\">\n <input \n type=\"text\" \n class=\"chat-input\" \n value=\"${escapeHtml(input)}\" \n placeholder=\"${escapeHtml(placeholder)}\" \n ${disabled || isLoading ? 'disabled' : ''}\n aria-label=\"Chat input\"\n />\n <button \n type=\"submit\" \n class=\"chat-send-button\"\n ${disabled || isLoading || !input.trim() ? 'disabled' : ''}\n aria-label=\"Send message\"\n >\n Send\n </button>\n </form>\n </div>\n `;\n\n // Store references\n messagesContainer = containerElement.querySelector('.chat-messages-container') as HTMLElement;\n inputElement = containerElement.querySelector('.chat-input') as HTMLInputElement;\n formElement = containerElement.querySelector('.chat-input-form') as HTMLFormElement;\n sendButtonElement = containerElement.querySelector('.chat-send-button') as HTMLButtonElement;\n clearButtonElement = containerElement.querySelector('.chat-clear-button') as HTMLButtonElement;\n const retryButton = containerElement.querySelector('.chat-retry-button') as HTMLButtonElement;\n\n // Restore input focus and cursor position if it was focused before re-render\n if (wasInputFocused && inputElement) {\n requestAnimationFrame(() => {\n if (inputElement) {\n inputElement.focus();\n const newCursorPosition = Math.min(inputCursorPosition, inputElement.value.length);\n inputElement.setSelectionRange(newCursorPosition, newCursorPosition);\n }\n });\n }\n\n // Attach event listeners\n if (formElement) {\n formElement.addEventListener('submit', (e) => {\n e.preventDefault();\n if (!disabled && !isLoading && input.trim()) {\n engine.sendMessage();\n }\n });\n }\n \n if (inputElement) {\n // Handle input changes\n inputElement.addEventListener('input', (e) => {\n // Skip if we're programmatically updating the input\n if (isUpdatingInputProgrammatically) {\n return;\n }\n const target = e.target as HTMLInputElement;\n engine.setInput(target.value);\n });\n\n // Keyboard shortcuts: Enter to send, Shift+Enter for newline\n inputElement.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n if (!disabled && !isLoading && input.trim()) {\n engine.sendMessage();\n }\n }\n // Shift+Enter allows default behavior (newline if textarea, ignored if input)\n });\n }\n\n // Clear button handler\n if (clearButtonElement) {\n clearButtonElement.addEventListener('click', () => {\n if (confirm('Are you sure you want to clear the conversation?')) {\n engine.clearMessages();\n }\n });\n }\n\n // Retry button handler\n if (retryButton) {\n retryButton.addEventListener('click', () => {\n engine.retryLastMessage();\n });\n }\n\n // Auto-scroll to bottom after render\n // Use setTimeout to ensure DOM is updated\n setTimeout(() => {\n scrollToBottom();\n }, 0);\n };\n\n // Subscribe to engine events\n engine.on('messagesChange', () => {\n render();\n // Scroll after messages change\n setTimeout(() => {\n scrollToBottom();\n }, 100);\n });\n \n // Helper function to update send button state\n const updateSendButtonState = () => {\n if (sendButtonElement) {\n const input = engine.getInput();\n const isLoading = engine.getIsLoading();\n const shouldDisable = disabled || isLoading || !input.trim();\n sendButtonElement.disabled = shouldDisable;\n }\n };\n\n engine.on('inputChange', (value) => {\n // Only update input value without full re-render to preserve focus\n if (inputElement && inputElement.isConnected) {\n const wasFocused = document.activeElement === inputElement;\n const cursorPosition = inputElement.selectionStart || 0;\n \n // Set flag to prevent input event from firing\n isUpdatingInputProgrammatically = true;\n inputElement.value = value || '';\n isUpdatingInputProgrammatically = false;\n \n // Update send button state\n updateSendButtonState();\n \n // Restore focus and cursor position if input was focused\n if (wasFocused) {\n // Use requestAnimationFrame to ensure DOM is ready\n requestAnimationFrame(() => {\n if (inputElement) {\n inputElement.focus();\n // Restore cursor position, accounting for value length changes\n const newCursorPosition = Math.min(cursorPosition, inputElement.value.length);\n inputElement.setSelectionRange(newCursorPosition, newCursorPosition);\n }\n });\n }\n } else {\n // If input element doesn't exist yet, do a full render\n render();\n }\n });\n \n engine.on('loadingChange', () => {\n // Update send button state without full re-render if possible\n updateSendButtonState();\n // Still do full render for loading indicator\n render();\n setTimeout(() => {\n scrollToBottom();\n }, 100);\n });\n \n engine.on('error', render);\n \n // Initial render\n render();\n \n const instance: ChatUIInstance = {\n engine,\n render,\n destroy: () => {\n engine.destroy();\n containerElement.innerHTML = '';\n // Clear references\n messagesContainer = null;\n inputElement = null;\n formElement = null;\n sendButtonElement = null;\n clearButtonElement = null;\n },\n clear: () => {\n engine.clearMessages();\n },\n scrollToBottom,\n getContainer: () => containerElement,\n };\n\n // Call onInit callback if provided\n if (onInit) {\n onInit(instance);\n }\n \n return instance;\n}\n\n/**\n * Global initialization helper for PHP/Laravel integration\n * Usage: window.ChatbotUI.init('container-id', { apiUrl: '...' })\n */\nif (typeof window !== 'undefined') {\n (window as any).ChatbotUI = {\n create: createChatUI,\n init: (containerId: string, options: Partial<ChatUIOptions> & { config: ChatConfig }) => {\n const container = typeof containerId === 'string' \n ? document.getElementById(containerId) || document.querySelector(containerId)\n : containerId;\n \n if (!container) {\n console.error(`Chatbot container not found: ${containerId}`);\n return null;\n }\n\n return createChatUI({\n container: container as HTMLElement,\n ...options,\n });\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";(()=>{async function N(a){try{if(typeof window>"u"||!window.location)return{isWhitelisted:!0};let t=window.location.origin;if(!t)return{isWhitelisted:!0};let e=`${a}/url-whitelist/check?url=${encodeURIComponent(t)}`,n=await fetch(e,{method:"GET",headers:{"Content-Type":"application/json"}});if(!n.ok){if(n.status===404)return{isWhitelisted:!0};let s=await n.json().catch(()=>({message:"Unknown error"}));return{isWhitelisted:!1,error:s.error?.message||s.message||"Failed to check URL whitelist"}}let r=await n.json();return(r.data||r).isWhitelisted?{isWhitelisted:!0}:{isWhitelisted:!1,error:`This chatbot is not authorized for ${t}. Please contact the administrator.`}}catch(t){return console.warn("URL whitelist check failed:",t),{isWhitelisted:!0}}}function I(a){return`This chatbot is not authorized to work on ${a}. Please contact the administrator to add this URL to the whitelist.`}var W=class{constructor(){this.logs=[];this.maxLogs=1e3;this.enabled=!0}setEnabled(t){this.enabled=t}log(t){if(!this.enabled)return;this.logs.push(t),this.logs.length>this.maxLogs&&this.logs.shift();let e=!1;if(typeof window<"u"&&(e=window.location?.hostname==="localhost"||window.location?.hostname==="127.0.0.1"),!e)try{let n=globalThis.process;n&&n.env&&n.env.NODE_ENV==="development"&&(e=!0)}catch{}e&&console.log("[API Logger]",t)}getLogs(){return[...this.logs]}clearLogs(){this.logs=[]}async exportToFile(t=`api-logs-${new Date().toISOString().replace(/[:.]/g,"-")}.json`){if(this.logs.length===0){console.warn("[API Logger] No logs to export");return}let e={exportedAt:new Date().toISOString(),totalLogs:this.logs.length,logs:this.logs},n=JSON.stringify(e,null,2),r=new Blob([n],{type:"application/json"});if("showSaveFilePicker"in window)try{let u=await(await window.showSaveFilePicker({suggestedName:t,types:[{description:"JSON files",accept:{"application/json":[".json"]}}]})).createWritable();await u.write(r),await u.close(),console.log(`[API Logger] Logs saved to file: ${t}`);return}catch(i){if(i.name!=="AbortError")console.warn("[API Logger] File System Access API failed, falling back to download:",i);else return}let c=URL.createObjectURL(r),s=document.createElement("a");s.href=c,s.download=t,document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(c),console.log(`[API Logger] Logs downloaded as: ${t}`)}async exportToTextFile(t=`api-logs-${new Date().toISOString().replace(/[:.]/g,"-")}.txt`){if(this.logs.length===0){console.warn("[API Logger] No logs to export");return}let e=`API Call Logs
|
|
2
|
+
`;e+=`Exported at: ${new Date().toISOString()}
|
|
3
|
+
`,e+=`Total logs: ${this.logs.length}
|
|
4
|
+
`,e+=`${"=".repeat(80)}
|
|
5
|
+
|
|
6
|
+
`,this.logs.forEach((s,i)=>{e+=`[${i+1}] ${s.timestamp}
|
|
7
|
+
`,e+=`URL: ${s.method} ${s.url}
|
|
8
|
+
`,s.requestHeaders&&(e+=`Request Headers:
|
|
9
|
+
`,Object.entries(s.requestHeaders).forEach(([u,g])=>{u.toLowerCase()==="authorization"?e+=` ${u}: ${g.substring(0,20)}...
|
|
10
|
+
`:e+=` ${u}: ${g}
|
|
11
|
+
`})),s.requestBody&&(e+=`Request Body:
|
|
12
|
+
${JSON.stringify(s.requestBody,null,2)}
|
|
13
|
+
`),s.error?e+=`Error: ${s.error}
|
|
14
|
+
`:s.responseStatus&&(e+=`Response Status: ${s.responseStatus}
|
|
15
|
+
`,s.responseBody&&(e+=`Response Body:
|
|
16
|
+
${JSON.stringify(s.responseBody,null,2)}
|
|
17
|
+
`)),s.duration!==void 0&&(e+=`Duration: ${s.duration}ms
|
|
18
|
+
`),e+=`${"-".repeat(80)}
|
|
19
|
+
|
|
20
|
+
`});let n=new Blob([e],{type:"text/plain"});if("showSaveFilePicker"in window)try{let i=await(await window.showSaveFilePicker({suggestedName:t,types:[{description:"Text files",accept:{"text/plain":[".txt"]}}]})).createWritable();await i.write(n),await i.close(),console.log(`[API Logger] Logs saved to file: ${t}`);return}catch(s){if(s.name!=="AbortError")console.warn("[API Logger] File System Access API failed, falling back to download:",s);else return}let r=URL.createObjectURL(n),c=document.createElement("a");c.href=r,c.download=t,document.body.appendChild(c),c.click(),document.body.removeChild(c),URL.revokeObjectURL(r),console.log(`[API Logger] Logs downloaded as: ${t}`)}},U=new W;var P=class{constructor(t){this.messages=[];this.input="";this.isLoading=!1;this.error=null;this.ws=null;this.listeners=new Map;this.abortController=null;this.conversationId=null;this.config=t,this.initializeWebSocket()}on(t,e){return this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e),()=>this.off(t,e)}off(t,e){this.listeners.get(t)?.delete(e)}emit(t,e){this.listeners.get(t)?.forEach(n=>n(e))}getMessages(){return[...this.messages]}getInput(){return this.input}getIsLoading(){return this.isLoading}getError(){return this.error}getConversationId(){return this.conversationId}setInput(t){this.input=t,this.emit("inputChange",t)}async sendMessage(t){console.log("[ChatEngine] sendMessage called",{content:t,input:this.input,isLoading:this.isLoading});let e=(t||this.input).trim();if(console.log("[ChatEngine] Message after trim:",e),!e||this.isLoading){console.log("[ChatEngine] Early return - message empty or loading",{message:e,isLoading:this.isLoading});return}let n={id:Date.now().toString(),content:e,role:"user",timestamp:new Date};this.messages.push(n),this.input="",this.emit("inputChange",""),this.emit("messagesChange",this.messages),this.emit("message",n),this.config.onMessage?.(n),this.isLoading=!0,this.error=null,this.emit("loadingChange",!0),this.emit("error",null),this.abortController&&this.abortController.abort(),this.abortController=new AbortController;try{if(console.log("[ChatEngine] Starting API call process",{enableUrlWhitelistCheck:this.config.enableUrlWhitelistCheck,apiUrl:this.config.apiUrl,enableWebSocket:this.config.enableWebSocket,wsReadyState:this.ws?.readyState}),this.config.enableUrlWhitelistCheck!==!1&&this.config.apiUrl){console.log("[ChatEngine] Checking URL whitelist...",this.config.apiUrl);try{let s=await N(this.config.apiUrl);if(console.log("[ChatEngine] Whitelist check result:",s),!s.isWhitelisted){let i=typeof window<"u"?window.location.origin:"unknown",u=s.error||I(i);console.error("[ChatEngine] URL not whitelisted:",u);let g=new Error(u);this.error=g,this.emit("error",g),this.config.onError?.(g),this.isLoading=!1,this.emit("loadingChange",!1);return}}catch(s){console.error("[ChatEngine] Whitelist check failed with error:",s),console.warn("[ChatEngine] Proceeding despite whitelist check error")}}else console.log("[ChatEngine] Skipping URL whitelist check",{enableUrlWhitelistCheck:this.config.enableUrlWhitelistCheck,hasApiUrl:!!this.config.apiUrl});if(this.config.enableWebSocket&&this.ws?.readyState===WebSocket.OPEN){console.log("[ChatEngine] Sending via WebSocket"),this.ws.send(JSON.stringify({type:"message",content:e,role:"user"})),this.isLoading=!1,this.emit("loadingChange",!1);return}console.log("[ChatEngine] Sending message to backend:",e);let r=await this.sendToBackend(e);console.log("[ChatEngine] Backend response:",r),r.conversationId&&(this.conversationId=r.conversationId);let c={id:r.id||Date.now().toString(),content:r.message||r.content||"",role:"assistant",timestamp:new Date(r.timestamp||Date.now()),metadata:r.metadata};this.messages.push(c),this.emit("messagesChange",this.messages),this.emit("message",c),this.config.onMessage?.(c)}catch(r){if(r instanceof Error&&r.name==="AbortError")return;this.error=r instanceof Error?r:new Error("Failed to send message"),this.emit("error",this.error),this.config.onError?.(this.error)}finally{this.isLoading=!1,this.emit("loadingChange",!1)}}async sendToBackend(t){if(!this.config.apiUrl)throw new Error("API URL is required");let e=`${this.config.apiUrl}/chat`;console.log("[ChatEngine] Making API call to:",e);let n=Date.now(),r={message:t,conversationId:this.conversationId||void 0},c={"Content-Type":"application/json",...this.config.headers};this.config.apiKey&&(c.Authorization=`Bearer ${this.config.apiKey}`);let s={timestamp:new Date().toISOString(),url:e,method:"POST",requestHeaders:{...c},requestBody:{...r}};s.requestHeaders&&s.requestHeaders.Authorization&&(s.requestHeaders.Authorization=s.requestHeaders.Authorization.substring(0,20)+"...");try{console.log("[ChatEngine] About to fetch:",{url:e,method:"POST",headers:c,body:r});let i=await fetch(e,{method:"POST",headers:c,body:JSON.stringify(r),signal:this.abortController?.signal});console.log("[ChatEngine] Fetch completed, status:",i.status);let u=Date.now()-n;s.duration=u;let g=null,b=i.headers.get("content-type");try{if(b&&b.includes("application/json"))g=await i.json();else{let p=await i.text();try{g=JSON.parse(p)}catch{g=p}}}catch{g=null}if(s.responseStatus=i.status,s.responseHeaders=Object.fromEntries(i.headers.entries()),s.responseBody=g,!i.ok){let p=`HTTP error! status: ${i.status}`;if(i.status===403)try{let v=g;if(v?.error?.message)p=v.error.message;else{let A=typeof window<"u"?window.location.origin:"unknown";p=I(A)}}catch{let v=typeof window<"u"?window.location.origin:"unknown";p=I(v)}else g?.error?.message?p=g.error.message:typeof g=="string"&&(p=g);throw s.error=p,U.log(s),new Error(p)}return U.log(s),g}catch(i){let u=Date.now()-n;throw s.duration=u,s.error=i instanceof Error?i.message:String(i),U.log(s),i}}initializeWebSocket(){if(this.config.enableWebSocket&&this.config.websocketUrl)try{this.ws=new WebSocket(this.config.websocketUrl),this.ws.onmessage=t=>{try{let e=JSON.parse(t.data);if(e.type==="message"){let n={id:e.id||Date.now().toString(),content:e.content,role:e.role||"assistant",timestamp:new Date(e.timestamp||Date.now()),metadata:e.metadata};this.messages.push(n),this.emit("messagesChange",this.messages),this.emit("message",n),this.config.onMessage?.(n),this.isLoading=!1,this.emit("loadingChange",!1)}}catch(e){let n=e instanceof Error?e:new Error("Failed to parse WebSocket message");this.error=n,this.emit("error",n),this.config.onError?.(n)}},this.ws.onerror=()=>{let t=new Error("WebSocket error");this.error=t,this.emit("error",t),this.config.onError?.(t)},this.ws.onclose=()=>{}}catch(t){let e=t instanceof Error?t:new Error("Failed to initialize WebSocket");this.error=e,this.emit("error",e),this.config.onError?.(e)}}addMessage(t){this.messages.push(t),this.emit("messagesChange",this.messages),this.emit("message",t),this.config.onMessage?.(t)}clearMessages(){this.messages=[],this.conversationId=null,this.error=null,this.emit("messagesChange",this.messages),this.emit("error",null)}retryLastMessage(){let t=[...this.messages].reverse().find(e=>e.role==="user");if(t){let e=this.messages.findIndex(n=>n.id===t.id);return e>=0&&(this.messages=this.messages.slice(0,e),this.emit("messagesChange",this.messages)),this.sendMessage(t.content)}return Promise.resolve()}destroy(){this.ws?.close(),this.abortController?.abort(),this.listeners.clear()}};function w(a){let t=document.createElement("div");return t.textContent=a,t.innerHTML}function z(a,t){if(!t)return w(a);let e=w(a);return e=e.replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>"),e=e.replace(/__(.+?)__/g,"<strong>$1</strong>"),e=e.replace(/\*(.+?)\*/g,"<em>$1</em>"),e=e.replace(/_(.+?)_/g,"<em>$1</em>"),e=e.replace(/`([^`]+)`/g,"<code>$1</code>"),e=e.replace(/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'),e=e.replace(/\n/g,"<br>"),e}function X(a){if(typeof a=="string"){let t=document.querySelector(a);if(!t)throw new Error(`Chatbot container not found: ${a}`);if(!(t instanceof HTMLElement))throw new Error(`Chatbot container is not an HTMLElement: ${a}`);return t}return a}function B(a){let{config:t,theme:e={},container:n,placeholder:r="Type your message...",showTimestamp:c=!0,showAvatar:s=!0,userAvatar:i,assistantAvatar:u,maxHeight:g="600px",disabled:b=!1,autoScroll:p=!0,enableMarkdown:v=!0,showClearButton:A=!0,emptyStateMessage:J="Start a conversation...",loadingMessage:_="Thinking...",customMessageRenderer:R,onInit:O}=a,f=X(n),h=new P(t),L=null,l=null,S=null,M=null,k=null,x=!1,K=()=>{let o=f;o.style.setProperty("--chat-primary-color",e.primaryColor||"#3b82f6"),o.style.setProperty("--chat-secondary-color",e.secondaryColor||"#64748b"),o.style.setProperty("--chat-background-color",e.backgroundColor||"#ffffff"),o.style.setProperty("--chat-text-color",e.textColor||"#1e293b"),o.style.setProperty("--chat-user-message-color",e.userMessageColor||"#3b82f6"),o.style.setProperty("--chat-assistant-message-color",e.assistantMessageColor||"#f1f5f9"),o.style.setProperty("--chat-input-background-color",e.inputBackgroundColor||"#f8fafc"),o.style.setProperty("--chat-input-text-color",e.inputTextColor||"#1e293b"),o.style.setProperty("--chat-border-radius",e.borderRadius||"0.5rem"),o.style.setProperty("--chat-font-family",e.fontFamily||"system-ui, sans-serif"),o.style.setProperty("--chat-font-size",e.fontSize||"1rem"),o.style.setProperty("--chat-max-height",g)},$=()=>{L&&p&&(L.scrollTop=L.scrollHeight)},G=o=>new Intl.DateTimeFormat("en-US",{hour:"2-digit",minute:"2-digit"}).format(o),D=o=>{if(!s)return"";let d=o==="user"?i:u;return d?`<div class="chat-avatar"><img src="${w(d)}" alt="${o}" /></div>`:`<div class="chat-avatar">${o==="user"?"U":"A"}</div>`},V=o=>{if(R)return R(o);let d=o.role==="user",m=o.role==="system",T=m?"":D(d?"user":"assistant"),H=v?`<div class="chat-markdown">${z(o.content,v)}</div>`:`<div class="chat-message-text">${z(o.content,!1)}</div>`;return`
|
|
21
|
+
<div class="chat-message ${d?"chat-message-user":m?"chat-message-system":"chat-message-assistant"}" data-message-id="${w(o.id)}">
|
|
22
|
+
${!d&&!m?T:""}
|
|
23
|
+
<div class="chat-message-content ${d?"chat-message-content-user":m?"chat-message-content-system":"chat-message-content-assistant"}">
|
|
24
|
+
${H}
|
|
25
|
+
${c?`<div class="chat-message-timestamp">${G(o.timestamp)}</div>`:""}
|
|
26
|
+
</div>
|
|
27
|
+
${d?T:""}
|
|
28
|
+
</div>
|
|
29
|
+
`},C=()=>{let o=h.getMessages(),d=h.getInput(),m=h.getIsLoading(),E=h.getError(),T=l&&document.activeElement===l,H=l&&l.selectionStart||0;K(),f.className=`chat-container ${f.className||""}`.trim(),f.setAttribute("data-chat-ui","true"),f.innerHTML=`
|
|
30
|
+
<div class="chat-messages-container">
|
|
31
|
+
${o.length===0?`<div class="chat-empty-state">${w(J)}</div>`:""}
|
|
32
|
+
${o.map(V).join("")}
|
|
33
|
+
${m?`
|
|
34
|
+
<div class="chat-message chat-message-assistant chat-message-loading">
|
|
35
|
+
${D("assistant")}
|
|
36
|
+
<div class="chat-message-content chat-message-content-assistant">
|
|
37
|
+
<div class="chat-message-text">${w(_)}</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
`:""}
|
|
41
|
+
</div>
|
|
42
|
+
${E?`<div class="chat-error">
|
|
43
|
+
<div class="chat-error-message">Error: ${w(E.message)}</div>
|
|
44
|
+
<button class="chat-retry-button" type="button">Retry</button>
|
|
45
|
+
</div>`:""}
|
|
46
|
+
${A&&o.length>0?`
|
|
47
|
+
<div class="chat-actions">
|
|
48
|
+
<button class="chat-clear-button" type="button" title="Clear conversation">Clear</button>
|
|
49
|
+
</div>
|
|
50
|
+
`:""}
|
|
51
|
+
<div class="chat-input-container">
|
|
52
|
+
<form class="chat-input-form">
|
|
53
|
+
<input
|
|
54
|
+
type="text"
|
|
55
|
+
class="chat-input"
|
|
56
|
+
value="${w(d)}"
|
|
57
|
+
placeholder="${w(r)}"
|
|
58
|
+
${b||m?"disabled":""}
|
|
59
|
+
aria-label="Chat input"
|
|
60
|
+
/>
|
|
61
|
+
<button
|
|
62
|
+
type="submit"
|
|
63
|
+
class="chat-send-button"
|
|
64
|
+
${b||m||!d.trim()?"disabled":""}
|
|
65
|
+
aria-label="Send message"
|
|
66
|
+
>
|
|
67
|
+
Send
|
|
68
|
+
</button>
|
|
69
|
+
</form>
|
|
70
|
+
</div>
|
|
71
|
+
`,L=f.querySelector(".chat-messages-container"),l=f.querySelector(".chat-input"),S=f.querySelector(".chat-input-form"),M=f.querySelector(".chat-send-button"),k=f.querySelector(".chat-clear-button");let j=f.querySelector(".chat-retry-button");T&&l&&requestAnimationFrame(()=>{if(l){l.focus();let y=Math.min(H,l.value.length);l.setSelectionRange(y,y)}}),S&&S.addEventListener("submit",y=>{y.preventDefault(),!b&&!m&&d.trim()&&h.sendMessage()}),l&&(l.addEventListener("input",y=>{if(x)return;let Q=y.target;h.setInput(Q.value)}),l.addEventListener("keydown",y=>{y.key==="Enter"&&!y.shiftKey&&(y.preventDefault(),!b&&!m&&d.trim()&&h.sendMessage())})),k&&k.addEventListener("click",()=>{confirm("Are you sure you want to clear the conversation?")&&h.clearMessages()}),j&&j.addEventListener("click",()=>{h.retryLastMessage()}),setTimeout(()=>{$()},0)};h.on("messagesChange",()=>{C(),setTimeout(()=>{$()},100)});let q=()=>{if(M){let o=h.getInput(),d=h.getIsLoading(),m=b||d||!o.trim();M.disabled=m}};h.on("inputChange",o=>{if(l&&l.isConnected){let d=document.activeElement===l,m=l.selectionStart||0;x=!0,l.value=o||"",x=!1,q(),d&&requestAnimationFrame(()=>{if(l){l.focus();let E=Math.min(m,l.value.length);l.setSelectionRange(E,E)}})}else C()}),h.on("loadingChange",()=>{q(),C(),setTimeout(()=>{$()},100)}),h.on("error",C),C();let F={engine:h,render:C,destroy:()=>{h.destroy(),f.innerHTML="",L=null,l=null,S=null,M=null,k=null},clear:()=>{h.clearMessages()},scrollToBottom:$,getContainer:()=>f};return O&&O(F),F}typeof window<"u"&&(window.ChatbotUI={create:B,init:(a,t)=>{let e=typeof a=="string"?document.getElementById(a)||document.querySelector(a):a;return e?B({container:e,...t}):(console.error(`Chatbot container not found: ${a}`),null)}});})();
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Chatbot</title>
|
|
7
|
+
<link rel="stylesheet" href="./chatbot-ui.css">
|
|
8
|
+
<style>
|
|
9
|
+
* {
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
body {
|
|
16
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
17
|
+
height: 100vh;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
background: #f5f5f5;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#chatbot-container {
|
|
23
|
+
width: 100%;
|
|
24
|
+
height: 100vh;
|
|
25
|
+
max-width: 100%;
|
|
26
|
+
max-height: 100vh;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Responsive adjustments */
|
|
30
|
+
@media (max-width: 640px) {
|
|
31
|
+
body {
|
|
32
|
+
padding: 0;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
36
|
+
</head>
|
|
37
|
+
<body>
|
|
38
|
+
<div id="chatbot-container"></div>
|
|
39
|
+
|
|
40
|
+
<script src="./chatbot-ui.js"></script>
|
|
41
|
+
<script>
|
|
42
|
+
// Get API URL from query parameter or use default
|
|
43
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
44
|
+
const apiUrl = urlParams.get('apiUrl') || urlParams.get('api') || 'http://localhost:3003/api';
|
|
45
|
+
|
|
46
|
+
// Get theme options from query parameters
|
|
47
|
+
const theme = {};
|
|
48
|
+
const primaryColor = urlParams.get('primaryColor');
|
|
49
|
+
if (primaryColor) theme.primaryColor = primaryColor;
|
|
50
|
+
|
|
51
|
+
const userMessageColor = urlParams.get('userMessageColor');
|
|
52
|
+
if (userMessageColor) theme.userMessageColor = userMessageColor;
|
|
53
|
+
|
|
54
|
+
const assistantMessageColor = urlParams.get('assistantMessageColor');
|
|
55
|
+
if (assistantMessageColor) theme.assistantMessageColor = assistantMessageColor;
|
|
56
|
+
|
|
57
|
+
const backgroundColor = urlParams.get('backgroundColor');
|
|
58
|
+
if (backgroundColor) theme.backgroundColor = backgroundColor;
|
|
59
|
+
|
|
60
|
+
const borderRadius = urlParams.get('borderRadius');
|
|
61
|
+
if (borderRadius) theme.borderRadius = borderRadius;
|
|
62
|
+
|
|
63
|
+
// Get other options
|
|
64
|
+
const placeholder = urlParams.get('placeholder') || 'Type your message...';
|
|
65
|
+
const showTimestamp = urlParams.get('showTimestamp') !== 'false';
|
|
66
|
+
const showAvatar = urlParams.get('showAvatar') !== 'false';
|
|
67
|
+
const maxHeight = urlParams.get('maxHeight') || '100%';
|
|
68
|
+
|
|
69
|
+
// Initialize chatbot when script loads
|
|
70
|
+
if (typeof ChatbotUI !== 'undefined') {
|
|
71
|
+
ChatbotUI.init('chatbot-container', {
|
|
72
|
+
config: {
|
|
73
|
+
apiUrl: apiUrl,
|
|
74
|
+
},
|
|
75
|
+
theme: Object.keys(theme).length > 0 ? theme : undefined,
|
|
76
|
+
placeholder: placeholder,
|
|
77
|
+
showTimestamp: showTimestamp,
|
|
78
|
+
showAvatar: showAvatar,
|
|
79
|
+
maxHeight: maxHeight,
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
console.error('ChatbotUI not loaded. Make sure chatbot-ui.js is accessible.');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Expose API for parent window communication (if in iframe)
|
|
86
|
+
if (window.parent !== window) {
|
|
87
|
+
window.addEventListener('message', function(event) {
|
|
88
|
+
// Handle messages from parent window if needed
|
|
89
|
+
if (event.data && event.data.type === 'chatbot-config') {
|
|
90
|
+
// Re-initialize with new config if needed
|
|
91
|
+
console.log('Received config from parent:', event.data);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Notify parent that chatbot is ready
|
|
96
|
+
window.parent.postMessage({ type: 'chatbot-ready' }, '*');
|
|
97
|
+
}
|
|
98
|
+
</script>
|
|
99
|
+
</body>
|
|
100
|
+
</html>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Message, ChatConfig, ChatEventType, ChatEventListener } from './types';
|
|
2
|
+
export declare class ChatEngine {
|
|
3
|
+
private messages;
|
|
4
|
+
private input;
|
|
5
|
+
private isLoading;
|
|
6
|
+
private error;
|
|
7
|
+
private ws;
|
|
8
|
+
private config;
|
|
9
|
+
private listeners;
|
|
10
|
+
private abortController;
|
|
11
|
+
private conversationId;
|
|
12
|
+
constructor(config: ChatConfig);
|
|
13
|
+
on(event: ChatEventType, callback: ChatEventListener): () => void;
|
|
14
|
+
off(event: ChatEventType, callback: ChatEventListener): void;
|
|
15
|
+
private emit;
|
|
16
|
+
getMessages(): Message[];
|
|
17
|
+
getInput(): string;
|
|
18
|
+
getIsLoading(): boolean;
|
|
19
|
+
getError(): Error | null;
|
|
20
|
+
getConversationId(): string | null;
|
|
21
|
+
setInput(value: string): void;
|
|
22
|
+
sendMessage(content?: string): Promise<void>;
|
|
23
|
+
private sendToBackend;
|
|
24
|
+
private initializeWebSocket;
|
|
25
|
+
addMessage(message: Message): void;
|
|
26
|
+
clearMessages(): void;
|
|
27
|
+
retryLastMessage(): Promise<void>;
|
|
28
|
+
destroy(): void;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=ChatEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatEngine.d.ts","sourceRoot":"","sources":["../../../core/src/ChatEngine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAIrF,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,SAAS,CAAyD;IAC1E,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,cAAc,CAAuB;gBAEjC,MAAM,EAAE,UAAU;IAM9B,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,iBAAiB,GAAG,MAAM,IAAI;IAQjE,GAAG,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI;IAI5D,OAAO,CAAC,IAAI;IAKZ,WAAW,IAAI,OAAO,EAAE;IAIxB,QAAQ,IAAI,MAAM;IAIlB,YAAY,IAAI,OAAO;IAIvB,QAAQ,IAAI,KAAK,GAAG,IAAI;IAIxB,iBAAiB,IAAI,MAAM,GAAG,IAAI;IAKlC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKvB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YA2HpC,aAAa;IAwH3B,OAAO,CAAC,mBAAmB;IAkD3B,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAOlC,aAAa,IAAI,IAAI;IAQrB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAcjC,OAAO,IAAI,IAAI;CAKhB"}
|