Sitelet https://github.com/boluwatifee4/foisit/tree/main/libs/angular-wrapper
Skip to content

Latest commit

 

History

History

README.md

@foisit/angular-wrapper

npm version License: MIT

The AI-Powered Conversational Assistant for Angular Applications

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.

[!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.


Table of Contents


Features

  • Natural Language Understanding - AI-powered intent matching (proxied securely)
  • Smart Slot Filling - Auto-generates forms for missing parameters
  • Critical Action Protection - Built-in confirmation dialogs for dangerous operations
  • Programmatic UI Triggers - Direct command execution via runCommand()
  • Rich Markdown Rendering - Enhanced response formatting with headings, code, and links
  • Advanced File Validations - Comprehensive client-side file validation with size, type, and dimension checks
  • Premium UI - Glass or solid theme with dark/light mode support
  • Zero Backend Required - Secure proxy architecture keeps API keys server-side
  • Angular Native - Uses Dependency Injection, Signals, and RxJS
  • Type-Safe - Full TypeScript support with comprehensive types
  • Responsive - Works flawlessly on desktop and mobile

Installation

npm install @foisit/angular-wrapper

Peer Dependencies

{
  "@angular/core": "^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0",
  "@angular/common": "^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0"
}

Quick Start

Step 1: Import the Module

For Standalone Apps (Recommended)

// app.config.ts
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { AssistantModule } from '@foisit/angular-wrapper';

export const appConfig: ApplicationConfig = {
  providers: [
    importProvidersFrom(
      AssistantModule.forRoot({
        introMessage: 'Welcome! How can I assist you today?',
        enableSmartIntent: true,
        commands: [
          {
            command: 'navigate to profile',
            action: () => console.log('Navigating to profile...'),
          },
        ],
      })
    ),
  ],
};

For Module-Based Apps

// app.module.ts
import { NgModule } from '@angular/core';
import { AssistantModule } from '@foisit/angular-wrapper';

@NgModule({
  imports: [
    AssistantModule.forRoot({
      introMessage: 'Welcome! How can I assist you today?',
      commands: [
        /* your commands */
      ],
    }),
  ],
})
export class AppModule {}

Step 2: Use the Service

// my-component.ts
import { Component } from '@angular/core';
import { AssistantService } from '@foisit/angular-wrapper';

@Component({
  selector: 'app-my-component',
  template: ` <button (click)="openAssistant()">Open Assistant</button> `,
})
export class MyComponent {
  constructor(private assistant: AssistantService) {}

  openAssistant() {
    this.assistant.toggle();
  }
}

Core Concepts

1. Commands

Commands are the building blocks of your assistant. Each command represents an action users can trigger through natural language.

{
  command: 'delete account',
  description: 'Permanently delete user account',
  action: () => this.accountService.delete()
}

2. Parameters (Slot Filling)

Define parameters and Foisit will automatically generate forms to collect them:

{
  command: 'create user',
  description: 'Create a new user account',
  parameters: [
    { name: 'username', label: 'Username', type: 'string', required: true },
    { name: 'email', label: 'Email address', type: 'string', required: true },
    { name: 'age', label: 'Age', type: 'number', required: false }
  ],
  action: (params) => this.userService.create(params)
}

Enterprise-safe param collection controls

  • collectRequiredViaForm (default: true): when enabled, any missing/invalid required params are collected via a form—no conversational guessing.
  • allowAiParamExtraction (default: true): when false, AI-extracted params are ignored; the assistant will always prompt the user for required fields.

Example:

{
  command: 'secure create user',
  description: 'No AI guessing, form-only',
  collectRequiredViaForm: true,
  allowAiParamExtraction: false,
  parameters: [
    { name: 'fullName', type: 'string', required: true },
    { name: 'email', type: 'string', required: true },
    { name: 'age', type: 'number', required: true, min: 18 },
  ],
  action: (params) => this.userService.create(params),
}

Supported Parameter Types:

  • string - Text input
  • number - Numeric input
  • date - Date picker
  • select - Dropdown (static or async options)
    • new: include multiple: true to allow the user to choose several values; the result will be an array.
  • file - File upload input

Field label text

  • By default, the form label uses description (or falls back to name).
  • Set label on a parameter when you want explicit, user-friendly label text.

3. File Parameters

Tip: you can also call overlayManager.addOptions(..., { allowMultiple: true }) or use a select parameter with multiple: true when you want pill‑style choices that support many selections.

Collect files via the built-in form UI and receive them in your command action.

{
  command: 'upload file',
  description: 'Pick a file and return it to the action',
  parameters: [
    {
      name: 'attachment',
      type: 'file',
      required: true,
      accept: ['image/*', 'audio/*', 'video/*'],
      multiple: false,
      // delivery: 'file' | 'base64' (default: 'file')
      delivery: 'file',
    },
  ],
  action: async (params) => {
    const file = params?.attachment as File | undefined;
    if (!file) return { type: 'error', message: 'No file provided.' };
    return {
      type: 'success',
      message: `File received. Name: ${file.name}, Type: ${file.type || 'unknown'}, Size: ${file.size} bytes`,
    };
  },
}

FileParameter supports advanced validations:

  • maxFiles: Maximum number of files allowed (default: 1 for single, 10 for multiple)
  • maxSizeBytes: Maximum size per file in bytes
  • maxTotalBytes: Maximum total size for all files combined
  • maxDurationSec: Maximum duration for media files (audio/video) in seconds
  • maxWidth / maxHeight: Maximum dimensions for images in pixels
  • accept: Allowed MIME types or file extensions (e.g., ['image/*', '.pdf'])
  • multiple: Allow selecting multiple files
  • capture: Hint for mobile devices ('camera' or 'microphone')
  • delivery: How files are delivered to your action ('file' or 'base64')

Files are validated client-side before submission, with clear error messages for violations.

4. Rich Markdown Rendering

Assistant responses support rich markdown formatting for enhanced readability:

  • Headings: # H1 through ###### H6
  • Text Formatting: **bold**, *italic*, ~~strikethrough~~
  • Code: Inline code and code blocks with syntax highlighting
  • Links: [text](url) with safe external linking
  • Lists: Ordered and unordered lists
  • Line breaks and paragraphs

Markdown is automatically rendered in AI responses, while user messages remain plain text.

5. Critical Actions

Protect dangerous operations with automatic confirmation dialogs:

{
  command: 'delete all data',
  critical: true, // Requires confirmation
  description: 'Permanently delete all application data',
  action: async () => {
    await this.dataService.deleteAll();
    return 'All data deleted successfully.';
  }
}

6. Select Parameters (Static)

Provide predefined options:

{
  command: 'set theme',
  parameters: [{
    name: 'theme',
    type: 'select',
    options: [
      { label: 'Light Mode', value: 'light' },
      { label: 'Dark Mode', value: 'dark' },
      { label: 'Auto', value: 'auto' }
    ]
  }],
  action: (params) => this.themeService.set(params.theme)
}

7. Dynamic Select Parameters

Load options from APIs:

{
  command: 'assign to user',
  parameters: [{
    name: 'userId',
    type: 'select',
    getOptions: async () => {
      const users = await this.userService.getAll();
      return users.map(u => ({
        label: `${u.name} (${u.email})`,
        value: u.id
      }));
    }
  }],
  action: (params) => this.taskService.assign(params.userId)
}

API Reference

AssistantService

Injectable service for programmatic control:

Methods

toggle(onSubmit?, onClose?)

Opens or closes the assistant overlay.

// Basic usage
this.assistant.toggle();

// With callbacks
this.assistant.toggle(
  (userInput) => console.log('User said:', userInput),
  () => console.log('Assistant closed')
);
addCommand(command, action?)

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).

