├── 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 | 7 |
8 | 9 |
10 |
11 |
12 | {{entity.name}} 13 |
14 |
15 | 18 | 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 |
2 | 3 | List of Intents 4 | 5 | 6 | 9 | 10 |
11 | 12 | 13 | 14 | 15 |
16 |
17 | {{intent.name}} 18 |
19 | 20 |
21 | 23 | 26 | 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 |
14 |
15 | 16 |
17 |
18 | 19 | 20 |
21 |
22 | 23 |
24 | 25 | 26 | 27 |
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 |
2 | 3 | Settings 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 |
12 |

Tune ML

13 | 14 | Intent Detection threshold = {{config_form.value.confidence_threshold | percent}} 15 |
16 |
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 |
40 |
41 | 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 | 10 |
11 |
12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 22 |
23 | 24 |
25 |
26 | 27 |

{{value.value}}

28 |
29 | 30 | 31 | 32 | {{synonym}} 33 | cancel 34 | 35 | 37 | 38 | 39 |
40 | 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 |
2 |
3 |

Train your intent

4 |
5 | 6 |
7 | 8 |
9 |

10 | Train your story for possible user inputs. Tag required parameters using mouse and give em labels 11 |

12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
{{example.text}}
32 | 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 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 76 | 77 |
EntityValue
{{entity.name}}{{entity.value}} 72 | 75 |
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 |
5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 |

Parameters 14 | 17 |

18 |
19 |
20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {{ intentTypes[intent] }} 30 | 31 | 32 | 33 | Required 34 | 35 | 36 | 37 | 38 | 41 |
42 |
43 | 44 | 45 |
46 | 47 | 48 |

49 | API trigger 50 |

51 | 52 |
53 |

HTTP Headers 54 | 57 |

58 |
59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 | 69 |
70 | 71 |
72 | 73 |
74 | 75 | 76 | 77 | 78 | 79 | 80 | GET 81 | POST 82 | PUT 83 | DELETE 84 | 85 | 86 |
87 | 88 | JSON payload 89 |
90 | Extracted parameters can be used to build your json. Example, 91 |
 {{ '\{ 
 92 |     "name": \{\{ parameters\[\"name\"\] \}\} 
 93 |  \}' }}
94 |
95 | 96 | 97 | 98 | 99 |
100 |
101 | 102 | 103 | 104 | 105 |

106 | Extracted parameters and api call response can be accessed from speech respone using "parameters" and "result" objects respectively. 107 | Jinja Templating enabled. 108 |

109 | 110 | 111 | 112 | 113 | 114 | 115 |
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 | [![Join the chat at https://gitter.im/ai-chatbot-framework/Lobby](https://badges.gitter.im/ai-chatbot-framework/Lobby.svg)](https://gitter.im/ai-chatbot-framework/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/alfredfrancis/ai-chatbot-framework.svg?branch=master)](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 | ![](https://image.ibb.co/eMJ9Wx/Screen_Shot_2018_04_28_at_1_45_28_PM.png) 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 | [![Deploy](https://www.herokucdn.com/deploy/button.png)](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 | ![](https://image.ibb.co/i9ReWx/Screen_Shot_2018_04_28_at_1_38_15_PM.png) 92 | --- 93 | ![](https://image.ibb.co/ivXKWx/Screen_Shot_2018_04_28_at_1_38_36_PM.png) 94 | --- 95 | ![](https://image.ibb.co/nf9Bdc/Screen_Shot_2018_04_28_at_1_38_57_PM.png) 96 | --- 97 | ![](https://image.ibb.co/b4q1dc/Screen_Shot_2018_04_28_at_1_43_06_PM.png) 98 | ### Tutorial 99 | 100 | Checkout this basic tutorial on youtube, 101 | 102 | [![IMAGE ALT TEXT HERE](https://preview.ibb.co/fj9N3v/Screenshot_from_2017_04_05_03_11_04.png)](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/images/0)](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/links/0)[![](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/images/1)](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/links/1)[![](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/images/2)](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/links/2)[![](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/images/3)](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/links/3)[![](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/images/4)](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/links/4)[![](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/images/5)](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/links/5)[![](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/images/6)](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/links/6)[![](https://sourcerer.io/fame/alfredfrancis/alfredfrancis/ai-chatbot-framework/images/7)](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 |
59 |
60 |
61 |
62 |
    63 |
64 |
65 |
66 | 67 |
68 |
69 |
70 | 71 |
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 |
    59 |
    60 |
    61 |
    62 |
      63 |
    64 |
    65 |
    66 | 67 |
    68 |
    69 |
    70 | 71 |
    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. --------------------------------------------------------------------------------