@courseecho/ai-widget-react 1.0.27 → 1.0.28
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/dist/components.js +18 -1
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +6 -3
- package/package.json +1 -1
package/dist/components.js
CHANGED
|
@@ -134,6 +134,19 @@ const DEFAULT_SUGGESTIONS = [
|
|
|
134
134
|
{ id: 'd5', text: 'What are the pricing options?', icon: '', category: 'Billing', description: 'Plans and pricing' },
|
|
135
135
|
{ id: 'd6', text: 'Contact support team', icon: '', category: 'Support', description: 'Talk to a human' },
|
|
136
136
|
];
|
|
137
|
+
function humanizeError(raw) {
|
|
138
|
+
if (/401|unauthorized/i.test(raw))
|
|
139
|
+
return '⚠️ Authentication failed. Please check your API key or token.';
|
|
140
|
+
if (/403|forbidden/i.test(raw))
|
|
141
|
+
return '⚠️ Access denied. You don\'t have permission to use this service.';
|
|
142
|
+
if (/429|rate.?limit/i.test(raw))
|
|
143
|
+
return '⚠️ Too many requests. Please wait a moment and try again.';
|
|
144
|
+
if (/5\d\d|server|internal/i.test(raw))
|
|
145
|
+
return '⚠️ The AI service is temporarily unavailable. Please try again shortly.';
|
|
146
|
+
if (/network|fetch|failed to fetch/i.test(raw))
|
|
147
|
+
return '⚠️ Network error. Please check your connection.';
|
|
148
|
+
return `⚠️ ${raw}`;
|
|
149
|
+
}
|
|
137
150
|
export const AiChatWidget = ({ config, apiKey, jwtToken, title = 'AI Assistant', subtitle = 'Online Ready to help', placeholder = 'Type a message', poweredBy, poweredByUrl = 'https://courseecho.com', onError, onMessageReceived, className, defaultMinimized = false, expandedHeight = '580px', }) => {
|
|
138
151
|
const [mergedConfig] = useState(() => {
|
|
139
152
|
const auto = autoDetectPageContext();
|
|
@@ -146,6 +159,7 @@ export const AiChatWidget = ({ config, apiKey, jwtToken, title = 'AI Assistant',
|
|
|
146
159
|
const [filteredSugs, setFilteredSugs] = useState([]);
|
|
147
160
|
const [activeIdx, setActiveIdx] = useState(-1);
|
|
148
161
|
const [chipsVisible, setChipsVisible] = useState(true);
|
|
162
|
+
const [dismissedError, setDismissedError] = useState(null);
|
|
149
163
|
const messagesEndRef = useRef(null);
|
|
150
164
|
const inputRef = useRef(null);
|
|
151
165
|
const allSugs = (suggestions && suggestions.length > 0) ? suggestions : DEFAULT_SUGGESTIONS;
|
|
@@ -162,6 +176,9 @@ export const AiChatWidget = ({ config, apiKey, jwtToken, title = 'AI Assistant',
|
|
|
162
176
|
}, [messages, onMessageReceived]);
|
|
163
177
|
useEffect(() => { if (error && onError)
|
|
164
178
|
onError(error); }, [error, onError]);
|
|
179
|
+
// Reset dismissed state when a new different error arrives
|
|
180
|
+
useEffect(() => { if (error !== dismissedError)
|
|
181
|
+
setDismissedError(null); }, [error]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
165
182
|
const filterSugs = (q) => {
|
|
166
183
|
const lq = q.toLowerCase();
|
|
167
184
|
const filtered = q.trim()
|
|
@@ -221,6 +238,6 @@ export const AiChatWidget = ({ config, apiKey, jwtToken, title = 'AI Assistant',
|
|
|
221
238
|
const isUser = msg.role === 'user';
|
|
222
239
|
const time = new Date(msg.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
223
240
|
return (_jsxs("div", { className: `aiwg-msg ${isUser ? 'aiwg-msg--user' : 'aiwg-msg--bot'}`, children: [_jsx("div", { className: "aiwg-msg-avatar", children: isUser ? '👤' : '🤖' }), _jsxs("div", { className: "aiwg-msg-body", children: [_jsx("div", { className: "aiwg-msg-bubble", children: isUser ? msg.content : renderMarkdown(msg.content) }), _jsx("div", { className: "aiwg-msg-time", children: time })] })] }, msg.id));
|
|
224
|
-
}), isLoading && (_jsxs("div", { className: "aiwg-msg aiwg-msg--bot", children: [_jsx("div", { className: "aiwg-msg-avatar", children: "\uD83E\uDD16" }), _jsx("div", { className: "aiwg-msg-body", children: _jsx("div", { className: "aiwg-msg-bubble aiwg-typing", children: _jsxs("div", { className: "aiwg-typing-dots", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] }) }) })] })), error && _jsxs("div", { className: "aiwg-error", style: { display: '
|
|
241
|
+
}), isLoading && (_jsxs("div", { className: "aiwg-msg aiwg-msg--bot", children: [_jsx("div", { className: "aiwg-msg-avatar", children: "\uD83E\uDD16" }), _jsx("div", { className: "aiwg-msg-body", children: _jsx("div", { className: "aiwg-msg-bubble aiwg-typing", children: _jsxs("div", { className: "aiwg-typing-dots", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] }) }) })] })), error && error !== dismissedError && (_jsxs("div", { className: "aiwg-error", style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsx("span", { children: humanizeError(error) }), _jsx("button", { onClick: () => setDismissedError(error), style: { background: 'none', border: 'none', color: 'inherit', cursor: 'pointer', fontSize: '16px', lineHeight: 1, padding: '0 0 0 8px' }, "aria-label": "Dismiss", children: "\u2715" })] })), _jsx("div", { ref: messagesEndRef })] }), chipsVisible && messages.length === 0 && (_jsx("div", { className: "aiwg-chip-row", children: allSugs.slice(0, 4).map(s => (_jsxs("button", { className: "aiwg-chip", onClick: () => selectSuggestion(s.text), children: [s.icon, " ", s.text] }, s.id))) })), _jsxs("div", { className: "aiwg-input-area", children: [showSuggestions && filteredSugs.length > 0 && (_jsxs("div", { className: "aiwg-suggestions", children: [_jsx("div", { className: "aiwg-suggestions-header", children: "Suggestions" }), _jsx("div", { className: "aiwg-suggestions-list", children: filteredSugs.map((s, i) => (_jsxs("div", { className: `aiwg-suggestion-item${i === activeIdx ? ' aiwg-active' : ''}`, onMouseEnter: () => setActiveIdx(i), onClick: () => selectSuggestion(s.text), children: [s.icon && _jsx("span", { className: "aiwg-suggestion-icon", children: s.icon }), _jsxs("div", { className: "aiwg-suggestion-main", children: [_jsx("div", { className: "aiwg-suggestion-text", children: s.text }), s.description && _jsx("div", { className: "aiwg-suggestion-desc", children: s.description })] }), s.category && _jsx("span", { className: "aiwg-suggestion-badge", children: s.category })] }, s.id))) }), _jsxs("div", { className: "aiwg-kbd-hint", children: [_jsxs("span", { children: [_jsx("kbd", {}), " navigate"] }), _jsxs("span", { children: [_jsx("kbd", {}), " select"] }), _jsxs("span", { children: [_jsx("kbd", { children: "Esc" }), " close"] })] })] })), _jsxs("div", { className: "aiwg-input-row", children: [_jsx("textarea", { ref: inputRef, className: "aiwg-input", placeholder: placeholder, value: userInput, rows: 1, onChange: handleInputChange, onFocus: () => filterSugs(userInput), onKeyDown: handleKeyDown, disabled: isLoading, "aria-label": "Chat message input" }), _jsx("button", { className: "aiwg-send-btn", type: "button", disabled: isLoading || !userInput.trim(), onClick: () => doSend(userInput), "aria-label": "Send", children: _jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("line", { x1: "22", y1: "2", x2: "11", y2: "13" }), _jsx("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })] }) })] })] }), _jsxs("div", { className: "aiwg-powered", children: ["Powered by", ' ', _jsx("a", { href: poweredByUrl, target: "_blank", rel: "noopener noreferrer", children: poweredByLabel })] })] }) }));
|
|
225
242
|
};
|
|
226
243
|
export default AiChatWidget;
|
package/dist/hooks.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { AiWidgetSDK, AiContextConfig, AiMessage, AiContext, AiSuggestion } from
|
|
|
11
11
|
*/
|
|
12
12
|
export declare function useAiChat(config: AiContextConfig, jwtToken?: string): {
|
|
13
13
|
sdk: AiWidgetSDK;
|
|
14
|
-
sendQuery: (query: string) => Promise<AiMessage>;
|
|
14
|
+
sendQuery: (query: string) => Promise<AiMessage | undefined>;
|
|
15
15
|
setJwt: (token: string) => void;
|
|
16
16
|
checkHealth: () => Promise<boolean>;
|
|
17
17
|
exportChats: (format?: "json" | "csv") => string;
|
|
@@ -54,7 +54,7 @@ export declare function useAiWidget(config: AiContextConfig, jwtToken?: string):
|
|
|
54
54
|
isLoading: boolean;
|
|
55
55
|
error: string | null;
|
|
56
56
|
context: AiContext;
|
|
57
|
-
sendQuery: (query: string) => Promise<AiMessage>;
|
|
57
|
+
sendQuery: (query: string) => Promise<AiMessage | undefined>;
|
|
58
58
|
updateContext: (newContext: Partial<AiContext>) => void;
|
|
59
59
|
updateContextProperty: (key: string, value: any) => void;
|
|
60
60
|
initialized: boolean;
|
package/dist/hooks.js
CHANGED
|
@@ -40,15 +40,18 @@ export function useAiChat(config, jwtToken) {
|
|
|
40
40
|
}, [jwtToken]);
|
|
41
41
|
const sdk = sdkRef.current;
|
|
42
42
|
const sendQuery = useCallback(async (query) => {
|
|
43
|
-
if (!sdk)
|
|
44
|
-
|
|
43
|
+
if (!sdk) {
|
|
44
|
+
setError('SDK not initialized');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
45
47
|
try {
|
|
46
48
|
setError(null);
|
|
47
49
|
return await sdk.sendQuery(query);
|
|
48
50
|
}
|
|
49
51
|
catch (err) {
|
|
50
52
|
setError(String(err));
|
|
51
|
-
throw
|
|
53
|
+
// Do NOT re-throw — error is captured in state and shown in the UI.
|
|
54
|
+
// Re-throwing would cause an unhandled promise rejection that crashes the app.
|
|
52
55
|
}
|
|
53
56
|
}, [sdk]);
|
|
54
57
|
const setJwt = useCallback((token) => {
|