// Add a simple command
this.assistant.addCommand('refresh data', () => {
  this.dataService.refresh();
  return 'Data refreshed!';
});

// Add a command with full config
this.assistant.addCommand({
  command: 'export report',
  description: 'Export data as PDF',
  parameters: [
    {
      name: 'format',
      type: 'select',
      options: [
        { label: 'PDF', value: 'pdf' },
        { label: 'Excel', value: 'xlsx' },
      ],
    },
  ],
  action: async (params) => {
    await this.reportService.export(params.format);
    return `Report exported as ${params.format}`;
  },
});

Dynamic Updates (Add / Remove / Update commands at runtime) ✅

  • Use addCommand to register a new command or to replace an existing command's behavior (remove first if you want a clean replacement).
  • Use removeCommand(commandPhrase) to unregister a command. This is useful for temporary or context-specific commands.
  • Commands are in-memory only; to persist them across reloads, re-register on app startup (e.g., in an initialization hook).

Example — register a temporary command and clean it up in ngOnDestroy:

// component.ts
import { OnDestroy } from '@angular/core';

export class MyComponent implements OnDestroy {
  constructor(private assistant: AssistantService) {
    this.assistant.addCommand('temp action', () => 'Temporary action executed');
  }

  ngOnDestroy() {
    // Remove the temporary command when component unmounts
    this.assistant.removeCommand('temp action');
  }
}

