@astermind/chatbot-template 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 (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +603 -0
  3. package/dist/astermind-chatbot.css +1 -0
  4. package/dist/astermind-chatbot.esm.js +891 -0
  5. package/dist/astermind-chatbot.esm.js.map +1 -0
  6. package/dist/astermind-chatbot.min.js +21 -0
  7. package/dist/astermind-chatbot.min.js.map +1 -0
  8. package/dist/astermind-chatbot.umd.js +915 -0
  9. package/dist/astermind-chatbot.umd.js.map +1 -0
  10. package/dist/components/ActionCard.d.ts +12 -0
  11. package/dist/components/ActionCard.d.ts.map +1 -0
  12. package/dist/components/ChatBubble.d.ts +12 -0
  13. package/dist/components/ChatBubble.d.ts.map +1 -0
  14. package/dist/components/ChatHeader.d.ts +13 -0
  15. package/dist/components/ChatHeader.d.ts.map +1 -0
  16. package/dist/components/ChatInput.d.ts +11 -0
  17. package/dist/components/ChatInput.d.ts.map +1 -0
  18. package/dist/components/ChatWindow.d.ts +21 -0
  19. package/dist/components/ChatWindow.d.ts.map +1 -0
  20. package/dist/components/ChatbotWidget.d.ts +9 -0
  21. package/dist/components/ChatbotWidget.d.ts.map +1 -0
  22. package/dist/components/MessageBubble.d.ts +10 -0
  23. package/dist/components/MessageBubble.d.ts.map +1 -0
  24. package/dist/components/MessageList.d.ts +11 -0
  25. package/dist/components/MessageList.d.ts.map +1 -0
  26. package/dist/components/SourceCitation.d.ts +10 -0
  27. package/dist/components/SourceCitation.d.ts.map +1 -0
  28. package/dist/components/StatusIndicator.d.ts +10 -0
  29. package/dist/components/StatusIndicator.d.ts.map +1 -0
  30. package/dist/components/TypingIndicator.d.ts +5 -0
  31. package/dist/components/TypingIndicator.d.ts.map +1 -0
  32. package/dist/components/index.d.ts +12 -0
  33. package/dist/components/index.d.ts.map +1 -0
  34. package/dist/context/ChatContext.d.ts +24 -0
  35. package/dist/context/ChatContext.d.ts.map +1 -0
  36. package/dist/context/ThemeContext.d.ts +20 -0
  37. package/dist/context/ThemeContext.d.ts.map +1 -0
  38. package/dist/hooks/index.d.ts +5 -0
  39. package/dist/hooks/index.d.ts.map +1 -0
  40. package/dist/hooks/useChat.d.ts +21 -0
  41. package/dist/hooks/useChat.d.ts.map +1 -0
  42. package/dist/hooks/useOmega.d.ts +27 -0
  43. package/dist/hooks/useOmega.d.ts.map +1 -0
  44. package/dist/hooks/useScrollToBottom.d.ts +15 -0
  45. package/dist/hooks/useScrollToBottom.d.ts.map +1 -0
  46. package/dist/hooks/useTheme.d.ts +47 -0
  47. package/dist/hooks/useTheme.d.ts.map +1 -0
  48. package/dist/index.d.ts +20 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/init.d.ts +11 -0
  51. package/dist/init.d.ts.map +1 -0
  52. package/dist/types.d.ts +229 -0
  53. package/dist/types.d.ts.map +1 -0
  54. package/dist/utils/markdown.d.ts +7 -0
  55. package/dist/utils/markdown.d.ts.map +1 -0
  56. package/dist/utils/time.d.ts +5 -0
  57. package/dist/utils/time.d.ts.map +1 -0
  58. package/package.json +99 -0
@@ -0,0 +1,891 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useState, useRef, useEffect, useMemo, createContext, useContext, useCallback } from 'react';
3
+
4
+ /**
5
+ * Floating chat trigger button
6
+ */
7
+ function ChatBubble({ onClick, position, unreadCount = 0 }) {
8
+ return (jsxs("button", { className: `astermind-bubble astermind-bubble--${position}`, onClick: onClick, "aria-label": "Open chat", type: "button", children: [jsx("svg", { className: "astermind-bubble__icon", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: jsx("path", { d: "M21 11.5C21.0034 12.8199 20.6951 14.1219 20.1 15.3C19.3944 16.7118 18.3098 17.8992 16.9674 18.7293C15.6251 19.5594 14.0782 19.9994 12.5 20C11.1801 20.0035 9.87812 19.6951 8.7 19.1L3 21L4.9 15.3C4.30493 14.1219 3.99656 12.8199 4 11.5C4.00061 9.92179 4.44061 8.37488 5.27072 7.03258C6.10083 5.69028 7.28825 4.6056 8.7 3.90003C9.87812 3.30496 11.1801 2.99659 12.5 3.00003H13C15.0843 3.11502 17.053 3.99479 18.5291 5.47089C20.0052 6.94699 20.885 8.91568 21 11V11.5Z", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }), unreadCount > 0 && (jsx("span", { className: "astermind-bubble__badge", children: unreadCount }))] }));
9
+ }
10
+
11
+ /**
12
+ * Chat window header with title and close button
13
+ */
14
+ function ChatHeader({ title, subtitle, onClose, children }) {
15
+ return (jsxs("div", { className: "astermind-header", children: [jsxs("div", { className: "astermind-header__info", children: [jsx("h3", { className: "astermind-header__title", children: title }), subtitle && jsx("p", { className: "astermind-header__subtitle", children: subtitle })] }), jsxs("div", { className: "astermind-header__actions", children: [children, jsx("button", { className: "astermind-header__close", onClick: onClose, "aria-label": "Close chat", type: "button", children: jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: jsx("path", { d: "M18 6L6 18M6 6l12 12" }) }) })] })] }));
16
+ }
17
+
18
+ /**
19
+ * Source citation display
20
+ */
21
+ function SourceCitation({ sources }) {
22
+ return (jsx("div", { className: "astermind-sources", children: sources.map((source, index) => (jsxs("div", { className: "astermind-source", children: [jsxs("div", { className: "astermind-source__header", children: [jsx("span", { className: "astermind-source__title", children: source.title }), jsxs("span", { className: "astermind-source__relevance", children: [Math.round(source.relevance * 100), "%"] })] }), jsx("p", { className: "astermind-source__snippet", children: source.snippet })] }, index))) }));
23
+ }
24
+
25
+ // src/utils/markdown.ts
26
+ // Simple markdown rendering
27
+ /**
28
+ * Render basic markdown to HTML
29
+ *
30
+ * Supports: bold, italic, code, links, lists
31
+ */
32
+ function renderMarkdown(text) {
33
+ if (!text)
34
+ return '';
35
+ const html = text
36
+ // Escape HTML
37
+ .replace(/&/g, '&')
38
+ .replace(/</g, '&lt;')
39
+ .replace(/>/g, '&gt;')
40
+ // Bold
41
+ .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
42
+ // Italic
43
+ .replace(/\*(.+?)\*/g, '<em>$1</em>')
44
+ // Inline code
45
+ .replace(/`(.+?)`/g, '<code>$1</code>')
46
+ // Links
47
+ .replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>')
48
+ // Line breaks
49
+ .replace(/\n/g, '<br />');
50
+ return html;
51
+ }
52
+
53
+ // src/utils/time.ts
54
+ // Time formatting utilities
55
+ /**
56
+ * Format timestamp for display
57
+ */
58
+ function formatTime(date) {
59
+ const now = new Date();
60
+ const diff = now.getTime() - date.getTime();
61
+ // Less than a minute
62
+ if (diff < 60000) {
63
+ return 'Just now';
64
+ }
65
+ // Less than an hour
66
+ if (diff < 3600000) {
67
+ const minutes = Math.floor(diff / 60000);
68
+ return `${minutes}m ago`;
69
+ }
70
+ // Same day
71
+ if (date.toDateString() === now.toDateString()) {
72
+ return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
73
+ }
74
+ // Different day
75
+ return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
76
+ }
77
+
78
+ /**
79
+ * Individual message bubble with markdown support
80
+ */
81
+ function MessageBubble({ message }) {
82
+ const [showSources, setShowSources] = useState(false);
83
+ return (jsxs("div", { className: `astermind-message astermind-message--${message.role}`, children: [jsx("div", { className: "astermind-message__bubble", children: message.isStreaming && !message.content ? (jsxs("span", { className: "astermind-message__typing", children: [jsx("span", {}), jsx("span", {}), jsx("span", {})] })) : (jsx("div", { className: "astermind-message__content", dangerouslySetInnerHTML: { __html: renderMarkdown(message.content) } })) }), jsxs("div", { className: "astermind-message__meta", children: [jsx("span", { className: "astermind-message__time", children: formatTime(message.timestamp) }), message.offline && (jsx("span", { className: "astermind-message__offline", title: "Offline response", children: "offline" })), message.confidence && message.confidence !== 'high' && (jsx("span", { className: `astermind-message__confidence astermind-message__confidence--${message.confidence}`, title: `Confidence: ${message.confidence}`, children: message.confidence }))] }), message.sources && message.sources.length > 0 && (jsxs("div", { className: "astermind-message__sources", children: [jsxs("button", { className: "astermind-message__sources-toggle", onClick: () => setShowSources(!showSources), type: "button", children: [showSources ? 'Hide' : 'Show', " sources (", message.sources.length, ")"] }), showSources && (jsx(SourceCitation, { sources: message.sources }))] }))] }));
84
+ }
85
+
86
+ // src/components/TypingIndicator.tsx
87
+ // Bot typing animation
88
+ /**
89
+ * Typing indicator animation
90
+ */
91
+ function TypingIndicator() {
92
+ return (jsxs("div", { className: "astermind-typing", children: [jsx("span", {}), jsx("span", {}), jsx("span", {})] }));
93
+ }
94
+
95
+ /**
96
+ * Scrollable message list with auto-scroll
97
+ */
98
+ function MessageList({ messages, isProcessing }) {
99
+ const listRef = useRef(null);
100
+ // Auto-scroll to bottom on new messages
101
+ useEffect(() => {
102
+ if (listRef.current) {
103
+ listRef.current.scrollTop = listRef.current.scrollHeight;
104
+ }
105
+ }, [messages, isProcessing]);
106
+ return (jsxs("div", { className: "astermind-messages", ref: listRef, children: [messages.map(message => (jsx(MessageBubble, { message: message }, message.id))), isProcessing && messages[messages.length - 1]?.content === '' && (jsx(TypingIndicator, {}))] }));
107
+ }
108
+
109
+ /**
110
+ * Chat input with send button
111
+ */
112
+ function ChatInput({ placeholder, disabled = false, onSend }) {
113
+ const [value, setValue] = useState('');
114
+ const inputRef = useRef(null);
115
+ const handleSend = () => {
116
+ if (value.trim() && !disabled) {
117
+ onSend(value.trim());
118
+ setValue('');
119
+ inputRef.current?.focus();
120
+ }
121
+ };
122
+ const handleKeyDown = (e) => {
123
+ if (e.key === 'Enter' && !e.shiftKey) {
124
+ e.preventDefault();
125
+ handleSend();
126
+ }
127
+ };
128
+ // Auto-resize textarea
129
+ const handleInput = (e) => {
130
+ const textarea = e.target;
131
+ setValue(textarea.value);
132
+ // Reset height to auto to get proper scrollHeight
133
+ textarea.style.height = 'auto';
134
+ // Set to scrollHeight, max 120px
135
+ textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
136
+ };
137
+ return (jsxs("div", { className: "astermind-input", children: [jsx("textarea", { ref: inputRef, className: "astermind-input__textarea", value: value, onChange: handleInput, onKeyDown: handleKeyDown, placeholder: placeholder, disabled: disabled, rows: 1, "aria-label": "Message input" }), jsx("button", { className: "astermind-input__send", onClick: handleSend, disabled: disabled || !value.trim(), "aria-label": "Send message", type: "button", children: jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: jsx("path", { d: "M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z" }) }) })] }));
138
+ }
139
+
140
+ /**
141
+ * Action confirmation card for agentic capabilities
142
+ */
143
+ function ActionCard({ action, onConfirm, onCancel }) {
144
+ const getActionIcon = (type) => {
145
+ switch (type) {
146
+ case 'navigate':
147
+ return (jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [jsx("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }), jsx("polyline", { points: "15,3 21,3 21,9" }), jsx("line", { x1: "10", y1: "14", x2: "21", y2: "3" })] }));
148
+ case 'fillForm':
149
+ return (jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [jsx("path", { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }), jsx("path", { d: "M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" })] }));
150
+ case 'clickElement':
151
+ return (jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [jsx("path", { d: "M15 15l-2 5L9 9l11 4-5 2z" }), jsx("path", { d: "M15 15l5 5" })] }));
152
+ default:
153
+ return (jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [jsx("circle", { cx: "12", cy: "12", r: "10" }), jsx("polyline", { points: "12,6 12,12 16,14" })] }));
154
+ }
155
+ };
156
+ const getActionLabel = (type) => {
157
+ switch (type) {
158
+ case 'navigate': return 'Navigate to';
159
+ case 'fillForm': return 'Fill form field';
160
+ case 'clickElement': return 'Click';
161
+ case 'triggerModal': return 'Open modal';
162
+ case 'scroll': return 'Scroll to';
163
+ case 'highlight': return 'Highlight';
164
+ case 'custom': return 'Execute action';
165
+ default: return 'Perform action';
166
+ }
167
+ };
168
+ return (jsxs("div", { className: "astermind-action-card", children: [jsxs("div", { className: "astermind-action-card__header", children: [jsx("div", { className: "astermind-action-card__icon", children: getActionIcon(action.type) }), jsxs("div", { className: "astermind-action-card__title", children: [jsx("span", { className: "astermind-action-card__label", children: getActionLabel(action.type) }), jsx("span", { className: "astermind-action-card__target", children: action.target })] })] }), action.explanation && (jsx("p", { className: "astermind-action-card__explanation", children: action.explanation })), jsxs("div", { className: "astermind-action-card__confidence", children: [jsx("div", { className: "astermind-action-card__confidence-bar", style: { width: `${action.confidence * 100}%` } }), jsxs("span", { children: [Math.round(action.confidence * 100), "% confident"] })] }), jsxs("div", { className: "astermind-action-card__actions", children: [jsx("button", { className: "astermind-action-card__cancel", onClick: onCancel, type: "button", children: "Cancel" }), jsx("button", { className: "astermind-action-card__confirm", onClick: onConfirm, type: "button", children: "Confirm" })] })] }));
169
+ }
170
+
171
+ /**
172
+ * Connection status indicator
173
+ */
174
+ function StatusIndicator({ status }) {
175
+ const getLabel = () => {
176
+ switch (status) {
177
+ case 'online': return 'Online';
178
+ case 'offline': return 'Offline';
179
+ case 'connecting': return 'Connecting...';
180
+ case 'error': return 'Error';
181
+ default: return 'Unknown';
182
+ }
183
+ };
184
+ return (jsxs("div", { className: `astermind-status astermind-status--${status}`, children: [jsx("span", { className: "astermind-status__dot" }), jsx("span", { className: "astermind-status__label", children: getLabel() })] }));
185
+ }
186
+
187
+ /**
188
+ * Chat window with header, messages, and input
189
+ */
190
+ function ChatWindow({ messages, isProcessing, connectionStatus, pendingAction, headerTitle, headerSubtitle, placeholder, showPoweredBy, onClose, onSendMessage, onActionConfirm, onActionCancel }) {
191
+ return (jsxs("div", { className: "astermind-window", children: [jsx(ChatHeader, { title: headerTitle, subtitle: headerSubtitle, onClose: onClose, children: jsx(StatusIndicator, { status: connectionStatus }) }), jsxs("div", { className: "astermind-window__body", children: [jsx(MessageList, { messages: messages, isProcessing: isProcessing }), pendingAction && (jsx(ActionCard, { action: pendingAction, onConfirm: () => onActionConfirm(pendingAction), onCancel: onActionCancel }))] }), jsxs("div", { className: "astermind-window__footer", children: [jsx(ChatInput, { placeholder: placeholder, disabled: isProcessing || connectionStatus === 'error', onSend: onSendMessage }), showPoweredBy && (jsxs("div", { className: "astermind-powered-by", children: ["Powered by ", jsx("a", { href: "https://astermind.ai", target: "_blank", rel: "noopener noreferrer", children: "AsterMind" })] }))] })] }));
192
+ }
193
+
194
+ // src/hooks/useTheme.ts
195
+ // Theme management hook
196
+ const defaultTheme = {
197
+ primaryColor: '#4F46E5',
198
+ primaryHover: '#4338CA',
199
+ backgroundColor: '#ffffff',
200
+ surfaceColor: '#f3f4f6',
201
+ textColor: '#1f2937',
202
+ textMuted: '#6b7280',
203
+ borderColor: '#e5e7eb',
204
+ userBubbleBackground: '#4F46E5',
205
+ userBubbleText: '#ffffff',
206
+ botBubbleBackground: '#f3f4f6',
207
+ botBubbleText: '#1f2937',
208
+ widgetWidth: '380px',
209
+ widgetHeight: '520px',
210
+ bubbleSize: '60px',
211
+ borderRadius: '12px',
212
+ fontFamily: "'Inter', system-ui, -apple-system, sans-serif",
213
+ fontSize: '14px',
214
+ shadow: '0 4px 20px rgba(0, 0, 0, 0.15)'
215
+ };
216
+ /**
217
+ * Hook for theme management
218
+ */
219
+ function useTheme(theme) {
220
+ const mergedTheme = useMemo(() => ({
221
+ ...defaultTheme,
222
+ ...theme
223
+ }), [theme]);
224
+ const cssVariables = useMemo(() => ({
225
+ '--astermind-primary': mergedTheme.primaryColor,
226
+ '--astermind-primary-hover': mergedTheme.primaryHover,
227
+ '--astermind-background': mergedTheme.backgroundColor,
228
+ '--astermind-surface': mergedTheme.surfaceColor,
229
+ '--astermind-text': mergedTheme.textColor,
230
+ '--astermind-text-muted': mergedTheme.textMuted,
231
+ '--astermind-border': mergedTheme.borderColor,
232
+ '--astermind-user-bg': mergedTheme.userBubbleBackground,
233
+ '--astermind-user-text': mergedTheme.userBubbleText,
234
+ '--astermind-bot-bg': mergedTheme.botBubbleBackground,
235
+ '--astermind-bot-text': mergedTheme.botBubbleText,
236
+ '--astermind-widget-width': mergedTheme.widgetWidth,
237
+ '--astermind-widget-height': mergedTheme.widgetHeight,
238
+ '--astermind-bubble-size': mergedTheme.bubbleSize,
239
+ '--astermind-border-radius': mergedTheme.borderRadius,
240
+ '--astermind-font-family': mergedTheme.fontFamily,
241
+ '--astermind-font-size': mergedTheme.fontSize,
242
+ '--astermind-shadow': mergedTheme.shadow
243
+ }), [mergedTheme]);
244
+ return {
245
+ theme: mergedTheme,
246
+ cssVariables
247
+ };
248
+ }
249
+
250
+ const ThemeContext = createContext(null);
251
+ /**
252
+ * Theme context provider
253
+ */
254
+ function ThemeProvider({ theme, children }) {
255
+ const themeValue = useTheme(theme);
256
+ return (jsx(ThemeContext.Provider, { value: themeValue, children: jsx("div", { style: themeValue.cssVariables, children: children }) }));
257
+ }
258
+ /**
259
+ * Use theme context
260
+ */
261
+ function useThemeContext() {
262
+ const context = useContext(ThemeContext);
263
+ if (!context) {
264
+ throw new Error('useThemeContext must be used within a ThemeProvider');
265
+ }
266
+ return context;
267
+ }
268
+
269
+ /**
270
+ * Simple HTTP client for API communication
271
+ * This is a placeholder until @astermind/cybernetic-chatbot-client is available
272
+ */
273
+ class SimpleCyberneticClient {
274
+ constructor(config) {
275
+ this.status = 'connecting';
276
+ this.apiUrl = config.apiUrl;
277
+ this.apiKey = config.apiKey;
278
+ this.onStatusChange = config.onStatusChange;
279
+ this.onError = config.onError;
280
+ // Check connection
281
+ this.checkConnection();
282
+ }
283
+ async checkConnection() {
284
+ try {
285
+ const response = await fetch(`${this.apiUrl}/api/external/health`, {
286
+ method: 'GET',
287
+ headers: {
288
+ 'X-API-Key': this.apiKey
289
+ }
290
+ });
291
+ if (response.ok) {
292
+ this.status = 'online';
293
+ }
294
+ else {
295
+ this.status = 'offline';
296
+ }
297
+ }
298
+ catch {
299
+ this.status = 'offline';
300
+ }
301
+ this.onStatusChange?.(this.status);
302
+ }
303
+ async ask(message, options) {
304
+ try {
305
+ const response = await fetch(`${this.apiUrl}/api/external/chat`, {
306
+ method: 'POST',
307
+ headers: {
308
+ 'Content-Type': 'application/json',
309
+ 'X-API-Key': this.apiKey
310
+ },
311
+ body: JSON.stringify({
312
+ message,
313
+ sessionId: options?.sessionId
314
+ })
315
+ });
316
+ if (!response.ok) {
317
+ throw new Error(`HTTP error: ${response.status}`);
318
+ }
319
+ const data = await response.json();
320
+ return {
321
+ reply: data.reply,
322
+ sessionId: data.sessionId,
323
+ sources: data.sources,
324
+ confidence: 'high',
325
+ offline: false
326
+ };
327
+ }
328
+ catch (error) {
329
+ this.onError?.({ message: error.message });
330
+ throw error;
331
+ }
332
+ }
333
+ async askStream(message, callbacks, options) {
334
+ try {
335
+ const response = await fetch(`${this.apiUrl}/api/external/chat/stream`, {
336
+ method: 'POST',
337
+ headers: {
338
+ 'Content-Type': 'application/json',
339
+ 'X-API-Key': this.apiKey
340
+ },
341
+ body: JSON.stringify({
342
+ message,
343
+ sessionId: options?.sessionId
344
+ })
345
+ });
346
+ if (!response.ok) {
347
+ throw new Error(`HTTP error: ${response.status}`);
348
+ }
349
+ const reader = response.body?.getReader();
350
+ if (!reader) {
351
+ throw new Error('No reader available');
352
+ }
353
+ const decoder = new TextDecoder();
354
+ let buffer = '';
355
+ let fullContent = '';
356
+ let sources = [];
357
+ while (true) {
358
+ const { done, value } = await reader.read();
359
+ if (done)
360
+ break;
361
+ buffer += decoder.decode(value, { stream: true });
362
+ const lines = buffer.split('\n');
363
+ buffer = lines.pop() || '';
364
+ for (const line of lines) {
365
+ if (line.startsWith('data: ')) {
366
+ try {
367
+ const data = JSON.parse(line.slice(6));
368
+ if (data.type === 'chunk') {
369
+ fullContent += data.content;
370
+ callbacks.onToken?.(data.content);
371
+ }
372
+ else if (data.type === 'sources') {
373
+ sources = data.sources;
374
+ callbacks.onSources?.(data.sources);
375
+ }
376
+ else if (data.type === 'done') {
377
+ callbacks.onComplete?.({
378
+ reply: fullContent,
379
+ sessionId: data.sessionId,
380
+ sources,
381
+ confidence: 'high',
382
+ offline: false
383
+ });
384
+ }
385
+ else if (data.type === 'error') {
386
+ callbacks.onError?.({ message: data.error });
387
+ }
388
+ }
389
+ catch {
390
+ // Ignore parse errors
391
+ }
392
+ }
393
+ }
394
+ }
395
+ }
396
+ catch (error) {
397
+ callbacks.onError?.({ message: error.message });
398
+ }
399
+ }
400
+ getStatus() {
401
+ return { connection: this.status };
402
+ }
403
+ }
404
+ /**
405
+ * Main AsterMind Chatbot Widget
406
+ *
407
+ * Drop-in chatbot component with full functionality.
408
+ */
409
+ function ChatbotWidget(props) {
410
+ const { apiKey, apiUrl = 'https://api.astermind.ai', position = 'bottom-right', theme = {}, greeting = 'Hi! How can I help you today?', placeholder = 'Type your message...', fallback = { enabled: true }, showPoweredBy = true, headerTitle = 'AsterMind', headerSubtitle = 'AI Assistant', defaultOpen = false, zIndex = 9999, onReady, onMessage, onAction, onError, onToggle } = props;
411
+ const [isOpen, setIsOpen] = useState(defaultOpen);
412
+ const [client, setClient] = useState(null);
413
+ const [messages, setMessages] = useState([]);
414
+ const [isProcessing, setIsProcessing] = useState(false);
415
+ const [sessionId, setSessionId] = useState();
416
+ const [pendingAction, setPendingAction] = useState();
417
+ const [connectionStatus, setConnectionStatus] = useState('connecting');
418
+ // Initialize client
419
+ useEffect(() => {
420
+ const cyberneticClient = new SimpleCyberneticClient({
421
+ apiUrl,
422
+ apiKey,
423
+ fallback: {
424
+ enabled: fallback?.enabled ?? true,
425
+ cacheOnConnect: true
426
+ },
427
+ onStatusChange: (status) => {
428
+ setConnectionStatus(status);
429
+ },
430
+ onError: (error) => {
431
+ onError?.(new Error(error.message));
432
+ }
433
+ });
434
+ setClient(cyberneticClient);
435
+ // Add greeting message
436
+ if (greeting) {
437
+ const greetingMsg = {
438
+ id: 'greeting',
439
+ role: 'assistant',
440
+ content: greeting,
441
+ timestamp: new Date()
442
+ };
443
+ setMessages([greetingMsg]);
444
+ }
445
+ onReady?.();
446
+ return () => {
447
+ // Cleanup if needed
448
+ };
449
+ }, [apiKey, apiUrl, fallback?.enabled, greeting, onError, onReady]);
450
+ // Handle toggle
451
+ const handleToggle = useCallback(() => {
452
+ const newState = !isOpen;
453
+ setIsOpen(newState);
454
+ onToggle?.(newState);
455
+ }, [isOpen, onToggle]);
456
+ // Handle close
457
+ const handleClose = useCallback(() => {
458
+ setIsOpen(false);
459
+ onToggle?.(false);
460
+ }, [onToggle]);
461
+ // Handle send message
462
+ const handleSendMessage = useCallback(async (content) => {
463
+ if (!client || !content.trim() || isProcessing)
464
+ return;
465
+ // Add user message
466
+ const userMessage = {
467
+ id: `user-${Date.now()}`,
468
+ role: 'user',
469
+ content: content.trim(),
470
+ timestamp: new Date()
471
+ };
472
+ setMessages(prev => [...prev, userMessage]);
473
+ onMessage?.(userMessage);
474
+ setIsProcessing(true);
475
+ // Add placeholder for assistant response
476
+ const assistantId = `assistant-${Date.now()}`;
477
+ const placeholderMessage = {
478
+ id: assistantId,
479
+ role: 'assistant',
480
+ content: '',
481
+ timestamp: new Date(),
482
+ isStreaming: true
483
+ };
484
+ setMessages(prev => [...prev, placeholderMessage]);
485
+ try {
486
+ // Use streaming if available
487
+ let fullContent = '';
488
+ await client.askStream(content, {
489
+ onToken: (token) => {
490
+ fullContent += token;
491
+ setMessages(prev => prev.map(msg => msg.id === assistantId
492
+ ? { ...msg, content: fullContent }
493
+ : msg));
494
+ },
495
+ onSources: (sources) => {
496
+ setMessages(prev => prev.map(msg => msg.id === assistantId
497
+ ? { ...msg, sources }
498
+ : msg));
499
+ },
500
+ onComplete: (response) => {
501
+ const finalMessage = {
502
+ id: assistantId,
503
+ role: 'assistant',
504
+ content: response.reply,
505
+ timestamp: new Date(),
506
+ sources: response.sources,
507
+ confidence: response.confidence,
508
+ offline: response.offline,
509
+ isStreaming: false
510
+ };
511
+ setMessages(prev => prev.map(msg => msg.id === assistantId ? finalMessage : msg));
512
+ setSessionId(response.sessionId);
513
+ onMessage?.(finalMessage);
514
+ },
515
+ onError: () => {
516
+ // Fallback to non-streaming on error
517
+ handleNonStreamingResponse(client, content, assistantId);
518
+ }
519
+ }, { sessionId });
520
+ }
521
+ catch {
522
+ // Handle error
523
+ const errorMessage = {
524
+ id: assistantId,
525
+ role: 'assistant',
526
+ content: 'I encountered an error processing your request. Please try again.',
527
+ timestamp: new Date(),
528
+ isStreaming: false
529
+ };
530
+ setMessages(prev => prev.map(msg => msg.id === assistantId ? errorMessage : msg));
531
+ onError?.(new Error('Failed to process message'));
532
+ }
533
+ finally {
534
+ setIsProcessing(false);
535
+ }
536
+ }, [client, isProcessing, sessionId, onMessage, onError]);
537
+ // Non-streaming fallback
538
+ const handleNonStreamingResponse = async (cyberneticClient, content, messageId) => {
539
+ try {
540
+ const response = await cyberneticClient.ask(content, { sessionId });
541
+ const finalMessage = {
542
+ id: messageId,
543
+ role: 'assistant',
544
+ content: response.reply,
545
+ timestamp: new Date(),
546
+ sources: response.sources,
547
+ confidence: response.confidence,
548
+ offline: response.offline,
549
+ isStreaming: false
550
+ };
551
+ setMessages(prev => prev.map(msg => msg.id === messageId ? finalMessage : msg));
552
+ setSessionId(response.sessionId);
553
+ onMessage?.(finalMessage);
554
+ }
555
+ catch {
556
+ const errorMessage = {
557
+ id: messageId,
558
+ role: 'assistant',
559
+ content: 'I\'m having trouble connecting. Please try again later.',
560
+ timestamp: new Date(),
561
+ isStreaming: false
562
+ };
563
+ setMessages(prev => prev.map(msg => msg.id === messageId ? errorMessage : msg));
564
+ }
565
+ };
566
+ // Handle action confirmation
567
+ const handleActionConfirm = useCallback(async (action) => {
568
+ if (!pendingAction)
569
+ return;
570
+ const updatedAction = {
571
+ ...pendingAction,
572
+ status: 'executed',
573
+ result: {
574
+ success: true,
575
+ message: `Action ${action.type} completed`
576
+ }
577
+ };
578
+ setPendingAction(undefined);
579
+ onAction?.(updatedAction);
580
+ }, [pendingAction, onAction]);
581
+ // Handle action cancel
582
+ const handleActionCancel = useCallback(() => {
583
+ if (!pendingAction)
584
+ return;
585
+ const cancelledAction = {
586
+ ...pendingAction,
587
+ status: 'cancelled'
588
+ };
589
+ setPendingAction(undefined);
590
+ onAction?.(cancelledAction);
591
+ }, [pendingAction, onAction]);
592
+ return (jsx(ThemeProvider, { theme: theme, children: jsxs("div", { className: `astermind-chatbot astermind-chatbot--${position}`, style: { zIndex }, children: [!isOpen && (jsx(ChatBubble, { onClick: handleToggle, position: position })), isOpen && (jsx(ChatWindow, { messages: messages, isProcessing: isProcessing, connectionStatus: connectionStatus, pendingAction: pendingAction, headerTitle: headerTitle, headerSubtitle: headerSubtitle, placeholder: placeholder, showPoweredBy: showPoweredBy, onClose: handleClose, onSendMessage: handleSendMessage, onActionConfirm: handleActionConfirm, onActionCancel: handleActionCancel }))] }) }));
593
+ }
594
+ // Alias for convenience
595
+ const AsterMindChatbot = ChatbotWidget;
596
+
597
+ // src/hooks/useOmega.ts
598
+ // CyberneticClient wrapper hook
599
+ /**
600
+ * Simple HTTP client for API communication
601
+ */
602
+ class SimpleClient {
603
+ constructor(config) {
604
+ this.status = 'connecting';
605
+ this.apiUrl = config.apiUrl;
606
+ this.apiKey = config.apiKey;
607
+ this.onStatusChange = config.onStatusChange;
608
+ this.onError = config.onError;
609
+ this.checkConnection();
610
+ }
611
+ async checkConnection() {
612
+ try {
613
+ const response = await fetch(`${this.apiUrl}/api/external/health`, {
614
+ method: 'GET',
615
+ headers: { 'X-API-Key': this.apiKey }
616
+ });
617
+ this.status = response.ok ? 'online' : 'offline';
618
+ if (!response.ok) {
619
+ this.onError?.({ message: `Health check failed: ${response.status}` });
620
+ }
621
+ }
622
+ catch (error) {
623
+ this.status = 'offline';
624
+ this.onError?.({ message: error.message });
625
+ }
626
+ this.onStatusChange?.(this.status);
627
+ }
628
+ async ask(message, options) {
629
+ const response = await fetch(`${this.apiUrl}/api/external/chat`, {
630
+ method: 'POST',
631
+ headers: {
632
+ 'Content-Type': 'application/json',
633
+ 'X-API-Key': this.apiKey
634
+ },
635
+ body: JSON.stringify({ message, sessionId: options?.sessionId })
636
+ });
637
+ if (!response.ok)
638
+ throw new Error(`HTTP error: ${response.status}`);
639
+ const data = await response.json();
640
+ return {
641
+ reply: data.reply,
642
+ sessionId: data.sessionId,
643
+ sources: data.sources,
644
+ confidence: 'high',
645
+ offline: false
646
+ };
647
+ }
648
+ async askStream(message, callbacks, options) {
649
+ try {
650
+ const response = await fetch(`${this.apiUrl}/api/external/chat/stream`, {
651
+ method: 'POST',
652
+ headers: {
653
+ 'Content-Type': 'application/json',
654
+ 'X-API-Key': this.apiKey
655
+ },
656
+ body: JSON.stringify({ message, sessionId: options?.sessionId })
657
+ });
658
+ if (!response.ok)
659
+ throw new Error(`HTTP error: ${response.status}`);
660
+ const reader = response.body?.getReader();
661
+ if (!reader)
662
+ throw new Error('No reader available');
663
+ const decoder = new TextDecoder();
664
+ let buffer = '';
665
+ let fullContent = '';
666
+ let sources = [];
667
+ while (true) {
668
+ const { done, value } = await reader.read();
669
+ if (done)
670
+ break;
671
+ buffer += decoder.decode(value, { stream: true });
672
+ const lines = buffer.split('\n');
673
+ buffer = lines.pop() || '';
674
+ for (const line of lines) {
675
+ if (line.startsWith('data: ')) {
676
+ try {
677
+ const data = JSON.parse(line.slice(6));
678
+ if (data.type === 'chunk') {
679
+ fullContent += data.content;
680
+ callbacks.onToken?.(data.content);
681
+ }
682
+ else if (data.type === 'sources') {
683
+ sources = data.sources;
684
+ callbacks.onSources?.(data.sources);
685
+ }
686
+ else if (data.type === 'done') {
687
+ callbacks.onComplete?.({
688
+ reply: fullContent,
689
+ sessionId: data.sessionId,
690
+ sources,
691
+ confidence: 'high',
692
+ offline: false
693
+ });
694
+ }
695
+ else if (data.type === 'error') {
696
+ callbacks.onError?.({ message: data.error });
697
+ }
698
+ }
699
+ catch {
700
+ // Ignore parse errors
701
+ }
702
+ }
703
+ }
704
+ }
705
+ }
706
+ catch (error) {
707
+ callbacks.onError?.({ message: error.message });
708
+ }
709
+ }
710
+ getStatus() {
711
+ return this.status;
712
+ }
713
+ }
714
+ /**
715
+ * React hook for CyberneticClient
716
+ */
717
+ function useOmega(options) {
718
+ const [connectionStatus, setConnectionStatus] = useState('connecting');
719
+ const [isProcessing, setIsProcessing] = useState(false);
720
+ const [lastError, setLastError] = useState(null);
721
+ const [sessionId, setSessionId] = useState();
722
+ const clientRef = useRef(null);
723
+ // Initialize client
724
+ useEffect(() => {
725
+ clientRef.current = new SimpleClient({
726
+ ...options,
727
+ onStatusChange: setConnectionStatus,
728
+ onError: (error) => setLastError(new Error(error.message))
729
+ });
730
+ return () => {
731
+ clientRef.current = null;
732
+ };
733
+ }, [options.apiUrl, options.apiKey]);
734
+ // Send message (non-streaming)
735
+ const sendMessage = useCallback(async (message) => {
736
+ if (!clientRef.current) {
737
+ throw new Error('Client not initialized');
738
+ }
739
+ setIsProcessing(true);
740
+ setLastError(null);
741
+ try {
742
+ const response = await clientRef.current.ask(message, { sessionId });
743
+ setSessionId(response.sessionId);
744
+ return response;
745
+ }
746
+ catch (error) {
747
+ setLastError(error);
748
+ throw error;
749
+ }
750
+ finally {
751
+ setIsProcessing(false);
752
+ }
753
+ }, [sessionId]);
754
+ // Send message (streaming)
755
+ const sendMessageStream = useCallback(async (message, onToken) => {
756
+ if (!clientRef.current) {
757
+ throw new Error('Client not initialized');
758
+ }
759
+ setIsProcessing(true);
760
+ setLastError(null);
761
+ return new Promise((resolve, reject) => {
762
+ clientRef.current.askStream(message, {
763
+ onToken,
764
+ onComplete: (response) => {
765
+ setSessionId(response.sessionId);
766
+ setIsProcessing(false);
767
+ resolve(response);
768
+ },
769
+ onError: (error) => {
770
+ setLastError(new Error(error.message));
771
+ setIsProcessing(false);
772
+ reject(error);
773
+ }
774
+ }, { sessionId });
775
+ });
776
+ }, [sessionId]);
777
+ // Clear session
778
+ const clearSession = useCallback(() => {
779
+ setSessionId(undefined);
780
+ }, []);
781
+ // Sync cache (placeholder)
782
+ const syncCache = useCallback(async () => {
783
+ // Cache sync functionality would go here
784
+ }, []);
785
+ return {
786
+ sendMessage,
787
+ sendMessageStream,
788
+ connectionStatus,
789
+ isProcessing,
790
+ lastError,
791
+ sessionId,
792
+ clearSession,
793
+ syncCache
794
+ };
795
+ }
796
+
797
+ // src/hooks/useChat.ts
798
+ // Chat state management hook
799
+ /**
800
+ * Hook for managing chat state
801
+ */
802
+ function useChat(initialMessages = []) {
803
+ const [messages, setMessages] = useState(initialMessages);
804
+ const [pendingAction, setPendingAction] = useState();
805
+ const addMessage = useCallback((message) => {
806
+ setMessages(prev => [...prev, message]);
807
+ }, []);
808
+ const updateMessage = useCallback((id, updates) => {
809
+ setMessages(prev => prev.map(msg => msg.id === id ? { ...msg, ...updates } : msg));
810
+ }, []);
811
+ const clearMessages = useCallback(() => {
812
+ setMessages([]);
813
+ }, []);
814
+ return {
815
+ messages,
816
+ addMessage,
817
+ updateMessage,
818
+ clearMessages,
819
+ pendingAction,
820
+ setPendingAction
821
+ };
822
+ }
823
+
824
+ // src/hooks/useScrollToBottom.ts
825
+ // Auto-scroll behavior hook
826
+ /**
827
+ * Hook for auto-scrolling to bottom of a container
828
+ */
829
+ function useScrollToBottom(options = {}) {
830
+ const { deps = [], enabled = true, behavior = 'smooth' } = options;
831
+ const ref = useRef(null);
832
+ useEffect(() => {
833
+ if (enabled && ref.current) {
834
+ ref.current.scrollTo({
835
+ top: ref.current.scrollHeight,
836
+ behavior
837
+ });
838
+ }
839
+ }, [enabled, behavior, ...deps]);
840
+ return ref;
841
+ }
842
+
843
+ const ChatContext = createContext(null);
844
+ /**
845
+ * Chat state context provider
846
+ */
847
+ function ChatProvider({ children, initialMessages = [] }) {
848
+ const [messages, setMessages] = useState(initialMessages);
849
+ const [isOpen, setIsOpen] = useState(false);
850
+ const [isProcessing, setIsProcessing] = useState(false);
851
+ const [connectionStatus] = useState('connecting');
852
+ const [sessionId] = useState();
853
+ const [pendingAction, setPendingAction] = useState();
854
+ const addMessage = (message) => {
855
+ setMessages(prev => [...prev, message]);
856
+ };
857
+ const updateMessage = (id, updates) => {
858
+ setMessages(prev => prev.map(msg => msg.id === id ? { ...msg, ...updates } : msg));
859
+ };
860
+ const clearMessages = () => {
861
+ setMessages([]);
862
+ };
863
+ const value = {
864
+ messages,
865
+ isOpen,
866
+ isProcessing,
867
+ connectionStatus,
868
+ sessionId,
869
+ pendingAction,
870
+ addMessage,
871
+ updateMessage,
872
+ clearMessages,
873
+ setIsOpen,
874
+ setIsProcessing,
875
+ setPendingAction
876
+ };
877
+ return (jsx(ChatContext.Provider, { value: value, children: children }));
878
+ }
879
+ /**
880
+ * Use chat context
881
+ */
882
+ function useChatContext() {
883
+ const context = useContext(ChatContext);
884
+ if (!context) {
885
+ throw new Error('useChatContext must be used within a ChatProvider');
886
+ }
887
+ return context;
888
+ }
889
+
890
+ export { ActionCard, AsterMindChatbot, ChatBubble, ChatHeader, ChatInput, ChatProvider, ChatWindow, ChatbotWidget, MessageBubble, MessageList, SourceCitation, StatusIndicator, ThemeProvider, TypingIndicator, useChat, useChatContext, useOmega, useScrollToBottom, useTheme, useThemeContext };
891
+ //# sourceMappingURL=astermind-chatbot.esm.js.map