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 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../adapters/solid/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createChat } from './createChat';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ChatEngine } from '../../core/src/ChatEngine';
|
|
2
|
+
import type { ChatConfig, Message, ChatTheme } from '../../core/src/types';
|
|
3
|
+
export interface ChatUIOptions {
|
|
4
|
+
config: ChatConfig;
|
|
5
|
+
theme?: ChatTheme;
|
|
6
|
+
container: HTMLElement | string;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
showTimestamp?: boolean;
|
|
9
|
+
showAvatar?: boolean;
|
|
10
|
+
userAvatar?: string;
|
|
11
|
+
assistantAvatar?: string;
|
|
12
|
+
maxHeight?: string;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
autoScroll?: boolean;
|
|
15
|
+
enableMarkdown?: boolean;
|
|
16
|
+
showClearButton?: boolean;
|
|
17
|
+
emptyStateMessage?: string;
|
|
18
|
+
loadingMessage?: string;
|
|
19
|
+
customMessageRenderer?: (message: Message) => string;
|
|
20
|
+
onInit?: (instance: ChatUIInstance) => void;
|
|
21
|
+
}
|
|
22
|
+
export interface ChatUIInstance {
|
|
23
|
+
engine: ChatEngine;
|
|
24
|
+
render: () => void;
|
|
25
|
+
destroy: () => void;
|
|
26
|
+
clear: () => void;
|
|
27
|
+
scrollToBottom: () => void;
|
|
28
|
+
getContainer: () => HTMLElement;
|
|
29
|
+
}
|
|
30
|
+
export declare function createChatUI(options: ChatUIOptions): ChatUIInstance;
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../adapters/vanilla/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAE3E,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,SAAS,EAAE,WAAW,GAAG,MAAM,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qBAAqB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;IACrD,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAC;CAC7C;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,WAAW,CAAC;CACjC;AA2DD,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc,CAyUnE"}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { ChatEngine } from '../../core/src/ChatEngine';
|
|
2
|
+
/**
|
|
3
|
+
* Escape HTML to prevent XSS
|
|
4
|
+
*/
|
|
5
|
+
function escapeHtml(text) {
|
|
6
|
+
const div = document.createElement('div');
|
|
7
|
+
div.textContent = text;
|
|
8
|
+
return div.innerHTML;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Simple markdown-like text formatting (basic support)
|
|
12
|
+
*/
|
|
13
|
+
function formatText(text, enableMarkdown) {
|
|
14
|
+
if (!enableMarkdown) {
|
|
15
|
+
return escapeHtml(text);
|
|
16
|
+
}
|
|
17
|
+
// Basic markdown support
|
|
18
|
+
let formatted = escapeHtml(text);
|
|
19
|
+
// Bold: **text** or __text__
|
|
20
|
+
formatted = formatted.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
21
|
+
formatted = formatted.replace(/__(.+?)__/g, '<strong>$1</strong>');
|
|
22
|
+
// Italic: *text* or _text_
|
|
23
|
+
formatted = formatted.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
24
|
+
formatted = formatted.replace(/_(.+?)_/g, '<em>$1</em>');
|
|
25
|
+
// Code: `code`
|
|
26
|
+
formatted = formatted.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
27
|
+
// Links: [text](url)
|
|
28
|
+
formatted = formatted.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
29
|
+
// Line breaks
|
|
30
|
+
formatted = formatted.replace(/\n/g, '<br>');
|
|
31
|
+
return formatted;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get container element from selector or element
|
|
35
|
+
*/
|
|
36
|
+
function getContainerElement(container) {
|
|
37
|
+
if (typeof container === 'string') {
|
|
38
|
+
const element = document.querySelector(container);
|
|
39
|
+
if (!element) {
|
|
40
|
+
throw new Error(`Chatbot container not found: ${container}`);
|
|
41
|
+
}
|
|
42
|
+
if (!(element instanceof HTMLElement)) {
|
|
43
|
+
throw new Error(`Chatbot container is not an HTMLElement: ${container}`);
|
|
44
|
+
}
|
|
45
|
+
return element;
|
|
46
|
+
}
|
|
47
|
+
return container;
|
|
48
|
+
}
|
|
49
|
+
export function createChatUI(options) {
|
|
50
|
+
const { config, theme = {}, container, placeholder = 'Type your message...', showTimestamp = true, showAvatar = true, userAvatar, assistantAvatar, maxHeight = '600px', disabled = false, autoScroll = true, enableMarkdown = true, showClearButton = true, emptyStateMessage = 'Start a conversation...', loadingMessage = 'Thinking...', customMessageRenderer, onInit, } = options;
|
|
51
|
+
// Get container element
|
|
52
|
+
const containerElement = getContainerElement(container);
|
|
53
|
+
const engine = new ChatEngine(config);
|
|
54
|
+
// Store references for cleanup
|
|
55
|
+
let messagesContainer = null;
|
|
56
|
+
let inputElement = null;
|
|
57
|
+
let formElement = null;
|
|
58
|
+
let sendButtonElement = null;
|
|
59
|
+
let clearButtonElement = null;
|
|
60
|
+
let isUpdatingInputProgrammatically = false; // Flag to prevent input event loop
|
|
61
|
+
// Apply CSS variables for theming
|
|
62
|
+
const applyTheme = () => {
|
|
63
|
+
const root = containerElement;
|
|
64
|
+
root.style.setProperty('--chat-primary-color', theme.primaryColor || '#3b82f6');
|
|
65
|
+
root.style.setProperty('--chat-secondary-color', theme.secondaryColor || '#64748b');
|
|
66
|
+
root.style.setProperty('--chat-background-color', theme.backgroundColor || '#ffffff');
|
|
67
|
+
root.style.setProperty('--chat-text-color', theme.textColor || '#1e293b');
|
|
68
|
+
root.style.setProperty('--chat-user-message-color', theme.userMessageColor || '#3b82f6');
|
|
69
|
+
root.style.setProperty('--chat-assistant-message-color', theme.assistantMessageColor || '#f1f5f9');
|
|
70
|
+
root.style.setProperty('--chat-input-background-color', theme.inputBackgroundColor || '#f8fafc');
|
|
71
|
+
root.style.setProperty('--chat-input-text-color', theme.inputTextColor || '#1e293b');
|
|
72
|
+
root.style.setProperty('--chat-border-radius', theme.borderRadius || '0.5rem');
|
|
73
|
+
root.style.setProperty('--chat-font-family', theme.fontFamily || 'system-ui, sans-serif');
|
|
74
|
+
root.style.setProperty('--chat-font-size', theme.fontSize || '1rem');
|
|
75
|
+
root.style.setProperty('--chat-max-height', maxHeight);
|
|
76
|
+
};
|
|
77
|
+
// Scroll to bottom helper
|
|
78
|
+
const scrollToBottom = () => {
|
|
79
|
+
if (messagesContainer && autoScroll) {
|
|
80
|
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const formatTimestamp = (date) => {
|
|
84
|
+
return new Intl.DateTimeFormat('en-US', {
|
|
85
|
+
hour: '2-digit',
|
|
86
|
+
minute: '2-digit',
|
|
87
|
+
}).format(date);
|
|
88
|
+
};
|
|
89
|
+
const renderAvatar = (role) => {
|
|
90
|
+
if (!showAvatar)
|
|
91
|
+
return '';
|
|
92
|
+
const avatar = role === 'user' ? userAvatar : assistantAvatar;
|
|
93
|
+
if (avatar) {
|
|
94
|
+
return `<div class="chat-avatar"><img src="${escapeHtml(avatar)}" alt="${role}" /></div>`;
|
|
95
|
+
}
|
|
96
|
+
return `<div class="chat-avatar">${role === 'user' ? 'U' : 'A'}</div>`;
|
|
97
|
+
};
|
|
98
|
+
const renderMessage = (message) => {
|
|
99
|
+
// Use custom renderer if provided
|
|
100
|
+
if (customMessageRenderer) {
|
|
101
|
+
return customMessageRenderer(message);
|
|
102
|
+
}
|
|
103
|
+
const isUser = message.role === 'user';
|
|
104
|
+
const isSystem = message.role === 'system';
|
|
105
|
+
const role = isUser ? 'user' : 'assistant';
|
|
106
|
+
const avatarHtml = !isSystem ? renderAvatar(role) : '';
|
|
107
|
+
const messageContent = enableMarkdown
|
|
108
|
+
? `<div class="chat-markdown">${formatText(message.content, enableMarkdown)}</div>`
|
|
109
|
+
: `<div class="chat-message-text">${formatText(message.content, false)}</div>`;
|
|
110
|
+
return `
|
|
111
|
+
<div class="chat-message ${isUser ? 'chat-message-user' : isSystem ? 'chat-message-system' : 'chat-message-assistant'}" data-message-id="${escapeHtml(message.id)}">
|
|
112
|
+
${!isUser && !isSystem ? avatarHtml : ''}
|
|
113
|
+
<div class="chat-message-content ${isUser ? 'chat-message-content-user' : isSystem ? 'chat-message-content-system' : 'chat-message-content-assistant'}">
|
|
114
|
+
${messageContent}
|
|
115
|
+
${showTimestamp ? `<div class="chat-message-timestamp">${formatTimestamp(message.timestamp)}</div>` : ''}
|
|
116
|
+
</div>
|
|
117
|
+
${isUser ? avatarHtml : ''}
|
|
118
|
+
</div>
|
|
119
|
+
`;
|
|
120
|
+
};
|
|
121
|
+
const render = () => {
|
|
122
|
+
const messages = engine.getMessages();
|
|
123
|
+
const input = engine.getInput();
|
|
124
|
+
const isLoading = engine.getIsLoading();
|
|
125
|
+
const error = engine.getError();
|
|
126
|
+
// Preserve input focus and cursor position before re-render
|
|
127
|
+
const wasInputFocused = inputElement && document.activeElement === inputElement;
|
|
128
|
+
const inputCursorPosition = inputElement ? inputElement.selectionStart || 0 : 0;
|
|
129
|
+
applyTheme();
|
|
130
|
+
containerElement.className = `chat-container ${containerElement.className || ''}`.trim();
|
|
131
|
+
containerElement.setAttribute('data-chat-ui', 'true');
|
|
132
|
+
containerElement.innerHTML = `
|
|
133
|
+
<div class="chat-messages-container">
|
|
134
|
+
${messages.length === 0 ? `<div class="chat-empty-state">${escapeHtml(emptyStateMessage)}</div>` : ''}
|
|
135
|
+
${messages.map(renderMessage).join('')}
|
|
136
|
+
${isLoading ? `
|
|
137
|
+
<div class="chat-message chat-message-assistant chat-message-loading">
|
|
138
|
+
${renderAvatar('assistant')}
|
|
139
|
+
<div class="chat-message-content chat-message-content-assistant">
|
|
140
|
+
<div class="chat-message-text">${escapeHtml(loadingMessage)}</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
` : ''}
|
|
144
|
+
</div>
|
|
145
|
+
${error ? `<div class="chat-error">
|
|
146
|
+
<div class="chat-error-message">Error: ${escapeHtml(error.message)}</div>
|
|
147
|
+
<button class="chat-retry-button" type="button">Retry</button>
|
|
148
|
+
</div>` : ''}
|
|
149
|
+
${showClearButton && messages.length > 0 ? `
|
|
150
|
+
<div class="chat-actions">
|
|
151
|
+
<button class="chat-clear-button" type="button" title="Clear conversation">Clear</button>
|
|
152
|
+
</div>
|
|
153
|
+
` : ''}
|
|
154
|
+
<div class="chat-input-container">
|
|
155
|
+
<form class="chat-input-form">
|
|
156
|
+
<input
|
|
157
|
+
type="text"
|
|
158
|
+
class="chat-input"
|
|
159
|
+
value="${escapeHtml(input)}"
|
|
160
|
+
placeholder="${escapeHtml(placeholder)}"
|
|
161
|
+
${disabled || isLoading ? 'disabled' : ''}
|
|
162
|
+
aria-label="Chat input"
|
|
163
|
+
/>
|
|
164
|
+
<button
|
|
165
|
+
type="submit"
|
|
166
|
+
class="chat-send-button"
|
|
167
|
+
${disabled || isLoading || !input.trim() ? 'disabled' : ''}
|
|
168
|
+
aria-label="Send message"
|
|
169
|
+
>
|
|
170
|
+
Send
|
|
171
|
+
</button>
|
|
172
|
+
</form>
|
|
173
|
+
</div>
|
|
174
|
+
`;
|
|
175
|
+
// Store references
|
|
176
|
+
messagesContainer = containerElement.querySelector('.chat-messages-container');
|
|
177
|
+
inputElement = containerElement.querySelector('.chat-input');
|
|
178
|
+
formElement = containerElement.querySelector('.chat-input-form');
|
|
179
|
+
sendButtonElement = containerElement.querySelector('.chat-send-button');
|
|
180
|
+
clearButtonElement = containerElement.querySelector('.chat-clear-button');
|
|
181
|
+
const retryButton = containerElement.querySelector('.chat-retry-button');
|
|
182
|
+
// Restore input focus and cursor position if it was focused before re-render
|
|
183
|
+
if (wasInputFocused && inputElement) {
|
|
184
|
+
requestAnimationFrame(() => {
|
|
185
|
+
if (inputElement) {
|
|
186
|
+
inputElement.focus();
|
|
187
|
+
const newCursorPosition = Math.min(inputCursorPosition, inputElement.value.length);
|
|
188
|
+
inputElement.setSelectionRange(newCursorPosition, newCursorPosition);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
// Attach event listeners
|
|
193
|
+
if (formElement) {
|
|
194
|
+
formElement.addEventListener('submit', (e) => {
|
|
195
|
+
e.preventDefault();
|
|
196
|
+
if (!disabled && !isLoading && input.trim()) {
|
|
197
|
+
engine.sendMessage();
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (inputElement) {
|
|
202
|
+
// Handle input changes
|
|
203
|
+
inputElement.addEventListener('input', (e) => {
|
|
204
|
+
// Skip if we're programmatically updating the input
|
|
205
|
+
if (isUpdatingInputProgrammatically) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const target = e.target;
|
|
209
|
+
engine.setInput(target.value);
|
|
210
|
+
});
|
|
211
|
+
// Keyboard shortcuts: Enter to send, Shift+Enter for newline
|
|
212
|
+
inputElement.addEventListener('keydown', (e) => {
|
|
213
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
214
|
+
e.preventDefault();
|
|
215
|
+
if (!disabled && !isLoading && input.trim()) {
|
|
216
|
+
engine.sendMessage();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Shift+Enter allows default behavior (newline if textarea, ignored if input)
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
// Clear button handler
|
|
223
|
+
if (clearButtonElement) {
|
|
224
|
+
clearButtonElement.addEventListener('click', () => {
|
|
225
|
+
if (confirm('Are you sure you want to clear the conversation?')) {
|
|
226
|
+
engine.clearMessages();
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
// Retry button handler
|
|
231
|
+
if (retryButton) {
|
|
232
|
+
retryButton.addEventListener('click', () => {
|
|
233
|
+
engine.retryLastMessage();
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
// Auto-scroll to bottom after render
|
|
237
|
+
// Use setTimeout to ensure DOM is updated
|
|
238
|
+
setTimeout(() => {
|
|
239
|
+
scrollToBottom();
|
|
240
|
+
}, 0);
|
|
241
|
+
};
|
|
242
|
+
// Subscribe to engine events
|
|
243
|
+
engine.on('messagesChange', () => {
|
|
244
|
+
render();
|
|
245
|
+
// Scroll after messages change
|
|
246
|
+
setTimeout(() => {
|
|
247
|
+
scrollToBottom();
|
|
248
|
+
}, 100);
|
|
249
|
+
});
|
|
250
|
+
// Helper function to update send button state
|
|
251
|
+
const updateSendButtonState = () => {
|
|
252
|
+
if (sendButtonElement) {
|
|
253
|
+
const input = engine.getInput();
|
|
254
|
+
const isLoading = engine.getIsLoading();
|
|
255
|
+
const shouldDisable = disabled || isLoading || !input.trim();
|
|
256
|
+
sendButtonElement.disabled = shouldDisable;
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
engine.on('inputChange', (value) => {
|
|
260
|
+
// Only update input value without full re-render to preserve focus
|
|
261
|
+
if (inputElement && inputElement.isConnected) {
|
|
262
|
+
const wasFocused = document.activeElement === inputElement;
|
|
263
|
+
const cursorPosition = inputElement.selectionStart || 0;
|
|
264
|
+
// Set flag to prevent input event from firing
|
|
265
|
+
isUpdatingInputProgrammatically = true;
|
|
266
|
+
inputElement.value = value || '';
|
|
267
|
+
isUpdatingInputProgrammatically = false;
|
|
268
|
+
// Update send button state
|
|
269
|
+
updateSendButtonState();
|
|
270
|
+
// Restore focus and cursor position if input was focused
|
|
271
|
+
if (wasFocused) {
|
|
272
|
+
// Use requestAnimationFrame to ensure DOM is ready
|
|
273
|
+
requestAnimationFrame(() => {
|
|
274
|
+
if (inputElement) {
|
|
275
|
+
inputElement.focus();
|
|
276
|
+
// Restore cursor position, accounting for value length changes
|
|
277
|
+
const newCursorPosition = Math.min(cursorPosition, inputElement.value.length);
|
|
278
|
+
inputElement.setSelectionRange(newCursorPosition, newCursorPosition);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
// If input element doesn't exist yet, do a full render
|
|
285
|
+
render();
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
engine.on('loadingChange', () => {
|
|
289
|
+
// Update send button state without full re-render if possible
|
|
290
|
+
updateSendButtonState();
|
|
291
|
+
// Still do full render for loading indicator
|
|
292
|
+
render();
|
|
293
|
+
setTimeout(() => {
|
|
294
|
+
scrollToBottom();
|
|
295
|
+
}, 100);
|
|
296
|
+
});
|
|
297
|
+
engine.on('error', render);
|
|
298
|
+
// Initial render
|
|
299
|
+
render();
|
|
300
|
+
const instance = {
|
|
301
|
+
engine,
|
|
302
|
+
render,
|
|
303
|
+
destroy: () => {
|
|
304
|
+
engine.destroy();
|
|
305
|
+
containerElement.innerHTML = '';
|
|
306
|
+
// Clear references
|
|
307
|
+
messagesContainer = null;
|
|
308
|
+
inputElement = null;
|
|
309
|
+
formElement = null;
|
|
310
|
+
sendButtonElement = null;
|
|
311
|
+
clearButtonElement = null;
|
|
312
|
+
},
|
|
313
|
+
clear: () => {
|
|
314
|
+
engine.clearMessages();
|
|
315
|
+
},
|
|
316
|
+
scrollToBottom,
|
|
317
|
+
getContainer: () => containerElement,
|
|
318
|
+
};
|
|
319
|
+
// Call onInit callback if provided
|
|
320
|
+
if (onInit) {
|
|
321
|
+
onInit(instance);
|
|
322
|
+
}
|
|
323
|
+
return instance;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Global initialization helper for PHP/Laravel integration
|
|
327
|
+
* Usage: window.ChatbotUI.init('container-id', { apiUrl: '...' })
|
|
328
|
+
*/
|
|
329
|
+
if (typeof window !== 'undefined') {
|
|
330
|
+
window.ChatbotUI = {
|
|
331
|
+
create: createChatUI,
|
|
332
|
+
init: (containerId, options) => {
|
|
333
|
+
const container = typeof containerId === 'string'
|
|
334
|
+
? document.getElementById(containerId) || document.querySelector(containerId)
|
|
335
|
+
: containerId;
|
|
336
|
+
if (!container) {
|
|
337
|
+
console.error(`Chatbot container not found: ${containerId}`);
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
return createChatUI({
|
|
341
|
+
container: container,
|
|
342
|
+
...options,
|
|
343
|
+
});
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,177 @@
|
|
|
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 UI - CDN Example</title>
|
|
7
|
+
|
|
8
|
+
<!-- Include Chatbot UI CSS -->
|
|
9
|
+
<link rel="stylesheet" href="./chatbot-ui.css">
|
|
10
|
+
|
|
11
|
+
<style>
|
|
12
|
+
body {
|
|
13
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
14
|
+
margin: 0;
|
|
15
|
+
padding: 20px;
|
|
16
|
+
background: #f5f5f5;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.container {
|
|
20
|
+
max-width: 800px;
|
|
21
|
+
margin: 0 auto;
|
|
22
|
+
background: white;
|
|
23
|
+
border-radius: 8px;
|
|
24
|
+
padding: 20px;
|
|
25
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
h1 {
|
|
29
|
+
margin-top: 0;
|
|
30
|
+
color: #1e293b;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.info {
|
|
34
|
+
background: #f0f9ff;
|
|
35
|
+
border-left: 4px solid #3b82f6;
|
|
36
|
+
padding: 12px;
|
|
37
|
+
margin-bottom: 20px;
|
|
38
|
+
border-radius: 4px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.code-block {
|
|
42
|
+
background: #1e293b;
|
|
43
|
+
color: #f1f5f9;
|
|
44
|
+
padding: 16px;
|
|
45
|
+
border-radius: 6px;
|
|
46
|
+
overflow-x: auto;
|
|
47
|
+
margin: 16px 0;
|
|
48
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
49
|
+
font-size: 14px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#chatbot-container {
|
|
53
|
+
width: 100%;
|
|
54
|
+
height: 600px;
|
|
55
|
+
border: 1px solid #e2e8f0;
|
|
56
|
+
border-radius: 8px;
|
|
57
|
+
margin-top: 20px;
|
|
58
|
+
}
|
|
59
|
+
</style>
|
|
60
|
+
</head>
|
|
61
|
+
<body>
|
|
62
|
+
<div class="container">
|
|
63
|
+
<h1>🤖 Chatbot UI - CDN Example</h1>
|
|
64
|
+
|
|
65
|
+
<div class="info">
|
|
66
|
+
<strong>📝 Usage:</strong> Include the chatbot-ui.css and chatbot-ui.min.js files in your HTML,
|
|
67
|
+
then initialize the chatbot using <code>ChatbotUI.init()</code> or <code>ChatbotUI.create()</code>.
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<h3>📋 HTML Setup:</h3>
|
|
71
|
+
<div class="code-block"><!-- Include CSS -->
|
|
72
|
+
<link rel="stylesheet" href="https://cdn.example.com/chatbot-ui.css">
|
|
73
|
+
|
|
74
|
+
<!-- Include JavaScript -->
|
|
75
|
+
<script src="https://cdn.example.com/chatbot-ui.min.js"></script>
|
|
76
|
+
|
|
77
|
+
<!-- Container element -->
|
|
78
|
+
<div id="chatbot-container"></div>
|
|
79
|
+
|
|
80
|
+
<!-- Initialize -->
|
|
81
|
+
<script>
|
|
82
|
+
ChatbotUI.init('chatbot-container', {
|
|
83
|
+
config: {
|
|
84
|
+
apiUrl: 'http://localhost:3003/api',
|
|
85
|
+
},
|
|
86
|
+
theme: {
|
|
87
|
+
primaryColor: '#3b82f6',
|
|
88
|
+
userMessageColor: '#3b82f6',
|
|
89
|
+
assistantMessageColor: '#f1f5f9',
|
|
90
|
+
},
|
|
91
|
+
placeholder: 'Type your message...',
|
|
92
|
+
showTimestamp: true,
|
|
93
|
+
showAvatar: true,
|
|
94
|
+
});
|
|
95
|
+
</script></div>
|
|
96
|
+
|
|
97
|
+
<h3>🎨 Customization Example:</h3>
|
|
98
|
+
<div class="code-block">ChatbotUI.init('chatbot-container', {
|
|
99
|
+
config: {
|
|
100
|
+
apiUrl: 'https://your-api.com/api/chat',
|
|
101
|
+
// Optional: Add headers
|
|
102
|
+
headers: {
|
|
103
|
+
'Authorization': 'Bearer YOUR_TOKEN',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
theme: {
|
|
107
|
+
primaryColor: '#8b5cf6',
|
|
108
|
+
userMessageColor: '#8b5cf6',
|
|
109
|
+
assistantMessageColor: '#f3e8ff',
|
|
110
|
+
backgroundColor: '#ffffff',
|
|
111
|
+
borderRadius: '12px',
|
|
112
|
+
},
|
|
113
|
+
placeholder: 'Ask me anything...',
|
|
114
|
+
showTimestamp: true,
|
|
115
|
+
showAvatar: true,
|
|
116
|
+
maxHeight: '600px',
|
|
117
|
+
enableMarkdown: true,
|
|
118
|
+
showClearButton: true,
|
|
119
|
+
});</div>
|
|
120
|
+
|
|
121
|
+
<h3>🎯 Advanced Usage:</h3>
|
|
122
|
+
<div class="code-block">// Using create() for more control
|
|
123
|
+
const chatInstance = ChatbotUI.create({
|
|
124
|
+
container: document.getElementById('chatbot-container'),
|
|
125
|
+
config: {
|
|
126
|
+
apiUrl: 'https://your-api.com/api/chat',
|
|
127
|
+
},
|
|
128
|
+
theme: {
|
|
129
|
+
primaryColor: '#10b981',
|
|
130
|
+
},
|
|
131
|
+
onInit: (instance) => {
|
|
132
|
+
console.log('Chatbot initialized!', instance);
|
|
133
|
+
// Access the engine
|
|
134
|
+
instance.engine.on('messagesChange', () => {
|
|
135
|
+
console.log('Messages updated');
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Later, you can interact with it
|
|
141
|
+
chatInstance.clear(); // Clear conversation
|
|
142
|
+
chatInstance.scrollToBottom(); // Scroll to bottom
|
|
143
|
+
chatInstance.destroy(); // Clean up</div>
|
|
144
|
+
|
|
145
|
+
<hr style="margin: 30px 0; border: none; border-top: 1px solid #e2e8f0;">
|
|
146
|
+
|
|
147
|
+
<h3>💬 Live Example:</h3>
|
|
148
|
+
<div id="chatbot-container"></div>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<!-- Initialize the chatbot -->
|
|
152
|
+
<script>
|
|
153
|
+
// Wait for ChatbotUI to be available
|
|
154
|
+
if (typeof ChatbotUI !== 'undefined') {
|
|
155
|
+
ChatbotUI.init('chatbot-container', {
|
|
156
|
+
config: {
|
|
157
|
+
apiUrl: 'http://localhost:3003/api',
|
|
158
|
+
},
|
|
159
|
+
theme: {
|
|
160
|
+
primaryColor: '#3b82f6',
|
|
161
|
+
userMessageColor: '#3b82f6',
|
|
162
|
+
assistantMessageColor: '#f1f5f9',
|
|
163
|
+
borderRadius: '8px',
|
|
164
|
+
},
|
|
165
|
+
placeholder: 'Type your message...',
|
|
166
|
+
showTimestamp: true,
|
|
167
|
+
showAvatar: true,
|
|
168
|
+
maxHeight: '600px',
|
|
169
|
+
enableMarkdown: true,
|
|
170
|
+
showClearButton: true,
|
|
171
|
+
});
|
|
172
|
+
} else {
|
|
173
|
+
console.error('ChatbotUI not loaded. Make sure chatbot-ui.min.js is included.');
|
|
174
|
+
}
|
|
175
|
+
</script>
|
|
176
|
+
</body>
|
|
177
|
+
</html>
|