@courseecho/ai-widget-react 1.0.24 → 1.0.25
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/ShadowWrapper.d.ts +3 -7
- package/dist/ShadowWrapper.js +6 -177
- package/package.json +1 -1
- package/dist/core/src/auto-suggestion-generator.d.ts +0 -118
- package/dist/core/src/auto-suggestion-generator.js +0 -538
- package/dist/core/src/communication-service.d.ts +0 -47
- package/dist/core/src/communication-service.js +0 -124
- package/dist/core/src/index.d.ts +0 -11
- package/dist/core/src/index.js +0 -15
- package/dist/core/src/models.d.ts +0 -137
- package/dist/core/src/models.js +0 -5
- package/dist/core/src/sdk.d.ts +0 -166
- package/dist/core/src/sdk.js +0 -387
- package/dist/core/src/state-manager.d.ts +0 -200
- package/dist/core/src/state-manager.js +0 -368
- package/dist/core/src/widget-css.d.ts +0 -14
- package/dist/core/src/widget-css.js +0 -389
- package/dist/react/src/ShadowWrapper.d.ts +0 -12
- package/dist/react/src/ShadowWrapper.js +0 -44
- package/dist/react/src/components.d.ts +0 -25
- package/dist/react/src/components.js +0 -226
- package/dist/react/src/hooks.d.ts +0 -64
- package/dist/react/src/hooks.js +0 -181
- package/dist/react/src/index.d.ts +0 -7
- package/dist/react/src/index.js +0 -5
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* AI Chat Widget - React Component (Modern Redesign)
|
|
4
|
-
* Usage: <AiChatWidget config={...} apiKey="..." />
|
|
5
|
-
*/
|
|
6
|
-
import { useRef, useEffect, useState, useCallback } from 'react';
|
|
7
|
-
import { useAiWidget } from './hooks';
|
|
8
|
-
import { ShadowWrapper } from './ShadowWrapper';
|
|
9
|
-
// Chat sound — Web Audio API (no external files)
|
|
10
|
-
function playSound(type) {
|
|
11
|
-
try {
|
|
12
|
-
const AudioCtx = window.AudioContext || window.webkitAudioContext;
|
|
13
|
-
if (!AudioCtx)
|
|
14
|
-
return;
|
|
15
|
-
const ctx = new AudioCtx();
|
|
16
|
-
if (type === 'send') {
|
|
17
|
-
const osc = ctx.createOscillator();
|
|
18
|
-
const gain = ctx.createGain();
|
|
19
|
-
osc.connect(gain);
|
|
20
|
-
gain.connect(ctx.destination);
|
|
21
|
-
osc.type = 'sine';
|
|
22
|
-
osc.frequency.setValueAtTime(880, ctx.currentTime);
|
|
23
|
-
osc.frequency.exponentialRampToValueAtTime(440, ctx.currentTime + 0.08);
|
|
24
|
-
gain.gain.setValueAtTime(0.18, ctx.currentTime);
|
|
25
|
-
gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.12);
|
|
26
|
-
osc.start(ctx.currentTime);
|
|
27
|
-
osc.stop(ctx.currentTime + 0.12);
|
|
28
|
-
osc.onended = () => ctx.close();
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
const playTone = (freq, startTime, duration) => {
|
|
32
|
-
const osc = ctx.createOscillator();
|
|
33
|
-
const gain = ctx.createGain();
|
|
34
|
-
osc.connect(gain);
|
|
35
|
-
gain.connect(ctx.destination);
|
|
36
|
-
osc.type = 'sine';
|
|
37
|
-
osc.frequency.setValueAtTime(freq, startTime);
|
|
38
|
-
gain.gain.setValueAtTime(0.0001, startTime);
|
|
39
|
-
gain.gain.linearRampToValueAtTime(0.22, startTime + 0.02);
|
|
40
|
-
gain.gain.exponentialRampToValueAtTime(0.0001, startTime + duration);
|
|
41
|
-
osc.start(startTime);
|
|
42
|
-
osc.stop(startTime + duration);
|
|
43
|
-
};
|
|
44
|
-
playTone(880, ctx.currentTime, 0.18);
|
|
45
|
-
playTone(1100, ctx.currentTime + 0.13, 0.22);
|
|
46
|
-
setTimeout(() => ctx.close(), 500);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
catch (_) { /* silently ignore audio errors */ }
|
|
50
|
-
}
|
|
51
|
-
// Lightweight markdown renderer (no external deps)
|
|
52
|
-
function renderMarkdown(text) {
|
|
53
|
-
const lines = text.split('\n');
|
|
54
|
-
const nodes = [];
|
|
55
|
-
let olItems = [];
|
|
56
|
-
let ulItems = [];
|
|
57
|
-
let key = 0;
|
|
58
|
-
const inlineFormat = (s) => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
59
|
-
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
|
60
|
-
.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
|
61
|
-
.replace(/`(.+?)`/g, '<code>$1</code>');
|
|
62
|
-
const flushLists = () => {
|
|
63
|
-
if (olItems.length) {
|
|
64
|
-
nodes.push(_jsx("ol", { children: olItems.map((t, i) => _jsx("li", { dangerouslySetInnerHTML: { __html: inlineFormat(t) } }, i)) }, key++));
|
|
65
|
-
olItems = [];
|
|
66
|
-
}
|
|
67
|
-
if (ulItems.length) {
|
|
68
|
-
nodes.push(_jsx("ul", { children: ulItems.map((t, i) => _jsx("li", { dangerouslySetInnerHTML: { __html: inlineFormat(t) } }, i)) }, key++));
|
|
69
|
-
ulItems = [];
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
lines.forEach((line) => {
|
|
73
|
-
const olMatch = line.match(/^\d+\.\s+(.*)/);
|
|
74
|
-
const ulMatch = line.match(/^[-*]\s+(.*)/);
|
|
75
|
-
const h3Match = line.match(/^###\s+(.*)/);
|
|
76
|
-
const h2Match = line.match(/^##\s+(.*)/);
|
|
77
|
-
if (h3Match || h2Match) {
|
|
78
|
-
flushLists();
|
|
79
|
-
const content = (h3Match || h2Match)[1];
|
|
80
|
-
nodes.push(h3Match
|
|
81
|
-
? _jsx("h4", { dangerouslySetInnerHTML: { __html: inlineFormat(content) } }, key++)
|
|
82
|
-
: _jsx("h3", { dangerouslySetInnerHTML: { __html: inlineFormat(content) } }, key++));
|
|
83
|
-
}
|
|
84
|
-
else if (olMatch) {
|
|
85
|
-
if (ulItems.length)
|
|
86
|
-
flushLists();
|
|
87
|
-
olItems.push(olMatch[1]);
|
|
88
|
-
}
|
|
89
|
-
else if (ulMatch) {
|
|
90
|
-
if (olItems.length)
|
|
91
|
-
flushLists();
|
|
92
|
-
ulItems.push(ulMatch[1]);
|
|
93
|
-
}
|
|
94
|
-
else if (line.trim() === '') {
|
|
95
|
-
flushLists();
|
|
96
|
-
nodes.push(_jsx("br", {}, key++));
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
flushLists();
|
|
100
|
-
nodes.push(_jsx("p", { dangerouslySetInnerHTML: { __html: inlineFormat(line) } }, key++));
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
flushLists();
|
|
104
|
-
return nodes;
|
|
105
|
-
}
|
|
106
|
-
function autoDetectPageContext() {
|
|
107
|
-
if (typeof document === 'undefined')
|
|
108
|
-
return {};
|
|
109
|
-
const get = (attr) => document.querySelector(`[data-${attr}]`)?.getAttribute(`data-${attr}`) ||
|
|
110
|
-
document.querySelector(`meta[name="${attr}"]`)?.getAttribute('content') || null;
|
|
111
|
-
let pageType = get('page-type') || get('pagetype');
|
|
112
|
-
let entityId = get('entity-id') || get('entityid');
|
|
113
|
-
if (!pageType) {
|
|
114
|
-
const seg = window.location.pathname.replace(/\/+$/, '').split('/').filter(Boolean);
|
|
115
|
-
const known = { course: 'course', courses: 'course', lesson: 'lesson', blog: 'blog', product: 'product', checkout: 'checkout', dashboard: 'dashboard', docs: 'documentation', doc: 'documentation' };
|
|
116
|
-
for (const s of seg) {
|
|
117
|
-
if (known[s]) {
|
|
118
|
-
pageType = known[s];
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (!entityId) {
|
|
124
|
-
const p = new URLSearchParams(window.location.search);
|
|
125
|
-
entityId = p.get('id') || p.get('courseId') || p.get('productId') || null;
|
|
126
|
-
}
|
|
127
|
-
return { pageType: pageType || undefined, entityId: entityId || undefined };
|
|
128
|
-
}
|
|
129
|
-
const DEFAULT_SUGGESTIONS = [
|
|
130
|
-
{ id: 'd1', text: 'What can you do for me?', icon: '', category: 'Getting started', description: 'Learn about my capabilities' },
|
|
131
|
-
{ id: 'd2', text: 'How do I get started?', icon: '', category: 'Getting started', description: 'Quick onboarding guide' },
|
|
132
|
-
{ id: 'd3', text: 'Show me the documentation', icon: '', category: 'Resources', description: 'Browse guides' },
|
|
133
|
-
{ id: 'd4', text: 'I need help with a problem', icon: '', category: 'Support', description: 'Troubleshoot an issue' },
|
|
134
|
-
{ id: 'd5', text: 'What are the pricing options?', icon: '', category: 'Billing', description: 'Plans and pricing' },
|
|
135
|
-
{ id: 'd6', text: 'Contact support team', icon: '', category: 'Support', description: 'Talk to a human' },
|
|
136
|
-
];
|
|
137
|
-
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
|
-
const [mergedConfig] = useState(() => {
|
|
139
|
-
const auto = autoDetectPageContext();
|
|
140
|
-
return { ...config, context: { ...auto, ...config.context } };
|
|
141
|
-
});
|
|
142
|
-
const { messages, isLoading, error, sendQuery, initialized, suggestions } = useAiWidget(mergedConfig, jwtToken);
|
|
143
|
-
const [isMinimized, setIsMinimized] = useState(defaultMinimized);
|
|
144
|
-
const [userInput, setUserInput] = useState('');
|
|
145
|
-
const [showSuggestions, setShowSuggestions] = useState(false);
|
|
146
|
-
const [filteredSugs, setFilteredSugs] = useState([]);
|
|
147
|
-
const [activeIdx, setActiveIdx] = useState(-1);
|
|
148
|
-
const [chipsVisible, setChipsVisible] = useState(true);
|
|
149
|
-
const messagesEndRef = useRef(null);
|
|
150
|
-
const inputRef = useRef(null);
|
|
151
|
-
const allSugs = (suggestions && suggestions.length > 0) ? suggestions : DEFAULT_SUGGESTIONS;
|
|
152
|
-
useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]);
|
|
153
|
-
useEffect(() => {
|
|
154
|
-
if (messages.length > 0) {
|
|
155
|
-
const last = messages[messages.length - 1];
|
|
156
|
-
if (last.role === 'assistant') {
|
|
157
|
-
playSound('receive');
|
|
158
|
-
if (onMessageReceived)
|
|
159
|
-
onMessageReceived(last);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}, [messages, onMessageReceived]);
|
|
163
|
-
useEffect(() => { if (error && onError)
|
|
164
|
-
onError(error); }, [error, onError]);
|
|
165
|
-
const filterSugs = (q) => {
|
|
166
|
-
const lq = q.toLowerCase();
|
|
167
|
-
const filtered = q.trim()
|
|
168
|
-
? allSugs.filter(s => s.text.toLowerCase().includes(lq) || s.category?.toLowerCase().includes(lq) || s.description?.toLowerCase().includes(lq))
|
|
169
|
-
: allSugs;
|
|
170
|
-
setFilteredSugs(filtered.slice(0, 6));
|
|
171
|
-
setShowSuggestions(true);
|
|
172
|
-
setActiveIdx(-1);
|
|
173
|
-
};
|
|
174
|
-
const doSend = useCallback(async (text) => {
|
|
175
|
-
if (!text.trim() || isLoading)
|
|
176
|
-
return;
|
|
177
|
-
setUserInput('');
|
|
178
|
-
setShowSuggestions(false);
|
|
179
|
-
setChipsVisible(false);
|
|
180
|
-
if (inputRef.current)
|
|
181
|
-
inputRef.current.style.height = 'auto';
|
|
182
|
-
playSound('send');
|
|
183
|
-
await sendQuery(text);
|
|
184
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
185
|
-
}, [isLoading, sendQuery]);
|
|
186
|
-
const selectSuggestion = (text) => doSend(text);
|
|
187
|
-
const handleInputChange = (e) => {
|
|
188
|
-
const val = e.target.value;
|
|
189
|
-
setUserInput(val);
|
|
190
|
-
const el = e.target;
|
|
191
|
-
el.style.height = 'auto';
|
|
192
|
-
el.style.height = Math.min(el.scrollHeight, 120) + 'px';
|
|
193
|
-
filterSugs(val);
|
|
194
|
-
};
|
|
195
|
-
const handleKeyDown = (e) => {
|
|
196
|
-
if (e.key === 'ArrowDown') {
|
|
197
|
-
e.preventDefault();
|
|
198
|
-
setActiveIdx(i => Math.min(i + 1, filteredSugs.length - 1));
|
|
199
|
-
}
|
|
200
|
-
else if (e.key === 'ArrowUp') {
|
|
201
|
-
e.preventDefault();
|
|
202
|
-
setActiveIdx(i => Math.max(i - 1, 0));
|
|
203
|
-
}
|
|
204
|
-
else if (e.key === 'Enter' && !e.shiftKey) {
|
|
205
|
-
e.preventDefault();
|
|
206
|
-
if (showSuggestions && activeIdx >= 0)
|
|
207
|
-
selectSuggestion(filteredSugs[activeIdx].text);
|
|
208
|
-
else
|
|
209
|
-
doSend(userInput);
|
|
210
|
-
}
|
|
211
|
-
else if (e.key === 'Escape') {
|
|
212
|
-
setShowSuggestions(false);
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
if (!initialized)
|
|
216
|
-
return _jsx(ShadowWrapper, { children: _jsx("div", { className: "aiwg-root", style: { padding: 20, textAlign: 'center' }, children: "Initializing" }) });
|
|
217
|
-
const themeClass = mergedConfig.theme === 'dark' ? 'aiwg-dark' : '';
|
|
218
|
-
const minimizedClass = isMinimized ? 'aiwg-minimized' : '';
|
|
219
|
-
const poweredByLabel = poweredBy || 'CourseEcho.com';
|
|
220
|
-
return (_jsx(ShadowWrapper, { children: _jsxs("div", { className: `aiwg-root ${themeClass} ${minimizedClass} ${className || ''}`, style: { height: isMinimized ? '72px' : expandedHeight }, children: [_jsxs("div", { className: "aiwg-header", onClick: () => isMinimized && setIsMinimized(false), children: [_jsx("div", { className: "aiwg-avatar" }), _jsxs("div", { className: "aiwg-header-info", children: [_jsx("div", { className: "aiwg-title", children: title }), _jsxs("div", { className: "aiwg-subtitle", children: [_jsx("span", { className: "aiwg-online-dot" }), subtitle] })] }), _jsx("button", { className: "aiwg-minimize-btn", "aria-label": "Minimize", onClick: (e) => { e.stopPropagation(); setIsMinimized(true); }, children: "\u2715" })] }), _jsxs("div", { className: "aiwg-messages", children: [messages.length === 0 && (_jsxs("div", { className: "aiwg-welcome", children: [_jsx("div", { className: "aiwg-welcome-icon" }), _jsx("h4", { children: "Hi there! I'm your AI assistant." }), _jsx("p", { children: "Ask me anything or pick a suggestion below." })] })), messages.map((msg) => {
|
|
221
|
-
const isUser = msg.role === 'user';
|
|
222
|
-
const time = new Date(msg.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
223
|
-
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" }), _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: 'block' }, children: [" ", error] }), _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
|
-
};
|
|
226
|
-
export default AiChatWidget;
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI Widget React - Custom Hooks
|
|
3
|
-
* useAiChat, useAiContext, useAiMessages
|
|
4
|
-
*/
|
|
5
|
-
import { AiWidgetSDK, AiContextConfig, AiMessage, AiContext, AiSuggestion } from '@courseecho/ai-core-sdk';
|
|
6
|
-
/**
|
|
7
|
-
* Hook to initialize and use the AI SDK
|
|
8
|
-
* @param config Configuration for the SDK
|
|
9
|
-
* @param jwtToken Optional JWT token for authentication
|
|
10
|
-
* @returns SDK instance and utility functions
|
|
11
|
-
*/
|
|
12
|
-
export declare function useAiChat(config: AiContextConfig, jwtToken?: string): {
|
|
13
|
-
sdk: AiWidgetSDK;
|
|
14
|
-
sendQuery: (query: string) => Promise<AiMessage>;
|
|
15
|
-
setJwt: (token: string) => void;
|
|
16
|
-
checkHealth: () => Promise<boolean>;
|
|
17
|
-
exportChats: (format?: "json" | "csv") => string;
|
|
18
|
-
initialized: boolean;
|
|
19
|
-
error: string | null;
|
|
20
|
-
};
|
|
21
|
-
/**
|
|
22
|
-
* Hook to subscribe to messages
|
|
23
|
-
*/
|
|
24
|
-
export declare function useAiMessages(sdk: AiWidgetSDK): AiMessage[];
|
|
25
|
-
/**
|
|
26
|
-
* Hook to subscribe to loading state
|
|
27
|
-
*/
|
|
28
|
-
export declare function useAiLoading(sdk: AiWidgetSDK): boolean;
|
|
29
|
-
/**
|
|
30
|
-
* Hook to subscribe to error state
|
|
31
|
-
*/
|
|
32
|
-
export declare function useAiError(sdk: AiWidgetSDK): string | null;
|
|
33
|
-
/**
|
|
34
|
-
* Hook to manage context
|
|
35
|
-
*/
|
|
36
|
-
export declare function useAiContext(sdk: AiWidgetSDK): {
|
|
37
|
-
context: AiContext;
|
|
38
|
-
updateContext: (newContext: Partial<AiContext>) => void;
|
|
39
|
-
updateContextProperty: (key: string, value: any) => void;
|
|
40
|
-
};
|
|
41
|
-
/**
|
|
42
|
-
* Hook to subscribe to suggestions
|
|
43
|
-
*/
|
|
44
|
-
export declare function useAiSuggestions(sdk: AiWidgetSDK): {
|
|
45
|
-
suggestions: AiSuggestion[];
|
|
46
|
-
filterSuggestions: (query: string) => AiSuggestion[];
|
|
47
|
-
};
|
|
48
|
-
/**
|
|
49
|
-
* Combined hook for common use cases
|
|
50
|
-
*/
|
|
51
|
-
export declare function useAiWidget(config: AiContextConfig, jwtToken?: string): {
|
|
52
|
-
sdk: AiWidgetSDK;
|
|
53
|
-
messages: AiMessage[];
|
|
54
|
-
isLoading: boolean;
|
|
55
|
-
error: string | null;
|
|
56
|
-
context: AiContext;
|
|
57
|
-
sendQuery: (query: string) => Promise<AiMessage>;
|
|
58
|
-
updateContext: (newContext: Partial<AiContext>) => void;
|
|
59
|
-
updateContextProperty: (key: string, value: any) => void;
|
|
60
|
-
initialized: boolean;
|
|
61
|
-
initError: string | null;
|
|
62
|
-
suggestions: AiSuggestion[];
|
|
63
|
-
filterSuggestions: (query: string) => AiSuggestion[];
|
|
64
|
-
};
|
package/dist/react/src/hooks.js
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI Widget React - Custom Hooks
|
|
3
|
-
* useAiChat, useAiContext, useAiMessages
|
|
4
|
-
*/
|
|
5
|
-
import { useEffect, useState, useRef, useCallback } from 'react';
|
|
6
|
-
import { AiWidgetSDK, } from '@courseecho/ai-core-sdk';
|
|
7
|
-
/**
|
|
8
|
-
* Hook to initialize and use the AI SDK
|
|
9
|
-
* @param config Configuration for the SDK
|
|
10
|
-
* @param jwtToken Optional JWT token for authentication
|
|
11
|
-
* @returns SDK instance and utility functions
|
|
12
|
-
*/
|
|
13
|
-
export function useAiChat(config, jwtToken) {
|
|
14
|
-
// Initialize synchronously on first render so the SDK is never null.
|
|
15
|
-
const sdkRef = useRef(null);
|
|
16
|
-
if (sdkRef.current === null) {
|
|
17
|
-
sdkRef.current = new AiWidgetSDK(config);
|
|
18
|
-
}
|
|
19
|
-
const [initialized, setInitialized] = useState(false);
|
|
20
|
-
const [error, setError] = useState(null);
|
|
21
|
-
// Apply JWT and side-effects after mount / when token changes.
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
const sdk = sdkRef.current;
|
|
24
|
-
try {
|
|
25
|
-
if (jwtToken) {
|
|
26
|
-
sdk.setJwtToken(jwtToken);
|
|
27
|
-
}
|
|
28
|
-
// Initialize auto-suggestions if enabled
|
|
29
|
-
if (config.suggestionConfig?.autoGenerate !== false) {
|
|
30
|
-
sdk.initializeAutoSuggestions().catch((err) => {
|
|
31
|
-
console.warn('Auto-suggestions initialization failed:', err);
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
setInitialized(true);
|
|
35
|
-
}
|
|
36
|
-
catch (err) {
|
|
37
|
-
setError(String(err));
|
|
38
|
-
}
|
|
39
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
40
|
-
}, [jwtToken]);
|
|
41
|
-
const sdk = sdkRef.current;
|
|
42
|
-
const sendQuery = useCallback(async (query) => {
|
|
43
|
-
if (!sdk)
|
|
44
|
-
throw new Error('SDK not initialized');
|
|
45
|
-
try {
|
|
46
|
-
setError(null);
|
|
47
|
-
return await sdk.sendQuery(query);
|
|
48
|
-
}
|
|
49
|
-
catch (err) {
|
|
50
|
-
setError(String(err));
|
|
51
|
-
throw err;
|
|
52
|
-
}
|
|
53
|
-
}, [sdk]);
|
|
54
|
-
const setJwt = useCallback((token) => {
|
|
55
|
-
if (!sdk)
|
|
56
|
-
throw new Error('SDK not initialized');
|
|
57
|
-
sdk.setJwtToken(token);
|
|
58
|
-
}, [sdk]);
|
|
59
|
-
const checkHealth = useCallback(async () => {
|
|
60
|
-
if (!sdk)
|
|
61
|
-
throw new Error('SDK not initialized');
|
|
62
|
-
return sdk.checkHealth();
|
|
63
|
-
}, [sdk]);
|
|
64
|
-
const exportChats = useCallback((format = 'json') => {
|
|
65
|
-
if (!sdk)
|
|
66
|
-
throw new Error('SDK not initialized');
|
|
67
|
-
return sdk.exportChats(format);
|
|
68
|
-
}, [sdk]);
|
|
69
|
-
return {
|
|
70
|
-
sdk,
|
|
71
|
-
sendQuery,
|
|
72
|
-
setJwt,
|
|
73
|
-
checkHealth,
|
|
74
|
-
exportChats,
|
|
75
|
-
initialized,
|
|
76
|
-
error,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Hook to subscribe to messages
|
|
81
|
-
*/
|
|
82
|
-
export function useAiMessages(sdk) {
|
|
83
|
-
const [messages, setMessages] = useState(() => sdk.getMessages());
|
|
84
|
-
useEffect(() => {
|
|
85
|
-
const unsubscribe = sdk.onMessagesChange((msgs) => {
|
|
86
|
-
setMessages([...msgs]);
|
|
87
|
-
});
|
|
88
|
-
return unsubscribe;
|
|
89
|
-
}, [sdk]);
|
|
90
|
-
return messages;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Hook to subscribe to loading state
|
|
94
|
-
*/
|
|
95
|
-
export function useAiLoading(sdk) {
|
|
96
|
-
const [isLoading, setIsLoading] = useState(() => sdk.isLoading());
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
const unsubscribe = sdk.onLoadingChange((loading) => {
|
|
99
|
-
setIsLoading(loading);
|
|
100
|
-
});
|
|
101
|
-
return unsubscribe;
|
|
102
|
-
}, [sdk]);
|
|
103
|
-
return isLoading;
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Hook to subscribe to error state
|
|
107
|
-
*/
|
|
108
|
-
export function useAiError(sdk) {
|
|
109
|
-
const [error, setError] = useState(() => sdk.getError());
|
|
110
|
-
useEffect(() => {
|
|
111
|
-
const unsubscribe = sdk.onErrorChange((err) => {
|
|
112
|
-
setError(err);
|
|
113
|
-
});
|
|
114
|
-
return unsubscribe;
|
|
115
|
-
}, [sdk]);
|
|
116
|
-
return error;
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Hook to manage context
|
|
120
|
-
*/
|
|
121
|
-
export function useAiContext(sdk) {
|
|
122
|
-
const [context, setContext] = useState(() => sdk.getContext());
|
|
123
|
-
useEffect(() => {
|
|
124
|
-
const unsubscribe = sdk.onContextChange((ctx) => {
|
|
125
|
-
setContext({ ...ctx });
|
|
126
|
-
});
|
|
127
|
-
return unsubscribe;
|
|
128
|
-
}, [sdk]);
|
|
129
|
-
const updateContext = useCallback((newContext) => {
|
|
130
|
-
sdk.setContext(newContext);
|
|
131
|
-
}, [sdk]);
|
|
132
|
-
const updateContextProperty = useCallback((key, value) => {
|
|
133
|
-
sdk.updateContextProperty(key, value);
|
|
134
|
-
}, [sdk]);
|
|
135
|
-
return {
|
|
136
|
-
context,
|
|
137
|
-
updateContext,
|
|
138
|
-
updateContextProperty,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Hook to subscribe to suggestions
|
|
143
|
-
*/
|
|
144
|
-
export function useAiSuggestions(sdk) {
|
|
145
|
-
const [suggestions, setSuggestions] = useState(() => sdk.getSuggestions());
|
|
146
|
-
useEffect(() => {
|
|
147
|
-
const unsubscribe = sdk.onSuggestionsChange((sugg) => {
|
|
148
|
-
setSuggestions([...sugg]);
|
|
149
|
-
});
|
|
150
|
-
return unsubscribe;
|
|
151
|
-
}, [sdk]);
|
|
152
|
-
const filterSuggestions = useCallback((query) => {
|
|
153
|
-
return sdk.filterSuggestions(query);
|
|
154
|
-
}, [sdk]);
|
|
155
|
-
return { suggestions, filterSuggestions };
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Combined hook for common use cases
|
|
159
|
-
*/
|
|
160
|
-
export function useAiWidget(config, jwtToken) {
|
|
161
|
-
const { sdk, sendQuery, initialized, error: initError } = useAiChat(config, jwtToken);
|
|
162
|
-
const messages = useAiMessages(sdk);
|
|
163
|
-
const isLoading = useAiLoading(sdk);
|
|
164
|
-
const error = useAiError(sdk);
|
|
165
|
-
const { context, updateContext, updateContextProperty } = useAiContext(sdk);
|
|
166
|
-
const { suggestions, filterSuggestions } = useAiSuggestions(sdk);
|
|
167
|
-
return {
|
|
168
|
-
sdk,
|
|
169
|
-
messages,
|
|
170
|
-
isLoading,
|
|
171
|
-
error,
|
|
172
|
-
context,
|
|
173
|
-
sendQuery,
|
|
174
|
-
updateContext,
|
|
175
|
-
updateContextProperty,
|
|
176
|
-
initialized,
|
|
177
|
-
initError,
|
|
178
|
-
suggestions,
|
|
179
|
-
filterSuggestions,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI Widget React - Public API
|
|
3
|
-
*/
|
|
4
|
-
export { AiChatWidget } from './components';
|
|
5
|
-
export type { AiChatWidgetProps } from './components';
|
|
6
|
-
export { useAiChat, useAiMessages, useAiLoading, useAiError, useAiContext, useAiWidget, } from './hooks';
|
|
7
|
-
export type { AiContextConfig, AiContext, AiMessage, AiChart, AiQueryResponse, } from '@courseecho/ai-core-sdk';
|