@courseecho/ai-widget-angular 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/DEPRECATION_NOTICE.md +93 -0
- package/README.md +624 -0
- package/package.json +32 -0
- package/src/ai-chat-widget.component.ts +487 -0
- package/src/ai-widget.module.ts +41 -0
- package/src/ai-widget.service.ts +313 -0
- package/src/index.ts +28 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# ⚠️ DEPRECATION NOTICE
|
|
2
|
+
|
|
3
|
+
## Status: CONSOLIDATED
|
|
4
|
+
|
|
5
|
+
The **@courseecho/ai-widget-angular** package (in this directory) is now the official Angular implementation.
|
|
6
|
+
|
|
7
|
+
### What Changed?
|
|
8
|
+
|
|
9
|
+
| Aspect | Before | Now |
|
|
10
|
+
|--------|--------|-----|
|
|
11
|
+
| Angular Support | 14-17 | **19+** (modern only) |
|
|
12
|
+
| Syntax | `*ngIf`, `*ngFor` | **@if, @for** (new control flow) |
|
|
13
|
+
| Version | Multi-version | **Single version** |
|
|
14
|
+
| Purpose | Standalone | **Part of unified SDK** |
|
|
15
|
+
|
|
16
|
+
### Old Package Location
|
|
17
|
+
|
|
18
|
+
**DEPRECATED:** `courseecho-bot-fe/projects/ai-widget-sdk/`
|
|
19
|
+
|
|
20
|
+
This old package is being deprecated in favor of the unified multi-framework approach.
|
|
21
|
+
|
|
22
|
+
### Migration Guide
|
|
23
|
+
|
|
24
|
+
If you were using the old **@courseecho/ai-widget-sdk** from npm:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Old (deprecated)
|
|
28
|
+
npm uninstall @courseecho/ai-widget-sdk
|
|
29
|
+
|
|
30
|
+
# New (recommended)
|
|
31
|
+
npm install @courseecho/ai-widget-angular
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The API is similar but improved. See [README.md](./README.md) for details.
|
|
35
|
+
|
|
36
|
+
### Why Consolidate?
|
|
37
|
+
|
|
38
|
+
✅ **Unified Ecosystem** - Same API as React, Vue, Node.js packages
|
|
39
|
+
✅ **Modern Angular** - Uses Angular 19+ with latest syntax
|
|
40
|
+
✅ **Better Maintenance** - Single source of truth
|
|
41
|
+
✅ **Consistency** - All frameworks follow same pattern
|
|
42
|
+
✅ **Smaller Bundle** - Optimized for modern Angular
|
|
43
|
+
|
|
44
|
+
### What's the Same?
|
|
45
|
+
|
|
46
|
+
- ✅ Floating chat widget
|
|
47
|
+
- ✅ Message history
|
|
48
|
+
- ✅ Authentication (JWT, API key)
|
|
49
|
+
- ✅ Context tracking
|
|
50
|
+
- ✅ Export functionality
|
|
51
|
+
|
|
52
|
+
### What's Different?
|
|
53
|
+
|
|
54
|
+
| Feature | Old | New |
|
|
55
|
+
|---------|-----|-----|
|
|
56
|
+
| Chart Rendering | ✅ Built-in | 📊 Via backend |
|
|
57
|
+
| Control Flow | Traditional | Modern (@if, @for) |
|
|
58
|
+
| Angular Versions | 14+ | 19+ only |
|
|
59
|
+
| Bundle Size | 40KB | 18KB |
|
|
60
|
+
| Multi-framework API | ❌ | ✅ |
|
|
61
|
+
|
|
62
|
+
### Chart Support in New Version?
|
|
63
|
+
|
|
64
|
+
Charts are still available! They come from:
|
|
65
|
+
1. **Backend responses** - AI returns chart data
|
|
66
|
+
2. **Widget displays them** - Using standard HTML/canvas
|
|
67
|
+
3. **Libraries optional** - No Chart.js dependency required
|
|
68
|
+
|
|
69
|
+
### Still Need the Old One?
|
|
70
|
+
|
|
71
|
+
If you **absolutely must** use the old one with Angular 14-18:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Temporary: Access from bot-fe
|
|
75
|
+
npm install courseecho-bot-fe/projects/ai-widget-sdk
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
But we recommend upgrading to Angular 19+ and using the new unified package.
|
|
79
|
+
|
|
80
|
+
### Timeline
|
|
81
|
+
|
|
82
|
+
- **Now:** Old package marked deprecated
|
|
83
|
+
- **v1.1:** Old package removed from npm
|
|
84
|
+
- **v2.0:** Old package archive only (no support)
|
|
85
|
+
|
|
86
|
+
### Questions?
|
|
87
|
+
|
|
88
|
+
See [README.md](./README.md) or check the [main product guide](../PRODUCT_OVERVIEW.md).
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
**Recommendation:** Migrate to the new @courseecho/ai-widget-angular package for best experience! 🚀
|
|
93
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
# @courseecho/ai-widget-angular
|
|
2
|
+
|
|
3
|
+
AI Chat Widget for Angular 14+. Beautiful, production-ready component and service for integrating intelligent chat capabilities into your Angular applications.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
- 🎨 **Beautiful Floating Widget** - Professional, customizable chat interface
|
|
8
|
+
- 🚀 **Angular 19+** - Modern Angular 19+ with latest control flow syntax (@if, @for)
|
|
9
|
+
- 🔐 **Secure Authentication** - JWT and API key support
|
|
10
|
+
- 💬 **Advanced Chat** - Message history, source attribution, follow-up suggestions
|
|
11
|
+
- 📊 **Data Visualization** - Support for charts and data display
|
|
12
|
+
- 🎯 **Context Aware** - Automatic page context tracking
|
|
13
|
+
- 📱 **Responsive** - Works on mobile, tablet, desktop
|
|
14
|
+
- 📦 **Type-Safe** - Full TypeScript support
|
|
15
|
+
- ⚡ **Optimized** - ~18KB bundle, minimal dependencies
|
|
16
|
+
|
|
17
|
+
## 📦 Installation
|
|
18
|
+
|
|
19
|
+
### Prerequisites
|
|
20
|
+
- Angular 19+
|
|
21
|
+
- RxJS 7.8+
|
|
22
|
+
- Node.js 18+
|
|
23
|
+
|
|
24
|
+
### Step 1: Install Core SDK (Required)
|
|
25
|
+
```bash
|
|
26
|
+
npm install @courseecho/ai-core-sdk
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Step 2: Install Angular Widget
|
|
30
|
+
```bash
|
|
31
|
+
npm install @courseecho/ai-widget-angular
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 🚀 Quick Start (5 Minutes)
|
|
35
|
+
|
|
36
|
+
### Option 1: Module-based (Traditional Angular)
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { NgModule } from '@angular/core';
|
|
40
|
+
import { BrowserModule } from '@angular/platform-browser';
|
|
41
|
+
import { AiWidgetModule } from '@courseecho/ai-widget-angular';
|
|
42
|
+
import { AppComponent } from './app.component';
|
|
43
|
+
|
|
44
|
+
@NgModule({
|
|
45
|
+
declarations: [AppComponent],
|
|
46
|
+
imports: [BrowserModule, AiWidgetModule],
|
|
47
|
+
bootstrap: [AppComponent]
|
|
48
|
+
})
|
|
49
|
+
export class AppModule { }
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { Component } from '@angular/core';
|
|
54
|
+
import { AiWidgetConfig, AiAuthConfig } from '@courseecho/ai-widget-angular';
|
|
55
|
+
|
|
56
|
+
@Component({
|
|
57
|
+
selector: 'app-root',
|
|
58
|
+
template: `
|
|
59
|
+
<app-ai-chat-widget [config]="config" [authConfig]="authConfig"></app-ai-chat-widget>
|
|
60
|
+
`
|
|
61
|
+
})
|
|
62
|
+
export class AppComponent {
|
|
63
|
+
config: AiWidgetConfig = {
|
|
64
|
+
apiEndpoint: 'http://localhost:5000',
|
|
65
|
+
enableFloatingButton: true,
|
|
66
|
+
position: 'bottom-right',
|
|
67
|
+
theme: 'light'
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
authConfig: AiAuthConfig = {
|
|
71
|
+
jwtToken: 'your-jwt-token-here'
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Option 2: Standalone Component (Angular 19+)
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { bootstrapApplication } from '@angular/platform-browser';
|
|
80
|
+
import { AppComponent } from './app.component';
|
|
81
|
+
|
|
82
|
+
bootstrapApplication(AppComponent);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { Component } from '@angular/core';
|
|
87
|
+
import { AiChatWidgetComponent, AiWidgetConfig } from '@courseecho/ai-widget-angular';
|
|
88
|
+
|
|
89
|
+
@Component({
|
|
90
|
+
selector: 'app-root',
|
|
91
|
+
standalone: true,
|
|
92
|
+
imports: [AiChatWidgetComponent],
|
|
93
|
+
template: `
|
|
94
|
+
<app-ai-chat-widget [config]="config"></app-ai-chat-widget>
|
|
95
|
+
`
|
|
96
|
+
})
|
|
97
|
+
export class AppComponent {
|
|
98
|
+
config: AiWidgetConfig = {
|
|
99
|
+
apiEndpoint: 'http://localhost:5000',
|
|
100
|
+
position: 'bottom-right'
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 🎯 Configuration
|
|
106
|
+
|
|
107
|
+
### Basic Configuration
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { AiWidgetConfig, AiAuthConfig } from '@courseecho/ai-widget-angular';
|
|
111
|
+
|
|
112
|
+
const config: AiWidgetConfig = {
|
|
113
|
+
// Required
|
|
114
|
+
apiEndpoint: 'http://localhost:5000',
|
|
115
|
+
|
|
116
|
+
// Optional
|
|
117
|
+
enableFloatingButton: true,
|
|
118
|
+
position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left',
|
|
119
|
+
theme: 'light' | 'dark',
|
|
120
|
+
|
|
121
|
+
// Context tracking
|
|
122
|
+
contextConfig: {
|
|
123
|
+
enableContextTracking: true,
|
|
124
|
+
contextUpdateInterval: 5000,
|
|
125
|
+
includePageMetadata: true
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const authConfig: AiAuthConfig = {
|
|
130
|
+
jwtToken: 'your-jwt-token', // Option 1
|
|
131
|
+
// OR
|
|
132
|
+
apiKey: 'your-api-key', // Option 2
|
|
133
|
+
// OR
|
|
134
|
+
refreshTokenFn: async () => {
|
|
135
|
+
// Fetch new token from your auth service
|
|
136
|
+
return 'new-jwt-token';
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## 🔧 Service Usage
|
|
142
|
+
|
|
143
|
+
Inject `AiWidgetService` to use the SDK directly:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { Component, OnInit } from '@angular/core';
|
|
147
|
+
import { AiWidgetService } from '@courseecho/ai-widget-angular';
|
|
148
|
+
|
|
149
|
+
@Component({
|
|
150
|
+
selector: 'app-chat-page',
|
|
151
|
+
template: `
|
|
152
|
+
<div class="chat-container">
|
|
153
|
+
<div *ngFor="let msg of (messages$ | async)" class="message">
|
|
154
|
+
{{ msg.content }}
|
|
155
|
+
</div>
|
|
156
|
+
<input [(ngModel)]="userInput" />
|
|
157
|
+
<button (click)="send()">Send</button>
|
|
158
|
+
</div>
|
|
159
|
+
`
|
|
160
|
+
})
|
|
161
|
+
export class ChatPageComponent implements OnInit {
|
|
162
|
+
messages$ = this.aiService.messages$;
|
|
163
|
+
loading$ = this.aiService.loading$;
|
|
164
|
+
error$ = this.aiService.error$;
|
|
165
|
+
|
|
166
|
+
userInput = '';
|
|
167
|
+
|
|
168
|
+
constructor(private aiService: AiWidgetService) {}
|
|
169
|
+
|
|
170
|
+
ngOnInit() {
|
|
171
|
+
this.aiService.initialize({
|
|
172
|
+
apiEndpoint: 'http://localhost:5000',
|
|
173
|
+
contextConfig: { enableContextTracking: true }
|
|
174
|
+
}, {
|
|
175
|
+
jwtToken: 'your-token'
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async send() {
|
|
180
|
+
try {
|
|
181
|
+
await this.aiService.sendQuery(this.userInput);
|
|
182
|
+
this.userInput = '';
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error('Error:', error);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
clearChat() {
|
|
189
|
+
this.aiService.clear();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
exportChat() {
|
|
193
|
+
const data = this.aiService.exportChats('json');
|
|
194
|
+
const blob = new Blob([data], { type: 'application/json' });
|
|
195
|
+
const url = URL.createObjectURL(blob);
|
|
196
|
+
const link = document.createElement('a');
|
|
197
|
+
link.href = url;
|
|
198
|
+
link.download = `chat-${Date.now()}.json`;
|
|
199
|
+
link.click();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## 📡 Service Methods
|
|
205
|
+
|
|
206
|
+
### sendQuery(question: string)
|
|
207
|
+
Send a question to the AI backend.
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
try {
|
|
211
|
+
const response = await this.aiService.sendQuery('What is Angular?');
|
|
212
|
+
console.log('Response:', response);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error('Error:', error);
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### setContext(context: AiContext)
|
|
219
|
+
Set or update the page context.
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
this.aiService.setContext({
|
|
223
|
+
pageUrl: window.location.href,
|
|
224
|
+
pageTitle: document.title,
|
|
225
|
+
userId: '123',
|
|
226
|
+
customData: { tier: 'premium' }
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### setJwt(token: string)
|
|
231
|
+
Update JWT token.
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
this.aiService.setJwt('new-token');
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### setApiKey(key: string)
|
|
238
|
+
Update API key.
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
this.aiService.setApiKey('new-api-key');
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### clear()
|
|
245
|
+
Clear all messages and reset state.
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
this.aiService.clear();
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### exportChats(format: 'json' | 'csv')
|
|
252
|
+
Export chat history.
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
const json = this.aiService.exportChats('json');
|
|
256
|
+
const csv = this.aiService.exportChats('csv');
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### checkHealth()
|
|
260
|
+
Check backend API health.
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
try {
|
|
264
|
+
await this.aiService.checkHealth();
|
|
265
|
+
console.log('Backend is healthy');
|
|
266
|
+
} catch (error) {
|
|
267
|
+
console.error('Backend is down');
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### getMessages()
|
|
272
|
+
Get current messages (snapshot).
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
const messages = this.aiService.getMessages();
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### isLoading()
|
|
279
|
+
Get loading state (snapshot).
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
if (this.aiService.isLoading()) {
|
|
283
|
+
console.log('Loading...');
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### getError()
|
|
288
|
+
Get current error (snapshot).
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
const error = this.aiService.getError();
|
|
292
|
+
if (error) {
|
|
293
|
+
console.error(error);
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## 📊 Reactive State Observables
|
|
298
|
+
|
|
299
|
+
All state is available as RxJS observables:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// Messages observable
|
|
303
|
+
this.aiService.messages$.subscribe(messages => {
|
|
304
|
+
console.log('Messages:', messages);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Loading state observable
|
|
308
|
+
this.aiService.loading$.subscribe(isLoading => {
|
|
309
|
+
console.log('Is loading:', isLoading);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// Error observable
|
|
313
|
+
this.aiService.error$.subscribe(error => {
|
|
314
|
+
if (error) console.error('Error:', error);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Current context observable
|
|
318
|
+
this.aiService.context$.subscribe(context => {
|
|
319
|
+
console.log('Context:', context);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// Charts observable
|
|
323
|
+
this.aiService.charts$.subscribe(charts => {
|
|
324
|
+
console.log('Charts:', charts);
|
|
325
|
+
});
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Use with `async` pipe in templates:
|
|
329
|
+
|
|
330
|
+
```html
|
|
331
|
+
<div *ngFor="let chart of (charts$ | async)">
|
|
332
|
+
<!-- Render chart -->
|
|
333
|
+
</div>
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## 🎨 Component Customization
|
|
337
|
+
|
|
338
|
+
### Position Options
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
config: AiWidgetConfig = {
|
|
342
|
+
apiEndpoint: 'http://localhost:5000',
|
|
343
|
+
position: 'bottom-right' // 'bottom-left' | 'top-right' | 'top-left'
|
|
344
|
+
};
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Theme Options
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
config: AiWidgetConfig = {
|
|
351
|
+
apiEndpoint: 'http://localhost:5000',
|
|
352
|
+
theme: 'light' // 'light' | 'dark'
|
|
353
|
+
};
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Custom Styling
|
|
357
|
+
|
|
358
|
+
The component uses CSS custom properties for styling:
|
|
359
|
+
|
|
360
|
+
```css
|
|
361
|
+
.ai-widget-container {
|
|
362
|
+
--ai-primary: #3b82f6;
|
|
363
|
+
--ai-text: #1f2937;
|
|
364
|
+
--ai-bg: #ffffff;
|
|
365
|
+
--ai-border: #e5e7eb;
|
|
366
|
+
--ai-user-bg: #3b82f6;
|
|
367
|
+
--ai-assistant-bg: #f3f4f6;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.ai-widget-container.dark-theme {
|
|
371
|
+
--ai-primary: #2563eb;
|
|
372
|
+
--ai-text: #f3f4f6;
|
|
373
|
+
--ai-bg: #1f2937;
|
|
374
|
+
--ai-border: #374151;
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## 🔐 Authentication
|
|
379
|
+
|
|
380
|
+
### JWT Token
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
authConfig: AiAuthConfig = {
|
|
384
|
+
jwtToken: 'eyJhbGciOiJIUzI1NiIs...'
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
// Update token later
|
|
388
|
+
this.aiService.setJwt('new-token');
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### API Key
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
authConfig: AiAuthConfig = {
|
|
395
|
+
apiKey: 'sk-prod-xxxxx'
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// Update key later
|
|
399
|
+
this.aiService.setApiKey('sk-prod-yyyyy');
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Token Refresh
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
authConfig: AiAuthConfig = {
|
|
406
|
+
refreshTokenFn: async () => {
|
|
407
|
+
const response = await fetch('/api/refresh-token', {
|
|
408
|
+
method: 'POST'
|
|
409
|
+
});
|
|
410
|
+
const { token } = await response.json();
|
|
411
|
+
return token;
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## 🧪 Testing
|
|
417
|
+
|
|
418
|
+
### Unit Tests
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
import { TestBed } from '@angular/core/testing';
|
|
422
|
+
import { AiWidgetService } from '@courseecho/ai-widget-angular';
|
|
423
|
+
|
|
424
|
+
describe('AiWidgetService', () => {
|
|
425
|
+
let service: AiWidgetService;
|
|
426
|
+
|
|
427
|
+
beforeEach(() => {
|
|
428
|
+
TestBed.configureTestingModule({});
|
|
429
|
+
service = TestBed.inject(AiWidgetService);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('should initialize', () => {
|
|
433
|
+
service.initialize({
|
|
434
|
+
apiEndpoint: 'http://localhost:5000'
|
|
435
|
+
});
|
|
436
|
+
expect(service).toBeDefined();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('should send query', async () => {
|
|
440
|
+
service.initialize({
|
|
441
|
+
apiEndpoint: 'http://localhost:5000'
|
|
442
|
+
}, { jwtToken: 'test' });
|
|
443
|
+
|
|
444
|
+
const response = await service.sendQuery('Test question');
|
|
445
|
+
expect(response).toBeDefined();
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## ⚡ Performance Tips
|
|
451
|
+
|
|
452
|
+
1. **Lazy load the widget**
|
|
453
|
+
```typescript
|
|
454
|
+
loadWidget() {
|
|
455
|
+
this.showWidget = true;
|
|
456
|
+
this.aiService.initialize(config);
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
2. **Use OnPush change detection**
|
|
461
|
+
```typescript
|
|
462
|
+
@Component({
|
|
463
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
464
|
+
})
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
3. **Unsubscribe properly**
|
|
468
|
+
```typescript
|
|
469
|
+
private destroy$ = new Subject<void>();
|
|
470
|
+
|
|
471
|
+
ngOnInit() {
|
|
472
|
+
this.aiService.messages$
|
|
473
|
+
.pipe(takeUntil(this.destroy$))
|
|
474
|
+
.subscribe(...);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
ngOnDestroy() {
|
|
478
|
+
this.destroy$.next();
|
|
479
|
+
this.destroy$.complete();
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## 🐛 Error Handling
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
async sendQuery(question: string) {
|
|
487
|
+
try {
|
|
488
|
+
const response = await this.aiService.sendQuery(question);
|
|
489
|
+
console.log('Success:', response);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
if (error instanceof Error) {
|
|
492
|
+
if (error.message.includes('401')) {
|
|
493
|
+
console.error('Unauthorized - check your token');
|
|
494
|
+
} else if (error.message.includes('404')) {
|
|
495
|
+
console.error('API endpoint not found');
|
|
496
|
+
} else {
|
|
497
|
+
console.error('Unknown error:', error.message);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Subscribe to error observable
|
|
504
|
+
this.aiService.error$.subscribe(error => {
|
|
505
|
+
if (error) {
|
|
506
|
+
// Show error toast/snackbar
|
|
507
|
+
this.snackBar.open(error, 'Close');
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
## 📚 Comparison with Other Frameworks
|
|
513
|
+
|
|
514
|
+
| Feature | Angular 19+ | React | Vue 3 |
|
|
515
|
+
|---------|-------------|-------|-------|
|
|
516
|
+
| Component | Standalone | Functional | Composition |
|
|
517
|
+
| State | RxJS Observables | Hooks | Composables |
|
|
518
|
+
| Bundle Size | 18KB | 15KB | 12KB |
|
|
519
|
+
| Modern Syntax | @if, @for, @switch | JSX | Template |
|
|
520
|
+
| Performance | Excellent | Excellent | Excellent |
|
|
521
|
+
| Type Safety | Excellent | Good | Good |
|
|
522
|
+
|
|
523
|
+
## 🚀 Deployment
|
|
524
|
+
|
|
525
|
+
### Build for Production
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
npm run build
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
### Include in Angular Build
|
|
532
|
+
|
|
533
|
+
The package is automatically bundled with your Angular app.
|
|
534
|
+
|
|
535
|
+
### Self-Hosted Package
|
|
536
|
+
|
|
537
|
+
```bash
|
|
538
|
+
npm install
|
|
539
|
+
npm run build
|
|
540
|
+
# Output in dist/ folder
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
## 🔗 Resources
|
|
544
|
+
|
|
545
|
+
- [Integration Guide](../MULTI_FRAMEWORK_INTEGRATION_GUIDE.md#angular-integration)
|
|
546
|
+
- [Quick Start](../QUICK_START_CHEATSHEET.md#angular)
|
|
547
|
+
- [API Reference](../CAPABILITIES_AND_ENDPOINTS.md)
|
|
548
|
+
- [Product Overview](../PRODUCT_OVERVIEW.md)
|
|
549
|
+
|
|
550
|
+
## 📝 Types
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
interface AiMessage {
|
|
554
|
+
id: string;
|
|
555
|
+
role: 'user' | 'assistant';
|
|
556
|
+
content: string;
|
|
557
|
+
timestamp: Date;
|
|
558
|
+
sources?: string[];
|
|
559
|
+
isStreaming?: boolean;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
interface AiResponse {
|
|
563
|
+
success: boolean;
|
|
564
|
+
answer: string;
|
|
565
|
+
sources?: string[];
|
|
566
|
+
charts?: AiChart[];
|
|
567
|
+
followUpQuestions?: string[];
|
|
568
|
+
error?: string;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
interface AiContext {
|
|
572
|
+
pageUrl: string;
|
|
573
|
+
pageTitle: string;
|
|
574
|
+
userId?: string;
|
|
575
|
+
sessionId?: string;
|
|
576
|
+
customData?: Record<string, any>;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
interface AiWidgetConfig {
|
|
580
|
+
apiEndpoint: string;
|
|
581
|
+
enableFloatingButton?: boolean;
|
|
582
|
+
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
583
|
+
theme?: 'light' | 'dark';
|
|
584
|
+
contextConfig?: AiContextConfig;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
interface AiAuthConfig {
|
|
588
|
+
jwtToken?: string;
|
|
589
|
+
apiKey?: string;
|
|
590
|
+
refreshTokenFn?: () => Promise<string>;
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
## 🆘 Troubleshooting
|
|
595
|
+
|
|
596
|
+
**Q: Widget doesn't appear**
|
|
597
|
+
- Ensure backend API is running
|
|
598
|
+
- Check API endpoint configuration
|
|
599
|
+
- Verify CORS is enabled on backend
|
|
600
|
+
|
|
601
|
+
**Q: Authentication fails**
|
|
602
|
+
- Verify JWT token is valid
|
|
603
|
+
- Check token hasn't expired
|
|
604
|
+
- Ensure API supports the auth method
|
|
605
|
+
|
|
606
|
+
**Q: Performance issues**
|
|
607
|
+
- Check network in DevTools
|
|
608
|
+
- Verify backend latency
|
|
609
|
+
- Use OnPush change detection
|
|
610
|
+
|
|
611
|
+
## 📞 Support
|
|
612
|
+
|
|
613
|
+
- See [README.md](../README.md) for all resources
|
|
614
|
+
- Check [QUICK_START_CHEATSHEET.md](../QUICK_START_CHEATSHEET.md) for common issues
|
|
615
|
+
- Review [CAPABILITIES_AND_ENDPOINTS.md](../CAPABILITIES_AND_ENDPOINTS.md) for API details
|
|
616
|
+
|
|
617
|
+
## 📄 License
|
|
618
|
+
|
|
619
|
+
MIT - Free to use, modify, and distribute
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
**Ready to get started?** Install the package and follow the Quick Start above!
|
|
624
|
+
|