@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.
@@ -1,538 +0,0 @@
1
- /**
2
- * Page Content Scanner and AI Suggestion Generator
3
- * Automatically extracts page content and generates AI-powered suggestions
4
- */
5
- /**
6
- * Scans page content and extracts relevant information
7
- */
8
- export class PageContentScanner {
9
- constructor(config) {
10
- this.cachedContent = new Map();
11
- this.config = {
12
- enabled: true,
13
- autoScan: true,
14
- maxTextLength: 5000,
15
- cacheDuration: 5 * 60 * 1000, // 5 minutes
16
- mergeWithUserSuggestions: true,
17
- ...config,
18
- };
19
- }
20
- /**
21
- * Scan page content
22
- */
23
- scanPage() {
24
- const url = window.location.href;
25
- // Check cache first
26
- const cached = this.getCachedContent(url);
27
- if (cached) {
28
- return cached;
29
- }
30
- const content = {
31
- url,
32
- title: this.extractTitle(),
33
- description: this.extractDescription(),
34
- headings: this.extractHeadings(),
35
- keywords: this.extractKeywords(),
36
- pageType: this.detectPageType(),
37
- rawText: this.extractMainContent(),
38
- };
39
- // Cache the content
40
- this.cacheContent(url, content);
41
- return content;
42
- }
43
- /**
44
- * Get cached content if still valid
45
- */
46
- getCachedContent(url) {
47
- const cached = this.cachedContent.get(url);
48
- if (!cached) {
49
- return null;
50
- }
51
- if (this.config.cacheDuration && Date.now() - cached.timestamp > this.config.cacheDuration) {
52
- this.cachedContent.delete(url);
53
- return null;
54
- }
55
- return cached.content;
56
- }
57
- /**
58
- * Cache content
59
- */
60
- cacheContent(url, content) {
61
- this.cachedContent.set(url, {
62
- content,
63
- timestamp: Date.now(),
64
- });
65
- }
66
- /**
67
- * Extract page title
68
- */
69
- extractTitle() {
70
- const titleTag = document.querySelector('title');
71
- if (titleTag) {
72
- return titleTag.textContent || '';
73
- }
74
- const h1 = document.querySelector('h1');
75
- if (h1) {
76
- return h1.textContent || '';
77
- }
78
- return document.title || 'Untitled Page';
79
- }
80
- /**
81
- * Extract meta description
82
- */
83
- extractDescription() {
84
- const metaDesc = document.querySelector('meta[name="description"]');
85
- if (metaDesc) {
86
- return metaDesc.getAttribute('content') || '';
87
- }
88
- const metaOgDesc = document.querySelector('meta[property="og:description"]');
89
- if (metaOgDesc) {
90
- return metaOgDesc.getAttribute('content') || '';
91
- }
92
- // Fallback: use first paragraph
93
- const firstP = document.querySelector('p');
94
- if (firstP) {
95
- return firstP.textContent?.slice(0, 160) || '';
96
- }
97
- return '';
98
- }
99
- /**
100
- * Extract all headings
101
- */
102
- extractHeadings() {
103
- const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'));
104
- return headings
105
- .map((h) => h.textContent || '')
106
- .filter((text) => text.length > 0)
107
- .slice(0, 20); // Limit to 20 headings
108
- }
109
- /**
110
- * Extract keywords from meta tags
111
- */
112
- extractKeywords() {
113
- const keywordsMeta = document.querySelector('meta[name="keywords"]');
114
- if (keywordsMeta) {
115
- const content = keywordsMeta.getAttribute('content') || '';
116
- return content.split(',').map((k) => k.trim());
117
- }
118
- // Generate keywords from headings and title
119
- const headings = this.extractHeadings();
120
- return headings.slice(0, 10);
121
- }
122
- /**
123
- * Detect page type (course, product, blog, etc.)
124
- */
125
- detectPageType() {
126
- const url = window.location.pathname.toLowerCase();
127
- const content = document.body.innerText.toLowerCase();
128
- if (url.includes('course') || content.includes('learning objectives')) {
129
- return 'course';
130
- }
131
- if (url.includes('product') || url.includes('shop')) {
132
- return 'product';
133
- }
134
- if (url.includes('blog') || url.includes('article')) {
135
- return 'blog';
136
- }
137
- if (url.includes('support') || url.includes('help') || url.includes('faq')) {
138
- return 'support';
139
- }
140
- if (url.includes('docs') || url.includes('documentation')) {
141
- return 'documentation';
142
- }
143
- return 'generic';
144
- }
145
- /**
146
- * Extract main text content
147
- */
148
- extractMainContent() {
149
- // Remove script, style, and excluded elements
150
- const clone = document.body.cloneNode(true);
151
- // Remove unwanted elements
152
- const toRemove = clone.querySelectorAll('script, style, nav, footer, .ads, .sidebar');
153
- toRemove.forEach((el) => el.remove());
154
- // Apply custom selectors if provided
155
- if (this.config.excludeSelectors) {
156
- this.config.excludeSelectors.forEach((selector) => {
157
- clone.querySelectorAll(selector).forEach((el) => el.remove());
158
- });
159
- }
160
- let text = clone.innerText || clone.textContent || '';
161
- // Clean up whitespace
162
- text = text
163
- .split('\n')
164
- .map((line) => line.trim())
165
- .filter((line) => line.length > 0)
166
- .join(' ');
167
- // Truncate to max length
168
- if (this.config.maxTextLength && text.length > this.config.maxTextLength) {
169
- text = text.slice(0, this.config.maxTextLength) + '...';
170
- }
171
- return text;
172
- }
173
- /**
174
- * Clear cache
175
- */
176
- clearCache() {
177
- this.cachedContent.clear();
178
- }
179
- /**
180
- * Update config
181
- */
182
- updateConfig(config) {
183
- this.config = { ...this.config, ...config };
184
- }
185
- }
186
- /**
187
- * Generates AI-powered suggestions based on page content
188
- */
189
- export class AiSuggestionGenerator {
190
- constructor(config, pageScanner) {
191
- this.config = {
192
- enabled: true,
193
- tier: 'free',
194
- timeoutMs: 5000,
195
- ...config,
196
- };
197
- this.pageScanner = pageScanner || new PageContentScanner();
198
- }
199
- /**
200
- * Generate suggestions based on page content
201
- */
202
- async generateSuggestions() {
203
- if (!this.config.enabled) {
204
- return [];
205
- }
206
- // Check tier (free gets limited suggestions)
207
- if (this.config.tier === 'free') {
208
- return this.generateFreeSuggestions();
209
- }
210
- // Premium tier: use backend AI
211
- return this.generatePremiumSuggestions();
212
- }
213
- /**
214
- * Free tier: Generate suggestions locally based on heuristics
215
- */
216
- generateFreeSuggestions() {
217
- const pageContent = this.pageScanner.scanPage();
218
- const suggestions = [];
219
- // Generate suggestions based on page type
220
- switch (pageContent.pageType) {
221
- case 'course':
222
- suggestions.push(...this.generateCourseSuggestions(pageContent));
223
- break;
224
- case 'product':
225
- suggestions.push(...this.generateProductSuggestions(pageContent));
226
- break;
227
- case 'blog':
228
- suggestions.push(...this.generateBlogSuggestions(pageContent));
229
- break;
230
- case 'support':
231
- suggestions.push(...this.generateSupportSuggestions(pageContent));
232
- break;
233
- case 'documentation':
234
- suggestions.push(...this.generateDocsSuggestions(pageContent));
235
- break;
236
- default:
237
- suggestions.push(...this.generateGenericSuggestions(pageContent));
238
- }
239
- return suggestions;
240
- }
241
- /**
242
- * Premium tier: Use backend AI for smart suggestions
243
- */
244
- async generatePremiumSuggestions() {
245
- if (!this.config.backendUrl) {
246
- console.warn('Backend URL not configured. Falling back to free tier.');
247
- return this.generateFreeSuggestions();
248
- }
249
- try {
250
- const pageContent = this.pageScanner.scanPage();
251
- const response = await fetch(`${this.config.backendUrl}/api/suggestions/generate`, {
252
- method: 'POST',
253
- headers: {
254
- 'Content-Type': 'application/json',
255
- ...(this.config.apiKey && { 'X-API-Key': this.config.apiKey }),
256
- },
257
- body: JSON.stringify({
258
- pageContent,
259
- tier: this.config.tier,
260
- }),
261
- signal: AbortSignal.timeout(this.config.timeoutMs || 5000),
262
- });
263
- if (!response.ok) {
264
- throw new Error(`API error: ${response.status}`);
265
- }
266
- return await response.json();
267
- }
268
- catch (error) {
269
- console.error('Error generating premium suggestions:', error);
270
- // Fallback to free tier
271
- return this.generateFreeSuggestions();
272
- }
273
- }
274
- /**
275
- * Course page suggestions
276
- */
277
- generateCourseSuggestions(pageContent) {
278
- return [
279
- {
280
- id: `auto-course-1-${Date.now()}`,
281
- text: 'What are the learning objectives?',
282
- icon: '📚',
283
- category: 'Course',
284
- description: 'Understand course goals and objectives',
285
- },
286
- {
287
- id: `auto-course-2-${Date.now()}`,
288
- text: 'How do I submit assignments?',
289
- icon: '📝',
290
- category: 'Assignments',
291
- description: 'Learn how to complete and submit work',
292
- },
293
- {
294
- id: `auto-course-3-${Date.now()}`,
295
- text: 'What resources are available?',
296
- icon: '🔍',
297
- category: 'Resources',
298
- description: 'Find course materials and references',
299
- },
300
- {
301
- id: `auto-course-4-${Date.now()}`,
302
- text: 'How is this course graded?',
303
- icon: '📊',
304
- category: 'Grading',
305
- description: 'Review grading criteria and rubrics',
306
- },
307
- {
308
- id: `auto-course-5-${Date.now()}`,
309
- text: 'Who is the instructor?',
310
- icon: '👨‍🏫',
311
- category: 'Instructor',
312
- description: 'Learn about the course instructor',
313
- },
314
- ];
315
- }
316
- /**
317
- * Product page suggestions
318
- */
319
- generateProductSuggestions(pageContent) {
320
- return [
321
- {
322
- id: `auto-product-1-${Date.now()}`,
323
- text: 'What are the product specifications?',
324
- icon: '⚙️',
325
- category: 'Details',
326
- description: 'View detailed product specs',
327
- },
328
- {
329
- id: `auto-product-2-${Date.now()}`,
330
- text: 'How much does this cost?',
331
- icon: '💰',
332
- category: 'Pricing',
333
- description: 'See pricing and available plans',
334
- },
335
- {
336
- id: `auto-product-3-${Date.now()}`,
337
- text: 'What shipping options are available?',
338
- icon: '🚚',
339
- category: 'Shipping',
340
- description: 'Check delivery options',
341
- },
342
- {
343
- id: `auto-product-4-${Date.now()}`,
344
- text: 'What is your return policy?',
345
- icon: '↩️',
346
- category: 'Returns',
347
- description: 'Learn about returns and refunds',
348
- },
349
- {
350
- id: `auto-product-5-${Date.now()}`,
351
- text: 'Do you have customer reviews?',
352
- icon: '⭐',
353
- category: 'Reviews',
354
- description: 'See what customers think',
355
- },
356
- ];
357
- }
358
- /**
359
- * Blog page suggestions
360
- */
361
- generateBlogSuggestions(pageContent) {
362
- return [
363
- {
364
- id: `auto-blog-1-${Date.now()}`,
365
- text: 'What is this article about?',
366
- icon: '📖',
367
- category: 'Content',
368
- description: 'Get summary of the article',
369
- },
370
- {
371
- id: `auto-blog-2-${Date.now()}`,
372
- text: 'Can you expand on this topic?',
373
- icon: '💭',
374
- category: 'Discussion',
375
- description: 'Dive deeper into the subject',
376
- },
377
- {
378
- id: `auto-blog-3-${Date.now()}`,
379
- text: 'Related articles and resources?',
380
- icon: '🔗',
381
- category: 'Links',
382
- description: 'Find related content',
383
- },
384
- {
385
- id: `auto-blog-4-${Date.now()}`,
386
- text: 'Who is the author?',
387
- icon: '✍️',
388
- category: 'Author',
389
- description: 'Learn about the writer',
390
- },
391
- {
392
- id: `auto-blog-5-${Date.now()}`,
393
- text: 'When was this published?',
394
- icon: '📅',
395
- category: 'Date',
396
- description: 'Check publication date',
397
- },
398
- ];
399
- }
400
- /**
401
- * Support page suggestions
402
- */
403
- generateSupportSuggestions(pageContent) {
404
- return [
405
- {
406
- id: `auto-support-1-${Date.now()}`,
407
- text: 'How do I reset my password?',
408
- icon: '🔐',
409
- category: 'Account',
410
- description: 'Password recovery help',
411
- },
412
- {
413
- id: `auto-support-2-${Date.now()}`,
414
- text: 'How do I contact support?',
415
- icon: '📞',
416
- category: 'Support',
417
- description: 'Get in touch with support',
418
- },
419
- {
420
- id: `auto-support-3-${Date.now()}`,
421
- text: 'What are my billing options?',
422
- icon: '💳',
423
- category: 'Billing',
424
- description: 'Payment information',
425
- },
426
- {
427
- id: `auto-support-4-${Date.now()}`,
428
- text: 'How do I cancel my subscription?',
429
- icon: '❌',
430
- category: 'Subscription',
431
- description: 'Manage subscription',
432
- },
433
- {
434
- id: `auto-support-5-${Date.now()}`,
435
- text: 'What are my rights?',
436
- icon: '⚖️',
437
- category: 'Legal',
438
- description: 'View terms and conditions',
439
- },
440
- ];
441
- }
442
- /**
443
- * Documentation page suggestions
444
- */
445
- generateDocsSuggestions(pageContent) {
446
- return [
447
- {
448
- id: `auto-docs-1-${Date.now()}`,
449
- text: 'How do I get started?',
450
- icon: '🚀',
451
- category: 'Getting Started',
452
- description: 'Quick start guide',
453
- },
454
- {
455
- id: `auto-docs-2-${Date.now()}`,
456
- text: 'What are the system requirements?',
457
- icon: '⚙️',
458
- category: 'Requirements',
459
- description: 'Technical specifications',
460
- },
461
- {
462
- id: `auto-docs-3-${Date.now()}`,
463
- text: 'Do you have code examples?',
464
- icon: '💻',
465
- category: 'Examples',
466
- description: 'View code samples',
467
- },
468
- {
469
- id: `auto-docs-4-${Date.now()}`,
470
- text: 'How is this documented?',
471
- icon: '📚',
472
- category: 'Docs',
473
- description: 'Browse all documentation',
474
- },
475
- {
476
- id: `auto-docs-5-${Date.now()}`,
477
- text: 'How do I report issues?',
478
- icon: '🐛',
479
- category: 'Issues',
480
- description: 'Report bugs and issues',
481
- },
482
- ];
483
- }
484
- /**
485
- * Generic page suggestions
486
- */
487
- generateGenericSuggestions(pageContent) {
488
- return [
489
- {
490
- id: `auto-generic-1-${Date.now()}`,
491
- text: 'Tell me more about this topic',
492
- icon: '❓',
493
- category: 'General',
494
- description: 'Get more information',
495
- },
496
- {
497
- id: `auto-generic-2-${Date.now()}`,
498
- text: 'What are the key points?',
499
- icon: '📍',
500
- category: 'Summary',
501
- description: 'See main highlights',
502
- },
503
- {
504
- id: `auto-generic-3-${Date.now()}`,
505
- text: 'How can I use this?',
506
- icon: '💡',
507
- category: 'Usage',
508
- description: 'Learn how to use this',
509
- },
510
- {
511
- id: `auto-generic-4-${Date.now()}`,
512
- text: 'Where can I learn more?',
513
- icon: '🔗',
514
- category: 'Resources',
515
- description: 'Find additional resources',
516
- },
517
- {
518
- id: `auto-generic-5-${Date.now()}`,
519
- text: 'How do I get help?',
520
- icon: '🤝',
521
- category: 'Help',
522
- description: 'Get support',
523
- },
524
- ];
525
- }
526
- /**
527
- * Update config
528
- */
529
- updateConfig(config) {
530
- this.config = { ...this.config, ...config };
531
- }
532
- /**
533
- * Get current tier
534
- */
535
- getTier() {
536
- return this.config.tier || 'free';
537
- }
538
- }
@@ -1,47 +0,0 @@
1
- /**
2
- * AI Widget SDK - Core Communication Service
3
- * Handle HTTP requests to backend API
4
- */
5
- import { AiQueryRequest, AiQueryResponse } from './models';
6
- export declare class AiCommunicationService {
7
- private apiEndpoint;
8
- private apiKey?;
9
- private jwtToken?;
10
- constructor(apiEndpoint: string, apiKey?: string);
11
- /**
12
- * Set JWT token for authentication
13
- */
14
- setJwtToken(token: string): void;
15
- /**
16
- * Get current JWT token
17
- */
18
- getJwtToken(): string | undefined;
19
- /**
20
- * Check if JWT token is set
21
- */
22
- isAuthenticated(): boolean;
23
- /**
24
- * Send query to backend API
25
- */
26
- sendQuery(request: AiQueryRequest): Promise<AiQueryResponse>;
27
- /**
28
- * Check backend health
29
- */
30
- checkHealth(): Promise<boolean>;
31
- /**
32
- * Get API endpoint
33
- */
34
- getApiEndpoint(): string;
35
- }
36
- /**
37
- * Custom API Error class
38
- */
39
- export declare class ApiError extends Error {
40
- statusCode: number;
41
- responseData?: any | undefined;
42
- constructor(message: string, statusCode: number, responseData?: any | undefined);
43
- isRateLimited(): boolean;
44
- isUnauthorized(): boolean;
45
- isBadRequest(): boolean;
46
- isServerError(): boolean;
47
- }
@@ -1,124 +0,0 @@
1
- /**
2
- * AI Widget SDK - Core Communication Service
3
- * Handle HTTP requests to backend API
4
- */
5
- /** Safely parse a Response body as JSON — returns null for empty/non-JSON bodies instead of throwing. */
6
- async function safeJson(res) {
7
- const text = await res.text();
8
- if (!text || !text.trim())
9
- return null;
10
- try {
11
- return JSON.parse(text);
12
- }
13
- catch {
14
- return null;
15
- }
16
- }
17
- export class AiCommunicationService {
18
- constructor(apiEndpoint, apiKey) {
19
- if (!apiEndpoint) {
20
- throw new Error('apiEndpoint is required');
21
- }
22
- this.apiEndpoint = apiEndpoint.replace(/\/$/, ''); // Remove trailing slash
23
- this.apiKey = apiKey;
24
- }
25
- /**
26
- * Set JWT token for authentication
27
- */
28
- setJwtToken(token) {
29
- this.jwtToken = token;
30
- }
31
- /**
32
- * Get current JWT token
33
- */
34
- getJwtToken() {
35
- return this.jwtToken;
36
- }
37
- /**
38
- * Check if JWT token is set
39
- */
40
- isAuthenticated() {
41
- return !!this.jwtToken;
42
- }
43
- /**
44
- * Send query to backend API
45
- */
46
- async sendQuery(request) {
47
- if (!this.jwtToken && !this.apiKey) {
48
- throw new Error('Authentication required: Set JWT token or API key. Call setJwtToken() first.');
49
- }
50
- const headers = {
51
- 'Content-Type': 'application/json',
52
- };
53
- if (this.jwtToken) {
54
- headers['Authorization'] = `Bearer ${this.jwtToken}`;
55
- }
56
- else if (this.apiKey) {
57
- headers['X-API-Key'] = this.apiKey;
58
- }
59
- try {
60
- const response = await fetch(`${this.apiEndpoint}/api/aiorchestrator/query`, {
61
- method: 'POST',
62
- headers,
63
- body: JSON.stringify(request),
64
- });
65
- if (!response.ok) {
66
- const errorData = await safeJson(response);
67
- throw new ApiError(errorData?.message || `HTTP ${response.status}`, response.status, errorData);
68
- }
69
- const data = (await safeJson(response));
70
- if (!data)
71
- throw new ApiError('Empty response from server', 500);
72
- // Ensure timestamp is a Date object
73
- data.timestamp = new Date(data.timestamp);
74
- return data;
75
- }
76
- catch (error) {
77
- if (error instanceof ApiError) {
78
- throw error;
79
- }
80
- throw new ApiError(`Failed to send query: ${String(error)}`, 500);
81
- }
82
- }
83
- /**
84
- * Check backend health
85
- */
86
- async checkHealth() {
87
- try {
88
- const response = await fetch(`${this.apiEndpoint}/health`);
89
- return response.ok;
90
- }
91
- catch {
92
- return false;
93
- }
94
- }
95
- /**
96
- * Get API endpoint
97
- */
98
- getApiEndpoint() {
99
- return this.apiEndpoint;
100
- }
101
- }
102
- /**
103
- * Custom API Error class
104
- */
105
- export class ApiError extends Error {
106
- constructor(message, statusCode, responseData) {
107
- super(message);
108
- this.statusCode = statusCode;
109
- this.responseData = responseData;
110
- this.name = 'ApiError';
111
- }
112
- isRateLimited() {
113
- return this.statusCode === 429;
114
- }
115
- isUnauthorized() {
116
- return this.statusCode === 401;
117
- }
118
- isBadRequest() {
119
- return this.statusCode === 400;
120
- }
121
- isServerError() {
122
- return this.statusCode >= 500;
123
- }
124
- }