Notes:

  • If a command has only optional params, consider returning a form InteractiveResponse to prompt the user when no params are provided.
  • Always remove transient commands in your cleanup lifecycle to avoid leaks and confusing UX.
removeCommand(commandPhrase)

Remove a registered command.

this.assistant.removeCommand('delete account');
getCommands()

Get list of all registered command names.

const commands = this.assistant.getCommands();
console.log('Available commands:', commands);
runCommand(options)

Programmatically trigger a command execution with optional parameters. This allows you to invoke commands directly from your code without user input.

// Basic usage - trigger a command by ID
this.assistant.runCommand({ commandId: 'refresh-data' });

// With parameters
this.assistant.runCommand({
  commandId: 'create-user',
  params: { name: 'John', email: 'john@example.com' },
});

// Open overlay and show the command invocation
this.assistant.runCommand({
  commandId: 'export-report',
  params: { format: 'pdf' },
  openOverlay: true,
  showInvocation: true,
});

Options:

  • commandId (required): The ID of the command to run
  • params (optional): Parameters to pass to the command action
  • openOverlay (optional): Whether to open the assistant overlay (default: false)
  • showInvocation (optional): Whether to display the command invocation in the chat (default: false)

Configuration Options

AssistantConfig

interface AssistantConfig {
  // Activation keyword (optional)
  activationCommand?: string;

  // Welcome message shown when assistant opens
  introMessage?: string;

  // Response for unrecognized inputs
  fallbackResponse?: string;

  // Enable AI-powered natural language understanding
  enableSmartIntent?: boolean;

  // Input field placeholder text
  inputPlaceholder?: string;

  // List of commands
  commands: AssistantCommand[];

  // Floating button configuration
  floatingButton?: {
    visible?: boolean;
    tooltip?: string;
    customHtml?: string;
    position?: { bottom: string; right: string };
  };

  // Theme mode: 'glass' (default) or 'solid'
  theme?: 'glass' | 'solid';

  // Custom colors for solid theme (ignored in glass mode)
  themeColors?: ThemeColors;
}

ThemeColors

Custom colors for solid theme mode:

interface ThemeColors {
  background?: string; // Background color (e.g., '#1e1e2e')
  text?: string; // Primary text color (e.g., '#ffffff')
  accent?: string; // Accent color for highlights (e.g., '#89b4fa' or a gradient)
  userBubbleBg?: string; // User message bubble background
  systemBubbleBg?: string; // System message bubble background
  border?: string; // Border color
  checkboxAccent?: string; // Checkbox accent color
  checkboxBorder?: string; // Checkbox border color
  checkboxCheck?: string; // Checkbox checkmark color
}

Theme Customization Example

AssistantModule.forRoot({
  commands: [...],
  // Use solid theme with custom colors
  theme: 'solid',
  themeColors: {
    background: '#1e1e2e',
    text: '#cdd6f4',
    accent: '#89b4fa',
    userBubbleBg: 'rgba(137, 180, 250, 0.2)',
    systemBubbleBg: 'rgba(255, 255, 255, 0.05)',
  },
})

Note: Glass theme (default) uses glassmorphism with blur effects and adapts to light/dark mode via prefers-color-scheme. Solid theme ignores system preferences and uses configured colors.


Advanced Usage

Example 1: Multi-Step Booking System

import { Component } from '@angular/core';
import { AssistantService } from '@foisit/angular-wrapper';

@Component({
  selector: 'app-booking',
  template: `<button (click)="setupBooking()">Enable Booking</button>`,
})
export class BookingComponent {
  constructor(private assistant: AssistantService, private bookingService: BookingService) {}

  setupBooking() {
    this.assistant.addCommand({
      command: 'book appointment',
      description: 'Schedule a new appointment',
      parameters: [
        {
          name: 'service',
          description: 'Type of service',
          type: 'select',
          required: true,
          getOptions: async () => {
            const services = await this.bookingService.getServices();
            return services.map((s) => ({
              label: s.name,
              value: s.id,
            }));
          },
        },
        {
          name: 'date',
          description: 'Preferred date',
          type: 'date',
          required: true,
        },
        {
          name: 'notes',
          description: 'Additional notes',
          type: 'string',
          required: false,
        },
      ],
      action: async (params) => {
        const booking = await this.bookingService.create(params);
        return {
          type: 'success',
          message: `✅ Appointment booked for ${params.date}!`,
        };
      },
    });
  }
}

