@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.
- package/LICENSE +21 -0
- package/README.md +603 -0
- package/dist/astermind-chatbot.css +1 -0
- package/dist/astermind-chatbot.esm.js +891 -0
- package/dist/astermind-chatbot.esm.js.map +1 -0
- package/dist/astermind-chatbot.min.js +21 -0
- package/dist/astermind-chatbot.min.js.map +1 -0
- package/dist/astermind-chatbot.umd.js +915 -0
- package/dist/astermind-chatbot.umd.js.map +1 -0
- package/dist/components/ActionCard.d.ts +12 -0
- package/dist/components/ActionCard.d.ts.map +1 -0
- package/dist/components/ChatBubble.d.ts +12 -0
- package/dist/components/ChatBubble.d.ts.map +1 -0
- package/dist/components/ChatHeader.d.ts +13 -0
- package/dist/components/ChatHeader.d.ts.map +1 -0
- package/dist/components/ChatInput.d.ts +11 -0
- package/dist/components/ChatInput.d.ts.map +1 -0
- package/dist/components/ChatWindow.d.ts +21 -0
- package/dist/components/ChatWindow.d.ts.map +1 -0
- package/dist/components/ChatbotWidget.d.ts +9 -0
- package/dist/components/ChatbotWidget.d.ts.map +1 -0
- package/dist/components/MessageBubble.d.ts +10 -0
- package/dist/components/MessageBubble.d.ts.map +1 -0
- package/dist/components/MessageList.d.ts +11 -0
- package/dist/components/MessageList.d.ts.map +1 -0
- package/dist/components/SourceCitation.d.ts +10 -0
- package/dist/components/SourceCitation.d.ts.map +1 -0
- package/dist/components/StatusIndicator.d.ts +10 -0
- package/dist/components/StatusIndicator.d.ts.map +1 -0
- package/dist/components/TypingIndicator.d.ts +5 -0
- package/dist/components/TypingIndicator.d.ts.map +1 -0
- package/dist/components/index.d.ts +12 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/context/ChatContext.d.ts +24 -0
- package/dist/context/ChatContext.d.ts.map +1 -0
- package/dist/context/ThemeContext.d.ts +20 -0
- package/dist/context/ThemeContext.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/useChat.d.ts +21 -0
- package/dist/hooks/useChat.d.ts.map +1 -0
- package/dist/hooks/useOmega.d.ts +27 -0
- package/dist/hooks/useOmega.d.ts.map +1 -0
- package/dist/hooks/useScrollToBottom.d.ts +15 -0
- package/dist/hooks/useScrollToBottom.d.ts.map +1 -0
- package/dist/hooks/useTheme.d.ts +47 -0
- package/dist/hooks/useTheme.d.ts.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/init.d.ts +11 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/types.d.ts +229 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/markdown.d.ts +7 -0
- package/dist/utils/markdown.d.ts.map +1 -0
- package/dist/utils/time.d.ts +5 -0
- package/dist/utils/time.d.ts.map +1 -0
- 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, '<')
|
|
42
|
+
.replace(/>/g, '>')
|
|
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
|