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