Example 2: E-Commerce Product Search

this.assistant.addCommand({
  command: 'search products',
  parameters: [
    { name: 'query', type: 'string', required: true },
    {
      name: 'category',
      type: 'select',
      required: false,
      options: [
        { label: 'Electronics', value: 'electronics' },
        { label: 'Clothing', value: 'clothing' },
        { label: 'Books', value: 'books' },
      ],
    },
    {
      name: 'minPrice',
      type: 'number',
      required: false,
    },
  ],
  action: async (params) => {
    const results = await this.productService.search(params);
    this.router.navigate(['/products'], {
      queryParams: { q: params.query },
    });
    return `Found ${results.length} products matching "${params.query}"`;
  },
});

Example 3: Form Validation with Error Handling

this.assistant.addCommand({
  command: 'create account',
  parameters: [
    { name: 'email', type: 'string', required: true },
    { name: 'password', type: 'string', required: true },
    { name: 'age', type: 'number', required: true },
  ],
  action: async (params) => {
    // Validation
    if (params.age < 18) {
      return {
        type: 'error',
        message: '❌ You must be 18 or older to create an account.',
      };
    }

    if (!params.email.includes('@')) {
      return {
        type: 'error',
        message: '❌ Please provide a valid email address.',
      };
    }

    // Create account
    try {
      await this.authService.register(params);
      return {
        type: 'success',
        message: '✅ Account created successfully! You can now log in.',
      };
    } catch (error) {
      return {
        type: 'error',
        message: `❌ Registration failed: ${error.message}`,
      };
    }
  },
});

TypeScript Support

Full Type Definitions

// Type-safe command definition
const myCommand: AssistantCommand = {
  command: 'update settings',
  description: 'Update user preferences',
  parameters: [
    {
      name: 'theme',
      type: 'select',
      required: true,
      options: [
        { label: 'Light', value: 'light' },
        { label: 'Dark', value: 'dark' },
      ],
    },
  ],
  action: async (params: { theme: string }): Promise<InteractiveResponse> => {
    await this.settingsService.update(params.theme);
    return {
      type: 'success',
      message: `Theme updated to ${params.theme}`,
    };
  },
};

Best Practices

1. Command Naming

✅ Good:

  • "create user"
  • "delete account"
  • "export report"

❌ Avoid:

  • "CreateUser" (not natural)
  • "usr_del" (not descriptive)
  • "do the thing" (too vague)

2. Descriptions

Always provide clear descriptions for AI matching:

{
  command: 'reset password',
  description: 'Reset the user password and send recovery email',
  // AI can match: "forgot my password", "can't log in", etc.
}

3. Error Handling

Return user-friendly error messages:

action: async (params) => {
  try {
    await this.api.call(params);
    return { type: 'success', message: '✅ Done!' };
  } catch (error) {
    return {
      type: 'error',
      message: `❌ Something went wrong: ${error.message}`,
    };
  }
};

4. Use Signals for Reactive State

import { signal } from '@angular/core';

export class MyComponent {
  theme = signal<'light' | 'dark'>('light');

  constructor(private assistant: AssistantService) {
    this.assistant.addCommand('toggle theme', () => {
      const newTheme = this.theme() === 'light' ? 'dark' : 'light';
      this.theme.set(newTheme);
      return `Theme switched to ${newTheme}`;
    });
  }
}

Testing

Unit Testing Commands

import { TestBed } from '@angular/core/testing';
import { AssistantService } from '@foisit/angular-wrapper';

describe('AssistantService', () => {
  let service: AssistantService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [AssistantModule.forRoot({ commands: [] })],
    });
    service = TestBed.inject(AssistantService);
  });

  it('should add and execute command', () => {
    let executed = false;
    service.addCommand('test', () => {
      executed = true;
    });

    // Trigger command execution
    service.toggle();
    // Test execution...

    expect(executed).toBe(true);
  });
});

Related Packages


Troubleshooting

Assistant not appearing

Ensure AssistantModule.forRoot() is imported in your app configuration and double-tap the floating button.

Commands not executing

Check browser console for errors. Ensure action functions are returning values or promises.

TypeScript errors

Make sure you're using Angular 17+ and have @angular/core installed.


License

MIT © Foisit


Contributing

Contributions are welcome! Please read our Contributing Guide first.


Support


Made by the Foisit Team