@foisit/angular-wrapper 1.2.0 → 2.0.1

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/README.md CHANGED
@@ -1,216 +1,714 @@
1
- # 🎙️ Foisit Angular Wrapper: Speak, and it’s Done.
1
+ # @foisit/angular-wrapper
2
2
 
3
- The **Foisit Angular Wrapper** brings voice interactivity to your Angular apps with the power of the **Foisit Assistant**. Imagine saying "background red" and watching your app come alive—now that's ✨magic✨!
3
+ [![npm version](https://img.shields.io/npm/v/@foisit/angular-wrapper.svg)](https://www.npmjs.com/package/@foisit/angular-wrapper)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
5
 
5
- ### **Why Foisit Angular Wrapper?**
6
+ > **The AI-Powered Conversational Assistant for Angular Applications**
6
7
 
7
- - 🧩 **Dynamic Commands**: Add or remove commands on the fly.
8
- - 🎨 **Visual Feedback**: Show visual cues when the assistant is active.
9
- - 🚀 **Effortless Integration**: Set up voice commands in minutes with minimal code.
10
- - 🗣️ **Voice Feedback**: Interactive voice responses make the assistant engaging.
11
- - 🔄 **Double Activation**: Activate or put the assistant to sleep with a double-tap.
8
+ Transform your Angular app into an intelligent, voice-ready platform. Foisit provides a drop-in AI layer that understands natural language, manages multi-step workflows, and executes actions—all with zero backend required.
12
9
 
13
- ### 🌐 **Live Demo**
10
+ > [!NOTE] > **Voice Support Status**: Voice recognition and responses are currently in development and will be released in a future update. The current version focuses on high-performance text-based interactions and AI intent matching.
14
11
 
15
- 🎉 [Test the Angular Assistant here!](https://ng-foisit-demo.netlify.app/)
12
+ ---
13
+
14
+ ## Table of Contents
15
+
16
+ - [Features](#features)
17
+ - [Installation](#installation)
18
+ - [Quick Start](#quick-start)
19
+ - [Core Concepts](#core-concepts)
20
+ - [API Reference](#api-reference)
21
+ - [Advanced Usage](#advanced-usage)
22
+ - [Examples](#examples)
23
+ - [TypeScript Support](#typescript-support)
24
+ - [Best Practices](#best-practices)
16
25
 
17
26
  ---
18
27
 
19
- ## 🚀 Installation
28
+ ## Features
20
29
 
21
- Get started by installing the library:
30
+ - **Natural Language Understanding** - AI-powered intent matching using GPT-4o mini (proxied securely)
31
+ - **Smart Slot Filling** - Auto-generates forms for missing parameters
32
+ - **Critical Action Protection** - Built-in confirmation dialogs for dangerous operations
33
+ - **Premium UI** - Glassmorphic overlay with dark/light mode support
34
+ - **Zero Backend Required** - Secure proxy architecture keeps API keys server-side
35
+ - **Angular Native** - Uses Dependency Injection, Signals, and RxJS
36
+ - **Type-Safe** - Full TypeScript support with comprehensive types
37
+ - **Responsive** - Works flawlessly on desktop and mobile
38
+
39
+ ---
40
+
41
+ ## Installation
22
42
 
23
43
  ```bash
24
44
  npm install @foisit/angular-wrapper
25
45
  ```
26
46
 
27
- or
47
+ ### Peer Dependencies
28
48
 
29
- ```bash
30
- yarn add @foisit/angular-wrapper
49
+ ```json
50
+ {
51
+ "@angular/core": "^17.0.0 || ^18.0.0",
52
+ "@angular/common": "^17.0.0 || ^18.0.0"
53
+ }
31
54
  ```
32
55
 
33
56
  ---
34
57
 
35
- ## 🔧 Setup
58
+ ## Quick Start
59
+
60
+ ### Step 1: Import the Module
61
+
62
+ #### For Standalone Apps (Recommended)
36
63
 
37
- Here's how you can integrate the Foisit Assistant into your Angular app in just a few steps:
64
+ ```typescript
65
+ // app.config.ts
66
+ import { ApplicationConfig, importProvidersFrom } from '@angular/core';
67
+ import { AssistantModule } from '@foisit/angular-wrapper';
38
68
 
39
- ### Step 1: Import the `AssistantModule`
69
+ export const appConfig: ApplicationConfig = {
70
+ providers: [
71
+ importProvidersFrom(
72
+ AssistantModule.forRoot({
73
+ introMessage: 'Welcome! How can I assist you today?',
74
+ enableSmartIntent: true,
75
+ commands: [
76
+ {
77
+ command: 'navigate to profile',
78
+ action: () => console.log('Navigating to profile...'),
79
+ },
80
+ ],
81
+ })
82
+ ),
83
+ ],
84
+ };
85
+ ```
40
86
 
41
- #### Using `forRoot` in `app.module.ts`
87
+ #### For Module-Based Apps
42
88
 
43
89
  ```typescript
90
+ // app.module.ts
44
91
  import { NgModule } from '@angular/core';
45
- import { BrowserModule } from '@angular/platform-browser';
46
- import { AppComponent } from './app.component';
47
92
  import { AssistantModule } from '@foisit/angular-wrapper';
48
93
 
49
94
  @NgModule({
50
- declarations: [AppComponent],
51
95
  imports: [
52
- BrowserModule,
53
96
  AssistantModule.forRoot({
54
- activationCommand: 'John',
55
- fallbackResponse: 'Sorry, I didn’t understand that.',
97
+ introMessage: 'Welcome! How can I assist you today?',
56
98
  commands: [
57
- { command: 'show profile', action: () => console.log('Showing profile...') },
58
- { command: 'log out', action: () => console.log('Logging out...') },
99
+ /* your commands */
59
100
  ],
60
101
  }),
61
102
  ],
62
- bootstrap: [AppComponent],
63
103
  })
64
104
  export class AppModule {}
65
105
  ```
66
106
 
67
- #### Or Using `app.config.ts` (Latest Versions)
107
+ ### Step 2: Use the Service
68
108
 
69
109
  ```typescript
70
- import { ApplicationConfig, provideZoneChangeDetection, importProvidersFrom } from '@angular/core';
71
- import { provideRouter } from '@angular/router';
72
- import { appRoutes } from './app.routes';
73
- import { AssistantModule } from '@foisit/angular-wrapper';
110
+ // my-component.ts
111
+ import { Component } from '@angular/core';
112
+ import { AssistantService } from '@foisit/angular-wrapper';
74
113
 
75
- export const appConfig: ApplicationConfig = {
76
- providers: [
77
- provideZoneChangeDetection({ eventCoalescing: true }),
78
- provideRouter(appRoutes),
79
- importProvidersFrom(
80
- AssistantModule.forRoot({
81
- activationCommand: 'John',
82
- fallbackResponse: 'Sorry, I didn’t understand that.',
83
- commands: [
84
- { command: 'show profile', action: () => console.log('Showing profile...') },
85
- { command: 'log out', action: () => console.log('Logging out...') },
86
- ],
87
- })
88
- ),
114
+ @Component({
115
+ selector: 'app-my-component',
116
+ template: ` <button (click)="openAssistant()">Open Assistant</button> `,
117
+ })
118
+ export class MyComponent {
119
+ constructor(private assistant: AssistantService) {}
120
+
121
+ openAssistant() {
122
+ this.assistant.toggle();
123
+ }
124
+ }
125
+ ```
126
+
127
+ ---
128
+
129
+ ## Core Concepts
130
+
131
+ ### 1. Commands
132
+
133
+ Commands are the building blocks of your assistant. Each command represents an action users can trigger through natural language.
134
+
135
+ ```typescript
136
+ {
137
+ command: 'delete account',
138
+ description: 'Permanently delete user account',
139
+ action: () => this.accountService.delete()
140
+ }
141
+ ```
142
+
143
+ ### 2. Parameters (Slot Filling)
144
+
145
+ Define parameters and Foisit will automatically generate forms to collect them:
146
+
147
+ ```typescript
148
+ {
149
+ command: 'create user',
150
+ description: 'Create a new user account',
151
+ parameters: [
152
+ { name: 'username', type: 'string', required: true },
153
+ { name: 'email', type: 'string', required: true },
154
+ { name: 'age', type: 'number', required: false }
89
155
  ],
90
- };
156
+ action: (params) => this.userService.create(params)
157
+ }
158
+ ```
159
+
160
+ **Supported Parameter Types:**
161
+
162
+ - `string` - Text input
163
+ - `number` - Numeric input
164
+ - `date` - Date picker
165
+ - `select` - Dropdown (static or async options)
166
+ - `file` - File upload input
167
+
168
+ ### 3. File Parameters
169
+
170
+ Collect files via the built-in form UI and receive them in your command `action`.
171
+
172
+ ```typescript
173
+ {
174
+ command: 'upload file',
175
+ description: 'Pick a file and return it to the action',
176
+ parameters: [
177
+ {
178
+ name: 'attachment',
179
+ type: 'file',
180
+ required: true,
181
+ accept: ['image/*', 'audio/*', 'video/*'],
182
+ multiple: false,
183
+ // delivery: 'file' | 'base64' (default: 'file')
184
+ delivery: 'file',
185
+ },
186
+ ],
187
+ action: async (params) => {
188
+ const file = params?.attachment as File | undefined;
189
+ if (!file) return { type: 'error', message: 'No file provided.' };
190
+ return {
191
+ type: 'success',
192
+ message: `File received. Name: ${file.name}, Type: ${file.type || 'unknown'}, Size: ${file.size} bytes`,
193
+ };
194
+ },
195
+ }
196
+ ```
197
+
198
+ `FileParameter` supports validations like `maxFiles`, `maxSizeBytes`, `maxTotalBytes`, and media/image constraints like `maxDurationSec`, `maxWidth`, and `maxHeight`.
199
+
200
+ ### 4. Critical Actions
201
+
202
+ Protect dangerous operations with automatic confirmation dialogs:
203
+
204
+ ```typescript
205
+ {
206
+ command: 'delete all data',
207
+ critical: true, // Requires confirmation
208
+ description: 'Permanently delete all application data',
209
+ action: async () => {
210
+ await this.dataService.deleteAll();
211
+ return 'All data deleted successfully.';
212
+ }
213
+ }
214
+ ```
215
+
216
+ ### 5. Select Parameters (Static)
217
+
218
+ Provide predefined options:
219
+
220
+ ```typescript
221
+ {
222
+ command: 'set theme',
223
+ parameters: [{
224
+ name: 'theme',
225
+ type: 'select',
226
+ options: [
227
+ { label: 'Light Mode', value: 'light' },
228
+ { label: 'Dark Mode', value: 'dark' },
229
+ { label: 'Auto', value: 'auto' }
230
+ ]
231
+ }],
232
+ action: (params) => this.themeService.set(params.theme)
233
+ }
234
+ ```
235
+
236
+ ### 6. Dynamic Select Parameters
237
+
238
+ Load options from APIs:
239
+
240
+ ```typescript
241
+ {
242
+ command: 'assign to user',
243
+ parameters: [{
244
+ name: 'userId',
245
+ type: 'select',
246
+ getOptions: async () => {
247
+ const users = await this.userService.getAll();
248
+ return users.map(u => ({
249
+ label: `${u.name} (${u.email})`,
250
+ value: u.id
251
+ }));
252
+ }
253
+ }],
254
+ action: (params) => this.taskService.assign(params.userId)
255
+ }
256
+ ```
257
+
258
+ ---
259
+
260
+ ## API Reference
261
+
262
+ ### `AssistantService`
263
+
264
+ Injectable service for programmatic control:
265
+
266
+ #### Methods
267
+
268
+ ##### `toggle(onSubmit?, onClose?)`
269
+
270
+ Opens or closes the assistant overlay.
271
+
272
+ ```typescript
273
+ // Basic usage
274
+ this.assistant.toggle();
275
+
276
+ // With callbacks
277
+ this.assistant.toggle(
278
+ (userInput) => console.log('User said:', userInput),
279
+ () => console.log('Assistant closed')
280
+ );
281
+ ```
282
+
283
+ ##### `addCommand(command, action?)`
284
+
285
+ Dynamically add or update a command at runtime. Commands added via `addCommand` take effect immediately in the running application; they are stored in memory for the current session (they are not persisted after a page reload unless you re-register them during app startup).
286
+
287
+ ```typescript
288
+ // Add a simple command
289
+ this.assistant.addCommand('refresh data', () => {
290
+ this.dataService.refresh();
291
+ return 'Data refreshed!';
292
+ });
293
+
294
+ // Add a command with full config
295
+ this.assistant.addCommand({
296
+ command: 'export report',
297
+ description: 'Export data as PDF',
298
+ parameters: [
299
+ {
300
+ name: 'format',
301
+ type: 'select',
302
+ options: [
303
+ { label: 'PDF', value: 'pdf' },
304
+ { label: 'Excel', value: 'xlsx' },
305
+ ],
306
+ },
307
+ ],
308
+ action: async (params) => {
309
+ await this.reportService.export(params.format);
310
+ return `Report exported as ${params.format}`;
311
+ },
312
+ });
313
+ ```
314
+
315
+ ### Dynamic Updates (Add / Remove / Update commands at runtime) ✅
316
+
317
+ - Use `addCommand` to register a new command or to replace an existing command's behavior (remove first if you want a clean replacement).
318
+ - Use `removeCommand(commandPhrase)` to unregister a command. This is useful for temporary or context-specific commands.
319
+ - Commands are in-memory only; to persist them across reloads, re-register on app startup (e.g., in an initialization hook).
320
+
321
+ Example — register a temporary command and clean it up in `ngOnDestroy`:
322
+
323
+ ```typescript
324
+ // component.ts
325
+ import { OnDestroy } from '@angular/core';
326
+
327
+ export class MyComponent implements OnDestroy {
328
+ constructor(private assistant: AssistantService) {
329
+ this.assistant.addCommand('temp action', () => 'Temporary action executed');
330
+ }
331
+
332
+ ngOnDestroy() {
333
+ // Remove the temporary command when component unmounts
334
+ this.assistant.removeCommand('temp action');
335
+ }
336
+ }
337
+ ```
338
+
339
+ Notes:
340
+
341
+ - If a command has only optional params, consider returning a `form` InteractiveResponse to prompt the user when no params are provided.
342
+ - Always remove transient commands in your cleanup lifecycle to avoid leaks and confusing UX.
343
+
344
+ ##### `removeCommand(commandPhrase)`
345
+
346
+ Remove a registered command.
347
+
348
+ ```typescript
349
+ this.assistant.removeCommand('delete account');
350
+ ```
351
+
352
+ ##### `getCommands()`
353
+
354
+ Get list of all registered command names.
355
+
356
+ ```typescript
357
+ const commands = this.assistant.getCommands();
358
+ console.log('Available commands:', commands);
91
359
  ```
92
360
 
93
361
  ---
94
362
 
95
- ### Step 2: Interact with the Service 🗣️
363
+ ## Configuration Options
364
+
365
+ ### `AssistantConfig`
366
+
367
+ ```typescript
368
+ interface AssistantConfig {
369
+ // Activation keyword (optional)
370
+ activationCommand?: string;
96
371
 
97
- Define and remove voice commands and inject the `AssistantService` into your components, start the party by calling `startListening()`.
372
+ // Welcome message shown when assistant opens
373
+ introMessage?: string;
98
374
 
99
- #### `app.component.ts`
375
+ // Response for unrecognized inputs
376
+ fallbackResponse?: string;
377
+
378
+ // Enable AI-powered natural language understanding
379
+ enableSmartIntent?: boolean;
380
+
381
+ // Input field placeholder text
382
+ inputPlaceholder?: string;
383
+
384
+ // List of commands
385
+ commands: AssistantCommand[];
386
+
387
+ // Floating button configuration
388
+ floatingButton?: {
389
+ visible?: boolean;
390
+ tooltip?: string;
391
+ customHtml?: string;
392
+ position?: { bottom: string; right: string };
393
+ };
394
+ }
395
+ ```
396
+
397
+ ---
398
+
399
+ ## Advanced Usage
400
+
401
+ ### Example 1: Multi-Step Booking System
100
402
 
101
403
  ```typescript
102
- import { Component, signal } from '@angular/core';
404
+ import { Component } from '@angular/core';
103
405
  import { AssistantService } from '@foisit/angular-wrapper';
104
406
 
105
407
  @Component({
106
- selector: 'app-root',
107
- templateUrl: './app.component.html',
108
- styleUrls: ['./app.component.scss'],
408
+ selector: 'app-booking',
409
+ template: `<button (click)="setupBooking()">Enable Booking</button>`,
109
410
  })
110
- export class AppComponent {
111
- title = 'Angular Assistant Demo';
112
- color = signal('transparent'); // Reactive color signal
113
-
114
- constructor(private assistantService: AssistantService) {
115
- // Define voice commands
116
- this.assistantService.addCommand('background red', () => {
117
- this.color.set('red');
411
+ export class BookingComponent {
412
+ constructor(private assistant: AssistantService, private bookingService: BookingService) {}
413
+
414
+ setupBooking() {
415
+ this.assistant.addCommand({
416
+ command: 'book appointment',
417
+ description: 'Schedule a new appointment',
418
+ parameters: [
419
+ {
420
+ name: 'service',
421
+ description: 'Type of service',
422
+ type: 'select',
423
+ required: true,
424
+ getOptions: async () => {
425
+ const services = await this.bookingService.getServices();
426
+ return services.map((s) => ({
427
+ label: s.name,
428
+ value: s.id,
429
+ }));
430
+ },
431
+ },
432
+ {
433
+ name: 'date',
434
+ description: 'Preferred date',
435
+ type: 'date',
436
+ required: true,
437
+ },
438
+ {
439
+ name: 'notes',
440
+ description: 'Additional notes',
441
+ type: 'string',
442
+ required: false,
443
+ },
444
+ ],
445
+ action: async (params) => {
446
+ const booking = await this.bookingService.create(params);
447
+ return {
448
+ type: 'success',
449
+ message: `✅ Appointment booked for ${params.date}!`,
450
+ };
451
+ },
118
452
  });
453
+ }
454
+ }
455
+ ```
119
456
 
120
- this.assistantService.addCommand('remove background', () => {
121
- this.color.set('transparent');
122
- });
457
+ ### Example 2: E-Commerce Product Search
123
458
 
124
- this.assistantService.addCommand('sleep', () => {
125
- this.assistantService.stopListening();
459
+ ```typescript
460
+ this.assistant.addCommand({
461
+ command: 'search products',
462
+ parameters: [
463
+ { name: 'query', type: 'string', required: true },
464
+ {
465
+ name: 'category',
466
+ type: 'select',
467
+ required: false,
468
+ options: [
469
+ { label: 'Electronics', value: 'electronics' },
470
+ { label: 'Clothing', value: 'clothing' },
471
+ { label: 'Books', value: 'books' },
472
+ ],
473
+ },
474
+ {
475
+ name: 'minPrice',
476
+ type: 'number',
477
+ required: false,
478
+ },
479
+ ],
480
+ action: async (params) => {
481
+ const results = await this.productService.search(params);
482
+ this.router.navigate(['/products'], {
483
+ queryParams: { q: params.query },
126
484
  });
485
+ return `Found ${results.length} products matching "${params.query}"`;
486
+ },
487
+ });
488
+ ```
127
489
 
128
- // Start listening immediately
129
- this.assistantService.startListening();
130
- }
131
- }
490
+ ### Example 3: Form Validation with Error Handling
491
+
492
+ ```typescript
493
+ this.assistant.addCommand({
494
+ command: 'create account',
495
+ parameters: [
496
+ { name: 'email', type: 'string', required: true },
497
+ { name: 'password', type: 'string', required: true },
498
+ { name: 'age', type: 'number', required: true },
499
+ ],
500
+ action: async (params) => {
501
+ // Validation
502
+ if (params.age < 18) {
503
+ return {
504
+ type: 'error',
505
+ message: '❌ You must be 18 or older to create an account.',
506
+ };
507
+ }
508
+
509
+ if (!params.email.includes('@')) {
510
+ return {
511
+ type: 'error',
512
+ message: '❌ Please provide a valid email address.',
513
+ };
514
+ }
515
+
516
+ // Create account
517
+ try {
518
+ await this.authService.register(params);
519
+ return {
520
+ type: 'success',
521
+ message: '✅ Account created successfully! You can now log in.',
522
+ };
523
+ } catch (error) {
524
+ return {
525
+ type: 'error',
526
+ message: `❌ Registration failed: ${error.message}`,
527
+ };
528
+ }
529
+ },
530
+ });
132
531
  ```
133
532
 
134
533
  ---
135
534
 
136
- ### Step 3: Craft the UI 🎨
137
-
138
- Create a clean UI to showcase your assistant's magic.
535
+ ## TypeScript Support
139
536
 
140
- #### `app.component.html`
537
+ ### Full Type Definitions
141
538
 
142
- ```html
143
- <div
144
- class="content-container"
145
- [ngStyle]="{
146
- 'background-color': color(),
147
- 'padding': '20px'
148
- }"
149
- >
150
- <h1>🧙‍♂️ Angular Assistant Demo</h1>
151
- <p>Say the magic words to see the assistant in action:</p>
152
- <ul>
153
- <li>🟥 Say <strong>"background red"</strong> to make the background red.</li>
154
- <li>🔄 Say <strong>"remove background"</strong> to reset the background.</li>
155
- <li>😴 Say <strong>"sleep"</strong> to put the assistant to rest.</li>
156
- </ul>
157
- <p>🎨 Current Background: <strong>{{ color() }}</strong></p>
158
- </div>
539
+ ```typescript
540
+ import { AssistantCommand, InteractiveResponse } from '@foisit/core';
541
+
542
+ // Type-safe command definition
543
+ const myCommand: AssistantCommand = {
544
+ command: 'update settings',
545
+ description: 'Update user preferences',
546
+ parameters: [
547
+ {
548
+ name: 'theme',
549
+ type: 'select',
550
+ required: true,
551
+ options: [
552
+ { label: 'Light', value: 'light' },
553
+ { label: 'Dark', value: 'dark' },
554
+ ],
555
+ },
556
+ ],
557
+ action: async (params: { theme: string }): Promise<InteractiveResponse> => {
558
+ await this.settingsService.update(params.theme);
559
+ return {
560
+ type: 'success',
561
+ message: `Theme updated to ${params.theme}`,
562
+ };
563
+ },
564
+ };
159
565
  ```
160
566
 
161
567
  ---
162
568
 
163
- ### Step 4: Run the App 🏃
569
+ ## Best Practices
164
570
 
165
- Start your Angular app and watch the magic happen! ✨
571
+ ### 1. Command Naming
166
572
 
167
- ```bash
168
- ng serve
573
+ ✅ **Good:**
574
+
575
+ - "create user"
576
+ - "delete account"
577
+ - "export report"
578
+
579
+ ❌ **Avoid:**
580
+
581
+ - "CreateUser" (not natural)
582
+ - "usr_del" (not descriptive)
583
+ - "do the thing" (too vague)
584
+
585
+ ### 2. Descriptions
586
+
587
+ Always provide clear descriptions for AI matching:
588
+
589
+ ```typescript
590
+ {
591
+ command: 'reset password',
592
+ description: 'Reset the user password and send recovery email',
593
+ // AI can match: "forgot my password", "can't log in", etc.
594
+ }
595
+ ```
596
+
597
+ ### 3. Error Handling
598
+
599
+ Return user-friendly error messages:
600
+
601
+ ```typescript
602
+ action: async (params) => {
603
+ try {
604
+ await this.api.call(params);
605
+ return { type: 'success', message: '✅ Done!' };
606
+ } catch (error) {
607
+ return {
608
+ type: 'error',
609
+ message: `❌ Something went wrong: ${error.message}`,
610
+ };
611
+ }
612
+ };
613
+ ```
614
+
615
+ ### 4. Use Signals for Reactive State
616
+
617
+ ```typescript
618
+ import { signal } from '@angular/core';
619
+
620
+ export class MyComponent {
621
+ theme = signal<'light' | 'dark'>('light');
622
+
623
+ constructor(private assistant: AssistantService) {
624
+ this.assistant.addCommand('toggle theme', () => {
625
+ const newTheme = this.theme() === 'light' ? 'dark' : 'light';
626
+ this.theme.set(newTheme);
627
+ return `Theme switched to ${newTheme}`;
628
+ });
629
+ }
630
+ }
169
631
  ```
170
632
 
171
633
  ---
172
634
 
173
- ## 🛠️ API Reference
635
+ ## Testing
174
636
 
175
- ### `AssistantConfig`
637
+ ### Unit Testing Commands
638
+
639
+ ```typescript
640
+ import { TestBed } from '@angular/core/testing';
641
+ import { AssistantService } from '@foisit/angular-wrapper';
176
642
 
177
- Configure your assistant's behavior with this object.
643
+ describe('AssistantService', () => {
644
+ let service: AssistantService;
178
645
 
179
- | Property | Type | Description |
180
- | ------------------- | -------- | ------------------------------------------------- |
181
- | `activationCommand` | `string` | The keyword to wake the assistant. |
182
- | `fallbackResponse` | `string` | The message when a command isn’t recognized. |
183
- | `commands` | `Array` | A list of `{ command: string, action: Function }` |
646
+ beforeEach(() => {
647
+ TestBed.configureTestingModule({
648
+ imports: [AssistantModule.forRoot({ commands: [] })],
649
+ });
650
+ service = TestBed.inject(AssistantService);
651
+ });
652
+
653
+ it('should add and execute command', () => {
654
+ let executed = false;
655
+ service.addCommand('test', () => {
656
+ executed = true;
657
+ });
658
+
659
+ // Trigger command execution
660
+ service.toggle();
661
+ // Test execution...
662
+
663
+ expect(executed).toBe(true);
664
+ });
665
+ });
666
+ ```
184
667
 
185
668
  ---
186
669
 
187
- ### 🔑 Service Methods
670
+ ## Related Packages
188
671
 
189
- | Method | Description |
190
- | ---------------- | --------------------------------------- |
191
- | `addCommand` | Dynamically add a new command. |
192
- | `removeCommand` | Remove an existing command dynamically. |
193
- | `startListening` | Start listening for voice commands. |
194
- | `stopListening` | Stop listening for voice commands. |
672
+ - **[@foisit/core](../core)** - Core engine (auto-installed)
673
+ - **[@foisit/react-wrapper](../react-wrapper)** - React integration
674
+ - **[@foisit/vue-wrapper](../vue-wrapper)** - Vue integration
195
675
 
196
676
  ---
197
677
 
198
- ## 🌟 Features
678
+ ## Troubleshooting
199
679
 
200
- - 🧩 **Dynamic Commands**: Add or remove commands on the fly.
201
- - 🎨 **Visual Feedback**: Show visual cues when the assistant is active.
202
- - 🚀 **Easy Integration**: Integrate the assistant with just a few lines of code.
203
- - 🗣️ **Voice Feedback**: The assistant can respond with voice feedback.
204
- - 🔄 **Double Activation**: The assistant can be activated with a double-click and also put to sleep with a double-click when active.
680
+ ### Assistant not appearing
681
+
682
+ Ensure `AssistantModule.forRoot()` is imported in your app configuration and double-tap the floating button.
683
+
684
+ ### Commands not executing
685
+
686
+ Check browser console for errors. Ensure `action` functions are returning values or promises.
687
+
688
+ ### TypeScript errors
689
+
690
+ Make sure you're using Angular 17+ and have `@angular/core` installed.
691
+
692
+ ---
693
+
694
+ ## License
695
+
696
+ MIT © [Foisit](https://github.com/boluwatifee4/foisit)
205
697
 
206
698
  ---
207
699
 
208
- ## 🤝 Contributing
700
+ ## Contributing
209
701
 
210
- Want to make the assistant even better? PRs are welcomed! 🙌
702
+ Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) first.
211
703
 
212
704
  ---
213
705
 
214
- ## 📄 License
706
+ ## Support
707
+
708
+ - Email: support@foisit.com
709
+ - Discord: [Join our community](https://discord.gg/foisit)
710
+ - Issues: [GitHub Issues](https://github.com/boluwatifee4/foisit/issues)
711
+
712
+ ---
215
713
 
216
- This library is licensed under the MIT License.
714
+ **Made by the Foisit Team**
@@ -0,0 +1,288 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Component, Inject, Injectable, NgModule } from '@angular/core';
3
+ import { CommonModule } from '@angular/common';
4
+ import { CommandHandler, FallbackHandler, VoiceProcessor, TextToSpeech, StateManager, GestureHandler, OverlayManager } from '@foisit/core';
5
+
6
+ class AngularWrapperComponent {
7
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AngularWrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.0.7", type: AngularWrapperComponent, isStandalone: true, selector: "lib-angular-wrapper", ngImport: i0, template: "<p>AngularWrapper works!</p>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
9
+ }
10
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AngularWrapperComponent, decorators: [{
11
+ type: Component,
12
+ args: [{ selector: 'lib-angular-wrapper', imports: [CommonModule], template: "<p>AngularWrapper works!</p>\n" }]
13
+ }] });
14
+
15
+ class AssistantService {
16
+ config;
17
+ commandHandler;
18
+ fallbackHandler;
19
+ voiceProcessor;
20
+ textToSpeech;
21
+ stateManager;
22
+ lastProcessedInput = '';
23
+ processingLock = false;
24
+ isActivated = false; // Tracks activation status
25
+ gestureHandler;
26
+ overlayManager;
27
+ defaultIntroMessage = 'How can I help you?';
28
+ constructor(config) {
29
+ this.config = config;
30
+ // Pass enableSmartIntent (default true) and potential apiKey (if we add it to config later)
31
+ this.commandHandler = new CommandHandler({
32
+ enableSmartIntent: this.config.enableSmartIntent !== false,
33
+ intentEndpoint: this.config.intentEndpoint,
34
+ });
35
+ this.fallbackHandler = new FallbackHandler();
36
+ this.voiceProcessor = new VoiceProcessor();
37
+ this.textToSpeech = new TextToSpeech();
38
+ this.stateManager = new StateManager();
39
+ this.gestureHandler = new GestureHandler();
40
+ this.overlayManager = new OverlayManager({
41
+ floatingButton: this.config.floatingButton,
42
+ inputPlaceholder: this.config.inputPlaceholder,
43
+ });
44
+ // Setup commands from config
45
+ this.config.commands.forEach((cmd) => this.commandHandler.addCommand(cmd));
46
+ // Setup fallback response
47
+ if (this.config.fallbackResponse) {
48
+ this.fallbackHandler.setFallbackMessage(this.config.fallbackResponse);
49
+ }
50
+ // Voice disabled for text-first pivot
51
+ // this.startListening();
52
+ // Setup double-tap/double-click listener
53
+ this.gestureHandler.setupDoubleTapListener(() => this.toggle());
54
+ // Register global callbacks for floating button
55
+ this.overlayManager.registerCallbacks(async (input) => {
56
+ if (typeof input === 'string') {
57
+ this.overlayManager.addMessage(input, 'user');
58
+ }
59
+ else if (input && typeof input === 'object') {
60
+ const label = this.extractUserLabel(input);
61
+ if (label) {
62
+ this.overlayManager.addMessage(label, 'user');
63
+ }
64
+ }
65
+ await this.handleCommand(input);
66
+ }, () => console.log('AssistantService: Overlay closed.'));
67
+ }
68
+ /** Start listening for activation and commands */
69
+ startListening() {
70
+ // Voice is currently disabled (text-only mode)
71
+ console.log('AssistantService: Voice is disabled; startListening() is a no-op.');
72
+ return;
73
+ /*
74
+ this.voiceProcessor.startListening(async (transcript: string) => {
75
+ if (this.processingLock) return;
76
+
77
+ const normalizedTranscript = transcript.toLowerCase().trim();
78
+
79
+ // Skip repeated or irrelevant inputs
80
+ if (
81
+ !normalizedTranscript ||
82
+ normalizedTranscript.length < 3 ||
83
+ normalizedTranscript === this.lastProcessedInput
84
+ ) {
85
+ console.log('AssistantService: Ignoring irrelevant input.');
86
+ return;
87
+ }
88
+
89
+ this.lastProcessedInput = normalizedTranscript;
90
+
91
+ if (!this.isActivated) {
92
+ await this.processActivation(normalizedTranscript);
93
+ return;
94
+ }
95
+
96
+ const isFallbackOrIntroMessage =
97
+ normalizedTranscript === this.config.fallbackResponse?.toLowerCase() ||
98
+ normalizedTranscript === this.config.introMessage?.toLowerCase() ||
99
+ normalizedTranscript === this.defaultIntroMessage.toLowerCase();
100
+
101
+ if (!isFallbackOrIntroMessage) {
102
+ await this.handleCommand(normalizedTranscript);
103
+ } else {
104
+ console.log('AssistantService: Ignoring fallback or intro message.');
105
+ }
106
+
107
+ // Throttle input processing to prevent rapid feedback
108
+ this.processingLock = true;
109
+ setTimeout(() => (this.processingLock = false), 1000);
110
+ });
111
+ */
112
+ }
113
+ /** Stop listening for voice input */
114
+ stopListening() {
115
+ // Voice is currently disabled (text-only mode)
116
+ console.log('AssistantService: Voice is disabled; stopListening() is a no-op.');
117
+ this.isActivated = false;
118
+ }
119
+ /** Reset activation state so the next activation flow can occur. */
120
+ reactivate() {
121
+ console.log('AssistantService: Reactivating assistant...');
122
+ this.isActivated = false;
123
+ try {
124
+ this.startListening();
125
+ }
126
+ catch {
127
+ // no-op
128
+ }
129
+ }
130
+ /** Process activation command */
131
+ async processActivation(transcript) {
132
+ const activationCmd = this.config.activationCommand?.toLowerCase();
133
+ // If no activation command is set, we can't activate via voice
134
+ if (!activationCmd)
135
+ return;
136
+ if (transcript === activationCmd) {
137
+ console.log('AssistantService: Activation matched.');
138
+ this.isActivated = true;
139
+ this.textToSpeech.speak(this.config.introMessage || this.defaultIntroMessage);
140
+ // this.stateManager.setState('listening'); // DISABLED - no gradient animation
141
+ }
142
+ else {
143
+ console.log('AssistantService: Activation command not recognized.');
144
+ }
145
+ }
146
+ /** Handle recognized commands */
147
+ async handleCommand(input) {
148
+ this.overlayManager.showLoading();
149
+ let response;
150
+ try {
151
+ response = await this.commandHandler.executeCommand(input);
152
+ }
153
+ finally {
154
+ this.overlayManager.hideLoading();
155
+ }
156
+ const originalText = typeof input === 'string' ? input : undefined;
157
+ this.processResponse(response, originalText);
158
+ }
159
+ /**
160
+ * Cleanup resources
161
+ */
162
+ destroy() {
163
+ this.voiceProcessor.stopListening();
164
+ this.gestureHandler.destroy();
165
+ this.overlayManager.destroy();
166
+ }
167
+ /** Unified response processing */
168
+ processResponse(response, originalText) {
169
+ if (response.type === 'error' && originalText) {
170
+ this.fallbackHandler.handleFallback(originalText);
171
+ this.overlayManager.addMessage(this.fallbackHandler.getFallbackMessage(), 'system');
172
+ return;
173
+ }
174
+ if (response.type === 'form' && response.fields) {
175
+ this.overlayManager.addForm(response.message, response.fields, async (data) => {
176
+ this.overlayManager.showLoading();
177
+ let nextReponse;
178
+ try {
179
+ nextReponse = await this.commandHandler.executeCommand(data);
180
+ }
181
+ finally {
182
+ this.overlayManager.hideLoading();
183
+ }
184
+ this.processResponse(nextReponse);
185
+ });
186
+ return;
187
+ }
188
+ if ((response.type === 'ambiguous' || response.type === 'confirm') && response.options) {
189
+ if (response.message) {
190
+ this.overlayManager.addMessage(response.message, 'system');
191
+ }
192
+ this.overlayManager.addOptions(response.options);
193
+ return;
194
+ }
195
+ if (response.message) {
196
+ this.overlayManager.addMessage(response.message, 'system');
197
+ }
198
+ }
199
+ /** Add a command dynamically (supports string or rich object) */
200
+ addCommand(commandOrObj, action) {
201
+ if (typeof commandOrObj === 'string') {
202
+ console.log(`AssistantService: Adding command "${commandOrObj}".`);
203
+ }
204
+ else {
205
+ console.log(`AssistantService: Adding rich command "${commandOrObj.command}".`);
206
+ }
207
+ this.commandHandler.addCommand(commandOrObj, action);
208
+ }
209
+ /** Remove a command dynamically */
210
+ removeCommand(command) {
211
+ console.log(`AssistantService: Removing command "${command}".`);
212
+ this.commandHandler.removeCommand(command);
213
+ }
214
+ /** Get all registered commands */
215
+ getCommands() {
216
+ return this.commandHandler.getCommands();
217
+ }
218
+ /** Toggle the assistant overlay */
219
+ toggle(onSubmit, onClose) {
220
+ console.log('AssistantService: Toggling overlay...');
221
+ this.overlayManager.toggle(async (input) => {
222
+ if (typeof input === 'string') {
223
+ this.overlayManager.addMessage(input, 'user');
224
+ }
225
+ else if (input && typeof input === 'object') {
226
+ const label = this.extractUserLabel(input);
227
+ if (label) {
228
+ this.overlayManager.addMessage(label, 'user');
229
+ }
230
+ }
231
+ if (onSubmit)
232
+ onSubmit(input);
233
+ await this.handleCommand(input);
234
+ }, () => {
235
+ console.log('AssistantService: Overlay closed.');
236
+ if (onClose)
237
+ onClose();
238
+ });
239
+ }
240
+ extractUserLabel(payload) {
241
+ const label = payload['label'];
242
+ if (typeof label === 'string' && label.trim()) {
243
+ return label.trim();
244
+ }
245
+ const commandId = payload['commandId'];
246
+ if (typeof commandId === 'string' && commandId.trim()) {
247
+ return commandId.trim();
248
+ }
249
+ return null;
250
+ }
251
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantService, deps: [{ token: 'ASSISTANT_CONFIG' }], target: i0.ɵɵFactoryTarget.Injectable });
252
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantService, providedIn: 'root' });
253
+ }
254
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantService, decorators: [{
255
+ type: Injectable,
256
+ args: [{
257
+ providedIn: 'root',
258
+ }]
259
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
260
+ type: Inject,
261
+ args: ['ASSISTANT_CONFIG']
262
+ }] }] });
263
+
264
+ class AssistantModule {
265
+ static forRoot(config) {
266
+ return {
267
+ ngModule: AssistantModule,
268
+ providers: [
269
+ { provide: 'ASSISTANT_CONFIG', useValue: config },
270
+ AssistantService,
271
+ ],
272
+ };
273
+ }
274
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
275
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.0.7", ngImport: i0, type: AssistantModule });
276
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantModule });
277
+ }
278
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantModule, decorators: [{
279
+ type: NgModule,
280
+ args: [{}]
281
+ }] });
282
+
283
+ /**
284
+ * Generated bundle index. Do not edit.
285
+ */
286
+
287
+ export { AngularWrapperComponent, AssistantModule, AssistantService };
288
+ //# sourceMappingURL=foisit-angular-wrapper.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foisit-angular-wrapper.mjs","sources":["../../../../libs/angular-wrapper/src/lib/angular-wrapper/angular-wrapper.component.ts","../../../../libs/angular-wrapper/src/lib/angular-wrapper/angular-wrapper.component.html","../../../../libs/angular-wrapper/src/lib/service/assistant.service.ts","../../../../libs/angular-wrapper/src/lib/assistant.module.ts","../../../../libs/angular-wrapper/src/foisit-angular-wrapper.ts"],"sourcesContent":["import { Component } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\n@Component({\n selector: 'lib-angular-wrapper',\n imports: [CommonModule],\n templateUrl: './angular-wrapper.component.html',\n styleUrl: './angular-wrapper.component.css',\n})\nexport class AngularWrapperComponent {}\n","<p>AngularWrapper works!</p>\n","import { Injectable, Inject } from '@angular/core';\nimport {\n CommandHandler,\n FallbackHandler,\n VoiceProcessor,\n StateManager,\n AssistantConfig,\n TextToSpeech,\n GestureHandler,\n OverlayManager,\n AssistantCommand,\n AssistantCommandParams,\n InteractiveResponse,\n} from '@foisit/core';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class AssistantService {\n private commandHandler: CommandHandler;\n private fallbackHandler: FallbackHandler;\n private voiceProcessor: VoiceProcessor;\n private textToSpeech: TextToSpeech;\n private stateManager: StateManager;\n private lastProcessedInput = '';\n private processingLock = false;\n private isActivated = false; // Tracks activation status\n private gestureHandler: GestureHandler;\n private overlayManager: OverlayManager;\n private defaultIntroMessage = 'How can I help you?';\n\n constructor(@Inject('ASSISTANT_CONFIG') private config: AssistantConfig) {\n // Pass enableSmartIntent (default true) and potential apiKey (if we add it to config later)\n this.commandHandler = new CommandHandler({\n enableSmartIntent: this.config.enableSmartIntent !== false,\n intentEndpoint: this.config.intentEndpoint,\n });\n this.fallbackHandler = new FallbackHandler();\n this.voiceProcessor = new VoiceProcessor();\n this.textToSpeech = new TextToSpeech();\n this.stateManager = new StateManager();\n this.gestureHandler = new GestureHandler();\n this.overlayManager = new OverlayManager({\n floatingButton: this.config.floatingButton,\n inputPlaceholder: this.config.inputPlaceholder,\n });\n\n // Setup commands from config\n this.config.commands.forEach((cmd) => this.commandHandler.addCommand(cmd));\n\n // Setup fallback response\n if (this.config.fallbackResponse) {\n this.fallbackHandler.setFallbackMessage(this.config.fallbackResponse);\n }\n\n // Voice disabled for text-first pivot\n // this.startListening();\n\n // Setup double-tap/double-click listener\n this.gestureHandler.setupDoubleTapListener(() => this.toggle());\n\n // Register global callbacks for floating button\n this.overlayManager.registerCallbacks(\n async (input) => {\n if (typeof input === 'string') {\n this.overlayManager.addMessage(input, 'user');\n } else if (input && typeof input === 'object') {\n const label = this.extractUserLabel(input as Record<string, unknown>);\n if (label) {\n this.overlayManager.addMessage(label, 'user');\n }\n }\n\n await this.handleCommand(input);\n },\n () => console.log('AssistantService: Overlay closed.')\n );\n }\n\n /** Start listening for activation and commands */\n startListening(): void {\n // Voice is currently disabled (text-only mode)\n console.log('AssistantService: Voice is disabled; startListening() is a no-op.');\n return;\n\n /*\n this.voiceProcessor.startListening(async (transcript: string) => {\n if (this.processingLock) return;\n\n const normalizedTranscript = transcript.toLowerCase().trim();\n\n // Skip repeated or irrelevant inputs\n if (\n !normalizedTranscript ||\n normalizedTranscript.length < 3 ||\n normalizedTranscript === this.lastProcessedInput\n ) {\n console.log('AssistantService: Ignoring irrelevant input.');\n return;\n }\n\n this.lastProcessedInput = normalizedTranscript;\n\n if (!this.isActivated) {\n await this.processActivation(normalizedTranscript);\n return;\n }\n\n const isFallbackOrIntroMessage =\n normalizedTranscript === this.config.fallbackResponse?.toLowerCase() ||\n normalizedTranscript === this.config.introMessage?.toLowerCase() ||\n normalizedTranscript === this.defaultIntroMessage.toLowerCase();\n\n if (!isFallbackOrIntroMessage) {\n await this.handleCommand(normalizedTranscript);\n } else {\n console.log('AssistantService: Ignoring fallback or intro message.');\n }\n\n // Throttle input processing to prevent rapid feedback\n this.processingLock = true;\n setTimeout(() => (this.processingLock = false), 1000);\n });\n */\n }\n\n /** Stop listening for voice input */\n stopListening(): void {\n // Voice is currently disabled (text-only mode)\n console.log('AssistantService: Voice is disabled; stopListening() is a no-op.');\n this.isActivated = false;\n }\n\n /** Reset activation state so the next activation flow can occur. */\n reactivate(): void {\n console.log('AssistantService: Reactivating assistant...');\n this.isActivated = false;\n try {\n this.startListening();\n } catch {\n // no-op\n }\n }\n\n /** Process activation command */\n private async processActivation(transcript: string): Promise<void> {\n const activationCmd = this.config.activationCommand?.toLowerCase();\n\n // If no activation command is set, we can't activate via voice\n if (!activationCmd) return;\n\n if (transcript === activationCmd) {\n console.log('AssistantService: Activation matched.');\n this.isActivated = true;\n this.textToSpeech.speak(\n this.config.introMessage || this.defaultIntroMessage\n );\n // this.stateManager.setState('listening'); // DISABLED - no gradient animation\n } else {\n console.log('AssistantService: Activation command not recognized.');\n }\n }\n\n /** Handle recognized commands */\n private async handleCommand(\n input: string | Record<string, unknown>\n ): Promise<void> {\n this.overlayManager.showLoading();\n let response: InteractiveResponse;\n try {\n response = await this.commandHandler.executeCommand(input);\n } finally {\n this.overlayManager.hideLoading();\n }\n\n const originalText = typeof input === 'string' ? input : undefined;\n this.processResponse(response, originalText);\n }\n\n /**\n * Cleanup resources\n */\n destroy(): void {\n this.voiceProcessor.stopListening();\n this.gestureHandler.destroy();\n this.overlayManager.destroy();\n }\n\n /** Unified response processing */\n private processResponse(\n response: InteractiveResponse,\n originalText?: string\n ): void {\n if (response.type === 'error' && originalText) {\n this.fallbackHandler.handleFallback(originalText);\n this.overlayManager.addMessage(this.fallbackHandler.getFallbackMessage(), 'system');\n return;\n }\n\n if (response.type === 'form' && response.fields) {\n this.overlayManager.addForm(\n response.message,\n response.fields,\n async (data: Record<string, unknown>) => {\n this.overlayManager.showLoading();\n let nextReponse: InteractiveResponse;\n try {\n nextReponse = await this.commandHandler.executeCommand(data);\n } finally {\n this.overlayManager.hideLoading();\n }\n this.processResponse(nextReponse);\n }\n );\n return;\n }\n\n if ((response.type === 'ambiguous' || response.type === 'confirm') && response.options) {\n if (response.message) {\n this.overlayManager.addMessage(response.message, 'system');\n }\n this.overlayManager.addOptions(response.options);\n return;\n }\n\n if (response.message) {\n this.overlayManager.addMessage(response.message, 'system');\n }\n }\n\n /** Add a command dynamically (supports string or rich object) */\n addCommand(\n commandOrObj: string | AssistantCommand,\n action?: (\n params?: AssistantCommandParams\n ) => Promise<string | InteractiveResponse | void> | void\n ): void {\n if (typeof commandOrObj === 'string') {\n console.log(`AssistantService: Adding command \"${commandOrObj}\".`);\n } else {\n console.log(\n `AssistantService: Adding rich command \"${commandOrObj.command}\".`\n );\n }\n this.commandHandler.addCommand(commandOrObj, action);\n }\n\n /** Remove a command dynamically */\n removeCommand(command: string): void {\n console.log(`AssistantService: Removing command \"${command}\".`);\n this.commandHandler.removeCommand(command);\n }\n\n /** Get all registered commands */\n getCommands(): string[] {\n return this.commandHandler.getCommands();\n }\n\n /** Toggle the assistant overlay */\n toggle(\n onSubmit?: (input: string | Record<string, unknown>) => void,\n onClose?: () => void\n ): void {\n console.log('AssistantService: Toggling overlay...');\n this.overlayManager.toggle(\n async (input) => {\n if (typeof input === 'string') {\n this.overlayManager.addMessage(input, 'user');\n } else if (input && typeof input === 'object') {\n const label = this.extractUserLabel(input);\n if (label) {\n this.overlayManager.addMessage(label, 'user');\n }\n }\n\n if (onSubmit) onSubmit(input);\n await this.handleCommand(input);\n },\n () => {\n console.log('AssistantService: Overlay closed.');\n if (onClose) onClose();\n }\n );\n }\n\n private extractUserLabel(payload: Record<string, unknown>): string | null {\n const label = payload['label'];\n if (typeof label === 'string' && label.trim()) {\n return label.trim();\n }\n\n const commandId = payload['commandId'];\n if (typeof commandId === 'string' && commandId.trim()) {\n return commandId.trim();\n }\n\n return null;\n }\n}\n","import { ModuleWithProviders, NgModule } from '@angular/core';\nimport { AssistantService } from './service/assistant.service';\nimport { AssistantConfig } from '@foisit/core';\n\n@NgModule({})\nexport class AssistantModule {\n static forRoot(config: AssistantConfig): ModuleWithProviders<AssistantModule> {\n return {\n ngModule: AssistantModule,\n providers: [\n { provide: 'ASSISTANT_CONFIG', useValue: config },\n AssistantService,\n ],\n };\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './lib/angular-wrapper/angular-wrapper.component';
2
+ export * from './lib/service/assistant.service';
3
+ export * from './lib/assistant.module';
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@foisit/angular-wrapper",
3
- "version": "1.2.0",
3
+ "version": "2.0.1",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^19.0.0",
6
6
  "@angular/core": "^19.0.0"
7
7
  },
8
8
  "sideEffects": false,
9
9
  "files": [
10
- "dist",
10
+ "fesm2022",
11
+ "esm2022",
12
+ "index.d.ts",
11
13
  "README.md"
12
14
  ],
13
15
  "description": "Foisit: Speak, and its done. A voice assistant library for angular apps",