├── logs
└── .keep
├── app
├── .gitignore
├── agents
│ ├── __init__.py
│ ├── models.py
│ └── controllers.py
├── commons
│ ├── __init__.py
│ ├── .gitignore
│ ├── error_codes.py
│ ├── logger.py
│ ├── build_response.py
│ └── utils.py
├── intents
│ ├── __init__.py
│ ├── models.py
│ └── controllers.py
├── nlu
│ ├── .gitignore
│ ├── classifiers
│ │ ├── __init__.py
│ │ ├── tf_intent_classifer.py
│ │ └── sklearn_intent_classifer.py
│ ├── __init__.py
│ ├── controllers.py
│ ├── tasks.py
│ └── entity_extractor.py
├── train
│ ├── __init__.py
│ └── controllers.py
├── endpoint
│ ├── __init__.py
│ ├── utils.py
│ └── controllers.py
├── entities
│ ├── __init__.py
│ ├── models.py
│ └── controllers.py
└── __init__.py
├── .python-version
├── frontend
├── src
│ ├── assets
│ │ ├── .gitkeep
│ │ ├── images
│ │ │ └── iky-logo.png
│ │ └── widget
│ │ │ ├── style.css
│ │ │ └── iky_widget.js
│ ├── app
│ │ ├── agent
│ │ │ ├── entity
│ │ │ │ ├── entity.component.scss
│ │ │ │ ├── entity.component.spec.ts
│ │ │ │ ├── entity.component.ts
│ │ │ │ └── entity.component.html
│ │ │ ├── intent
│ │ │ │ ├── intent.component.scss
│ │ │ │ ├── intent.component.ts
│ │ │ │ └── intent.component.html
│ │ │ ├── intents
│ │ │ │ ├── intents.component.scss
│ │ │ │ ├── intents.component.html
│ │ │ │ └── intents.component.ts
│ │ │ ├── entities
│ │ │ │ ├── entities.component.scss
│ │ │ │ ├── entities.component.spec.ts
│ │ │ │ ├── entities.component.html
│ │ │ │ └── entities.component.ts
│ │ │ ├── train
│ │ │ │ ├── train.component.scss
│ │ │ │ ├── train.component.html
│ │ │ │ └── train.component.ts
│ │ │ ├── settings
│ │ │ │ ├── settings.component.spec.ts
│ │ │ │ ├── settings.component.scss
│ │ │ │ ├── settings.component.html
│ │ │ │ └── settings.component.ts
│ │ │ ├── chat
│ │ │ │ ├── chat.component.html
│ │ │ │ ├── chat.component.scss
│ │ │ │ └── chat.component.ts
│ │ │ ├── agent-routing.module.ts
│ │ │ └── agent.module.ts
│ │ ├── app.component.html
│ │ ├── dashboard
│ │ │ ├── layout
│ │ │ │ ├── layout.component.scss
│ │ │ │ ├── layout.component.html
│ │ │ │ ├── layout.component.ts
│ │ │ │ └── layout.component.spec.ts
│ │ │ ├── sidebar
│ │ │ │ ├── sidebar.component.scss
│ │ │ │ ├── sidebar.component.ts
│ │ │ │ ├── sidebar.component.spec.ts
│ │ │ │ └── sidebar.component.html
│ │ │ └── dashboard.module.ts
│ │ ├── app.component.scss
│ │ ├── directives
│ │ │ ├── autofocus.directive.spec.ts
│ │ │ └── autofocus.directive.ts
│ │ ├── services
│ │ │ ├── chat.service.ts
│ │ │ ├── agents.service.ts
│ │ │ ├── training.service.ts
│ │ │ ├── intent-resolver.service.ts
│ │ │ ├── utils.service.ts
│ │ │ ├── intent.service.ts
│ │ │ └── entities.service.ts
│ │ ├── commons
│ │ │ └── commons.module.ts
│ │ ├── app.component.ts
│ │ ├── app-routing.module.ts
│ │ └── app.module.ts
│ ├── favicon.ico
│ ├── environments
│ │ ├── environment.prod.ts
│ │ ├── environment.docker.ts
│ │ ├── environment.test.ts
│ │ └── environment.ts
│ ├── typings.d.ts
│ ├── styles.scss
│ ├── tsconfig.app.json
│ ├── tsconfig.spec.json
│ ├── main.ts
│ ├── index.html
│ ├── test.ts
│ └── polyfills.ts
├── dist
│ ├── favicon.ico
│ ├── assets
│ │ ├── images
│ │ │ └── iky-logo.png
│ │ └── widget
│ │ │ ├── style.css
│ │ │ └── iky_widget.js
│ ├── index.html
│ ├── inline.2543f7f010b342baf7ef.bundle.js
│ └── 3rdpartylicenses.txt
├── e2e
│ ├── app.po.ts
│ ├── tsconfig.e2e.json
│ └── app.e2e-spec.ts
├── .editorconfig
├── tsconfig.json
├── .dockerignore
├── .gitignore
├── nginx
│ └── default.conf
├── protractor.conf.js
├── Dockerfile
├── karma.conf.js
├── README.md
├── .angular-cli.json
├── package.json
└── tslint.json
├── .gitignore
├── .vscode
└── settings.json
├── model_files
└── .gitignore
├── .dockerignore
├── .travis.yml
├── Procfile
├── run.py
├── tests
└── test_sample.py
├── requirements.txt
├── docker-compose.yml
├── Dockerfile
├── app.json
├── config.py
├── examples
├── python
│ └── app.py
├── ruby
│ └── request.rb
├── default_intents.json
├── nodejs
│ └── discordRequest.js
└── restaurant_search.json
├── Makefile
├── License.txt
├── manage.py
└── README.md
/logs/.keep:
--------------------------------------------------------------------------------
1 | *.*
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
--------------------------------------------------------------------------------
/app/agents/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/commons/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/intents/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/nlu/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
--------------------------------------------------------------------------------
/app/train/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
1 | 3.6.9
2 |
--------------------------------------------------------------------------------
/app/commons/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
--------------------------------------------------------------------------------
/app/endpoint/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/entities/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/nlu/classifiers/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/nlu/__init__.py:
--------------------------------------------------------------------------------
1 | import spacy
2 | spacy_tokenizer = spacy.load("en")
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | logs/*
3 | !logs/.keep
4 | *.pyc
5 | venv/
6 | env/
7 | .DS_Store
--------------------------------------------------------------------------------
/frontend/src/app/agent/entity/entity.component.scss:
--------------------------------------------------------------------------------
1 | .values{
2 | margin-top:20px;
3 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.pythonPath": "${workspaceFolder}/venv/bin/python"
3 | }
--------------------------------------------------------------------------------
/model_files/.gitignore:
--------------------------------------------------------------------------------
1 | *.model
2 | *.pkl
3 | *.hd5
4 | *.index
5 | *.meta
6 | checkpoint
7 | intent_*
--------------------------------------------------------------------------------
/frontend/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/ai-chatbot-framework/master/frontend/src/favicon.ico
--------------------------------------------------------------------------------
/frontend/dist/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/ai-chatbot-framework/master/frontend/dist/favicon.ico
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | logs/*
3 | !logs/.keep
4 | *.pyc
5 | venv/
6 | env/
7 | .DS_Store
8 | model_files/*.model
9 | frontend/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "2.7"
4 | install:
5 | - pip install -r requirements.txt
6 | script:
7 | - pytest
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | release: APPLICATION_ENV="Heroku" python manage.py migrate
2 | web: APPLICATION_ENV="Heroku" gunicorn -k gevent run:app
3 |
--------------------------------------------------------------------------------
/frontend/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/frontend/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | ikyBackend: "/"
4 | };
5 |
--------------------------------------------------------------------------------
/run.py:
--------------------------------------------------------------------------------
1 | from app import app
2 |
3 | if __name__ == '__main__':
4 | app.run(host='127.0.0.1', port=8080, debug=True, threaded=True)
5 |
--------------------------------------------------------------------------------
/frontend/src/environments/environment.docker.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | ikyBackend: "/gateway/"
4 | };
5 |
--------------------------------------------------------------------------------
/frontend/src/assets/images/iky-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/ai-chatbot-framework/master/frontend/src/assets/images/iky-logo.png
--------------------------------------------------------------------------------
/tests/test_sample.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | def f():
5 | return 4
6 |
7 |
8 | def test_function():
9 | assert f() == 4
10 |
--------------------------------------------------------------------------------
/frontend/dist/assets/images/iky-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/ai-chatbot-framework/master/frontend/dist/assets/images/iky-logo.png
--------------------------------------------------------------------------------
/frontend/src/environments/environment.test.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | ikyBackend: "http://localhost:8080/"
4 | };
5 |
--------------------------------------------------------------------------------
/frontend/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var module: NodeModule;
3 | interface NodeModule {
4 | id: string;
5 | }
6 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/layout/layout.component.scss:
--------------------------------------------------------------------------------
1 | mat-sidenav{
2 | min-width: 260px;
3 | max-width: 350px;
4 | }
5 | mat-sidenav{
6 | border-right: 1px solid #e7e7e7;
7 | }
--------------------------------------------------------------------------------
/frontend/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 | @import "~@angular/material/prebuilt-themes/indigo-pink.css";
3 |
4 |
5 | mat-card.container{
6 | margin:10px;
7 | }
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/sidebar/sidebar.component.scss:
--------------------------------------------------------------------------------
1 | .bottom{
2 | position: absolute;
3 | width: 100%;
4 | bottom: 0;
5 | height: 120px;
6 | left: 0;
7 | }
8 |
9 | .main-logo{
10 | border-bottom: 1px solid #efefef;
11 | }
--------------------------------------------------------------------------------
/frontend/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 | mat-spinner{
2 | position: fixed;
3 | z-index: 999;
4 | height: 2em;
5 | width: 2em;
6 | overflow: show;
7 | margin: auto;
8 | top: 0;
9 | left: 0;
10 | bottom: 0;
11 | right: 0;
12 | }
--------------------------------------------------------------------------------
/frontend/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, by, element } from 'protractor';
2 |
3 | export class AppPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('app-root h1')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "baseUrl": "./",
6 | "module": "es2015",
7 | "types": []
8 | },
9 | "exclude": [
10 | "test.ts",
11 | "**/*.spec.ts"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/src/app/directives/autofocus.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { AutofocusDirective } from './autofocus.directive';
2 |
3 | describe('AutofocusDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new AutofocusDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/layout/layout.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/frontend/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/frontend/e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/e2e",
5 | "baseUrl": "./",
6 | "module": "commonjs",
7 | "target": "es5",
8 | "types": [
9 | "jasmine",
10 | "jasminewd2",
11 | "node"
12 | ]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/intent/intent.component.scss:
--------------------------------------------------------------------------------
1 | mat-card{
2 | margin: 10px;
3 | .full-width {
4 | width: 100%;
5 | }
6 | .parameter{
7 | padding:15px;
8 | margin-bottom: 10px;
9 | }
10 | pre{
11 | background-color:#efefef;
12 | padding: 5px;
13 | }
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/app/agents/models.py:
--------------------------------------------------------------------------------
1 | from mongoengine.fields import DictField
2 | from mongoengine.fields import Document
3 | from mongoengine.fields import StringField
4 |
5 |
6 | class Bot(Document):
7 | name = StringField(max_length=100, required=True, unique=True)
8 | config = DictField(required=True, default={
9 | "confidence_threshold": .70
10 | })
11 |
--------------------------------------------------------------------------------
/frontend/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AppPage } from './app.po';
2 |
3 | describe('frontend App', () => {
4 | let page: AppPage;
5 |
6 | beforeEach(() => {
7 | page = new AppPage();
8 | });
9 |
10 | it('should display welcome message', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toEqual('Welcome to app!');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/layout/layout.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-layout',
5 | templateUrl: './layout.component.html',
6 | styleUrls: ['./layout.component.scss']
7 | })
8 | export class LayoutComponent implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit() {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/sidebar/sidebar.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-sidebar',
5 | templateUrl: './sidebar.component.html',
6 | styleUrls: ['./sidebar.component.scss']
7 | })
8 | export class SidebarComponent implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit() {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "baseUrl": "./",
6 | "module": "commonjs",
7 | "types": [
8 | "jasmine",
9 | "node"
10 | ]
11 | },
12 | "files": [
13 | "test.ts"
14 | ],
15 | "include": [
16 | "**/*.spec.ts",
17 | "**/*.d.ts"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/app/directives/autofocus.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, AfterViewInit, ElementRef } from '@angular/core';
2 |
3 | @Directive({
4 | selector: '[appAutofocus]'
5 | })
6 | export class AutofocusDirective implements AfterViewInit {
7 |
8 | constructor(private el: ElementRef) {
9 | }
10 |
11 | ngAfterViewInit() {
12 | this.el.nativeElement.focus();
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/intents/intents.component.scss:
--------------------------------------------------------------------------------
1 | mat-card{
2 | margin:10px;
3 | .intent-container{
4 | border: 1px solid rgb(221, 221, 221);
5 | border-radius: 5px;
6 | padding:10px;
7 | margin-bottom: 8px;
8 | }
9 | }
10 | .header{
11 | margin: 10px;
12 | }
13 | .example-spacer {
14 | flex: 1 1 auto;
15 | }
16 | .margin-left{
17 | margin-left:5px;
18 | }
--------------------------------------------------------------------------------
/app/nlu/controllers.py:
--------------------------------------------------------------------------------
1 | from flask import Blueprint
2 |
3 | from app.commons import build_response
4 | from app.nlu.tasks import train_models
5 |
6 | nlu = Blueprint('nlu_blueprint', __name__, url_prefix='/nlu')
7 |
8 |
9 | @nlu.route('/build_models', methods=['POST'])
10 | def build_models():
11 | """
12 | Build Intent classification and NER Models
13 | :return:
14 | """
15 | train_models()
16 | return build_response.sent_ok()
17 |
--------------------------------------------------------------------------------
/frontend/src/app/services/chat.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient } from '@angular/common/http';
3 | import { environment } from '../../environments/environment';
4 | @Injectable()
5 | export class ChatService {
6 |
7 | constructor(private http:HttpClient) { }
8 |
9 | converse(intent, botId = 'default') {
10 | return this.http.post(environment.ikyBackend + `api/v1`, intent).toPromise();
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | import 'hammerjs';
8 |
9 | if (environment.production) {
10 | enableProdMode();
11 | }
12 |
13 | platformBrowserDynamic().bootstrapModule(AppModule)
14 | .catch(err => console.log(err));
15 |
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "outDir": "./dist/out-tsc",
5 | "sourceMap": true,
6 | "declaration": false,
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "target": "es5",
11 | "typeRoots": [
12 | "node_modules/@types"
13 | ],
14 | "lib": [
15 | "es2017",
16 | "dom"
17 | ]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Frontend
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/frontend/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `.angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false,
8 | ikyBackend: "http://localhost:8080/"
9 | };
10 |
--------------------------------------------------------------------------------
/app/commons/error_codes.py:
--------------------------------------------------------------------------------
1 | emptyInput = {"errorCode": 601, "description": "empty input"}
2 | InvalidInput = {"errorCode": 602, "description": "Invalid input"}
3 |
4 | UnidentifiedIntent = {
5 | "errorCode": 701,
6 | "description": "Can't identify the intent"}
7 | NotEnoughData = {
8 | "errorCode": 702,
9 | "description": "Not enough Training Data. Please Add more intents"}
10 |
11 | UnableToextractentities = {"errorCode": 801,
12 | "description": "Unable extract entities"}
13 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 |
2 | Flask==0.12.4
3 | flask-mongoengine==0.9.5
4 | Flask-Script==2.0.6
5 | Flask-Cors==3.0.3
6 | blinker==1.4
7 |
8 | bs4==0.0.1
9 | html2text==2017.10.4
10 | simplejson==3.13.2
11 | pytest==3.4.2
12 |
13 | scipy==1.4.1
14 | numpy==1.16.0
15 | pandas==0.25.2
16 |
17 | spacy==2.2.2
18 |
19 | scikit-learn==0.19.1
20 | cloudpickle==0.5.2
21 | h5py==2.7.1
22 | tensorflow==1.15.2
23 |
24 | parsedatetime==2.4
25 | python-crfsuite==0.9.5
26 | python-dateutil==2.6.1
27 | requests==2.18.4
28 |
29 | gunicorn==19.7.1
30 | gevent==1.2.2
--------------------------------------------------------------------------------
/frontend/src/app/agent/entities/entities.component.scss:
--------------------------------------------------------------------------------
1 | mat-card{
2 | margin: 10px;
3 | .full-width {
4 | width: 100%;
5 | }
6 | .entities{
7 |
8 | margin: 10px;
9 | .entity{
10 | display: flex;
11 | align-items: center;
12 |
13 | padding:5px;
14 | margin-bottom: 3px;
15 | cursor: pointer;
16 | }
17 | }
18 | .entity-container{
19 | border-bottom: 1px solid lightgray;
20 | padding:10px;
21 | }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/frontend/src/app/services/agents.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient } from '@angular/common/http';
3 | import { environment } from '../../environments/environment';
4 |
5 | @Injectable()
6 | export class AgentsService {
7 |
8 | constructor(public http: HttpClient) { }
9 |
10 | get_config() {
11 | return this.http.get(environment.ikyBackend + `agents/default/config`).toPromise();
12 | }
13 | update_config(data) {
14 | return this.http.put(environment.ikyBackend + `agents/default/config`, data).toPromise();
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/frontend/src/app/commons/commons.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule,ModuleWithProviders } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import {UtilsService} from '../services/utils.service'
4 |
5 | @NgModule({
6 | imports: [
7 | CommonModule
8 | ],
9 | declarations: [],
10 | providers:[UtilsService]
11 | })
12 | export class CommonsModule {
13 |
14 | constructor(utilsService: UtilsService){
15 |
16 | }
17 | static forRoot(): ModuleWithProviders {
18 | return {
19 | ngModule: CommonsModule,
20 | providers: [UtilsService]
21 | };
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | iky_backend:
4 | container_name: iky_backend
5 | build: .
6 | environment:
7 | APPLICATION_ENV: Production
8 | depends_on:
9 | - mongodb
10 |
11 | iky_gateway:
12 | container_name: iky_gateway
13 | build: frontend/
14 | ports:
15 | - "8080:80"
16 | depends_on:
17 | - iky_backend
18 |
19 | mongodb:
20 | container_name: mongodb
21 | image: mongo
22 | hostname: mongodb
23 | ports:
24 | - "27017:27017"
25 | volumes:
26 | - mongodbdata:/data
27 |
28 | volumes:
29 | mongodbdata:
30 |
--------------------------------------------------------------------------------
/frontend/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import {UtilsService} from './services/utils.service'
3 |
4 | @Component({
5 | selector: 'app-root',
6 | templateUrl: './app.component.html',
7 | styleUrls: ['./app.component.scss']
8 | })
9 | export class AppComponent implements OnInit {
10 | title = 'app';
11 | showLoader: boolean;
12 |
13 | constructor(
14 | private coreService: UtilsService) {
15 | }
16 | ngOnInit() {
17 | this.coreService.status.subscribe((val: boolean) => {
18 | console.log("value changed")
19 | this.showLoader = val;
20 | });
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.6-slim
2 |
3 | # Install common libraries
4 | RUN apt-get update -qq \
5 | && apt-get install -y --no-install-recommends \
6 | # required by psycopg2 at build and runtime
7 | libpq-dev \
8 | && apt-get autoremove -y
9 |
10 | # Install all required build libraries
11 | RUN apt-get update -qq \
12 | && apt-get install -y --no-install-recommends \
13 | build-essential
14 |
15 | WORKDIR /usr/src/app
16 |
17 | COPY requirements.txt ./
18 | RUN pip install --no-cache-dir -r requirements.txt
19 |
20 | RUN python -m spacy download en; python
21 |
22 | EXPOSE 8080
23 |
24 | COPY . .
25 |
26 | CMD ["make","run_docker"]
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "IKY - AI chatbot framework",
3 | "description": "A python chatbot framework with Natural Language Understanding and Artificial Intelligence.",
4 | "logo": "https://raw.githubusercontent.com/alfredfrancis/ai-chatbot-framework/master/app/static/images/iky.png",
5 | "image": "heroku/python",
6 | "repository": "https://github.com/alfredfrancis/ai-chatbot-framework/",
7 | "keywords": ["python", "flask","spacy","scikit-learn"],
8 | "addons": [ "mongolab" ],
9 | "buildpacks": [
10 | {
11 | "url": "https://github.com/heroku/exec-buildpack"
12 | },
13 | {
14 | "url": "heroku/python"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/frontend/.dockerignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist
3 | /dist-server
4 | /tmp
5 | /out-tsc
6 |
7 | # dependencies
8 | /node_modules
9 |
10 | # IDEs and editors
11 | /.idea
12 | .project
13 | .classpath
14 | .c9/
15 | *.launch
16 | .settings/
17 | *.sublime-workspace
18 |
19 | # IDE - VSCode
20 | .vscode/*
21 | !.vscode/settings.json
22 | !.vscode/tasks.json
23 | !.vscode/launch.json
24 | !.vscode/extensions.json
25 |
26 | # misc
27 | /.sass-cache
28 | /connect.lock
29 | /coverage
30 | /libpeerconnection.log
31 | npm-debug.log
32 | yarn-error.log
33 | testem.log
34 | /typings
35 |
36 | # e2e
37 | /e2e/*.js
38 | /e2e/*.map
39 |
40 | # System Files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/frontend/dist/index.html:
--------------------------------------------------------------------------------
1 | Frontend
--------------------------------------------------------------------------------
/app/commons/logger.py:
--------------------------------------------------------------------------------
1 | import logging.handlers
2 |
3 | import json_log_formatter
4 |
5 | formatter = json_log_formatter.JSONFormatter()
6 |
7 | logger = logging.getLogger("query")
8 | logger.setLevel(logging.INFO)
9 |
10 | stream_handler = logging.StreamHandler()
11 | stream_handler.setLevel(logging.INFO)
12 | stream_handler.setFormatter(formatter)
13 |
14 | log_file_path = "logs/query-log.json"
15 | file_handler = logging.handlers.TimedRotatingFileHandler(
16 | filename=log_file_path, when='midnight', backupCount=30)
17 | file_handler.setFormatter(formatter)
18 | file_handler.setLevel(logging.INFO)
19 |
20 | logger.addHandler(file_handler)
21 | logger.addHandler(stream_handler)
22 |
--------------------------------------------------------------------------------
/frontend/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 |
5 | import {LayoutComponent} from './dashboard/layout/layout.component'
6 | const routes: Routes = [
7 | { path: '', redirectTo: 'agent/default', pathMatch: 'full' },
8 | {
9 | path: 'agent/default',
10 | component: LayoutComponent,
11 | loadChildren: './agent/agent.module#AgentModule'
12 | },
13 | {
14 | path: '**',
15 | redirectTo: 'agent/default'
16 | }
17 | ];
18 |
19 | @NgModule({
20 | imports: [RouterModule.forRoot(routes, {useHash: true})],
21 | exports: [RouterModule]
22 | })
23 | export class AppRoutingModule { }
24 |
--------------------------------------------------------------------------------
/app/commons/build_response.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from flask import Response
4 |
5 |
6 | def build_json(result):
7 | return Response(response=json.dumps(result),
8 | status=200,
9 | mimetype="application/json")
10 |
11 |
12 | def sent_json(result):
13 | return Response(response=result,
14 | status=200,
15 | mimetype="application/json")
16 |
17 |
18 | def sent_ok():
19 | return Response(response=json.dumps({"result": True}),
20 | status=200,
21 | mimetype="application/json")
22 |
23 |
24 | def sent_plain_text(result):
25 | return Response(response=result.strip(), status=200, mimetype="text")
26 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/train/train.component.scss:
--------------------------------------------------------------------------------
1 | .round-button{
2 | border:1px solid;border-radius:5px;padding:5px
3 | }
4 | .round-button:hover{
5 | cursor: pointer;
6 | background-color: gray;
7 | color:#fff;
8 | }
9 | .add-entity{
10 | margin-top: 10px;
11 | }
12 | .entity-table{
13 | padding:5px;
14 |
15 | }
16 | .full-width {
17 | width: 100%;
18 | }
19 |
20 | mat-card{
21 | margin: 5px;
22 |
23 | }
24 | .mat-accordion {
25 | margin: 5px;
26 | }
27 |
28 | .header{
29 | padding:5px;
30 | }
31 | #textarea_highlight{
32 | border-radius: 5px;
33 | padding:10px;
34 | background-color:#efefef;
35 | }
36 | .table-header{
37 | padding: 10px;
38 | background: #e9eaeb;
39 | }
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | class Config(object):
4 | DEBUG = False
5 | MONGODB_HOST = "mongodb://127.0.0.1:27017/iky-ai"
6 |
7 | # Intent Classifier model details
8 | MODELS_DIR = "model_files/"
9 | INTENT_MODEL_NAME = "intent.model"
10 | DEFAULT_FALLBACK_INTENT_NAME = "fallback"
11 | DEFAULT_WELCOME_INTENT_NAME = "init_conversation"
12 | USE_WORD_VECTORS = True
13 |
14 |
15 | class Development(Config):
16 | DEBUG = True
17 |
18 | class Production(Config):
19 | # MongoDB Database Details
20 | MONGODB_HOST = "mongodb://mongodb:27017/iky-ai"
21 |
22 | # Web Server details
23 | WEB_SERVER_PORT = 8001
24 |
25 | class Heroku(Production):
26 | MONGODB_HOST = os.environ.get('MONGO_URL')
--------------------------------------------------------------------------------
/frontend/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/dist/zone-testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | declare const require: any;
11 |
12 | // First, initialize the Angular testing environment.
13 | getTestBed().initTestEnvironment(
14 | BrowserDynamicTestingModule,
15 | platformBrowserDynamicTesting()
16 | );
17 | // Then we find all the tests.
18 | const context = require.context('./', true, /\.spec\.ts$/);
19 | // And load the modules.
20 | context.keys().map(context);
21 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist-server
5 | /tmp
6 | /out-tsc
7 |
8 | # dependencies
9 | /node_modules
10 |
11 | # IDEs and editors
12 | /.idea
13 | .project
14 | .classpath
15 | .c9/
16 | *.launch
17 | .settings/
18 | *.sublime-workspace
19 |
20 | # IDE - VSCode
21 | .vscode/*
22 | !.vscode/settings.json
23 | !.vscode/tasks.json
24 | !.vscode/launch.json
25 | !.vscode/extensions.json
26 |
27 | # misc
28 | /.sass-cache
29 | /connect.lock
30 | /coverage
31 | /libpeerconnection.log
32 | npm-debug.log
33 | yarn-error.log
34 | testem.log
35 | /typings
36 |
37 | # e2e
38 | /e2e/*.js
39 | /e2e/*.map
40 |
41 | # System Files
42 | .DS_Store
43 | Thumbs.db
44 |
--------------------------------------------------------------------------------
/frontend/src/app/services/training.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient } from '@angular/common/http';
3 | import { environment } from '../../environments/environment';
4 |
5 | @Injectable()
6 | export class TrainingService {
7 |
8 | constructor(public http: HttpClient) {}
9 |
10 | saveTrainingData(intent_id,data) {
11 | return this.http.post(environment.ikyBackend + `train/${intent_id}/data`, data).toPromise();
12 | }
13 |
14 | getTrainingData(intent_id) {
15 | return this.http.get(environment.ikyBackend + `train/${intent_id}/data`).toPromise();
16 | }
17 |
18 | trainModels() {
19 | return this.http.post(environment.ikyBackend + `nlu/build_models`, {}).toPromise();
20 | }
21 |
22 | }
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/entities/models.py:
--------------------------------------------------------------------------------
1 | from mongoengine.fields import Document
2 | from mongoengine.fields import EmbeddedDocument
3 | from mongoengine.fields import EmbeddedDocumentListField
4 | from mongoengine.fields import ListField
5 | from mongoengine.fields import StringField
6 |
7 |
8 | class EntityValue(EmbeddedDocument):
9 | value = StringField(required=True)
10 | synonyms = ListField(required=True, default=[])
11 |
12 |
13 | class Entity(Document):
14 | name = StringField(max_length=100, required=True, unique=True)
15 | entity_values = EmbeddedDocumentListField(EntityValue)
16 | meta = {
17 | 'indexes': [
18 | {
19 | 'fields': ['$name'],
20 | 'default_language': 'english'
21 | }
22 | ]}
23 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/entity/entity.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { EntityComponent } from './entity.component';
4 |
5 | describe('EntityComponent', () => {
6 | let component: EntityComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ EntityComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(EntityComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/examples/python/app.py:
--------------------------------------------------------------------------------
1 | import requests,json
2 |
3 | '''
4 | define initial payload
5 | set input = 'init_conversation' so that bot will return default welcome message
6 | '''
7 | payload = {
8 | "currentNode": "",
9 | "complete": None,
10 | "context": {},
11 | "parameters": [],
12 | "extractedParameters": {},
13 | "speechResponse": "",
14 | "intent": {},
15 | "input": "init_conversation",
16 | "missingParameters": []
17 | }
18 |
19 | while True:
20 | r = requests.post("http://localhost:8001/api/v1", json=payload)
21 | # replace payload variable with api result
22 | payload = json.loads(r.text)
23 |
24 | print("Iky\t" + payload.get("speechResponse"))
25 |
26 | # read user input
27 | payload["input"]=raw_input("You:\t")
28 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/layout/layout.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { LayoutComponent } from './layout.component';
4 |
5 | describe('LayoutComponent', () => {
6 | let component: LayoutComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ LayoutComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(LayoutComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/sidebar/sidebar.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { SidebarComponent } from './sidebar.component';
4 |
5 | describe('SidebarComponent', () => {
6 | let component: SidebarComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ SidebarComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(SidebarComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/entities/entities.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { EntitiesComponent } from './entities.component';
4 |
5 | describe('EntitiesComponent', () => {
6 | let component: EntitiesComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ EntitiesComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(EntitiesComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/settings/settings.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { SettingsComponent } from './settings.component';
4 |
5 | describe('SettingsComponent', () => {
6 | let component: SettingsComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ SettingsComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(SettingsComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/nginx/default.conf:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | server {
5 |
6 | listen 80;
7 |
8 | sendfile on;
9 |
10 | default_type application/octet-stream;
11 |
12 |
13 | gzip on;
14 | gzip_http_version 1.1;
15 | gzip_disable "MSIE [1-6]\.";
16 | gzip_min_length 256;
17 | gzip_vary on;
18 | gzip_proxied expired no-cache no-store private auth;
19 | gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
20 | gzip_comp_level 9;
21 |
22 |
23 | root /usr/share/nginx/html;
24 |
25 |
26 | location / {
27 | try_files $uri $uri/ /index.html =404;
28 | }
29 | location /gateway {
30 | proxy_pass http://iky_backend:8080/;
31 | proxy_set_header Host $host;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/examples/ruby/request.rb:
--------------------------------------------------------------------------------
1 | require 'net/http'
2 | require 'json'
3 |
4 | uri = URI('http://localhost:8001/api/v1')
5 | http = Net::HTTP.new(uri.host, uri.port)
6 | request = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
7 |
8 | request.body = {
9 | 'currentNode': '',
10 | 'complete': nil,
11 | 'context': {},
12 | 'parameters': [],
13 | 'extractedParameters': {},
14 | 'speechResponse': '',
15 | 'intent': {},
16 | 'input': 'init_conversation',
17 | 'missingParameters': []
18 | }.to_json
19 |
20 | while true
21 | response = http.request(request).body
22 | json_response = JSON.load(response)
23 |
24 | puts "Iky #{json_response['speechResponse']}"
25 |
26 | original_request_body = JSON.load(request.body)
27 | original_request_body['input'] = gets.chomp
28 | request.body = original_request_body.to_json
29 | end
30 |
--------------------------------------------------------------------------------
/frontend/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 11000,
8 | specs: [
9 | './e2e/**/*.e2e-spec.ts'
10 | ],
11 | capabilities: {
12 | 'browserName': 'chrome'
13 | },
14 | directConnect: true,
15 | baseUrl: 'http://localhost:4200/',
16 | framework: 'jasmine',
17 | jasmineNodeOpts: {
18 | showColors: true,
19 | defaultTimeoutInterval: 30000,
20 | print: function() {}
21 | },
22 | onPrepare() {
23 | require('ts-node').register({
24 | project: 'e2e/tsconfig.e2e.json'
25 | });
26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/app/agents/controllers.py:
--------------------------------------------------------------------------------
1 | from flask import Blueprint, request
2 |
3 | from app.agents.models import Bot
4 | from app.commons import build_response
5 |
6 | bots = Blueprint('bots_blueprint', __name__,
7 | url_prefix='/agents/')
8 |
9 |
10 | @bots.route('/config', methods=['PUT'])
11 | def set_config(bot_name):
12 | """
13 | Read bot config
14 | :param bot_name:
15 | :return:
16 | """
17 |
18 | content = request.get_json(silent=True)
19 | bot = Bot.objects.get(name=bot_name)
20 | bot.config = content
21 | bot.save()
22 | return build_response.sent_ok()
23 |
24 |
25 | @bots.route('/config', methods=['GET'])
26 | def get_config(bot_name):
27 | """
28 | Update bot config
29 | :return:
30 | """
31 | bot = Bot.objects.get(name=bot_name)
32 |
33 | return build_response.build_json(bot.config)
34 |
--------------------------------------------------------------------------------
/frontend/src/app/services/intent-resolver.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import {Resolve, Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
3 | import {IntentService } from './intent.service';
4 |
5 | @Injectable()
6 | export class IntentResolverService implements Resolve {
7 |
8 | constructor(private intentService: IntentService, private _router: Router) { }
9 |
10 | resolve(route: ActivatedRouteSnapshot): Promise | boolean {
11 | return new Promise((resolve,reject)=>{
12 | this.intentService.getIntent(route.params['intent_id']).then(
13 | (result) => {
14 | console.log("intent details resolved")
15 | resolve(result)
16 | },
17 | (err)=>{
18 | new Error("Could'nt get intent details")
19 | }
20 | )
21 | });
22 | }
23 | }
--------------------------------------------------------------------------------
/frontend/src/app/services/utils.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { FormGroup } from '@angular/forms';
3 | import { BehaviorSubject } from 'rxjs/BehaviorSubject';
4 |
5 | @Injectable()
6 | export class UtilsService {
7 |
8 | public status: BehaviorSubject = new BehaviorSubject(false);
9 |
10 | constructor() { }
11 |
12 | setDataForm(form, keys, data) {
13 | if (!data) { return; }
14 | const formData = {};
15 | for (const key in keys) {
16 | if (key in data) {
17 | const value = data[key];
18 | formData[key] = value;
19 | } else { formData[key] = ''; }
20 | }
21 | // (form).setValue(formData, { onlySelf: true });
22 | (form).patchValue(formData, { onlySelf: true });
23 | }
24 |
25 |
26 | displayLoader(value: boolean) {
27 | this.status.next(value);
28 | console.log("loader",value)
29 | }
30 |
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/dashboard.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { LayoutComponent } from './layout/layout.component';
4 | import { SidebarComponent } from './sidebar/sidebar.component';
5 |
6 | import {RouterModule } from '@angular/router';
7 |
8 |
9 | /* Material UI imports begins here */
10 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
11 |
12 | import { MatToolbarModule, MatIconModule,
13 | MatListModule,MatSidenavModule, MatFormFieldModule
14 |
15 | } from '@angular/material';
16 |
17 |
18 | /* Material UI imports ends here */
19 |
20 | @NgModule({
21 | imports: [
22 | CommonModule,
23 | RouterModule,
24 | MatToolbarModule,
25 | MatIconModule,
26 | MatListModule,
27 | MatSidenavModule,
28 | MatFormFieldModule
29 | ],
30 | declarations: [LayoutComponent, SidebarComponent]
31 | })
32 | export class DashboardModule { }
33 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/settings/settings.component.scss:
--------------------------------------------------------------------------------
1 | mat-card{
2 | margin:10px;
3 | .import-group{
4 | width:500px;
5 | border: 1px solid lightgray;
6 | padding: 5px;
7 | }
8 |
9 | /* Code Styles */
10 | pre {
11 | white-space: pre-wrap;
12 | white-space: -moz-pre-wrap;
13 | white-space: -o-pre-wrap;
14 | word-wrap: break-word;
15 |
16 | margin: 20px 0;
17 | width: 800px;
18 | max-width: 100%;
19 | outline: none;
20 | white-space: normal;
21 | background-color: #f6f8fa;
22 | border-color: #ccc;
23 | color: #666;
24 | font-size: 15px!important;
25 | padding: 7px;
26 | box-shadow: 1px 2px 3px lightgrey;
27 | }
28 |
29 | code {
30 | font-family: Courier, 'New Courier', monospace;
31 | font-size: 12px;
32 | }
33 |
34 | #codeStyler {
35 | border-radius: 5px;
36 | -moz-border-radius: 5px;
37 | -webkit-border-radius: 5px;
38 | margin: 1em 0;
39 | }
40 | }
--------------------------------------------------------------------------------
/app/train/controllers.py:
--------------------------------------------------------------------------------
1 | from bson.objectid import ObjectId
2 | from flask import Blueprint, request
3 |
4 | from app.commons import build_response
5 | from app.intents.models import Intent
6 |
7 | train = Blueprint('train_blueprint', __name__,
8 | url_prefix='/train')
9 |
10 |
11 | @train.route('//data', methods=['POST'])
12 | def save_training_data(story_id):
13 | """
14 | Save training data for given story
15 | :param story_id:
16 | :return:
17 | """
18 | story = Intent.objects.get(id=ObjectId(story_id))
19 | story.trainingData = request.json
20 | story.save()
21 | return build_response.sent_ok()
22 |
23 |
24 | @train.route('//data', methods=['GET'])
25 | def get_training_data(story_id):
26 | """
27 | retrieve training data for a given story
28 | :param story_id:
29 | :return:
30 | """
31 | story = Intent.objects.get(id=ObjectId(story_id))
32 | return build_response.build_json(story.trainingData)
33 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | setup: venv/bin/activate
2 |
3 | venv/bin/activate: requirements.txt
4 | test -d venv || virtualenv -p python3 venv
5 | . venv/bin/activate; pip install -Ur requirements.txt
6 | . venv/bin/activate; python manage.py install_nltk_dependencies
7 | . venv/bin/activate; python -m spacy download en_core_web_md && python -m spacy link en_core_web_md en
8 |
9 | restore_db:
10 | mongorestore --drop --db=iky-ai --dir=dump/iky-ai/
11 |
12 | init: restore_db setup
13 | . venv/bin/activate && python setup.py
14 |
15 | setup_spacy:
16 | . venv/bin/activate && python -m spacy download en_core_web_md && python -m spacy link en_core_web_md en
17 |
18 | run_dev:
19 | . venv/bin/activate && python run.py
20 |
21 | run_prod:
22 | . venv/bin/activate && APPLICATION_ENV="Production" gunicorn -k gevent --bind 0.0.0.0:8080 run:app
23 |
24 | run_docker:
25 | gunicorn run:app --bind 0.0.0.0:8080 --access-logfile=logs/gunicorn-access.log --error-logfile logs/gunicorn-error.log
26 |
27 | clean:
28 | rm -rf venv
29 | find -iname "*.pyc" -delete
30 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/entities/entities.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Add
7 |
8 |
9 |
10 |
11 |
12 | {{entity.name}}
13 |
14 |
15 |
16 | edit
17 |
18 |
19 | delete
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/frontend/Dockerfile:
--------------------------------------------------------------------------------
1 | ### STAGE 1: Build ###
2 |
3 | # We label our stage as 'builder'
4 | FROM node:8-alpine as builder
5 |
6 | COPY package.json package-lock.json ./
7 |
8 | RUN npm set progress=false && npm config set depth 0 && npm cache clean --force
9 |
10 | ## Storing node modules on a separate layer will prevent unnecessary npm installs at each build
11 | RUN npm i && mkdir /ng-app && cp -R ./node_modules ./ng-app
12 |
13 | WORKDIR /ng-app
14 |
15 | COPY . .
16 |
17 | ## Build the angular app in production mode and store the artifacts in dist folder
18 | RUN $(npm bin)/ng build --environment=docker --prod --build-optimizer
19 |
20 | ### STAGE 2: Setup ###
21 |
22 | FROM nginx:1.13.3-alpine
23 |
24 | ## Copy our default nginx config
25 | COPY nginx/default.conf /etc/nginx/conf.d/
26 |
27 | ## Remove default nginx website
28 | RUN rm -rf /usr/share/nginx/html/*
29 |
30 | ## From 'builder' stage copy over the artifacts in dist folder to default nginx public folder
31 | COPY --from=builder /ng-app/dist /usr/share/nginx/html
32 |
33 | CMD ["nginx", "-g", "daemon off;"]
34 |
--------------------------------------------------------------------------------
/frontend/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular/cli'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular/cli/plugins/karma')
14 | ],
15 | client:{
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | reports: [ 'html', 'lcovonly' ],
20 | fixWebpackSourcePaths: true
21 | },
22 | angularCli: {
23 | environment: 'dev'
24 | },
25 | reporters: ['progress', 'kjhtml'],
26 | port: 9876,
27 | colors: true,
28 | logLevel: config.LOG_INFO,
29 | autoWatch: true,
30 | browsers: ['Chrome'],
31 | singleRun: false
32 | });
33 | };
34 |
--------------------------------------------------------------------------------
/frontend/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule } from '@angular/core';
3 |
4 | import { AppRoutingModule } from './app-routing.module';
5 | import { AppComponent } from './app.component';
6 |
7 |
8 |
9 | /* Material UI imports begins here */
10 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
11 | import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
12 | /* Material UI imports ends here */
13 |
14 |
15 | /* Project Components imports begins here */
16 | import {CommonsModule} from './commons/commons.module'
17 | import { DashboardModule } from './dashboard/dashboard.module';
18 | /* Project Components imports ends here */
19 |
20 |
21 | @NgModule({
22 | declarations: [
23 | AppComponent
24 | ],
25 | imports: [
26 | BrowserModule,
27 | AppRoutingModule,
28 | BrowserAnimationsModule,
29 | CommonsModule.forRoot(),
30 | DashboardModule,
31 | MatProgressSpinnerModule
32 | ],
33 | providers: [],
34 | bootstrap: [AppComponent]
35 | })
36 | export class AppModule { }
37 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # Frontend
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.7.3.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
16 |
17 | ## Running unit tests
18 |
19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20 |
21 | ## Running end-to-end tests
22 |
23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
28 |
--------------------------------------------------------------------------------
/License.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Alfred Francis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/examples/default_intents.json:
--------------------------------------------------------------------------------
1 | [{"name": "Default Fallback intent", "userDefined": false, "apiTrigger": false, "intentId": "fallback", "_id": {"$oid": "59aae7bd26f6f60007b06fb3"}, "speechResponse": "{{ \n\n[\n \"Sorry ### I'm having trouble understanding you.\",\n \"Hmm ### I cant handle that yet.\",\n \"Can you please re-phrase your query ?\"\n] | random \n\n}}\ufeff\n\n"}, {"name": "cancel", "trainingData": [{"text": "i want to cancel", "entities": []}, {"text": "cancel that", "entities": []}, {"text": "cancel", "entities": []}], "userDefined": false, "apiTrigger": false, "intentId": "cancel", "_id": {"$oid": "59aae7bd26f6f60007b06fb5"}, "speechResponse": "Ok. Canceled."}, {"name": "Welcome message", "trainingData": [{"text": "hello there", "entities": []}, {"text": "hey there", "entities": []}, {"text": "hii", "entities": []}, {"text": "heyy", "entities": []}, {"text": "howdy", "entities": []}, {"text": "hey", "entities": []}, {"text": "hello", "entities": []}, {"text": "hi", "entities": []}], "userDefined": false, "apiTrigger": false, "intentId": "init_conversation", "_id": {"$oid": "59aae7bd26f6f60007b06fb7"}, "speechResponse": "Hi {{context[\"username\"] }} ### What can i do for you ?"}]
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | from flask_script import Manager
2 |
3 | from app import app
4 |
5 | manager = Manager(app)
6 |
7 | @manager.command
8 | def migrate():
9 | from app.agents.models import Bot
10 |
11 | try:
12 | # create default bot
13 | bot = Bot()
14 | bot.name = "default"
15 | bot.save()
16 | print("Created default bot")
17 | except:
18 | print("Default agent exists.. skipping..")
19 |
20 |
21 | # import some default intents
22 | from app.intents.controllers import import_json
23 | json_file = open("examples/default_intents.json", "r+")
24 | stories = import_json(json_file)
25 | print("Imported {} Stories".format(len(stories)))
26 |
27 | try:
28 | print("Training models..")
29 | from app.nlu.tasks import train_models
30 | train_models()
31 | print("Training models finished..")
32 | except Exception as e:
33 | e = str(e)
34 | if e == "NO_DATA":
35 | e = "load Data first into mongodb. Reffer Readme."
36 | print("Could not train models..skipping.. (reason: {})".format(e))
37 |
38 |
39 | if __name__ == "__main__":
40 | manager.run()
41 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/intents/intents.component.html:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {{intent.name}}
18 |
19 |
20 |
21 |
22 | add
23 |
24 | edit
25 |
26 |
27 | delete
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/chat/chat.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{message.content}}
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
28 |
29 |
30 |
31 | POST /api/v1
32 | {{prettyChatCurrent}}
33 |
34 |
--------------------------------------------------------------------------------
/frontend/dist/inline.2543f7f010b342baf7ef.bundle.js:
--------------------------------------------------------------------------------
1 | !function(e){var n=window.webpackJsonp;window.webpackJsonp=function(r,c,u){for(var i,a,f,l=0,s=[];l
2 |
3 |
4 |
5 |
6 |
7 |
8 | speaker_notes
9 | Intents
10 | Create and Manage your Intents
11 |
12 |
13 | playlist_add_check
14 | Entities
15 | Entities and Synonyms
16 |
17 |
18 | question_answer
19 | Chat
20 | Test and Debug your agent response
21 |
22 |
23 | settings
24 | Settings
25 | Tune ML, export data
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | help_outline
36 | Github
37 | Learn, Contribute and support
38 |
39 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "ng": "ng",
7 | "start": "ng serve",
8 | "build": "ng build --prod",
9 | "test": "ng test",
10 | "lint": "ng lint",
11 | "e2e": "ng e2e"
12 | },
13 | "private": true,
14 | "dependencies": {
15 | "@angular/animations": "^5.2.9",
16 | "@angular/cdk": "^5.2.4",
17 | "@angular/common": "^5.2.0",
18 | "@angular/compiler": "^5.2.0",
19 | "@angular/core": "^5.2.0",
20 | "@angular/flex-layout": "^5.0.0-beta.14",
21 | "@angular/forms": "^5.2.0",
22 | "@angular/http": "^5.2.0",
23 | "@angular/material": "^5.2.4",
24 | "@angular/platform-browser": "^5.2.0",
25 | "@angular/platform-browser-dynamic": "^5.2.0",
26 | "@angular/router": "^5.2.0",
27 | "core-js": "^2.4.1",
28 | "hammerjs": "^2.0.8",
29 | "node-sass": "^4.13.1",
30 | "rxjs": "^5.5.6",
31 | "zone.js": "^0.8.19"
32 | },
33 | "devDependencies": {
34 | "@angular/cli": "~1.7.3",
35 | "@angular/compiler-cli": "^5.2.0",
36 | "@angular/language-service": "^5.2.0",
37 | "@types/jasmine": "~2.8.3",
38 | "@types/jasminewd2": "~2.0.2",
39 | "@types/node": "~6.0.60",
40 | "codelyzer": "^4.0.1",
41 | "jasmine-core": "~2.8.0",
42 | "jasmine-spec-reporter": "~4.2.1",
43 | "karma": "~2.0.0",
44 | "karma-chrome-launcher": "~2.2.0",
45 | "karma-coverage-istanbul-reporter": "^1.2.1",
46 | "karma-jasmine": "~1.1.0",
47 | "karma-jasmine-html-reporter": "^0.2.2",
48 | "protractor": "~5.1.2",
49 | "ts-node": "~4.1.0",
50 | "tslint": "~5.9.1",
51 | "typescript": "~2.5.3"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | from blinker import Namespace
3 | from flask import Flask,send_from_directory
4 | from flask_cors import CORS
5 | from flask_mongoengine import MongoEngine
6 |
7 | APP_ROOT = os.path.dirname(os.path.abspath(__file__ + "../../"))
8 |
9 | app = Flask(__name__)
10 |
11 | CORS(app)
12 |
13 | # Configurations
14 | try:
15 | env = os.environ['APPLICATION_ENV']
16 | except KeyError as e:
17 | # logging.error('Unknown environment key, defaulting to Development')
18 | env = 'Development'
19 |
20 | app.config.from_object('config.%s' % env)
21 | app.config.update(
22 | DEBUG=True,
23 | TESTING=True,
24 | TEMPLATES_AUTO_RELOAD=True)
25 |
26 | db = MongoEngine(app)
27 |
28 | my_signals = Namespace()
29 |
30 | from app.agents.controllers import bots
31 | from app.nlu.controllers import nlu
32 | from app.intents.controllers import intents
33 | from app.train.controllers import train
34 | from app.endpoint.controllers import endpoint
35 | from app.entities.controllers import entities_blueprint
36 |
37 | app.register_blueprint(nlu)
38 | app.register_blueprint(intents)
39 | app.register_blueprint(train)
40 | app.register_blueprint(endpoint)
41 | app.register_blueprint(bots)
42 | app.register_blueprint(entities_blueprint)
43 |
44 |
45 | admin_panel_dist = os.path.join(APP_ROOT, 'frontend/dist/')
46 |
47 | @app.route('/', methods=['GET'])
48 | def static_proxy(path):
49 | return send_from_directory(admin_panel_dist, path)
50 |
51 | @app.route('/')
52 | def root():
53 | print(admin_panel_dist)
54 | return send_from_directory(admin_panel_dist, 'index.html')
55 |
56 | @app.errorhandler(404)
57 | def not_found(error):
58 | return "Not found", 404
59 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/entities/entities.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { EntitiesService } from '../../services/entities.service'
3 | import {Router} from "@angular/router";
4 | import {UtilsService} from "../../services/utils.service";
5 | @Component({
6 | selector: 'app-entities',
7 | templateUrl: './entities.component.html',
8 | styleUrls: ['./entities.component.scss']
9 | })
10 | export class EntitiesComponent implements OnInit {
11 |
12 | constructor(private _router: Router, private coreService:UtilsService, private entitiesService: EntitiesService) { }
13 |
14 | entities = []
15 |
16 | ngOnInit() {
17 | this.entitiesService.getEntities().then(
18 | (result: Array) => {
19 | this.entities = result
20 | }
21 | )
22 | }
23 |
24 | newEntity(name) {
25 | if (this.entities.find(x => x.name === name)) {
26 | alert("Entity already exist");
27 | return;
28 | }
29 | this.entitiesService.create_entity({ "name": name }).then(
30 | (result) => {
31 | this.entities.push({
32 | "_id":{
33 | "$oid":result["_id"]
34 | },
35 | "name": name
36 | })
37 | }
38 | )
39 | }
40 |
41 | edit(entity) {
42 | this._router.navigate(["/agent/default/edit-entity", entity._id.$oid])
43 | }
44 |
45 | deleteEntity(id,i){
46 | if (confirm('Are u sure want to delete this entity?')) {
47 | this.coreService.displayLoader(true);
48 | this.entitiesService.delete_entity(id).then(
49 | () => {
50 | this.entities.splice(i, 1);
51 | this.ngOnInit();
52 | this.coreService.displayLoader(false);
53 | });
54 | }
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/agent-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | import {IntentsComponent} from './intents/intents.component'
5 | import {IntentComponent} from './intent/intent.component';
6 | import {TrainComponent} from './train/train.component';
7 | import {SettingsComponent} from './settings/settings.component';
8 | import {ChatComponent} from './chat/chat.component';
9 | import {EntitiesComponent} from './entities/entities.component'
10 | import {EntityComponent} from './entity/entity.component'
11 | import {EntityResolverService} from '../services/entities.service'
12 | import {IntentResolverService} from '../services/intent-resolver.service';
13 |
14 | const routes: Routes = [
15 | { path: '', redirectTo: 'intents'},
16 | {
17 | path: 'intents', component: IntentsComponent,
18 | },
19 | {
20 | path: 'create-intent', component: IntentComponent,
21 | },
22 | {
23 | resolve: {
24 | story: IntentResolverService,
25 | },
26 | path: 'edit-intent/:intent_id', component: IntentComponent,
27 | },
28 | {
29 | path: 'entities', component: EntitiesComponent,
30 | },
31 | {
32 | resolve: {
33 | entity: EntityResolverService,
34 | },
35 | path: 'edit-entity/:entity_id', component: EntityComponent,
36 | },
37 | {
38 | resolve: {
39 | story: IntentResolverService,
40 | },
41 | path: 'train-intent/:intent_id', component: TrainComponent,
42 | },
43 | {
44 | path: 'settings', component: SettingsComponent,
45 | },
46 | {
47 | path: 'chat', component: ChatComponent,
48 | }
49 | ];
50 |
51 |
52 | @NgModule({
53 | imports: [RouterModule.forChild(routes)],
54 | exports: [RouterModule]
55 | })
56 | export class AgentRoutingModule { }
57 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/intents/intents.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Inject, Input } from '@angular/core';
2 |
3 | import { ActivatedRoute, Params, Router } from '@angular/router';
4 |
5 | import { IntentService } from '../../services/intent.service';
6 | import {TrainingService} from '../../services/training.service'
7 | import { UtilsService } from '../../services/utils.service';
8 |
9 | @Component({
10 | selector: 'app-intents',
11 | templateUrl: './intents.component.html',
12 | styleUrls: ['./intents.component.scss']
13 | })
14 | export class IntentsComponent implements OnInit {
15 |
16 |
17 |
18 | intents: any;
19 |
20 | constructor(public intentService: IntentService, private _activatedRoute: ActivatedRoute,
21 | private _router: Router,private trainingService:TrainingService, private coreService: UtilsService) { }
22 |
23 | ngOnInit() {
24 |
25 | this.intentService.getIntents().then((s: any) => {
26 | this.intents = s;
27 | });
28 | }
29 |
30 |
31 | add() {
32 | this._router.navigate(["/agent/default/create-intent"])
33 | }
34 |
35 | edit(intent) {
36 | this._router.navigate(["/agent/default/edit-intent", intent._id.$oid])
37 | }
38 |
39 | train(intent) {
40 | this._router.navigate(["/agent/default/train-intent", intent._id.$oid])
41 | }
42 |
43 | delete(intent) {
44 | if (confirm('Are u sure want to delete this story?')) {
45 | this.coreService.displayLoader(true);
46 | this.intentService.delete_intent(intent._id.$oid).then((s: any) => {
47 | this.ngOnInit();
48 | this.coreService.displayLoader(false);
49 | });
50 | }
51 | }
52 |
53 | trainModels() {
54 | this.coreService.displayLoader(true);
55 | this.trainingService.trainModels().then((s: any) => {
56 | this.coreService.displayLoader(false);
57 | });
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/commons/utils.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 |
3 | import parsedatetime as pdt
4 | from mongoengine.fields import EmbeddedDocumentField
5 | from mongoengine.fields import EmbeddedDocumentListField
6 | from mongoengine.fields import GenericEmbeddedDocumentField
7 | from mongoengine.fields import GenericReferenceField
8 | from mongoengine.fields import ListField
9 | from mongoengine.fields import ReferenceField
10 | from mongoengine.fields import SortedListField
11 |
12 |
13 | def date_from_string(timeString):
14 | cal = pdt.Calendar()
15 | now = datetime.now()
16 | result = str(cal.parseDT(timeString.strip(), now)[0])
17 | return result
18 |
19 |
20 | def update_document(document, data_dict):
21 | """
22 | Recreate Document object from python dictionary
23 | :param document:
24 | :param data_dict:
25 | :return:
26 | """
27 |
28 | def field_value(field, value):
29 |
30 | if field.__class__ in (
31 | ListField,
32 | SortedListField,
33 | EmbeddedDocumentListField):
34 | return [
35 | field_value(field.field, item)
36 | for item in value
37 | ]
38 | if field.__class__ in (
39 | EmbeddedDocumentField,
40 | GenericEmbeddedDocumentField,
41 | ReferenceField,
42 | GenericReferenceField
43 | ):
44 | return field.document_type(**value)
45 | else:
46 | return value
47 |
48 | [setattr(
49 | document, key.replace("_id", "id"),
50 | field_value(document._fields[key.replace("_id", "id")], value)
51 | ) for key, value in data_dict.items()]
52 |
53 | return document
54 |
55 |
56 | def is_list_empty(inList):
57 | if isinstance(inList, list): # Is a list
58 | return all(map(is_list_empty, inList))
59 | return False # Not a list
60 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/settings/settings.component.html:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
17 |
18 |
Chat Widget
19 |
Copy and paste the below snippet into your HTML code.
20 |
21 | {{ code }}
22 |
23 |
24 | In the above code snippet,
25 | replace win.iky_base_url value with your iky installation in following format "ip:port/" and win.chat_context with any global context
26 |
27 |
28 |
29 |
30 | Import/Export Intents
31 |
32 |
33 |
34 |
35 |
38 |
39 |
Import Intents
40 |
41 |
Export Intents
42 |
43 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/chat/chat.component.scss:
--------------------------------------------------------------------------------
1 | .textright {
2 | text-align: right;
3 | }
4 |
5 | .textleft {
6 | text-align: left;
7 | }
8 |
9 | .textcenter {
10 | text-align: center;
11 | }
12 |
13 | .fullwidth {
14 | width: 100%;
15 | }
16 | mat-card{
17 | margin: 10px;
18 |
19 | height: 600px;
20 | }
21 |
22 | ::-webkit-scrollbar {
23 | width: 0px; /* remove scrollbar space */
24 | background: transparent; /* optional: just make scrollbar invisible */
25 | }
26 |
27 | .chat-container {
28 | overflow-y: auto;
29 | }
30 |
31 | .chat-message {
32 | margin: 10px 0;
33 | min-height: 20px;
34 |
35 | animation: fadeIn 0.5s linear;
36 | animation-fill-mode: both;
37 |
38 | &:after {
39 | display: block;
40 | content: "";
41 | clear: both;
42 | }
43 | }
44 |
45 | .chat-message-content {
46 | width: auto;
47 | max-width: 85%;
48 | display: inline-block;
49 |
50 | padding: 7px 13px;
51 | border-radius: 15px;
52 | color: #595a5a;
53 | background-color: #eeeeee;
54 |
55 | &.human {
56 | float: right;
57 | color: #f7f8f8;
58 | background-color: #3392fb;
59 | }
60 | }
61 |
62 |
63 | .json-response{
64 | background-color:#efefef;
65 | pre{
66 | height: auto;
67 | height: 500px;
68 | overflow: auto;
69 | background-color: #eeeeee;
70 | word-break: normal !important;
71 | word-wrap: normal !important;
72 | white-space: pre !important;
73 | }
74 |
75 | }
76 |
77 | $total-items: 5;
78 |
79 | // Set delay per List Item
80 | @for $i from 1 through $total-items {
81 | .chat-message:nth-child(#{$i}) {
82 | animation-delay: .25s * $i;
83 | }
84 | }
85 |
86 | // Keyframe animation
87 | @-webkit-keyframes fadeIn {
88 | 0% {
89 | opacity: 0;
90 | }
91 | 75% {
92 | opacity: 0.5;
93 | }
94 | 100% {
95 | opacity: 1;
96 | }
97 | }
--------------------------------------------------------------------------------
/frontend/src/app/services/entities.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient } from '@angular/common/http';
3 | import { environment } from '../../environments/environment';
4 | @Injectable()
5 | export class EntitiesService {
6 | constructor(public http: HttpClient) {
7 | }
8 |
9 | getEntities() {
10 | return this.http.get(environment.ikyBackend + 'entities/').toPromise();
11 | }
12 |
13 | getEntity(id) {
14 | return this.http.get(environment.ikyBackend + `entities/${id}`).toPromise();
15 | }
16 |
17 | saveEntity(entity) {
18 | if (entity._id) {
19 | return this.update_entity(entity);
20 | } else {
21 | delete entity._id;
22 | return this.create_entity(entity);
23 | }
24 | }
25 |
26 | create_entity(entity) {
27 | return this.http.post(environment.ikyBackend + `entities/`, entity).toPromise();
28 | }
29 |
30 | update_entity(entity) {
31 | return this.http.put(environment.ikyBackend + `entities/${entity._id.$oid}`, entity).toPromise();
32 | }
33 |
34 | delete_entity(id) {
35 | return this.http.delete(environment.ikyBackend + `entities/${id}`, {}).toPromise();
36 | }
37 | }
38 |
39 |
40 | import {Resolve, Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
41 | @Injectable()
42 | export class EntityResolverService implements Resolve {
43 |
44 | constructor(private _router: Router,private entityService: EntitiesService) { }
45 |
46 | resolve(route: ActivatedRouteSnapshot): Promise | boolean {
47 | return new Promise((resolve,reject)=>{
48 | this.entityService.getEntity(route.params['entity_id']).then(
49 | (result) => {
50 | console.log("intent details resolved");
51 | resolve(result);
52 | },
53 | (err)=>{
54 | new Error("Could'nt get intent details")
55 | }
56 | )
57 | });
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/entity/entity.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { ActivatedRoute, Params, Router } from '@angular/router';
3 | import { EntitiesService } from '../../services/entities.service'
4 |
5 | import {MatChipInputEvent} from '@angular/material';
6 | import {ENTER, COMMA} from '@angular/cdk/keycodes';
7 |
8 | @Component({
9 | selector: 'app-entity',
10 | templateUrl: './entity.component.html',
11 | styleUrls: ['./entity.component.scss']
12 | })
13 | export class EntityComponent implements OnInit {
14 |
15 | constructor(private entitiesService: EntitiesService,private _activatedRoute: ActivatedRoute) { }
16 |
17 | separatorKeysCodes = [ENTER, COMMA];
18 | entity;
19 | ngOnInit() {
20 |
21 | this._activatedRoute.data
22 | .subscribe((data:{entity:any}) => {
23 | console.log("selected entity =>>",data.entity)
24 | this.entity = data.entity
25 | });
26 |
27 | }
28 | newValue(value){
29 | this.entity["entity_values"].push({
30 | "value":value,
31 | "synonyms":[]
32 | })
33 | }
34 |
35 | add(value_index,event: MatChipInputEvent): void {
36 | let input = event.input;
37 | let value = event.value;
38 |
39 | // Add our fruit
40 | if ((value || '').trim()) {
41 | this.entity.entity_values[value_index].synonyms.push(value);
42 | }
43 |
44 | // Reset the input value
45 | if (input) {
46 | input.value = '';
47 | }
48 | }
49 |
50 | remove_synonym(value_index,synonym_index): void {
51 |
52 | if (synonym_index >= 0) {
53 | this.entity.entity_values[value_index].synonyms.splice(synonym_index, 1);
54 | }
55 | }
56 | saveEntity(){
57 | this.entitiesService.saveEntity(this.entity).then(
58 | (result:any)=>{
59 | console.log("Success: " + JSON.stringify(result));
60 | }
61 | )
62 | }
63 | deleteEntityValue(value_index){
64 | this.entity["entity_values"].splice(value_index,1);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/intents/models.py:
--------------------------------------------------------------------------------
1 | from bson.objectid import ObjectId
2 | from mongoengine.fields import BooleanField
3 | from mongoengine.fields import Document
4 | from mongoengine.fields import EmbeddedDocument
5 | from mongoengine.fields import EmbeddedDocumentField
6 | from mongoengine.fields import EmbeddedDocumentListField
7 | from mongoengine.fields import ListField
8 | from mongoengine.fields import ObjectIdField
9 | from mongoengine.fields import StringField
10 |
11 |
12 | class LabeledSentences(EmbeddedDocument):
13 | id = ObjectIdField(required=True, default=lambda: ObjectId())
14 | data = ListField(required=True)
15 |
16 |
17 | class Parameter(EmbeddedDocument):
18 | id = ObjectIdField(default=lambda: ObjectId())
19 | name = StringField(required=True)
20 | required = BooleanField(default=False)
21 | type = StringField(required=False)
22 | prompt = StringField()
23 |
24 |
25 | class ApiDetails(EmbeddedDocument):
26 | url = StringField(required=True)
27 | requestType = StringField(
28 | choices=[
29 | "POST",
30 | "GET",
31 | "DELETE",
32 | "PUT"],
33 | required=True)
34 | headers = ListField(default=[])
35 | isJson = BooleanField(default=False)
36 | jsonData = StringField(default="{}")
37 |
38 | def get_headers(self):
39 | headers = {}
40 | for header in self.headers:
41 | headers[header["headerKey"]] = header["headerValue"]
42 | return headers
43 |
44 |
45 | class Intent(Document):
46 | name = StringField(max_length=100, required=True, unique=True)
47 | userDefined = BooleanField(default=True)
48 | intentId = StringField(required=True, unique=True)
49 | apiTrigger = BooleanField(required=True)
50 | apiDetails = EmbeddedDocumentField(ApiDetails)
51 | speechResponse = StringField(required=True)
52 | parameters = ListField(EmbeddedDocumentField(Parameter))
53 | labeledSentences = EmbeddedDocumentListField(LabeledSentences)
54 | trainingData = ListField(required=False)
55 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/entity/entity.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Save
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | add
21 |
22 |
23 |
24 |
25 |
26 |
27 | {{value.value}}
28 |
29 |
30 |
31 |
32 | {{synonym}}
33 | cancel
34 |
35 |
37 |
38 |
39 |
40 |
41 | delete
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/settings/settings.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { environment } from '../../../environments/environment';
3 | import { FormGroup, FormBuilder, FormArray } from '@angular/forms';
4 | import {IntentService} from '../../services/intent.service'
5 | import {AgentsService} from '../../services/agents.service'
6 | import { UtilsService } from '../../services/utils.service';
7 |
8 | @Component({
9 | selector: 'app-settings',
10 | templateUrl: './settings.component.html',
11 | styleUrls: ['./settings.component.scss']
12 | })
13 | export class SettingsComponent implements OnInit {
14 |
15 |
16 | fileToUpload: File = null;
17 | config_form = this.fb.group({
18 | "confidence_threshold":[]
19 | });
20 |
21 | constructor(private intentService:IntentService,private agent_service:AgentsService,
22 | public fb: FormBuilder,private utilsService:UtilsService) { }
23 |
24 | code = `
25 |
26 |
31 | `
32 | ngOnInit() {
33 | this.agent_service.get_config().then(
34 | (result)=>{
35 | this.config_form.setValue(result);
36 | }
37 | )
38 | }
39 |
40 | threshold_value_changed(){
41 | this.save_config()
42 | }
43 | save_config(){
44 | this.agent_service.update_config(this.config_form.value)
45 | console.log(this.config_form.value)
46 | }
47 |
48 | export(){
49 | window.open(environment.ikyBackend+"intents/export","_blank")
50 | }
51 | handleFileInput(files: FileList) {
52 | this.fileToUpload = files.item(0);
53 | }
54 |
55 | uploadFileToActivity() {
56 | this.utilsService.displayLoader(true)
57 | this.intentService.importIntents(this.fileToUpload).then ((result)=>{
58 | this.utilsService.displayLoader(false)
59 | console.log(result)
60 | })
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/app/entities/controllers.py:
--------------------------------------------------------------------------------
1 | from bson.json_util import dumps, loads
2 | from bson.objectid import ObjectId
3 | from flask import Blueprint, request, Response
4 |
5 | from app.commons import build_response
6 | from app.commons.utils import update_document
7 | from app.entities.models import Entity
8 |
9 | entities_blueprint = Blueprint('entities_blueprint', __name__,
10 | url_prefix='/entities')
11 |
12 |
13 | @entities_blueprint.route('/', methods=['POST'])
14 | def create_entity():
15 | """
16 | Create a story from the provided json
17 | :return:
18 | """
19 | content = request.get_json(silent=True)
20 |
21 | entity = Entity()
22 | entity.name = content.get("name")
23 | entity.entity_values = []
24 |
25 | try:
26 | entity_id = entity.save()
27 | except Exception as e:
28 | return build_response.build_json({"error": str(e)})
29 |
30 | return build_response.build_json({
31 | "_id": str(entity_id.id)
32 | })
33 |
34 |
35 | @entities_blueprint.route('/')
36 | def read_entities():
37 | """
38 | find list of entities
39 | :return:
40 | """
41 | intents = Entity.objects.only('name', 'id')
42 | return build_response.sent_json(intents.to_json())
43 |
44 |
45 | @entities_blueprint.route('/')
46 | def read_entity(id):
47 | """
48 | Find details for the given entity name
49 | :param id:
50 | :return:
51 | """
52 | return Response(
53 | response=dumps(Entity.objects.get(
54 | id=ObjectId(id)).to_mongo().to_dict()),
55 | status=200, mimetype="application/json")
56 |
57 |
58 | @entities_blueprint.route('/', methods=['PUT'])
59 | def update_entity(id):
60 | """
61 | Update a story from the provided json
62 | :param id:
63 | :return:
64 | """
65 | json_data = loads(request.get_data())
66 | entity = Entity.objects.get(id=ObjectId(id))
67 | entity = update_document(entity, json_data)
68 | entity.save()
69 | return build_response.sent_ok()
70 |
71 |
72 | @entities_blueprint.route('/', methods=['DELETE'])
73 | def delete_entity(id):
74 | """
75 | Delete a intent
76 | :param id:
77 | :return:
78 | """
79 | Entity.objects.get(id=ObjectId(id)).delete()
80 |
81 | return build_response.sent_ok()
82 |
--------------------------------------------------------------------------------
/app/endpoint/utils.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import requests
4 | from jinja2 import Undefined
5 |
6 | from app import app
7 | from app.entities.models import Entity
8 |
9 |
10 | def split_sentence(sentence):
11 | return sentence.split("###")
12 |
13 |
14 | def get_synonyms():
15 | """
16 | Build synonyms dict from DB
17 | :return:
18 | """
19 | synonyms = {}
20 |
21 | for entity in Entity.objects:
22 | for value in entity.entity_values:
23 | for synonym in value.synonyms:
24 | synonyms[synonym] = value.value
25 | app.logger.info("loaded synonyms %s", synonyms)
26 | return synonyms
27 |
28 |
29 | def call_api(url, type, headers={}, parameters={}, is_json=False):
30 | """
31 | Call external API
32 | :param url:
33 | :param type:
34 | :param parameters:
35 | :param is_json:
36 | :return:
37 | """
38 | app.logger.info("Initiating API Call with following info: url => {} payload => {}".format(url, parameters))
39 | if "GET" in type:
40 | response = requests.get(url, headers=headers, params=parameters, timeout=5)
41 | elif "POST" in type:
42 | if is_json:
43 | response = requests.post(url, headers=headers, json=parameters, timeout=5)
44 | else:
45 | response = requests.post(url, headers=headers, params=parameters, timeout=5)
46 | elif "PUT" in type:
47 | if is_json:
48 | response = requests.put(url, headers=headers, json=parameters, timeout=5)
49 | else:
50 | response = requests.put(url, headers=headers, params=parameters, timeout=5)
51 | elif "DELETE" in type:
52 | response = requests.delete(url, headers=headers, params=parameters, timeout=5)
53 | else:
54 | raise Exception("unsupported request method.")
55 | result = json.loads(response.text)
56 | app.logger.info("API response => %s", result)
57 | return result
58 |
59 |
60 | class SilentUndefined(Undefined):
61 | """
62 | Class to suppress jinja2 errors and warnings
63 | """
64 |
65 | def _fail_with_undefined_error(self, *args, **kwargs):
66 | return 'undefined'
67 |
68 | __add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
69 | __truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \
70 | __mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
71 | __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = __int__ = \
72 | __float__ = __complex__ = __pow__ = __rpow__ = \
73 | _fail_with_undefined_error
74 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/agent.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { ReactiveFormsModule } from '@angular/forms';
4 | import { HttpClientModule } from '@angular/common/http';
5 | import { FormsModule } from '@angular/forms';
6 | import { FlexLayoutModule } from '@angular/flex-layout';
7 |
8 | import { AgentRoutingModule } from './agent-routing.module';
9 | import { IntentsComponent } from './intents/intents.component';
10 |
11 | /* Material UI imports begins here */
12 | import {MatIconModule,MatCardModule,MatInputModule,
13 | MatOptionModule,MatSelectModule,MatCheckboxModule,MatButtonModule} from '@angular/material';
14 | import {MatGridListModule} from '@angular/material/grid-list';
15 | import {MatDividerModule} from '@angular/material/divider';
16 | import {MatExpansionModule} from '@angular/material/expansion';
17 | import {MatSliderModule} from '@angular/material/slider';
18 | import {MatChipsModule} from '@angular/material/chips';
19 | import {MatAutocompleteModule} from '@angular/material/autocomplete';
20 | import {MatToolbarModule} from '@angular/material/toolbar';
21 | import {MatTooltipModule} from '@angular/material/tooltip';
22 | /* Material UI imports ends here */
23 |
24 |
25 | /* Services imports begins here */
26 | import { IntentService } from '../services/intent.service';
27 | import {TrainingService} from '../services/training.service'
28 | import {IntentResolverService} from '../services/intent-resolver.service';
29 | import {ChatService} from '../services/chat.service'
30 | import {AgentsService} from '../services/agents.service'
31 | import {EntitiesService,EntityResolverService} from '../services/entities.service'
32 |
33 | /* Services imports ends here */
34 |
35 | import { SettingsComponent } from './settings/settings.component';
36 | import { ChatComponent } from './chat/chat.component'
37 | import { IntentComponent } from './intent/intent.component';
38 | import { TrainComponent } from './train/train.component';
39 |
40 | import { AutofocusDirective } from '../directives/autofocus.directive';
41 | import { EntitiesComponent } from './entities/entities.component';
42 | import { EntityComponent } from './entity/entity.component';
43 |
44 | @NgModule({
45 | imports: [
46 | CommonModule,
47 | ReactiveFormsModule,
48 | HttpClientModule,
49 | FormsModule,
50 | FlexLayoutModule,
51 |
52 | AgentRoutingModule,
53 |
54 | MatIconModule,
55 | MatCardModule,
56 | MatInputModule,
57 | MatOptionModule,
58 | MatSelectModule,
59 | MatCheckboxModule,
60 | MatButtonModule,
61 | MatGridListModule,
62 | MatDividerModule,
63 | MatExpansionModule,
64 | MatSliderModule,
65 | MatChipsModule,
66 | MatAutocompleteModule,
67 | MatToolbarModule,
68 | MatTooltipModule
69 |
70 | ],
71 | declarations: [IntentsComponent, IntentComponent, TrainComponent, SettingsComponent,
72 | ChatComponent,AutofocusDirective, EntitiesComponent, EntityComponent],
73 | providers:[AgentsService,IntentService,
74 | IntentResolverService,TrainingService,ChatService,EntitiesService,EntityResolverService]
75 | })
76 | export class AgentModule { }
77 |
--------------------------------------------------------------------------------
/app/nlu/tasks.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from app import app
4 | from app import my_signals
5 | from app.intents.models import Intent
6 | from app.nlu import spacy_tokenizer
7 | from app.nlu.classifiers.starspace_intent_classifier import \
8 | EmbeddingIntentClassifier
9 | from app.nlu.entity_extractor import EntityExtractor
10 |
11 | model_updated_signal = my_signals.signal('model-updated')
12 |
13 |
14 | def train_models():
15 | """
16 | Initiate NER and Intent Classification training
17 | :return:
18 | """
19 | # generate intent classifier training data
20 | intents = Intent.objects
21 |
22 | if not intents:
23 | raise Exception("NO_DATA")
24 |
25 | # train intent classifier on all intents
26 | train_intent_classifier(intents)
27 |
28 | # train ner model for each Stories
29 | for intent in intents:
30 | train_all_ner(intent.intentId, intent.trainingData)
31 |
32 | model_updated_signal.send(app, message="Training Completed.")
33 |
34 |
35 | def train_intent_classifier(intents):
36 | """
37 | Train intent classifier model
38 | :param intents:
39 | :return:
40 | """
41 | X = []
42 | y = []
43 | for intent in intents:
44 | training_data = intent.trainingData
45 | for example in training_data:
46 | if example.get("text").strip() == "":
47 | continue
48 | X.append(example.get("text"))
49 | y.append(intent.intentId)
50 |
51 | intent_classifier = EmbeddingIntentClassifier(use_word_vectors=app.config['USE_WORD_VECTORS'])
52 | intent_classifier.train(X, y)
53 | intent_classifier.persist(model_dir=app.config["MODELS_DIR"])
54 |
55 |
56 | def train_all_ner(story_id, training_data):
57 | """
58 | Train NER model for single Story
59 | :param story_id:
60 | :param training_data:
61 | :return:
62 | """
63 | entityExtraction = EntityExtractor()
64 | # generate crf training data
65 | ner_training_data = entityExtraction.json2crf(training_data)
66 | # train and store ner model
67 | entityExtraction.train(ner_training_data, story_id)
68 |
69 |
70 | # Load and initialize Perceptron tagger
71 |
72 | def pos_tagger(sentence):
73 | """
74 | perform POS tagging on a given sentence
75 | :param sentence:
76 | :return:
77 | """
78 | doc = spacy_tokenizer(sentence)
79 | taged_sentance = []
80 | for token in doc:
81 | taged_sentance.append((token.text, token.tag_))
82 | return taged_sentance
83 |
84 |
85 | def pos_tag_and_label(sentence):
86 | """
87 | Perform POS tagging and BIO labeling on given sentence
88 | :param sentence:
89 | :return:
90 | """
91 | tagged_sentence = pos_tagger(sentence)
92 | tagged_sentence_json = []
93 | for token, postag in tagged_sentence:
94 | tagged_sentence_json.append([token, postag, "O"])
95 | return tagged_sentence_json
96 |
97 |
98 | def sentence_tokenize(sentences):
99 | """
100 | Sentence tokenizer
101 | :param sentences:
102 | :return:
103 | """
104 | doc = spacy_tokenizer(sentences)
105 | words = [token.text for token in doc]
106 | return " ".join(words)
107 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/train/train.component.html:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 | Add to training set
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
{{example.text}}
32 |
33 | delete
34 |
35 |
36 |
37 |
38 |
39 | Select text to add an entity
40 |
41 |
42 |
43 |
44 |
45 |
46 | {{ entity.name }}
47 |
48 |
49 |
50 |
51 |
52 |
53 | Add an entity for
54 | "{{this.selectionInfo.value}}"
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Entities
63 |
68 |
69 | {{entity.name}}
70 | {{entity.value}}
71 |
72 |
73 | close
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/frontend/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
22 | // import 'core-js/es6/symbol';
23 | // import 'core-js/es6/object';
24 | // import 'core-js/es6/function';
25 | // import 'core-js/es6/parse-int';
26 | // import 'core-js/es6/parse-float';
27 | // import 'core-js/es6/number';
28 | // import 'core-js/es6/math';
29 | // import 'core-js/es6/string';
30 | // import 'core-js/es6/date';
31 | // import 'core-js/es6/array';
32 | // import 'core-js/es6/regexp';
33 | // import 'core-js/es6/map';
34 | // import 'core-js/es6/weak-map';
35 | // import 'core-js/es6/set';
36 |
37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
38 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
39 |
40 | /** IE10 and IE11 requires the following for the Reflect API. */
41 | // import 'core-js/es6/reflect';
42 |
43 |
44 | /** Evergreen browsers require these. **/
45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
46 | import 'core-js/es7/reflect';
47 |
48 |
49 | /**
50 | * Required to support Web Animations `@angular/platform-browser/animations`.
51 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
52 | **/
53 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
54 |
55 | /**
56 | * By default, zone.js will patch all possible macroTask and DomEvents
57 | * user can disable parts of macroTask/DomEvents patch by setting following flags
58 | */
59 |
60 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
61 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
62 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
63 |
64 | /*
65 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
66 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
67 | */
68 | // (window as any).__Zone_enable_cross_context_check = true;
69 |
70 | /***************************************************************************************************
71 | * Zone JS is required by default for Angular itself.
72 | */
73 | import 'zone.js/dist/zone'; // Included with Angular CLI.
74 |
75 |
76 |
77 | /***************************************************************************************************
78 | * APPLICATION IMPORTS
79 | */
80 |
--------------------------------------------------------------------------------
/frontend/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "arrow-return-shorthand": true,
7 | "callable-types": true,
8 | "class-name": true,
9 | "comment-format": [
10 | true,
11 | "check-space"
12 | ],
13 | "curly": true,
14 | "deprecation": {
15 | "severity": "warn"
16 | },
17 | "eofline": true,
18 | "forin": true,
19 | "import-blacklist": [
20 | true,
21 | "rxjs",
22 | "rxjs/Rx"
23 | ],
24 | "import-spacing": true,
25 | "indent": [
26 | true,
27 | "spaces"
28 | ],
29 | "interface-over-type-literal": true,
30 | "label-position": true,
31 | "max-line-length": [
32 | true,
33 | 140
34 | ],
35 | "member-access": false,
36 | "member-ordering": [
37 | true,
38 | {
39 | "order": [
40 | "static-field",
41 | "instance-field",
42 | "static-method",
43 | "instance-method"
44 | ]
45 | }
46 | ],
47 | "no-arg": true,
48 | "no-bitwise": true,
49 | "no-console": [
50 | true,
51 | "debug",
52 | "info",
53 | "time",
54 | "timeEnd",
55 | "trace"
56 | ],
57 | "no-construct": true,
58 | "no-debugger": true,
59 | "no-duplicate-super": true,
60 | "no-empty": false,
61 | "no-empty-interface": true,
62 | "no-eval": true,
63 | "no-inferrable-types": [
64 | true,
65 | "ignore-params"
66 | ],
67 | "no-misused-new": true,
68 | "no-non-null-assertion": true,
69 | "no-shadowed-variable": true,
70 | "no-string-literal": false,
71 | "no-string-throw": true,
72 | "no-switch-case-fall-through": true,
73 | "no-trailing-whitespace": true,
74 | "no-unnecessary-initializer": true,
75 | "no-unused-expression": true,
76 | "no-use-before-declare": true,
77 | "no-var-keyword": true,
78 | "object-literal-sort-keys": false,
79 | "one-line": [
80 | true,
81 | "check-open-brace",
82 | "check-catch",
83 | "check-else",
84 | "check-whitespace"
85 | ],
86 | "prefer-const": true,
87 | "quotemark": [
88 | true,
89 | "single"
90 | ],
91 | "radix": true,
92 | "semicolon": [
93 | true,
94 | "always"
95 | ],
96 | "triple-equals": [
97 | true,
98 | "allow-null-check"
99 | ],
100 | "typedef-whitespace": [
101 | true,
102 | {
103 | "call-signature": "nospace",
104 | "index-signature": "nospace",
105 | "parameter": "nospace",
106 | "property-declaration": "nospace",
107 | "variable-declaration": "nospace"
108 | }
109 | ],
110 | "unified-signatures": true,
111 | "variable-name": false,
112 | "whitespace": [
113 | true,
114 | "check-branch",
115 | "check-decl",
116 | "check-operator",
117 | "check-separator",
118 | "check-type"
119 | ],
120 | "directive-selector": [
121 | true,
122 | "attribute",
123 | "app",
124 | "camelCase"
125 | ],
126 | "component-selector": [
127 | true,
128 | "element",
129 | "app",
130 | "kebab-case"
131 | ],
132 | "no-output-on-prefix": true,
133 | "use-input-property-decorator": true,
134 | "use-output-property-decorator": true,
135 | "use-host-property-decorator": true,
136 | "no-input-rename": true,
137 | "no-output-rename": true,
138 | "use-life-cycle-interface": true,
139 | "use-pipe-transform-interface": true,
140 | "component-class-suffix": true,
141 | "directive-class-suffix": true
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/chat/chat.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input,AfterViewChecked, ElementRef, ViewChild } from '@angular/core';
2 | import { ChatService } from '../../services/chat.service';
3 | import { FormBuilder, FormGroup } from '@angular/forms';
4 | import { trigger,style,transition,animate,keyframes,query,stagger } from '@angular/animations';
5 |
6 | @Component({
7 | // tslint:disable-next-line:component-selector
8 | selector: 'app-chat',
9 | templateUrl: './chat.component.html',
10 | styleUrls: ['./chat.component.scss'],
11 | animations: [
12 |
13 | trigger('listAnimation', [
14 | transition('* => *', [
15 |
16 | query(':enter', style({ opacity: 0 }), {optional: true}),
17 |
18 | query(':enter', stagger('500ms', [
19 | animate('.3s ease-in-out', keyframes([
20 | style({opacity: 0, offset: 0}),
21 | style({opacity: .5, offset: 0.5}),
22 | style({opacity: 1, offset: 1.0}),
23 | ]))]), {optional: true})
24 | ])
25 | ])
26 |
27 | ]
28 | })
29 |
30 | export class ChatComponent implements OnInit {
31 | chatInitial;
32 | chatCurrent;
33 |
34 | messages: Message[] = [];
35 | prettyChatCurrent;
36 |
37 | chatForm: FormGroup;
38 | chatFormFields: any;
39 | @ViewChild('scrollMe') private myScrollContainer: ElementRef;
40 |
41 | constructor(
42 | public fb: FormBuilder,
43 | public chatService: ChatService) {
44 |
45 | this.chatFormFields = {
46 | input: [''],
47 | };
48 | this.chatForm = this.fb.group(this.chatFormFields);
49 |
50 | }
51 |
52 | ngOnInit() {
53 | this.chatInitial = {
54 | 'currentNode': '',
55 | 'complete': null, 'context': {},
56 | 'parameters': [],
57 | 'extractedParameters': {},
58 | 'speechResponse': '',
59 | 'intent': {},
60 | 'input': 'init_conversation',
61 | 'missingParameters': []
62 | };
63 |
64 | this.chatService.converse(this.chatInitial)
65 | .then((c: any) => {
66 | c.owner = 'chat';
67 | this.changeCurrent(c);
68 |
69 | this.render_bubbles(c)
70 | });
71 | }
72 |
73 |
74 | scrollToBottom(): void {
75 | try {
76 | this.myScrollContainer.nativeElement.scrollTop = this.myScrollContainer.nativeElement.scrollHeight;
77 | } catch(err) { }
78 | }
79 |
80 | render_bubbles(c){
81 | c.speechResponse.forEach((item, index) => {
82 | if (index == 0){
83 | this.add_to_messages(item,"chat")
84 | }else{
85 | setTimeout(()=>{
86 | this.add_to_messages(item,"chat")
87 | },500)
88 | }
89 |
90 | });
91 | }
92 | add_to_messages(message,author){
93 | let new_message = new Message(message,author)
94 | this.messages.push(new_message);
95 | setTimeout(()=>{
96 | this.scrollToBottom();
97 | },300)
98 |
99 | }
100 |
101 | changeCurrent(c) {
102 | c.date = new Date();
103 | this.chatCurrent = c;
104 | this.prettyChatCurrent = JSON ? JSON.stringify(c, null, ' ') : 'your browser doesnt support JSON so cant pretty print';
105 | }
106 |
107 | send() {
108 | const form = this.chatForm.value;
109 | const sendMessage = {
110 | ... this.chatCurrent,
111 | input: form.input,
112 | owner: 'user'
113 | };
114 | this.add_to_messages(form.input,"user")
115 |
116 | this.changeCurrent(sendMessage);
117 | this.chatService.converse(sendMessage)
118 | .then((c: any) => {
119 | c.owner = 'chat';
120 | this.changeCurrent(c);
121 | this.chatForm.reset();
122 | setTimeout(
123 | ()=>{
124 | this.render_bubbles(c);
125 | },1000
126 | )
127 |
128 | });
129 |
130 | }
131 |
132 | }
133 |
134 | export class Message {
135 | content: string;
136 | author: string;
137 |
138 | constructor(content: string, author: string){
139 | this.content = content;
140 | this.author = author;
141 | }
142 | }
--------------------------------------------------------------------------------
/frontend/src/app/agent/intent/intent.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input } from '@angular/core';
2 | import { FormGroup, FormBuilder, FormArray,Validators } from '@angular/forms';
3 | import { ActivatedRoute, Params, Router } from '@angular/router';
4 |
5 | import { UtilsService } from '../../services/utils.service';
6 | import {IntentService } from '../../services/intent.service';
7 |
8 | @Component({
9 | selector: 'app-intent',
10 | templateUrl: './intent.component.html',
11 | styleUrls: ['./intent.component.scss']
12 | })
13 | export class IntentComponent implements OnInit {
14 |
15 | intent: any;
16 | intentForm: FormGroup;
17 | intentFormFields: any;
18 |
19 | intentTypes;
20 | intentTypesArray;
21 | message;
22 |
23 | constructor(
24 | public fb: FormBuilder,
25 | public coreService: UtilsService,
26 | public intentService: IntentService,
27 | private _activatedRoute: ActivatedRoute, private _router: Router) {
28 |
29 |
30 | this.intentTypes = IntentService.intentTypes;
31 | this.intentTypesArray = Object.keys(this.intentTypes);
32 |
33 |
34 | }
35 |
36 | loadForm(){
37 | this.intentFormFields = {
38 | _id: [''],
39 | name: [''],
40 | intentId: [''],
41 | userDefined: [true],
42 | speechResponse: [''],
43 | apiTrigger: [false],
44 | apiDetails: this.initApiDetails(),
45 | parameters: this.fb.array(
46 | this.intent && this.intent.parameters ? this.intent.parameters.map(n => {
47 | return this.initParameter(n);
48 | }) : []
49 | )
50 | };
51 | this.intentForm = this.fb.group(this.intentFormFields);
52 | }
53 |
54 | ngOnInit() {
55 | this.loadForm()
56 |
57 | this._activatedRoute.params.subscribe((params: Params) => {
58 | console.log("active agent reached " + params['intent_id'])
59 | });
60 |
61 |
62 | this._activatedRoute.data
63 | .subscribe((data:{story:any}) => {
64 | console.log("selected intent =>>")
65 | console.log(data.story)
66 | this.intent = data.story;
67 | this.loadForm();
68 | this.coreService.setDataForm(this.intentForm, this.intentFormFields, this.intent);
69 | });
70 |
71 |
72 |
73 | }
74 |
75 | addParameter() {
76 | const control = this.intentForm.controls['parameters'];
77 | control.push(this.initParameter());
78 | }
79 |
80 | initParameter(parameter = null) {
81 | const fields = {
82 | name: ['', Validators.required],
83 | type: ['', Validators.required],
84 | required: [false],
85 | prompt: ['']
86 | };
87 | const g = this.fb.group(fields);
88 | if (parameter) {
89 | // setdataform
90 | }
91 | return g;
92 | }
93 |
94 | deleteParameter(i) {
95 | const control = this.intentForm.controls['parameters'];
96 | control.removeAt(i);
97 | }
98 |
99 |
100 | initApiDetails(parameter = null) {
101 | const fields = {
102 | isJson: [false],
103 | url: [''],
104 | headers: this.fb.array(
105 | this.intent && this.intent.apiTrigger ? this.intent.apiDetails.headers.map(n => {
106 | return this.initApiHeaders();
107 | }) : []),
108 | requestType: [''],
109 | jsonData: ['']
110 | };
111 | const g = this.fb.group(fields);
112 | if (parameter) {
113 | // setdataform
114 | }
115 | return g;
116 | }
117 | initApiHeaders() {
118 | const fields = {
119 | headerKey: ['', Validators.required],
120 | headerValue: ['', Validators.required],
121 | };
122 | const g = this.fb.group(fields);
123 | return g;
124 | }
125 |
126 | addHeader(){
127 | const header = this.intentForm.controls["apiDetails"]["controls"]["headers"];
128 | header.push(this.initApiHeaders());
129 |
130 | }
131 | deleteHeader(j) {
132 | const control = this.intentForm.controls["apiDetails"]["controls"]["headers"];
133 | control.removeAt(j);
134 | }
135 | save() {
136 | const form = this.intentForm.value;
137 | if (form._id && form._id.$oid) {
138 | form._id = form._id.$oid;
139 | }
140 | if (!this.apiTrigger()) {
141 | delete form.apiDetails;
142 | }
143 |
144 | this.intentService.saveIntent(form)
145 | .then(c => {
146 | this.message = 'Intent created!';
147 | this._router.navigate(["/agent/default/edit-intent", c["_id"]])
148 | })
149 | }
150 |
151 | apiTrigger() {
152 | return this.intentForm.value.apiTrigger;
153 | }
154 |
155 | }
156 |
--------------------------------------------------------------------------------
/examples/restaurant_search.json:
--------------------------------------------------------------------------------
1 | [{"name": "Restaurant search", "parameters": [{"prompt": "Sure ### tell me your location?", "required": true, "type": "free_text", "id": {"$oid": "5ae0dd474ae5357959ad5f1d"}, "name": "location"}, {"prompt": "Ok. ### What type cuisine are you looking for?", "required": true, "type": "free_text", "id": {"$oid": "5ae0dd474ae5357959ad5f1e"}, "name": "cuisine"}], "userDefined": true, "trainingData": [{"text": "im looking for a place in banglore serving Chinese", "entities": [{"begin": 26, "end": 34, "name": "location", "value": "banglore"}, {"begin": 43, "end": 50, "name": "cuisine", "value": "Chinese"}]}, {"text": "i'm looking for Chinese food", "entities": [{"begin": 16, "end": 23, "name": "cuisine", "value": "Chinese"}]}, {"text": "I'm looking for south indian places", "entities": [{"begin": 12, "end": 28, "name": "cuisine", "value": "for south indian"}]}, {"text": "im looking for a place near banglore", "entities": [{"begin": 28, "end": 36, "name": "location", "value": "banglore"}]}, {"text": "i'm looking for a place to eat near down town la", "entities": [{"begin": 36, "end": 48, "name": "location", "value": "down town la"}]}, {"text": "i'm looking for a place in new york", "entities": [{"begin": 27, "end": 35, "name": "location", "value": "new york"}]}, {"text": "im looking for a place in banglore", "entities": [{"begin": 26, "end": 34, "name": "location", "value": "banglore"}]}, {"text": "looking for indian cuisine in new york", "entities": [{"begin": 12, "end": 18, "name": "cuisine", "value": "indian"}, {"begin": 30, "end": 38, "name": "location", "value": "new york"}]}, {"text": "central indian restaurant", "entities": [{"begin": 0, "end": 7, "name": "location", "value": "central"}, {"begin": 8, "end": 14, "name": "cuisine", "value": "indian"}]}, {"text": "I am looking for mexican indian fusion", "entities": [{"begin": 17, "end": 38, "name": "cuisine", "value": "mexican indian fusion"}]}, {"text": "I am looking a restaurant in 29432", "entities": [{"begin": 29, "end": 34, "name": "location", "value": "29432"}]}, {"text": "I am looking for asian fusion food", "entities": [{"begin": 17, "end": 29, "name": "cuisine", "value": "asian fusion"}]}, {"text": "anywhere near 18328", "entities": [{"begin": 14, "end": 19, "name": "location", "value": "18328"}]}, {"text": "anywhere in the west", "entities": [{"begin": 16, "end": 20, "name": "location", "value": "west"}]}, {"text": "search for restaurants", "entities": []}, {"text": "i am looking for an indian spot called olaolaolaolaolaola", "entities": [{"begin": 39, "end": 57, "name": "location", "value": "olaolaolaolaolaola"}, {"begin": 20, "end": 26, "name": "cuisine", "value": "indian"}]}, {"text": "show me a mexican place in the centre", "entities": [{"begin": 10, "end": 17, "name": "cuisine", "value": "mexican"}, {"begin": 31, "end": 37, "name": "location", "value": "centre"}]}, {"text": "show me chines restaurants in the north", "entities": [{"begin": 8, "end": 14, "name": "cuisine", "value": "chines"}, {"begin": 34, "end": 39, "name": "location", "value": "north"}]}, {"text": "show me chinese restaurants", "entities": [{"begin": 8, "end": 15, "name": "cuisine", "value": "chinese"}]}, {"text": "i'm looking for a place in the north of town", "entities": [{"begin": 31, "end": 44, "name": "location", "value": "north of town"}]}, {"text": "I am searching for a dinner spot", "entities": []}, {"text": "I want to grab lunch", "entities": []}, {"text": "i'm looking for a place to eat", "entities": []}], "apiTrigger": false, "intentId": "restaurant_search", "_id": {"$oid": "5adb265507440e00128fcfa1"}, "speechResponse": "Ok ### I found following restaurants serving {{parameters[\"cuisine\"] }} in {{parameters[\"location\"] }} \n###\n Restaurant Name 1 \n###\n Restaurant Name 2 "}, {"name": "Affirm", "trainingData": [{"text": "sounds really good", "entities": []}, {"text": "great choice\n", "entities": []}, {"text": "correct\n", "entities": []}, {"text": "right, thank you\n", "entities": []}, {"text": "great\n", "entities": []}, {"text": "ok\n", "entities": []}, {"text": "that's right\n", "entities": []}, {"text": "indeed\n", "entities": []}, {"text": "yeah\n", "entities": []}, {"text": "yep\n", "entities": []}, {"text": "yes\n", "entities": []}], "userDefined": true, "apiTrigger": false, "intentId": "affirm", "_id": {"$oid": "5adc8a3d4ae5353f40be85ca"}, "speechResponse": "I'm glad that you agree"}, {"name": "Goodbye", "trainingData": [{"text": "have a good one", "entities": []}, {"text": "Bye bye\n", "entities": []}, {"text": "farewell\n", "entities": []}, {"text": "end\n", "entities": []}, {"text": "stop\n", "entities": []}, {"text": "good bye\n", "entities": []}, {"text": "goodbye\n", "entities": []}, {"text": "bye", "entities": []}], "userDefined": true, "apiTrigger": false, "intentId": "goodbye", "_id": {"$oid": "5adc8a9c4ae5353f40be85cf"}, "speechResponse": "It was nice talking to you sir"}, {"name": "Thank you", "trainingData": [{"text": "thank you iky", "entities": []}, {"text": "thanks", "entities": []}, {"text": "thank you very much", "entities": []}], "userDefined": true, "apiTrigger": false, "intentId": "thank_you", "_id": {"$oid": "5adcb6314ae535517d9f8218"}, "speechResponse": "I'm happy to help :)"}]
--------------------------------------------------------------------------------
/frontend/src/app/agent/intent/intent.component.html:
--------------------------------------------------------------------------------
1 |
2 | {{message}}
3 |
4 |
116 |
--------------------------------------------------------------------------------
/app/intents/controllers.py:
--------------------------------------------------------------------------------
1 | import os
2 | from io import StringIO
3 |
4 | from bson.json_util import dumps
5 | from bson.json_util import loads
6 | from bson.objectid import ObjectId
7 | from flask import Blueprint, request, Response
8 | from flask import abort
9 | from flask import current_app as app
10 | from flask import send_file
11 |
12 | from app.commons import build_response
13 | from app.commons.utils import update_document
14 | from app.intents.models import ApiDetails
15 | from app.intents.models import Intent
16 | from app.intents.models import Parameter
17 | from app.nlu.tasks import train_models
18 |
19 | intents = Blueprint('intents_blueprint', __name__,
20 | url_prefix='/intents')
21 |
22 |
23 | @intents.route('/', methods=['POST'])
24 | def create_intent():
25 | """
26 | Create a story from the provided json
27 | :return:
28 | """
29 | content = request.get_json(silent=True)
30 |
31 | intent = Intent()
32 | intent.name = content.get("name")
33 | intent.intentId = content.get("intentId")
34 | intent.speechResponse = content.get("speechResponse")
35 | intent.trainingData = []
36 |
37 | if content.get("apiTrigger") is True:
38 | intent.apiTrigger = True
39 | api_details = ApiDetails()
40 | isJson = content.get("apiDetails").get("isJson")
41 | api_details.isJson = isJson
42 | if isJson:
43 | api_details.jsonData = content.get("apiDetails").get("jsonData")
44 |
45 | api_details.url = content.get("apiDetails").get("url")
46 | api_details.headers = content.get("apiDetails").get("headers")
47 | api_details.requestType = content.get("apiDetails").get("requestType")
48 | intent.apiDetails = api_details
49 | else:
50 | intent.apiTrigger = False
51 |
52 | if content.get("parameters"):
53 | for param in content.get("parameters"):
54 | parameter = Parameter()
55 | update_document(parameter, param)
56 | intent.parameters.append(parameter)
57 | try:
58 | story_id = intent.save()
59 | except Exception as e:
60 | return build_response.build_json({"error": str(e)})
61 |
62 | return build_response.build_json({
63 | "_id": str(story_id.id)
64 | })
65 |
66 |
67 | @intents.route('/')
68 | def read_intents():
69 | """
70 | find list of intents for the agent
71 | :return:
72 | """
73 | all_intents = Intent.objects
74 | return build_response.sent_json(all_intents.to_json())
75 |
76 |
77 | @intents.route('/')
78 | def read_intent(id):
79 | """
80 | Find details for the given intent id
81 | :param id:
82 | :return:
83 | """
84 | return Response(response=dumps(
85 | Intent.objects.get(
86 | id=ObjectId(id)).to_mongo().to_dict()),
87 | status=200,
88 | mimetype="application/json")
89 |
90 |
91 | @intents.route('/', methods=['PUT'])
92 | def update_intent(id):
93 | """
94 | Update a story from the provided json
95 | :return:
96 | """
97 | json_data = loads(request.get_data())
98 | intent = Intent.objects.get(id=ObjectId(id))
99 | intent = update_document(intent, json_data)
100 | intent.save()
101 | return 'success', 200
102 |
103 |
104 | @intents.route('/', methods=['DELETE'])
105 | def delete_intent(id):
106 | """
107 | Delete a intent
108 | :param id:
109 | :return:
110 | """
111 | Intent.objects.get(id=ObjectId(id)).delete()
112 |
113 | try:
114 | train_models()
115 | except BaseException:
116 | pass
117 |
118 | # remove NER model for the deleted story
119 | try:
120 | os.remove("{}/{}.model".format(app.config["MODELS_DIR"], id))
121 | except OSError:
122 | pass
123 | return build_response.sent_ok()
124 |
125 |
126 | @intents.route('/export', methods=['GET'])
127 | def export_intents():
128 | """
129 | Deserialize and export Mongoengines as jsonfile
130 | :return:
131 | """
132 | try:
133 | strIO = StringIO.StringIO()
134 | except AttributeError:
135 | strIO = StringIO()
136 |
137 | strIO.write(Intent.objects.to_json())
138 | strIO.seek(0)
139 | return send_file(strIO,
140 | attachment_filename="iky_intents.json",
141 | as_attachment=True)
142 |
143 |
144 | @intents.route('/import', methods=['POST'])
145 | def import_intents():
146 | """
147 | Convert json files to Intents objects and insert to MongoDB
148 | :return:
149 | """
150 | # check if the post request has the file part
151 | if 'file' not in request.files:
152 | abort(400, 'No file part')
153 | json_file = request.files['file']
154 | all_intents = import_json(json_file)
155 |
156 | return build_response.build_json({"num_intents_created": len(all_intents)})
157 |
158 |
159 | def import_json(json_file):
160 | json_data = json_file.read()
161 | # intents = Intent.objects.from_json(json_data)
162 | all_intents = loads(json_data)
163 |
164 | creates_intents = []
165 | for intent in all_intents:
166 | new_intent = Intent()
167 | new_intent = update_document(new_intent, intent)
168 | new_intent.save()
169 | creates_intents.append(new_intent)
170 | return creates_intents
171 |
--------------------------------------------------------------------------------
/frontend/src/assets/widget/style.css:
--------------------------------------------------------------------------------
1 | body{
2 | background: #f6f6f6;
3 | margin: 0;
4 | padding: 0;
5 | font-family: 'Open Sans', sans-serif;
6 | overflow: -moz-scrollbars-none;
7 | position: relative;
8 | }
9 |
10 | .iky_chatbox{
11 | width: 400px;
12 | position: fixed;
13 | right: 20px;
14 | bottom: 20px;
15 | font-size: 14px;
16 | }
17 | .iky_container{
18 | width: cacl(100% - 20px);
19 | height: auto;
20 | background: #fff;
21 | position: relative;
22 | height: 450px;
23 | padding: 50px 10px 0;
24 | box-shadow: 0 0 80px -30px #a0a0a0;
25 | border-radius: 10px;
26 | }
27 | .iky_input{
28 | border-top: 1px solid #ded9d9;
29 | width: 100%;
30 |
31 | }
32 | .iky_input input{
33 | padding: 10px;
34 | font-size: 20px;
35 | font-weight: lighter;
36 | width: calc(100% - 24px);
37 | border: 0px;
38 | border-radius: 5px;
39 | margin-bottom: 5px;
40 | }
41 | .iky_input input:focus{
42 | outline: none;
43 | color: #666;
44 | }
45 | .iky_btn_action{
46 | width: 100%;
47 | position: relative;
48 | /*bottom: -60px;*/
49 | left: 0;
50 | padding: 0 0;
51 | height: 50px;
52 |
53 | }
54 | .iky_action{
55 | float: right;
56 | border-radius: 100px;
57 | background:#124E78;
58 | padding: 6px 20px;
59 | border: none;
60 | color: #fff;
61 | font-size: 18px;
62 | line-height: 25px;
63 | margin-top: 20px;
64 | }
65 | .iky_action:focus{
66 | outline: none;
67 | }
68 | .iky_smile{
69 | width: 25px;
70 | float: right;
71 | margin-left: 5px
72 | }
73 |
74 | .iky_chat_holder{
75 | position: relative;
76 | height: 400px;
77 | overflow: hidden;
78 | padding:0;
79 | margin: 0;
80 | }
81 | .iky_Chat_container{
82 | margin: 0px;
83 | padding:0px 10px 0px 10px;
84 | width: calc(100% - 20px);
85 | height: 100%;
86 | overflow-y: scroll;
87 | list-style-type: none;
88 | }
89 | .iky_Chat_container::-webkit-scrollbar {
90 | width: 0px; /* remove scrollbar space */
91 | background: transparent; /* optional: just make scrollbar invisible */
92 | }
93 |
94 | .message_row{
95 | float: left;
96 | min-height: 20px;
97 | border-radius: 100px;
98 | padding: .5rem 1rem;
99 | margin-bottom: .2rem;
100 | clear: both;
101 |
102 | }
103 | .iky_text{
104 | width: auto;
105 | max-width: 85%;
106 | display: inline-block;
107 |
108 | padding: 7px 13px;
109 | border-radius: 15px;
110 | color: #595a5a;
111 | background-color: #ebebeb;
112 |
113 | }
114 | .iky_user_text{
115 | width: auto;
116 | max-width: 85%;
117 | display: inline-block;
118 |
119 | padding: 7px 13px;
120 | border-radius: 15px;
121 | color: #595a5a;
122 | background-color: #ebebeb;
123 |
124 | float: right;
125 | color: #f7f8f8;
126 | background-color: #28635a;
127 | }
128 |
129 | .iky_sugestions{
130 | width: 370px;
131 | padding: .5rem 0;
132 | height: 60px;
133 | overflow-x: scroll;
134 | overflow-y: hidden;
135 | white-space: nowrap;
136 | overflow: -moz-scrollbars-vertical;
137 | }
138 | .iky_sugestions span{
139 | background: #f6f6f6;
140 | color: #000;
141 | border-radius: 10rem;
142 | border: 1px dashed #999;
143 | padding: .3rem .5rem;
144 | display: inline-block;
145 | margin-right: .5rem;
146 |
147 |
148 | }
149 | .iky_menu{
150 | position: absolute;
151 | right: 5px;
152 | top: 5px;
153 | width: 100px;
154 | padding: 5px;
155 |
156 | }
157 | .btn_close{
158 | text-align: center;
159 | border-radius: 100px;
160 | background: #ccc;
161 | color: #494747;
162 | border: none;
163 | float: right;
164 | outline: none;
165 | border: 1px solid #ccc;
166 |
167 | font-size: 25px;
168 | font-weight: lighter;
169 | display: inline-block;
170 | line-height: 0px;
171 | padding: 11px 5px;
172 |
173 | }
174 | .btn_close:before {
175 | content: "x";
176 |
177 | }
178 |
179 | .typing-indicator {
180 | background-color: #ebebeb;
181 | will-change: transform;
182 | width: auto;
183 | border-radius:25px;
184 | padding-top: 10px;
185 | display: table;
186 | -webkit-animation: 2s bulge infinite ease-out;
187 | animation: 2s bulge infinite ease-out;
188 | }
189 | .typing-indicator span {
190 | height: 15px;
191 | width: 15px;
192 | float: left;
193 | margin: 0 1px;
194 | background-color: #9E9EA1;
195 | display: block;
196 | border-radius: 50%;
197 | opacity: 0.4;
198 | margin-bottom: 0px;
199 | }
200 | .typing-indicator span:nth-of-type(1) {
201 | -webkit-animation: 1s blink infinite 0.3333s;
202 | animation: 1s blink infinite 0.3333s;
203 | }
204 | .typing-indicator span:nth-of-type(2) {
205 | -webkit-animation: 1s blink infinite 0.6666s;
206 | animation: 1s blink infinite 0.6666s;
207 | }
208 | .typing-indicator span:nth-of-type(3) {
209 | -webkit-animation: 1s blink infinite 0.9999s;
210 | animation: 1s blink infinite 0.9999s;
211 | }
212 |
213 | @-webkit-keyframes blink {
214 | 50% {
215 | opacity: 1;
216 | }
217 | }
218 |
219 | @keyframes blink {
220 | 50% {
221 | opacity: 1;
222 | }
223 | }
224 | @-webkit-keyframes bulge {
225 | 50% {
226 | -webkit-transform: scale(1.05);
227 | transform: scale(1.05);
228 | }
229 | }
230 | @keyframes bulge {
231 | 50% {
232 | -webkit-transform: scale(1.05);
233 | transform: scale(1.05);
234 | }
235 | }
--------------------------------------------------------------------------------
/frontend/dist/assets/widget/style.css:
--------------------------------------------------------------------------------
1 | body{
2 | background: #f6f6f6;
3 | margin: 0;
4 | padding: 0;
5 | font-family: 'Open Sans', sans-serif;
6 | overflow: -moz-scrollbars-none;
7 | position: relative;
8 | }
9 |
10 | .iky_chatbox{
11 | width: 400px;
12 | position: fixed;
13 | right: 20px;
14 | bottom: 20px;
15 | font-size: 14px;
16 | }
17 | .iky_container{
18 | width: cacl(100% - 20px);
19 | height: auto;
20 | background: #fff;
21 | position: relative;
22 | height: 450px;
23 | padding: 50px 10px 0;
24 | box-shadow: 0 0 80px -30px #a0a0a0;
25 | border-radius: 10px;
26 | }
27 | .iky_input{
28 | border-top: 1px solid #ded9d9;
29 | width: 100%;
30 |
31 | }
32 | .iky_input input{
33 | padding: 10px;
34 | font-size: 20px;
35 | font-weight: lighter;
36 | width: calc(100% - 24px);
37 | border: 0px;
38 | border-radius: 5px;
39 | margin-bottom: 5px;
40 | }
41 | .iky_input input:focus{
42 | outline: none;
43 | color: #666;
44 | }
45 | .iky_btn_action{
46 | width: 100%;
47 | position: relative;
48 | /*bottom: -60px;*/
49 | left: 0;
50 | padding: 0 0;
51 | height: 50px;
52 |
53 | }
54 | .iky_action{
55 | float: right;
56 | border-radius: 100px;
57 | background:#124E78;
58 | padding: 6px 20px;
59 | border: none;
60 | color: #fff;
61 | font-size: 18px;
62 | line-height: 25px;
63 | margin-top: 20px;
64 | }
65 | .iky_action:focus{
66 | outline: none;
67 | }
68 | .iky_smile{
69 | width: 25px;
70 | float: right;
71 | margin-left: 5px
72 | }
73 |
74 | .iky_chat_holder{
75 | position: relative;
76 | height: 400px;
77 | overflow: hidden;
78 | padding:0;
79 | margin: 0;
80 | }
81 | .iky_Chat_container{
82 | margin: 0px;
83 | padding:0px 10px 0px 10px;
84 | width: calc(100% - 20px);
85 | height: 100%;
86 | overflow-y: scroll;
87 | list-style-type: none;
88 | }
89 | .iky_Chat_container::-webkit-scrollbar {
90 | width: 0px; /* remove scrollbar space */
91 | background: transparent; /* optional: just make scrollbar invisible */
92 | }
93 |
94 | .message_row{
95 | float: left;
96 | min-height: 20px;
97 | border-radius: 100px;
98 | padding: .5rem 1rem;
99 | margin-bottom: .2rem;
100 | clear: both;
101 |
102 | }
103 | .iky_text{
104 | width: auto;
105 | max-width: 85%;
106 | display: inline-block;
107 |
108 | padding: 7px 13px;
109 | border-radius: 15px;
110 | color: #595a5a;
111 | background-color: #ebebeb;
112 |
113 | }
114 | .iky_user_text{
115 | width: auto;
116 | max-width: 85%;
117 | display: inline-block;
118 |
119 | padding: 7px 13px;
120 | border-radius: 15px;
121 | color: #595a5a;
122 | background-color: #ebebeb;
123 |
124 | float: right;
125 | color: #f7f8f8;
126 | background-color: #28635a;
127 | }
128 |
129 | .iky_sugestions{
130 | width: 370px;
131 | padding: .5rem 0;
132 | height: 60px;
133 | overflow-x: scroll;
134 | overflow-y: hidden;
135 | white-space: nowrap;
136 | overflow: -moz-scrollbars-vertical;
137 | }
138 | .iky_sugestions span{
139 | background: #f6f6f6;
140 | color: #000;
141 | border-radius: 10rem;
142 | border: 1px dashed #999;
143 | padding: .3rem .5rem;
144 | display: inline-block;
145 | margin-right: .5rem;
146 |
147 |
148 | }
149 | .iky_menu{
150 | position: absolute;
151 | right: 5px;
152 | top: 5px;
153 | width: 100px;
154 | padding: 5px;
155 |
156 | }
157 | .btn_close{
158 | text-align: center;
159 | border-radius: 100px;
160 | background: #ccc;
161 | color: #494747;
162 | border: none;
163 | float: right;
164 | outline: none;
165 | border: 1px solid #ccc;
166 |
167 | font-size: 25px;
168 | font-weight: lighter;
169 | display: inline-block;
170 | line-height: 0px;
171 | padding: 11px 5px;
172 |
173 | }
174 | .btn_close:before {
175 | content: "x";
176 |
177 | }
178 |
179 | .typing-indicator {
180 | background-color: #ebebeb;
181 | will-change: transform;
182 | width: auto;
183 | border-radius:25px;
184 | padding-top: 10px;
185 | display: table;
186 | -webkit-animation: 2s bulge infinite ease-out;
187 | animation: 2s bulge infinite ease-out;
188 | }
189 | .typing-indicator span {
190 | height: 15px;
191 | width: 15px;
192 | float: left;
193 | margin: 0 1px;
194 | background-color: #9E9EA1;
195 | display: block;
196 | border-radius: 50%;
197 | opacity: 0.4;
198 | margin-bottom: 0px;
199 | }
200 | .typing-indicator span:nth-of-type(1) {
201 | -webkit-animation: 1s blink infinite 0.3333s;
202 | animation: 1s blink infinite 0.3333s;
203 | }
204 | .typing-indicator span:nth-of-type(2) {
205 | -webkit-animation: 1s blink infinite 0.6666s;
206 | animation: 1s blink infinite 0.6666s;
207 | }
208 | .typing-indicator span:nth-of-type(3) {
209 | -webkit-animation: 1s blink infinite 0.9999s;
210 | animation: 1s blink infinite 0.9999s;
211 | }
212 |
213 | @-webkit-keyframes blink {
214 | 50% {
215 | opacity: 1;
216 | }
217 | }
218 |
219 | @keyframes blink {
220 | 50% {
221 | opacity: 1;
222 | }
223 | }
224 | @-webkit-keyframes bulge {
225 | 50% {
226 | -webkit-transform: scale(1.05);
227 | transform: scale(1.05);
228 | }
229 | }
230 | @keyframes bulge {
231 | 50% {
232 | -webkit-transform: scale(1.05);
233 | transform: scale(1.05);
234 | }
235 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [](https://gitter.im/ai-chatbot-framework/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://travis-ci.org/alfredfrancis/ai-chatbot-framework)
4 |
5 |
6 |
7 | ### An AI Chatbot framework built in Python
8 |
9 | Building a chatbot can sound daunting, but it’s totally doable. AI Chatbot Framework is an AI powered conversational dialog interface built in Python. With this tool, it’s easy to create Natural Language conversational scenarios with no coding efforts whatsoever. The smooth UI makes it effortless to create and train conversations to the bot and it continuously gets smarter as it learns from conversations it has with people. AI Chatbot Framework can live on any channel of your choice (such as Messenger, Slack etc.) by integrating it’s API with that platform.
10 |
11 | You don’t need to be an expert at artificial intelligence to create an awesome chatbot that has AI capabilities. With this boilerplate project you can create an AI powered chatting machine in no time.There may be scores of bugs. So feel free to contribute via pull requests.
12 |
13 | 
14 |
15 | ### Installation
16 |
17 | ### Using docker-compose (Recommended)
18 | ```sh
19 | docker-compose build
20 | docker-compose up -d
21 | docker-compose exec iky_backend python manage.py migrate
22 | ```
23 |
24 | ### Using Docker
25 | ```sh
26 |
27 | # build docker images
28 | docker build -t iky_backend:2.0.0 .
29 | docker build -t iky_gateway:2.0.0 frontend/.
30 |
31 | # start a mongodb server
32 | docker run --name mongodb -d mongo:3.6
33 |
34 | # start iky backend
35 | docker run --name=iky_backend --link mongodb:mongodb -e="APPLICATION_ENV=Production" iky_backend:2.0.0
36 |
37 | # setup default intents
38 | docker exec -it iky_backend python manage.py migrate
39 |
40 | # start iky gateway with frontend
41 | docker run --name=iky_gateway --link iky_backend:iky_backend -p 8080:80 iky_gateway:2.0.0
42 |
43 | ```
44 |
45 | ### without docker
46 |
47 | * Setup Virtualenv and install python requirements
48 | ```sh
49 | make setup
50 |
51 | make run_dev
52 |
53 | source venv/bin/activate && python manage.py migrate
54 | ```
55 | * Production
56 | ```sh
57 | make run_prod
58 | ```
59 | * Open http://localhost:8080/
60 |
61 | #### Update Frontend Dist
62 | * Run Development mode
63 | ```sh
64 | cd frontend
65 | npm install
66 | ng serve
67 | ```
68 | * Take Production build
69 | ```sh
70 | cd frontend
71 | ng build --prod --optimize
72 | ```
73 |
74 | ### Heroku
75 | [](https://heroku.com/deploy)
76 |
77 | * add your dev/production configurations in config.py
78 |
79 | ### DB
80 |
81 | #### Restore
82 | You can import some default intents using follwing steps
83 |
84 | - goto http://localhost:8080/agent/default/settings
85 | - click 'choose file'
86 | - choose 'examples/default_intents.json file'
87 | - click import
88 |
89 | ### Screenshots
90 |
91 | 
92 | ---
93 | 
94 | ---
95 | 
96 | ---
97 | 
98 | ### Tutorial
99 |
100 | Checkout this basic tutorial on youtube,
101 |
102 | [](https://www.youtube.com/watch?v=S1Fj7WinaBA)
103 |
104 |
105 | Watch tutorial on [Fullfilling your Chatbot Intent with an API Call - Recipe Search Bot](https://www.youtube.com/watch?v=gqO69ojLobQ)
106 |
107 | Please visit my [website](http://alfredfrancis.github.io) to see my personal chatbot in action
108 |
109 | ### Todos
110 | * Write Unit Tests
111 | * Multilingual Intent Classifier
112 | * PyCRFSuite to sklearn-crfsuite migration
113 | * Support follow up conversations
114 |
115 | ### Dependencies documentations
116 | * [SKLearn documentation](http://scikit-learn.org/)
117 | * [CRFsuite documentation](http://www.chokkan.org/software/crfsuite/)
118 | * [python CRfSuite](https://python-crfsuite.readthedocs.io/en/latest/)
119 |
120 | ## Contributors
121 |
122 | [](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/links/0)[](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/links/1)[](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/links/2)[](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/links/3)[](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/links/4)[](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/links/5)[](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/links/6)[](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/links/7)
123 |
124 | **Free Software, Hell Yeah!**
125 |
126 |
127 | _Made with :heart: at God's Own Country_.
128 |
--------------------------------------------------------------------------------
/app/nlu/classifiers/tf_intent_classifer.py:
--------------------------------------------------------------------------------
1 | import os
2 | import time
3 |
4 | import cloudpickle
5 | import numpy as np
6 | import spacy
7 | import tensorflow as tf
8 | from sklearn.preprocessing import LabelBinarizer
9 | from tensorflow.python.keras import Sequential
10 | from tensorflow.python.layers.core import Dense
11 | from tensorflow.python.layers.core import Dropout
12 |
13 | np.random.seed(1)
14 |
15 |
16 | class TfIntentClassifier:
17 |
18 | def __init__(self):
19 | self.model = None
20 | self.nlp = spacy.load('en')
21 | self.label_encoder = LabelBinarizer()
22 | self.graph = None
23 |
24 | def train(self, X, y, models_dir=None, verbose=True):
25 | """
26 | Train intent classifier for given training data
27 | :param X:
28 | :param y:
29 | :param models_dir:
30 | :param verbose:
31 | :return:
32 | """
33 |
34 | def create_model():
35 | """
36 | Define and return tensorflow model.
37 | """
38 | model = Sequential()
39 | model.add(Dense(256, activation=tf.nn.relu,
40 | input_shape=(vocab_size,)))
41 | model.add(Dropout(0.2))
42 | model.add(Dense(128, activation=tf.nn.relu))
43 | model.add(Dropout(0.2))
44 | model.add(Dense(num_labels, activation=tf.nn.softmax))
45 |
46 | """
47 | tried:
48 | loss functions => categorical_crossentropy, binary_crossentropy
49 | optimizers => adam, rmsprop
50 | """
51 |
52 | model.compile(loss='categorical_crossentropy',
53 | optimizer='adam',
54 | metrics=['accuracy'])
55 |
56 | model.summary()
57 |
58 | return model
59 |
60 | # spacy context vector size
61 | vocab_size = 384
62 |
63 | # create spacy doc vector matrix
64 | x_train = np.array([list(self.nlp(x).vector) for x in X])
65 |
66 | num_labels = len(set(y))
67 | self.label_encoder.fit(y)
68 | y_train = self.label_encoder.transform(y)
69 |
70 | del self.model
71 | tf.keras.backend.clear_session()
72 | time.sleep(3)
73 |
74 | self.model = create_model()
75 | # start training
76 | self.model.fit(x_train, y_train, shuffle=True, epochs=300, verbose=1)
77 |
78 | if models_dir:
79 | tf.keras.models.save_model(
80 | self.model,
81 | os.path.join(models_dir, "tf_intent_model.hd5")
82 |
83 | )
84 | if verbose:
85 | print("TF Model written out to {}"
86 | .format(os.path.join(models_dir, "tf_intent_model.hd5")))
87 |
88 | cloudpickle.dump(self.label_encoder, open(
89 | os.path.join(models_dir, "labels.pkl"), 'wb'))
90 |
91 | if verbose:
92 | print("Labels written out to {}"
93 | .format(os.path.join(models_dir, "labels.pkl")))
94 |
95 | def load(self, models_dir):
96 | try:
97 | del self.model
98 |
99 | tf.keras.backend.clear_session()
100 |
101 | self.model = tf.keras.models.load_model(
102 | os.path.join(models_dir, "tf_intent_model.hd5"), compile=True)
103 |
104 | self.graph = tf.get_default_graph()
105 |
106 | print("Tf model loaded")
107 |
108 | with open(os.path.join(models_dir, "labels.pkl"), 'rb') as f:
109 | self.label_encoder = cloudpickle.load(f)
110 | print("Labels model loaded")
111 |
112 | except IOError:
113 | return False
114 |
115 | def predict(self, text):
116 | """
117 | Predict class label for given model
118 | :param text:
119 | :return:
120 | """
121 | return self.process(text)
122 |
123 | def predict_proba(self, x):
124 | """Given a bow vector of an input text, predict most probable label.
125 | Returns only the most likely label.
126 |
127 | :param x: raw input text
128 | :return: tuple of first, the most probable label and second,
129 | its probability"""
130 |
131 | x_predict = [self.nlp(x).vector]
132 | with self.graph.as_default():
133 | pred_result = self.model.predict(np.array([x_predict[0]]))
134 | sorted_indices = np.fliplr(np.argsort(pred_result, axis=1))
135 | return sorted_indices, pred_result[:, sorted_indices]
136 |
137 | def process(self, x, return_type="intent", INTENT_RANKING_LENGTH=5):
138 | """Returns the most likely intent and
139 | its probability for the input text."""
140 |
141 | if not self.model:
142 | print("no class")
143 | intent = None
144 | intent_ranking = []
145 | else:
146 | intents, probabilities = self.predict_proba(x)
147 | intents = [self.label_encoder.classes_[intent]
148 | for intent in intents.flatten()]
149 | probabilities = probabilities.flatten()
150 |
151 | if len(intents) > 0 and len(probabilities) > 0:
152 | ranking = list(zip(list(intents), list(probabilities)))
153 | ranking = ranking[:INTENT_RANKING_LENGTH]
154 |
155 | intent = {"intent": intents[0],
156 | "confidence": float("%.2f" % probabilities[0])}
157 |
158 | intent_ranking = [{"intent": intent_name,
159 | "confidence": float("%.2f" % score)}
160 | for intent_name, score in ranking]
161 |
162 | else:
163 | intent = {"name": None, "confidence": 0.0}
164 | intent_ranking = []
165 | if return_type == "intent":
166 | return intent
167 | else:
168 | return intent_ranking
169 |
--------------------------------------------------------------------------------
/app/nlu/classifiers/sklearn_intent_classifer.py:
--------------------------------------------------------------------------------
1 | import string
2 |
3 | import cloudpickle
4 | import numpy as np
5 | import spacy
6 | from sklearn.feature_extraction.stop_words import ENGLISH_STOP_WORDS
7 | from sklearn.feature_extraction.text import TfidfVectorizer
8 | from sklearn.model_selection import GridSearchCV
9 | from sklearn.pipeline import Pipeline
10 | from sklearn.svm import SVC
11 | from spacy.lang.en.stop_words import STOP_WORDS
12 |
13 |
14 | class SklearnIntentClassifier:
15 |
16 | def __init__(self):
17 |
18 | self.model = None
19 |
20 | self.spacynlp = spacy.load('en')
21 |
22 | self.stopwords = set(STOP_WORDS +
23 | ["n't", "'s", "'m", "ca"] +
24 | list(ENGLISH_STOP_WORDS))
25 |
26 | self.punctuations = " ".join(string.punctuation).split(" ") + \
27 | ["-----", "---", "...", "'ve"]
28 |
29 | def spacy_tokenizer(self, sentence):
30 | """
31 | perform basic cleaning,tokenization and lemmatization
32 | :param sentence:
33 | :return list of clean tokens:
34 | """
35 | tokens = self.spacynlp(sentence)
36 |
37 | tokens = [tok.lemma_.lower().strip() if
38 | tok.lemma_ != "-PRON-" else tok.lower_ for tok in tokens]
39 |
40 | tokens = [tok for tok in tokens if
41 | (tok not in self.stopwords and tok not in self.punctuations)]
42 |
43 | while "" in tokens:
44 | tokens.remove("")
45 | while " " in tokens:
46 | tokens.remove(" ")
47 | while "\n" in tokens:
48 | tokens.remove("\n")
49 | while "\n\n" in tokens:
50 | tokens.remove("\n\n")
51 | return tokens
52 |
53 | def train(self, X, y, outpath=None, verbose=True):
54 | """
55 | Train intent classifier for given training data
56 | :param X:
57 | :param y:
58 | :param outpath:
59 | :param verbose:
60 | :return:
61 | """
62 |
63 | def build(X, y=None):
64 | """
65 | Inner build function that builds a single model.
66 | :param X:
67 | :param y:
68 | :return:
69 | """
70 | model = Pipeline([
71 | ('vectorizer', TfidfVectorizer(
72 | tokenizer=self.spacy_tokenizer,
73 | preprocessor=None, lowercase=False)
74 | ),
75 |
76 | ('clf', SVC(C=1, kernel="linear",
77 | probability=True, class_weight='balanced')
78 | )])
79 |
80 | items, counts = np.unique(y, return_counts=True)
81 |
82 | cv_splits = max(2, min(5, np.min(counts) // 5))
83 |
84 | Cs = [0.01, 0.25, 1, 2, 5, 10, 20, 100]
85 | param_grid = {'clf__C': Cs, 'clf__kernel': ["linear"]}
86 | grid_search = GridSearchCV(model,
87 | param_grid=param_grid,
88 | scoring='f1_weighted',
89 | cv=cv_splits,
90 | verbose=2,
91 | n_jobs=-1
92 | )
93 | grid_search.fit(X, y)
94 |
95 | return grid_search
96 |
97 | model = build(X, y)
98 |
99 | if outpath:
100 | with open(outpath, 'wb') as f:
101 | cloudpickle.dump(model, f)
102 |
103 | if verbose:
104 | print("Model written out to {}".format(outpath))
105 |
106 | return model
107 |
108 | def load(self, PATH):
109 | """
110 | load trained model from given path
111 | :param PATH:
112 | :return:
113 | """
114 | try:
115 | with open(PATH, 'rb') as f:
116 | self.model = cloudpickle.load(f)
117 | except IOError:
118 | return False
119 |
120 | def predict(self, text, return_all=False, INTENT_RANKING_LENGTH=5):
121 | """
122 | Predict class label for given model
123 | """
124 | return self.process(text, return_all, INTENT_RANKING_LENGTH)
125 |
126 | def predict_proba(self, X):
127 | """Given a bow vector of an input text, predict most probable label.
128 | Returns only the most likely label.
129 |
130 | :param X: bow of input text
131 | :return: tuple of first, the most probable label
132 | and second, its probability"""
133 |
134 | pred_result = self.model.predict_proba(X)
135 | print(pred_result)
136 | # sort the probabilities retrieving the indices of the elements
137 | sorted_indices = np.fliplr(np.argsort(pred_result, axis=1))
138 | return sorted_indices, pred_result[:, sorted_indices]
139 |
140 | def process(self, x, return_all=False, INTENT_RANKING_LENGTH=5):
141 | """Returns the most likely intent and
142 | its probability for the input text."""
143 |
144 | if not self.model:
145 | print("no class")
146 | intent = None
147 | intent_ranking = []
148 | else:
149 | intents, probabilities = self.predict_proba([x])
150 | intents = [self.model.classes_[intent]
151 | for intent in intents.flatten()]
152 | probabilities = probabilities.flatten()
153 |
154 | if len(intents) > 0 and len(probabilities) > 0:
155 | ranking = list(zip(list(intents), list(probabilities)))
156 | ranking = ranking[:INTENT_RANKING_LENGTH]
157 |
158 | intent = {"intent": intents[0], "confidence": probabilities[0]}
159 | intent_ranking = [{"intent": intent_name, "confidence": score}
160 | for intent_name, score in ranking]
161 |
162 | else:
163 | intent = {"name": None, "confidence": 0.0}
164 | intent_ranking = []
165 |
166 | if return_all:
167 | return intent_ranking
168 | else:
169 | return intent
170 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/train/train.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input } from '@angular/core';
2 | import { FormGroup, FormBuilder } from '@angular/forms';
3 | import { ActivatedRoute, Params, Router } from '@angular/router';
4 |
5 | import {IntentService } from '../../services/intent.service';
6 |
7 | import { TrainingService } from '../../services/training.service';
8 | import { EntitiesService } from '../../services/entities.service'
9 |
10 | @Component({
11 | selector: 'app-train',
12 | templateUrl: './train.component.html',
13 | styleUrls: ['./train.component.scss']
14 | })
15 | export class TrainComponent implements OnInit {
16 |
17 | selectionInfo = {
18 | "value":"",
19 | "begin":0,
20 | "end":0
21 | };
22 |
23 | intentId:string = null ;
24 | trainingData: Array;
25 | newExampleText: String;
26 | newEntityName: String;
27 |
28 | story: any;
29 | entities: Array = [];
30 |
31 | constructor(
32 | public storyService: IntentService,
33 | private _activatedRoute: ActivatedRoute,
34 | private _router: Router,
35 | private trainingService: TrainingService,
36 | private entitiesService: EntitiesService) {
37 |
38 | this.trainingData = []
39 |
40 | this.newEntityName = null;
41 | }
42 |
43 | ngOnInit() {
44 | this._activatedRoute.params.subscribe((params: Params) => {
45 | console.log("current intent " + params['intent_id'])
46 | this.intentId = params['intent_id']
47 | this.trainingService.getTrainingData(params['intent_id']).then(
48 | (result: Array)=>{
49 | this.trainingData = result;
50 | }
51 | )
52 |
53 | });
54 |
55 |
56 | this._activatedRoute.data
57 | .subscribe((data:{story:any}) => {
58 | console.log("selected intent =>>")
59 | console.log(data.story)
60 | this.story = data.story;
61 |
62 | });
63 |
64 | if (this.story) {
65 | if (this.story._id && this.story._id.$oid) {
66 | this.story._id = this.story._id.$oid;
67 | }
68 | }
69 |
70 | this.entitiesService.getEntities().then(
71 | (result: Array) => {
72 | this.entities = result
73 | }
74 | )
75 |
76 | }
77 |
78 | // 123456
79 | getAnnotatedText(example){
80 | let text = example.text
81 | example.entities.forEach(entity => {
82 | var key =entity.value;
83 | var regex = new RegExp(key,'g');
84 | text = text.replace(regex,' '+key+' ' );
85 | });
86 | return text
87 | }
88 | // updateValue($event,example_index){
89 | // this.trainingData[example_index]["text"]=$event.srcElement.outerText;
90 |
91 | // }
92 |
93 | addNewExample(){
94 | this.trainingData.unshift({
95 | "text":this.newExampleText,
96 | "entities":[]
97 | })
98 | this.newExampleText = "";
99 | }
100 |
101 | deleteExample(example_index){
102 | this.trainingData.splice(example_index,1)
103 | }
104 |
105 | deleteEntity(example_index,entity_index){
106 | this.trainingData[example_index].entities.splice(entity_index,1)
107 |
108 | }
109 |
110 | getSelectionInfo():any {
111 | let selection = window.getSelection();
112 | if (selection.anchorOffset == selection.extentOffset)
113 | return false;
114 |
115 | let result = {
116 | "value":selection.toString(),
117 | }
118 |
119 | if (selection.anchorOffset > selection.extentOffset)
120 | {
121 | result["begin"] = selection.extentOffset;
122 | result["end"] = selection.anchorOffset;
123 | }
124 | else if (selection.anchorOffset < selection.extentOffset){
125 | result["begin"] = selection.anchorOffset;
126 | result["end"] = selection.extentOffset;
127 | }
128 |
129 | return result;
130 |
131 | }
132 |
133 | addNewEntity(example_index){
134 | this.trainingData[example_index]["entities"].push({
135 | "value":this.selectionInfo.value,
136 | "begin":this.selectionInfo.begin,
137 | "end":this.selectionInfo.end,
138 | "name":this.newEntityName
139 | })
140 | this.newEntityName = null;
141 | }
142 |
143 | annotate(){
144 | // snap selection to the word
145 | this.snapSelectionToWord();
146 | let result = this.getSelectionInfo()
147 | if (result)
148 | this.selectionInfo = result;
149 |
150 | console.log(this.selectionInfo);
151 | }
152 |
153 | updateTrainingData(){
154 | this.trainingService.saveTrainingData(this.intentId,this.trainingData).then(()=>{
155 | console.log("Success");
156 | })
157 | }
158 |
159 | snapSelectionToWord() {
160 | var sel;
161 |
162 | // Check for existence of window.getSelection() and that it has a
163 | // modify() method. IE 9 has both selection APIs but no modify() method.
164 | if (window.getSelection && ((sel = window.getSelection())).modify) {
165 | sel = window.getSelection();
166 | if (!sel.isCollapsed) {
167 |
168 | // Detect if selection is backwards
169 | var range = document.createRange();
170 | range.setStart(sel.anchorNode, sel.anchorOffset);
171 | range.setEnd(sel.focusNode, sel.focusOffset);
172 | var backwards = range.collapsed;
173 | range.detach();
174 |
175 | // modify() works on the focus of the selection
176 | var endNode = sel.focusNode, endOffset = sel.focusOffset;
177 | sel.collapse(sel.anchorNode, sel.anchorOffset);
178 |
179 | var direction = [];
180 | if (backwards) {
181 | direction = ['backward', 'forward'];
182 | } else {
183 | direction = ['forward', 'backward'];
184 | }
185 |
186 | sel.modify("move", direction[0], "character");
187 | sel.modify("move", direction[1], "word");
188 | sel.extend(endNode, endOffset);
189 | sel.modify("extend", direction[1], "character");
190 | sel.modify("extend", direction[0], "word");
191 | }
192 | } else if ( (sel = (document).selection) && sel.type != "Control") {
193 | var textRange = sel.createRange();
194 | if (textRange.text) {
195 | textRange.expand("word");
196 | // Move the end back to not include the word's trailing space(s),
197 | // if necessary
198 | while (/\s$/.test(textRange.text)) {
199 | textRange.moveEnd("character", -1);
200 | }
201 | textRange.select();
202 | }
203 | }
204 | }
205 |
206 | //place curser at the end of content editable div
207 | placeCaretAtEnd(el) {
208 | el.focus();
209 | if (typeof window.getSelection != "undefined"
210 | && typeof document.createRange != "undefined") {
211 | var range = document.createRange();
212 | range.selectNodeContents(el);
213 | range.collapse(false);
214 | var sel = window.getSelection();
215 | sel.removeAllRanges();
216 | sel.addRange(range);
217 | } else if (typeof (document.body).createTextRange != "undefined") {
218 | var textRange = (document.body).createTextRange();
219 | textRange.moveToElementText(el);
220 | textRange.collapse(false);
221 | textRange.select();
222 | }
223 | }
224 |
225 | }
226 |
--------------------------------------------------------------------------------
/app/nlu/entity_extractor.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import pycrfsuite
4 | from flask import current_app as app
5 |
6 | from app.nlu import spacy_tokenizer
7 |
8 |
9 | class EntityExtractor:
10 | """
11 | Performs NER training, prediction, model import/export
12 | """
13 |
14 | def __init__(self, synonyms=[]):
15 | self.synonyms = synonyms
16 |
17 | def replace_synonyms(self, entities):
18 | """
19 | replace extracted entity values with
20 | root word by matching with synonyms dict.
21 | :param entities:
22 | :return:
23 | """
24 | for entity in entities.keys():
25 |
26 | entity_value = str(entities[entity])
27 |
28 | if entity_value.lower() in self.synonyms:
29 | entities[entity] = self.synonyms[entity_value.lower()]
30 |
31 | return entities
32 |
33 | def extract_features(self, sent, i):
34 | """
35 | Extract features for a given sentence
36 | :param sent:
37 | :param i:
38 | :return:
39 | """
40 | word = sent[i][0]
41 | postag = sent[i][1]
42 | features = [
43 | 'bias',
44 | 'word.lower=' + word.lower(),
45 | 'word[-3:]=' + word[-3:],
46 | 'word[-2:]=' + word[-2:],
47 | 'word.isupper=%s' % word.isupper(),
48 | 'word.istitle=%s' % word.istitle(),
49 | 'word.isdigit=%s' % word.isdigit(),
50 | 'postag=' + postag,
51 | 'postag[:2]=' + postag[:2],
52 | ]
53 | if i > 0:
54 | word1 = sent[i - 1][0]
55 | postag1 = sent[i - 1][1]
56 | features.extend([
57 | '-1:word.lower=' + word1.lower(),
58 | '-1:word.istitle=%s' % word1.istitle(),
59 | '-1:word.isupper=%s' % word1.isupper(),
60 | '-1:postag=' + postag1,
61 | '-1:postag[:2]=' + postag1[:2],
62 | ])
63 | else:
64 | features.append('BOS')
65 |
66 | if i < len(sent) - 1:
67 | word1 = sent[i + 1][0]
68 | postag1 = sent[i + 1][1]
69 | features.extend([
70 | '+1:word.lower=' + word1.lower(),
71 | '+1:word.istitle=%s' % word1.istitle(),
72 | '+1:word.isupper=%s' % word1.isupper(),
73 | '+1:postag=' + postag1,
74 | '+1:postag[:2]=' + postag1[:2],
75 | ])
76 | else:
77 | features.append('EOS')
78 |
79 | return features
80 |
81 | def sent_to_features(self, sent):
82 | """
83 | Extract features from training Data
84 | :param sent:
85 | :return:
86 | """
87 | return [self.extract_features(sent, i) for i in range(len(sent))]
88 |
89 | def sent_to_labels(self, sent):
90 | """
91 | Extract labels from training data
92 | :param sent:
93 | :return:
94 | """
95 | return [label for token, postag, label in sent]
96 |
97 | def sent_to_tokens(self, sent):
98 | """
99 | Extract tokens from training data
100 | :param sent:
101 | :return:
102 | """
103 | return [token for token, postag, label in sent]
104 |
105 | def train(self, train_sentences, model_name):
106 | """
107 | Train NER model for given model
108 | :param train_sentences:
109 | :param model_name:
110 | :return:
111 | """
112 | features = [self.sent_to_features(s) for s in train_sentences]
113 | labels = [self.sent_to_labels(s) for s in train_sentences]
114 |
115 | trainer = pycrfsuite.Trainer(verbose=False)
116 | for xseq, yseq in zip(features, labels):
117 | trainer.append(xseq, yseq)
118 |
119 | trainer.set_params({
120 | 'c1': 1.0, # coefficient for L1 penalty
121 | 'c2': 1e-3, # coefficient for L2 penalty
122 | 'max_iterations': 50, # stop earlier
123 |
124 | # include transitions that are possible, but not observed
125 | 'feature.possible_transitions': True
126 | })
127 | trainer.train('model_files/%s.model' % model_name)
128 | return True
129 |
130 | # Extract Labels from BIO tagged sentence
131 | def crf2json(self, tagged_sentence):
132 | """
133 | Extract label-value pair from NER prediction output
134 | :param tagged_sentence:
135 | :return:
136 | """
137 | labeled = {}
138 | labels = set()
139 | for s, tp in tagged_sentence:
140 | if tp != "O":
141 | label = tp[2:]
142 | if tp.startswith("B"):
143 | labeled[label] = s
144 | labels.add(label)
145 | elif tp.startswith("I") and (label in labels):
146 | labeled[label] += " %s" % s
147 | return labeled
148 |
149 | def extract_ner_labels(self, predicted_labels):
150 | """
151 | Extract name of labels from NER
152 | :param predicted_labels:
153 | :return:
154 | """
155 | labels = []
156 | for tp in predicted_labels:
157 | if tp != "O":
158 | labels.append(tp[2:])
159 | return labels
160 |
161 | def predict(self, model_name, sentence):
162 | """
163 | Predict NER labels for given model and query
164 | :param model_name:
165 | :param sentence:
166 | :return:
167 | """
168 | from app.nlu.tasks import pos_tagger
169 |
170 | doc = spacy_tokenizer(sentence)
171 | words = [token.text for token in doc]
172 | tagged_token = pos_tagger(sentence)
173 | tagger = pycrfsuite.Tagger()
174 | tagger.open("{}/{}.model".format(app.config["MODELS_DIR"], model_name))
175 | predicted_labels = tagger.tag(self.sent_to_features(tagged_token))
176 | extracted_entities = self.crf2json(
177 | zip(words, predicted_labels))
178 | return self.replace_synonyms(extracted_entities)
179 |
180 | @staticmethod
181 | def json2crf(training_data):
182 | """
183 | Takes json annotated data and converts to
184 | CRFSuite training data representation
185 | :param training_data:
186 | :return labeled_examples:
187 | """
188 | from app.nlu.tasks import sentence_tokenize, pos_tag_and_label
189 |
190 | labeled_examples = []
191 |
192 | for example in training_data:
193 | # POS tag and initialize bio label as 'O' for all the tokens
194 | tagged_example = pos_tag_and_label(example.get("text"))
195 |
196 | # find no of words before selection
197 | for enitity in example.get("entities"):
198 |
199 | try:
200 | begin_index = enitity.get("begin")
201 | end_index = enitity.get("end")
202 | # find no of words before the entity
203 | inverse_selection = example.get("text")[0:begin_index - 1]
204 | inverse_selection = sentence_tokenize(inverse_selection)
205 | inverse_selection = inverse_selection.split(" ")
206 | inverse_word_count = len(inverse_selection)
207 |
208 | # get the entity value from selection
209 | selection = example.get("text")[begin_index:end_index]
210 |
211 | tokens = sentence_tokenize(selection).split(" ")
212 |
213 | selection_word_count = len(tokens)
214 |
215 | # build BIO tagging
216 | for i in range(1, selection_word_count + 1):
217 | if i == 1:
218 | bio = "B-" + enitity.get("name")
219 | else:
220 | bio = "I-" + enitity.get("name")
221 | tagged_example[(inverse_word_count + i) - 1][2] = bio
222 | except:
223 | # catches and skips invalid offsets and annotation
224 | continue
225 |
226 | labeled_examples.append(tagged_example)
227 | return labeled_examples
228 |
--------------------------------------------------------------------------------
/frontend/dist/assets/widget/iky_widget.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var config = {
3 | "base_url": window.iky_base_url,
4 | "chat_context": window.chat_context
5 | }
6 |
7 | // Localize jQuery variable
8 | var jQuery;
9 |
10 | /******** Load jQuery if not present *********/
11 | if (window.jQuery === undefined) {
12 | var script_tag = document.createElement('script');
13 | script_tag.setAttribute("type", "text/javascript");
14 | script_tag.setAttribute("src",
15 | "http://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js");
16 | if (script_tag.readyState) {
17 | script_tag.onreadystatechange = function () { // For old versions of IE
18 | if (this.readyState == 'complete' || this.readyState == 'loaded') {
19 | scriptLoadHandler();
20 | }
21 | };
22 | } else {
23 | script_tag.onload = scriptLoadHandler;
24 | }
25 | // Try to find the head, otherwise default to the documentElement
26 | (document.getElementsByTagName("head")[0] || document.documentElement).appendChild(script_tag);
27 | } else {
28 | // The jQuery version on the window is the one we want to use
29 | jQuery = window.jQuery;
30 | main(config);
31 | }
32 |
33 | /******** Called once jQuery has loaded ******/
34 | function scriptLoadHandler() {
35 | // Restore $ and window.jQuery to their previous values and store the
36 | // new jQuery in our local jQuery variable
37 | jQuery = window.jQuery.noConflict(true);
38 | // Call our main function
39 | main(config);
40 | }
41 |
42 | /******** Our main function ********/
43 | function main(config) {
44 | jQuery(document).ready(function ($) {
45 | console.log("received", config)
46 | /******* Load CSS *******/
47 | var css_link = $(" ", {
48 | rel: "stylesheet",
49 | type: "text/css",
50 | href: config["base_url"] +"assets/widget/style.css"
51 | });
52 | css_link.appendTo('head');
53 |
54 |
55 | content = `
56 |
57 |
58 |
61 |
65 |
66 |
67 |
68 |
69 |
72 |
73 | `
74 |
75 | $("body").append(content);
76 |
77 | $(".iky_container").hide();
78 |
79 |
80 | /******* chat begins *******/
81 |
82 | if (typeof payload == "undefined") {
83 | payload =
84 | {
85 | "currentNode": "",
86 | "complete": true,
87 | "parameters": [],
88 | "extractedParameters": {},
89 | "missingParameters": [],
90 | "intent": {},
91 | "context": config["chat_context"],
92 | "input": "init_conversation",
93 | "speechResponse": []
94 | }
95 |
96 |
97 | }
98 |
99 | $.ajaxSetup({
100 | headers: {
101 | 'Content-Type': 'application/json',
102 | 'Accept': 'application/json'
103 | }
104 | });
105 |
106 | var send_req = (userQuery)=> {
107 | showTyping();
108 | // send request to bot
109 | payload["input"] = userQuery;
110 |
111 | $.post(config["base_url"]+'gateway/api/v1', JSON.stringify(payload))
112 | .done(((response)=>{
113 | successRoutes(response);}))
114 | .fail((x,t,m)=>{
115 | errorRoutes(x,t,m);
116 | });
117 |
118 | // $.ajax({
119 | // // url: config["base_url"]+'gateway/api/v1',
120 | // url: 'http://localhost:8080/gateway/api/v1',
121 | // type: 'POST',
122 | // data: JSON.stringify(payload),
123 | // contentType: 'application/json; charset=utf-8',
124 | // datatype: "json",
125 | // success: successRoutes,
126 | // error: errorRoutes
127 |
128 | // });
129 | return true;
130 | };
131 |
132 | function showTyping(){
133 | $("input.iky_user_input_field").prop('disabled', true);
134 | html_data = ' ';
135 | $("ul.iky_Chat_container").append(html_data);
136 | scrollToBottom();
137 |
138 | }
139 |
140 | function hideTyping(){
141 | $('li#typing-indicator').remove();
142 | $("input.iky_user_input_field").prop('disabled', false);
143 | }
144 |
145 |
146 | successRoutes = function (response) {
147 | hideTyping();
148 | var responseObject;
149 | if (typeof response == 'object') {
150 | responseObject = response;
151 | }
152 | else {
153 | var parsedResponse = JSON.parse(response);
154 | responseObject = parsedResponse.responseData;
155 | }
156 | payload = responseObject;
157 | put_text(responseObject);
158 | };
159 |
160 | errorRoutes = function (x, t, m) {
161 | hideTyping();
162 | responseObject = payload;
163 | if (t === "timeout") {
164 | responseObject["speechResponse"] = ["Due to band-width constraints, I'm not able to serve you now","please try again later"]
165 | } else {
166 | responseObject["speechResponse"] = ["I'm not able to serve you at the moment"," please try again later"]
167 | }
168 | payload = responseObject;
169 | put_text(responseObject);
170 | };
171 |
172 | function scrollToBottom() {
173 | $(".iky_Chat_container").stop().animate({ scrollTop: $(".iky_Chat_container")[0].scrollHeight}, 1000);
174 | }
175 |
176 | var put_text = function (bot_say) {
177 | $.each(bot_say["speechResponse"],function (index, data) {
178 | html_data = ''+ data +' ';
179 | $("ul.iky_Chat_container").append(html_data);
180 | });
181 | scrollToBottom();
182 | };
183 |
184 |
185 |
186 | send_req("init_conversation");
187 |
188 |
189 | $('.iky_user_input_field').keydown(function (e) {
190 | if (e.keyCode == 13) {
191 | userQuery = $(".iky_user_input_field").val();
192 | $(".iky_user_input_field").val("");
193 | html_data = ' '+userQuery+' ';
194 | $("ul.iky_Chat_container").append(html_data);
195 | send_req(userQuery);
196 |
197 | }
198 | })
199 | $(".iky_action, .btn_close").click(function(){
200 | $(".iky_container").toggle();
201 | $("input.iky_user_input_field").focus();
202 | });
203 |
204 | });
205 |
206 |
207 | }
208 |
209 | })(); // We call our anonymous function immediately
--------------------------------------------------------------------------------
/frontend/src/assets/widget/iky_widget.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var config = {
3 | "base_url": window.iky_base_url,
4 | "chat_context": window.chat_context
5 | }
6 |
7 | // Localize jQuery variable
8 | var jQuery;
9 |
10 | /******** Load jQuery if not present *********/
11 | if (window.jQuery === undefined) {
12 | var script_tag = document.createElement('script');
13 | script_tag.setAttribute("type", "text/javascript");
14 | script_tag.setAttribute("src",
15 | "http://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js");
16 | if (script_tag.readyState) {
17 | script_tag.onreadystatechange = function () { // For old versions of IE
18 | if (this.readyState == 'complete' || this.readyState == 'loaded') {
19 | scriptLoadHandler();
20 | }
21 | };
22 | } else {
23 | script_tag.onload = scriptLoadHandler;
24 | }
25 | // Try to find the head, otherwise default to the documentElement
26 | (document.getElementsByTagName("head")[0] || document.documentElement).appendChild(script_tag);
27 | } else {
28 | // The jQuery version on the window is the one we want to use
29 | jQuery = window.jQuery;
30 | main(config);
31 | }
32 |
33 | /******** Called once jQuery has loaded ******/
34 | function scriptLoadHandler() {
35 | // Restore $ and window.jQuery to their previous values and store the
36 | // new jQuery in our local jQuery variable
37 | jQuery = window.jQuery.noConflict(true);
38 | // Call our main function
39 | main(config);
40 | }
41 |
42 | /******** Our main function ********/
43 | function main(config) {
44 | jQuery(document).ready(function ($) {
45 | console.log("received", config)
46 | /******* Load CSS *******/
47 | var css_link = $(" ", {
48 | rel: "stylesheet",
49 | type: "text/css",
50 | href: config["base_url"] +"assets/widget/style.css"
51 | });
52 | css_link.appendTo('head');
53 |
54 |
55 | content = `
56 |
57 |
58 |
61 |
65 |
66 |
67 |
68 |
69 |
72 |
73 | `
74 |
75 | $("body").append(content);
76 |
77 | $(".iky_container").hide();
78 |
79 |
80 | /******* chat begins *******/
81 |
82 | if (typeof payload == "undefined") {
83 | payload =
84 | {
85 | "currentNode": "",
86 | "complete": true,
87 | "parameters": [],
88 | "extractedParameters": {},
89 | "missingParameters": [],
90 | "intent": {},
91 | "context": config["chat_context"],
92 | "input": "init_conversation",
93 | "speechResponse": []
94 | }
95 |
96 |
97 | }
98 |
99 | $.ajaxSetup({
100 | headers: {
101 | 'Content-Type': 'application/json',
102 | 'Accept': 'application/json'
103 | }
104 | });
105 |
106 | var send_req = (userQuery)=> {
107 | showTyping();
108 | // send request to bot
109 | payload["input"] = userQuery;
110 |
111 | $.post(config["base_url"]+'gateway/api/v1', JSON.stringify(payload))
112 | .done(((response)=>{
113 | successRoutes(response);}))
114 | .fail((x,t,m)=>{
115 | errorRoutes(x,t,m);
116 | });
117 |
118 | // $.ajax({
119 | // // url: config["base_url"]+'gateway/api/v1',
120 | // url: 'http://localhost:8080/gateway/api/v1',
121 | // type: 'POST',
122 | // data: JSON.stringify(payload),
123 | // contentType: 'application/json; charset=utf-8',
124 | // datatype: "json",
125 | // success: successRoutes,
126 | // error: errorRoutes
127 |
128 | // });
129 | return true;
130 | };
131 |
132 | function showTyping(){
133 | $("input.iky_user_input_field").prop('disabled', true);
134 | html_data = ' ';
135 | $("ul.iky_Chat_container").append(html_data);
136 | scrollToBottom();
137 |
138 | }
139 |
140 | function hideTyping(){
141 | $('li#typing-indicator').remove();
142 | $("input.iky_user_input_field").prop('disabled', false);
143 | }
144 |
145 |
146 | successRoutes = function (response) {
147 | hideTyping();
148 | var responseObject;
149 | if (typeof response == 'object') {
150 | responseObject = response;
151 | }
152 | else {
153 | var parsedResponse = JSON.parse(response);
154 | responseObject = parsedResponse.responseData;
155 | }
156 | payload = responseObject;
157 | put_text(responseObject);
158 | };
159 |
160 | errorRoutes = function (x, t, m) {
161 | hideTyping();
162 | responseObject = payload;
163 | if (t === "timeout") {
164 | responseObject["speechResponse"] = ["Due to band-width constraints, I'm not able to serve you now","please try again later"]
165 | } else {
166 | responseObject["speechResponse"] = ["I'm not able to serve you at the moment"," please try again later"]
167 | }
168 | payload = responseObject;
169 | put_text(responseObject);
170 | };
171 |
172 | function scrollToBottom() {
173 | $(".iky_Chat_container").stop().animate({ scrollTop: $(".iky_Chat_container")[0].scrollHeight}, 1000);
174 | }
175 |
176 | var put_text = function (bot_say) {
177 | $.each(bot_say["speechResponse"],function (index, data) {
178 | html_data = ''+ data +' ';
179 | $("ul.iky_Chat_container").append(html_data);
180 | });
181 | scrollToBottom();
182 | };
183 |
184 |
185 |
186 | send_req("init_conversation");
187 |
188 |
189 | $('.iky_user_input_field').keydown(function (e) {
190 | if (e.keyCode == 13) {
191 | userQuery = $(".iky_user_input_field").val();
192 | $(".iky_user_input_field").val("");
193 | html_data = ' '+userQuery+' ';
194 | $("ul.iky_Chat_container").append(html_data);
195 | send_req(userQuery);
196 |
197 | }
198 | })
199 | $(".iky_action, .btn_close").click(function(){
200 | $(".iky_container").toggle();
201 | $("input.iky_user_input_field").focus();
202 | });
203 |
204 | });
205 |
206 |
207 | }
208 |
209 | })(); // We call our anonymous function immediately
--------------------------------------------------------------------------------
/app/endpoint/controllers.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import json
4 |
5 | from flask import Blueprint, request, abort
6 | from jinja2 import Template
7 |
8 | from app import app
9 | from app.agents.models import Bot
10 | from app.commons import build_response
11 | from app.endpoint.utils import SilentUndefined
12 | from app.endpoint.utils import call_api
13 | from app.endpoint.utils import get_synonyms
14 | from app.endpoint.utils import split_sentence
15 | from app.intents.models import Intent
16 | from app.nlu.classifiers.starspace_intent_classifier import \
17 | EmbeddingIntentClassifier
18 | from app.nlu.entity_extractor import EntityExtractor
19 | from app.nlu.tasks import model_updated_signal
20 |
21 | endpoint = Blueprint('api', __name__, url_prefix='/api')
22 |
23 | sentence_classifier = None
24 | synonyms = None
25 | entity_extraction = None
26 |
27 |
28 | # Request Handler
29 | @endpoint.route('/v1', methods=['POST'])
30 | def api():
31 | """
32 | Endpoint to converse with chatbot.
33 | Chat context is maintained by exchanging the payload between client and bot.
34 |
35 | sample input/output payload =>
36 |
37 | {
38 | "currentNode": "",
39 | "complete": false,
40 | "parameters": [],
41 | "extractedParameters": {},
42 | "missingParameters": [],
43 | "intent": {
44 | },
45 | "context": {},
46 | "input": "hello",
47 | "speechResponse": [
48 | ]
49 | }
50 |
51 | :param json:
52 | :return json:
53 | """
54 | request_json = request.get_json(silent=True)
55 | result_json = request_json
56 |
57 | if request_json:
58 |
59 | context = {"context": request_json["context"]}
60 |
61 | if app.config["DEFAULT_WELCOME_INTENT_NAME"] in request_json.get(
62 | "input"):
63 | intent = Intent.objects(
64 | intentId=app.config["DEFAULT_WELCOME_INTENT_NAME"]).first()
65 | result_json["complete"] = True
66 | result_json["intent"]["object_id"] = str(intent.id)
67 | result_json["intent"]["id"] = intent.intentId
68 | result_json["input"] = request_json.get("input")
69 | template = Template(
70 | intent.speechResponse,
71 | undefined=SilentUndefined)
72 | result_json["speechResponse"] = split_sentence(template.render(**context))
73 |
74 | app.logger.info(request_json.get("input"), extra=result_json)
75 | return build_response.build_json(result_json)
76 |
77 | # check if input method is event or raw text
78 | elif request_json.get("event"):
79 | intent_id = request_json.get("event")
80 | confidence = 1
81 | result_json["event"]=None
82 | else:
83 | intent_id, confidence, suggestions = predict(request_json.get("input"))
84 | app.logger.info("intent_id => %s" % intent_id)
85 | intent = Intent.objects.get(intentId=intent_id)
86 |
87 | if intent.parameters:
88 | parameters = intent.parameters
89 | result_json["extractedParameters"] = request_json.get("extractedParameters") or {}
90 | else:
91 | parameters = []
92 |
93 | if ((request_json.get("complete") is None) or (request_json.get("complete") is True)):
94 | result_json["intent"] = {
95 | "object_id": str(intent.id),
96 | "confidence": confidence,
97 | "id": intent.intentId
98 | }
99 |
100 | if parameters:
101 | # Extract NER entities
102 | result_json["extractedParameters"].update(entity_extraction.predict(
103 | intent_id, request_json.get("input")))
104 |
105 | missing_parameters = []
106 | result_json["missingParameters"] = []
107 | result_json["parameters"] = []
108 | for parameter in parameters:
109 | result_json["parameters"].append({
110 | "name": parameter.name,
111 | "type": parameter.type,
112 | "required": parameter.required
113 | })
114 |
115 | if parameter.required:
116 | if parameter.name not in result_json["extractedParameters"].keys():
117 | result_json["missingParameters"].append(
118 | parameter.name)
119 | missing_parameters.append(parameter)
120 |
121 | if missing_parameters:
122 | result_json["complete"] = False
123 | current_node = missing_parameters[0]
124 | result_json["currentNode"] = current_node["name"]
125 | result_json["speechResponse"] = split_sentence(current_node["prompt"])
126 | else:
127 | result_json["complete"] = True
128 | context["parameters"] = result_json["extractedParameters"]
129 | else:
130 | result_json["complete"] = True
131 |
132 | elif request_json.get("complete") is False:
133 | if "cancel" not in intent.name:
134 | intent_id = request_json["intent"]["id"]
135 | app.logger.info(intent_id)
136 | intent = Intent.objects.get(intentId=intent_id)
137 |
138 | extracted_parameter = entity_extraction.replace_synonyms({
139 | request_json.get("currentNode"): request_json.get("input")
140 | })
141 |
142 | # replace synonyms for entity values
143 | result_json["extractedParameters"].update(extracted_parameter)
144 |
145 | result_json["missingParameters"].remove(
146 | request_json.get("currentNode"))
147 |
148 | if len(result_json["missingParameters"]) == 0:
149 | result_json["complete"] = True
150 | context = {"parameters": result_json["extractedParameters"],
151 | "context": request_json["context"]}
152 | else:
153 | missing_parameter = result_json["missingParameters"][0]
154 | result_json["complete"] = False
155 | current_node = [
156 | node for node in intent.parameters if missing_parameter in node.name][0]
157 | result_json["currentNode"] = current_node.name
158 | result_json["speechResponse"] = split_sentence(current_node.prompt)
159 | else:
160 | result_json["currentNode"] = None
161 | result_json["missingParameters"] = []
162 | result_json["parameters"] = {}
163 | result_json["intent"] = {}
164 | result_json["complete"] = True
165 |
166 | if result_json["complete"]:
167 | if intent.apiTrigger:
168 | isJson = False
169 | parameters = result_json["extractedParameters"]
170 | headers = intent.apiDetails.get_headers()
171 | app.logger.info("headers %s" % headers)
172 | url_template = Template(
173 | intent.apiDetails.url, undefined=SilentUndefined)
174 | rendered_url = url_template.render(**context)
175 | if intent.apiDetails.isJson:
176 | isJson = True
177 | request_template = Template(
178 | intent.apiDetails.jsonData, undefined=SilentUndefined)
179 | parameters = json.loads(request_template.render(**context))
180 |
181 | try:
182 | result = call_api(rendered_url,
183 | intent.apiDetails.requestType, headers,
184 | parameters, isJson)
185 | except Exception as e:
186 | app.logger.warn("API call failed", e)
187 | result_json["speechResponse"] = ["Service is not available. Please try again later."]
188 | else:
189 | context["result"] = result
190 | template = Template(
191 | intent.speechResponse, undefined=SilentUndefined)
192 | result_json["speechResponse"] = split_sentence(template.render(**context))
193 | else:
194 | context["result"] = {}
195 | template = Template(intent.speechResponse,
196 | undefined=SilentUndefined)
197 | result_json["speechResponse"] = split_sentence(template.render(**context))
198 | app.logger.info(request_json.get("input"), extra=result_json)
199 | return build_response.build_json(result_json)
200 | else:
201 | return abort(400)
202 |
203 |
204 | def update_model(app, message, **extra):
205 | """
206 | Signal hook to be called after training is completed.
207 | Reloads ml models and synonyms.
208 | :param app:
209 | :param message:
210 | :param extra:
211 | :return:
212 | """
213 | global sentence_classifier
214 |
215 | sentence_classifier = EmbeddingIntentClassifier.load(
216 | app.config["MODELS_DIR"], app.config["USE_WORD_VECTORS"])
217 |
218 | synonyms = get_synonyms()
219 |
220 | global entity_extraction
221 |
222 | entity_extraction = EntityExtractor(synonyms)
223 |
224 | app.logger.info("Intent Model updated")
225 |
226 |
227 | with app.app_context():
228 | update_model(app, "Models updated")
229 |
230 | model_updated_signal.connect(update_model, app)
231 |
232 |
233 | def predict(sentence):
234 | """
235 | Predict Intent using Intent classifier
236 | :param sentence:
237 | :return:
238 | """
239 | bot = Bot.objects.get(name="default")
240 | predicted, intents = sentence_classifier.process(sentence)
241 | app.logger.info("predicted intent %s", predicted)
242 | if predicted["confidence"] < bot.config.get("confidence_threshold", .90):
243 | intents = Intent.objects(intentId=app.config["DEFAULT_FALLBACK_INTENT_NAME"])
244 | intents = intents.first().intentId
245 | return intents, 1.0, []
246 | else:
247 | return predicted["intent"], predicted["confidence"], intents[1:]
248 |
--------------------------------------------------------------------------------
/frontend/dist/3rdpartylicenses.txt:
--------------------------------------------------------------------------------
1 | @angular/core@5.2.9
2 | MIT
3 | MIT
4 |
5 | @angular/common@5.2.9
6 | MIT
7 | MIT
8 |
9 | @angular/forms@5.2.9
10 | MIT
11 | MIT
12 |
13 | @angular/flex-layout@5.0.0-beta.14
14 | MIT
15 | The MIT License
16 |
17 | Copyright (c) 2018 Google LLC.
18 |
19 | Permission is hereby granted, free of charge, to any person obtaining a copy
20 | of this software and associated documentation files (the "Software"), to deal
21 | in the Software without restriction, including without limitation the rights
22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
23 | copies of the Software, and to permit persons to whom the Software is
24 | furnished to do so, subject to the following conditions:
25 |
26 | The above copyright notice and this permission notice shall be included in
27 | all copies or substantial portions of the Software.
28 |
29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
35 | THE SOFTWARE.
36 |
37 | @angular/material@5.2.4
38 | MIT
39 | The MIT License
40 |
41 | Copyright (c) 2018 Google LLC.
42 |
43 | Permission is hereby granted, free of charge, to any person obtaining a copy
44 | of this software and associated documentation files (the "Software"), to deal
45 | in the Software without restriction, including without limitation the rights
46 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
47 | copies of the Software, and to permit persons to whom the Software is
48 | furnished to do so, subject to the following conditions:
49 |
50 | The above copyright notice and this permission notice shall be included in
51 | all copies or substantial portions of the Software.
52 |
53 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
54 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
55 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
56 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
57 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
58 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
59 | THE SOFTWARE.
60 |
61 | cache-loader@1.2.2
62 | MIT
63 | Copyright JS Foundation and other contributors
64 |
65 | Permission is hereby granted, free of charge, to any person obtaining
66 | a copy of this software and associated documentation files (the
67 | 'Software'), to deal in the Software without restriction, including
68 | without limitation the rights to use, copy, modify, merge, publish,
69 | distribute, sublicense, and/or sell copies of the Software, and to
70 | permit persons to whom the Software is furnished to do so, subject to
71 | the following conditions:
72 |
73 | The above copyright notice and this permission notice shall be
74 | included in all copies or substantial portions of the Software.
75 |
76 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
77 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
78 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
79 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
80 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
81 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
82 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
83 |
84 | @angular-devkit/build-optimizer@0.3.2
85 | MIT
86 | The MIT License
87 |
88 | Copyright (c) 2017 Google, Inc.
89 |
90 | Permission is hereby granted, free of charge, to any person obtaining a copy
91 | of this software and associated documentation files (the "Software"), to deal
92 | in the Software without restriction, including without limitation the rights
93 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
94 | copies of the Software, and to permit persons to whom the Software is
95 | furnished to do so, subject to the following conditions:
96 |
97 | The above copyright notice and this permission notice shall be included in all
98 | copies or substantial portions of the Software.
99 |
100 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
101 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
102 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
103 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
104 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
105 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
106 | SOFTWARE.
107 |
108 | @angular/cdk@5.2.4
109 | MIT
110 | The MIT License
111 |
112 | Copyright (c) 2018 Google LLC.
113 |
114 | Permission is hereby granted, free of charge, to any person obtaining a copy
115 | of this software and associated documentation files (the "Software"), to deal
116 | in the Software without restriction, including without limitation the rights
117 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
118 | copies of the Software, and to permit persons to whom the Software is
119 | furnished to do so, subject to the following conditions:
120 |
121 | The above copyright notice and this permission notice shall be included in
122 | all copies or substantial portions of the Software.
123 |
124 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
125 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
126 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
127 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
128 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
129 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
130 | THE SOFTWARE.
131 |
132 | @angular/router@5.2.9
133 | MIT
134 | MIT
135 |
136 | @angular/animations@5.2.9
137 | MIT
138 | MIT
139 |
140 | webpack@3.11.0
141 | MIT
142 | Copyright JS Foundation and other contributors
143 |
144 | Permission is hereby granted, free of charge, to any person obtaining
145 | a copy of this software and associated documentation files (the
146 | 'Software'), to deal in the Software without restriction, including
147 | without limitation the rights to use, copy, modify, merge, publish,
148 | distribute, sublicense, and/or sell copies of the Software, and to
149 | permit persons to whom the Software is furnished to do so, subject to
150 | the following conditions:
151 |
152 | The above copyright notice and this permission notice shall be
153 | included in all copies or substantial portions of the Software.
154 |
155 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
156 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
157 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
158 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
159 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
160 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
161 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
162 |
163 | @angular/platform-browser@5.2.9
164 | MIT
165 | MIT
166 |
167 | hammerjs@2.0.8
168 | MIT
169 | The MIT License (MIT)
170 |
171 | Copyright (C) 2011-2014 by Jorik Tangelder (Eight Media)
172 |
173 | Permission is hereby granted, free of charge, to any person obtaining a copy
174 | of this software and associated documentation files (the "Software"), to deal
175 | in the Software without restriction, including without limitation the rights
176 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
177 | copies of the Software, and to permit persons to whom the Software is
178 | furnished to do so, subject to the following conditions:
179 |
180 | The above copyright notice and this permission notice shall be included in
181 | all copies or substantial portions of the Software.
182 |
183 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
184 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
185 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
186 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
187 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
188 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
189 | THE SOFTWARE.
190 |
191 | @angular/platform-browser-dynamic@5.2.9
192 | MIT
193 | MIT
194 |
195 | core-js@2.5.4
196 | MIT
197 | Copyright (c) 2014-2018 Denis Pushkarev
198 |
199 | Permission is hereby granted, free of charge, to any person obtaining a copy
200 | of this software and associated documentation files (the "Software"), to deal
201 | in the Software without restriction, including without limitation the rights
202 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
203 | copies of the Software, and to permit persons to whom the Software is
204 | furnished to do so, subject to the following conditions:
205 |
206 | The above copyright notice and this permission notice shall be included in
207 | all copies or substantial portions of the Software.
208 |
209 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
210 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
211 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
212 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
213 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
214 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
215 | THE SOFTWARE.
216 |
217 | zone.js@0.8.20
218 | MIT
219 | The MIT License
220 |
221 | Copyright (c) 2016 Google, Inc.
222 |
223 | Permission is hereby granted, free of charge, to any person obtaining a copy
224 | of this software and associated documentation files (the "Software"), to deal
225 | in the Software without restriction, including without limitation the rights
226 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
227 | copies of the Software, and to permit persons to whom the Software is
228 | furnished to do so, subject to the following conditions:
229 |
230 | The above copyright notice and this permission notice shall be included in
231 | all copies or substantial portions of the Software.
232 |
233 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
234 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
235 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
236 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
237 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
238 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
239 | THE SOFTWARE.
--------------------------------------------------------------------------------