├── .dockerignore
├── .gitignore
├── .python-version
├── .travis.yml
├── Dockerfile
├── License.txt
├── Procfile
├── README.md
├── app.json
├── app
├── .gitignore
├── __init__.py
├── agents
│ ├── __init__.py
│ ├── controllers.py
│ └── models.py
├── commons
│ ├── .gitignore
│ ├── __init__.py
│ ├── build_response.py
│ ├── error_codes.py
│ ├── logger.py
│ └── utils.py
├── endpoint
│ ├── __init__.py
│ ├── controllers.py
│ └── utils.py
├── entities
│ ├── __init__.py
│ ├── controllers.py
│ └── models.py
├── intents
│ ├── __init__.py
│ ├── controllers.py
│ └── models.py
├── nlu
│ ├── .gitignore
│ ├── __init__.py
│ ├── classifiers
│ │ ├── __init__.py
│ │ ├── sklearn_intent_classifer.py
│ │ └── tf_intent_classifer.py
│ ├── controllers.py
│ ├── entity_extractor.py
│ └── tasks.py
└── train
│ ├── __init__.py
│ └── controllers.py
├── config.py
├── docker-compose.yml
├── examples
├── default_intents.json
├── nodejs
│ └── discordRequest.js
├── python
│ └── app.py
├── restaurant_search.json
└── ruby
│ └── request.rb
├── frontend
├── .angular-cli.json
├── .dockerignore
├── .editorconfig
├── .gitignore
├── Dockerfile
├── README.md
├── e2e
│ ├── app.e2e-spec.ts
│ ├── app.po.ts
│ └── tsconfig.e2e.json
├── karma.conf.js
├── nginx
│ └── default.conf
├── package-lock.json
├── package.json
├── protractor.conf.js
├── src
│ ├── app
│ │ ├── agent
│ │ │ ├── agent-routing.module.ts
│ │ │ ├── agent.module.ts
│ │ │ ├── chat
│ │ │ │ ├── chat.component.html
│ │ │ │ ├── chat.component.scss
│ │ │ │ └── chat.component.ts
│ │ │ ├── entities
│ │ │ │ ├── entities.component.html
│ │ │ │ ├── entities.component.scss
│ │ │ │ ├── entities.component.spec.ts
│ │ │ │ └── entities.component.ts
│ │ │ ├── entity
│ │ │ │ ├── entity.component.html
│ │ │ │ ├── entity.component.scss
│ │ │ │ ├── entity.component.spec.ts
│ │ │ │ └── entity.component.ts
│ │ │ ├── intent
│ │ │ │ ├── intent.component.html
│ │ │ │ ├── intent.component.scss
│ │ │ │ └── intent.component.ts
│ │ │ ├── intents
│ │ │ │ ├── intents.component.html
│ │ │ │ ├── intents.component.scss
│ │ │ │ └── intents.component.ts
│ │ │ ├── settings
│ │ │ │ ├── settings.component.html
│ │ │ │ ├── settings.component.scss
│ │ │ │ ├── settings.component.spec.ts
│ │ │ │ └── settings.component.ts
│ │ │ └── train
│ │ │ │ ├── train.component.html
│ │ │ │ ├── train.component.scss
│ │ │ │ └── train.component.ts
│ │ ├── app-routing.module.ts
│ │ ├── app.component.html
│ │ ├── app.component.scss
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ ├── commons
│ │ │ └── commons.module.ts
│ │ ├── dashboard
│ │ │ ├── dashboard.module.ts
│ │ │ ├── layout
│ │ │ │ ├── layout.component.html
│ │ │ │ ├── layout.component.scss
│ │ │ │ ├── layout.component.spec.ts
│ │ │ │ └── layout.component.ts
│ │ │ └── sidebar
│ │ │ │ ├── sidebar.component.html
│ │ │ │ ├── sidebar.component.scss
│ │ │ │ ├── sidebar.component.spec.ts
│ │ │ │ └── sidebar.component.ts
│ │ ├── directives
│ │ │ ├── autofocus.directive.spec.ts
│ │ │ └── autofocus.directive.ts
│ │ └── services
│ │ │ ├── agents.service.ts
│ │ │ ├── chat.service.ts
│ │ │ ├── entities.service.ts
│ │ │ ├── intent-resolver.service.ts
│ │ │ ├── intent.service.ts
│ │ │ ├── training.service.ts
│ │ │ └── utils.service.ts
│ ├── assets
│ │ ├── .gitkeep
│ │ ├── images
│ │ │ └── iky-logo.png
│ │ └── widget
│ │ │ ├── iky_widget.js
│ │ │ └── style.css
│ ├── environments
│ │ ├── environment.docker.ts
│ │ ├── environment.prod.ts
│ │ ├── environment.test.ts
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ ├── polyfills.ts
│ ├── styles.scss
│ ├── test.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.spec.json
│ └── typings.d.ts
├── tsconfig.json
└── tslint.json
├── helm
└── ai-chatbot-framework
│ ├── .gitignore
│ ├── .helmignore
│ ├── Chart.yaml
│ ├── templates
│ ├── _helpers.tpl
│ ├── deployment-backend.yaml
│ ├── deployment-frontend.yaml
│ ├── ingress.yaml
│ └── service.yaml
│ └── values.yaml
├── logs
└── .keep
├── manage.py
├── model_files
└── .gitignore
├── requirements.txt
├── run.py
├── swagger.yaml
└── tests
├── __init__.py
└── test_nlu.py
/.dockerignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | logs/*
3 | !logs/.keep
4 | *.pyc
5 | venv/
6 | env/
7 | .DS_Store
8 | model_files/*.model
9 | frontend/
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | logs/*
3 | !logs/.keep
4 | *.pyc
5 | *.pyo
6 | venv/
7 | env/
8 | .DS_Store
9 | .pytest_cache/
10 | .vscode
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
1 | 3.9.6
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "3.9.6"
4 | install:
5 | - pip install -r requirements.txt
6 | script:
7 | - pytest
8 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.9.6-slim
2 |
3 | # Install common libraries
4 | RUN apt-get update -qq \
5 | && apt-get install -y --no-install-recommends build-essential && apt-get autoremove -y
6 |
7 | WORKDIR /usr/src/app
8 |
9 | COPY requirements.txt ./
10 | RUN pip install --no-cache-dir -r requirements.txt
11 |
12 | EXPOSE 80
13 |
14 | COPY . .
15 |
16 | CMD ["gunicorn", "run:app" ,"--log-level=debug", "--timeout", "90","--bind", "0.0.0.0:80" ]
17 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | release: APPLICATION_ENV="Heroku" python manage.py migrate
2 | web: APPLICATION_ENV="Heroku" gunicorn -k gevent run:app
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [](https://gitter.im/ai-chatbot-framework/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://travis-ci.com/alfredfrancis/ai-chatbot-framework.svg?branch=master)
4 |
5 |
6 |
7 | ### An AI Chatbot framework built in Python
8 |
9 | Building a chatbot can sound daunting, but it’s totally doable. AI Chatbot Framework is an AI powered conversational dialog interface built in Python. With this tool, it’s easy to create Natural Language conversational scenarios with no coding efforts whatsoever. The smooth UI makes it effortless to create and train conversations to the bot and it continuously gets smarter as it learns from conversations it has with people. AI Chatbot Framework can live on any channel of your choice (such as Messenger, Slack etc.) by integrating it’s API with that platform.
10 |
11 | You don’t need to be an expert at artificial intelligence to create an awesome chatbot that has AI capabilities. With this boilerplate project you can create an AI powered chatting machine in no time.There may be scores of bugs. So feel free to contribute via pull requests.
12 |
13 | 
14 |
15 | ### Installation
16 |
17 | ### Using docker-compose
18 | ```sh
19 | docker-compose up -d
20 | ```
21 |
22 | ### Using Helm
23 |
24 | ```sh
25 | helm dep update helm/ai-chatbot-framework
26 |
27 | helm upgrade --install --create-namespace -n ai-chatbot-framework ai-chatbot-framework helm/ai-chatbot-framework
28 |
29 | # port forward for local installation
30 | kubectl port-forward --namespace=ai-chatbot-framework service/ingress-nginx-controller 8080:80
31 | ```
32 |
33 | ### Using Docker
34 | ```sh
35 |
36 | # pull docker images
37 | docker pull alfredfrancis/ai-chatbot-framework_backend:latest
38 | docker pull alfredfrancis/ai-chatbot-framework_frontend:latest
39 |
40 | # start a mongodb server
41 | docker run --name mongodb -d mongo:3.6
42 |
43 | # start iky backend
44 | docker run -d --name=iky_backend --link mongodb:mongodb -e="APPLICATION_ENV=Production" alfredfrancis/ai-chatbot-framework_backend:latest
45 |
46 | # setup default intents
47 | docker exec -it iky_backend python manage.py migrate
48 |
49 | # start iky gateway with frontend
50 | docker run -d --name=iky_gateway --link iky_backend:iky_backend -p 8080:80 alfredfrancis/ai-chatbot-framework_frontend:latest
51 |
52 | ```
53 |
54 | ### without docker
55 |
56 | * Setup Virtualenv and install python requirements
57 | ```sh
58 | virtualenv -p python3 venv
59 | source venv/bin/activate
60 | pip install -r requirements.txt
61 | python manage.py migrate
62 | python run.py
63 | ```
64 | * Production
65 | ```sh
66 | APPLICATION_ENV="Production" gunicorn -k gevent --bind 0.0.0.0:8080 run:app
67 | ```
68 | * Open http://localhost:8080/
69 |
70 | #### Update Frontend Dist
71 | * Run Development mode
72 | ```sh
73 | cd frontend
74 | npm install
75 | ng serve
76 | ```
77 | * Take Production build
78 | ```sh
79 | cd frontend
80 | ng build --prod --optimize
81 | ```
82 |
83 | ### Heroku
84 | [](https://heroku.com/deploy)
85 |
86 | * add your dev/production configurations in config.py
87 |
88 | ### DB
89 |
90 | #### Restore
91 | You can import some default intents using following steps
92 |
93 | - goto http://localhost:8080/agent/default/settings
94 | - click 'choose file'
95 | - choose 'examples/default_intents.json file'
96 | - click import
97 |
98 | ### Screenshots
99 |
100 | 
101 | ---
102 | 
103 | ---
104 | 
105 | ---
106 | 
107 | ### Tutorial
108 |
109 | Checkout this basic tutorial on youtube,
110 |
111 | [](https://www.youtube.com/watch?v=S1Fj7WinaBA)
112 |
113 |
114 | Watch tutorial on [Fullfilling your Chatbot Intent with an API Call - Recipe Search Bot](https://www.youtube.com/watch?v=gqO69ojLobQ)
115 |
116 | ### Todos
117 | * Write Unit Tests
118 | * Multilingual Intent Classifier
119 | * PyCRFSuite to sklearn-crfsuite migration
120 | * Support follow up conversations
121 |
122 | ### Dependencies documentations
123 | * [SKLearn documentation](http://scikit-learn.org/)
124 | * [CRFsuite documentation](http://www.chokkan.org/software/crfsuite/)
125 | * [python CRfSuite](https://python-crfsuite.readthedocs.io/en/latest/)
126 |
127 | **Free Software, Hell Yeah!**
128 |
129 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
--------------------------------------------------------------------------------
/app/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | from flask import Flask,send_from_directory
3 | from flask_cors import CORS
4 | from flask_mongoengine import MongoEngine
5 |
6 | APP_ROOT = os.path.dirname(os.path.abspath(__file__ + "../../"))
7 |
8 | db = MongoEngine()
9 |
10 | spacy_tokenizer = None
11 |
12 | def create_app(env = 'Development'):
13 | app = Flask(__name__)
14 | CORS(app)
15 | # Configurations
16 | try:
17 | env = os.environ['APPLICATION_ENV']
18 | except KeyError as e:
19 | app.logger.info('Unknown environment key, defaulting to Development')
20 |
21 | app.config.from_object('config.%s' % env)
22 | db.init_app(app)
23 |
24 | import spacy
25 | global spacy_tokenizer
26 | spacy_tokenizer = spacy.load(app.config["SPACY_LANG_MODEL"])
27 |
28 | from app.agents.controllers import bots
29 | from app.nlu.controllers import nlu
30 | from app.intents.controllers import intents
31 | from app.train.controllers import train
32 | from app.endpoint.controllers import endpoint
33 | from app.entities.controllers import entities_blueprint
34 |
35 | app.register_blueprint(nlu)
36 | app.register_blueprint(intents)
37 | app.register_blueprint(train)
38 | app.register_blueprint(endpoint)
39 | app.register_blueprint(bots)
40 | app.register_blueprint(entities_blueprint)
41 |
42 | admin_panel_dist = os.path.join(APP_ROOT, 'frontend/dist/')
43 |
44 | @app.route('/ready')
45 | def ready():
46 | return "ok",200
47 |
48 | @app.route('/', methods=['GET'])
49 | def static_proxy(path):
50 | return send_from_directory(admin_panel_dist, path)
51 |
52 | @app.route('/')
53 | def root():
54 | print(admin_panel_dist)
55 | return send_from_directory(admin_panel_dist, 'index.html')
56 |
57 | @app.errorhandler(404)
58 | def not_found(error):
59 | return "Not found", 404
60 |
61 | from app.endpoint.controllers import update_model
62 | with app.app_context():
63 | update_model()
64 |
65 | return app
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/app/agents/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Polymath1108/Chatbot-builder/7be874edba55a5e4100d29ad8526dde39699fefe/app/agents/__init__.py
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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": .50
10 | })
11 |
--------------------------------------------------------------------------------
/app/commons/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
--------------------------------------------------------------------------------
/app/commons/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Polymath1108/Chatbot-builder/7be874edba55a5e4100d29ad8526dde39699fefe/app/commons/__init__.py
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/endpoint/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Polymath1108/Chatbot-builder/7be874edba55a5e4100d29ad8526dde39699fefe/app/endpoint/__init__.py
--------------------------------------------------------------------------------
/app/endpoint/controllers.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import copy
3 | import json
4 | from flask import Blueprint, request, abort
5 | from jinja2 import Template
6 | from flask import current_app as app
7 | from app.agents.models import Bot
8 | from app.commons import build_response
9 | from app.endpoint.utils import SilentUndefined
10 | from app.endpoint.utils import call_api
11 | from app.endpoint.utils import get_synonyms
12 | from app.endpoint.utils import split_sentence
13 | from app.intents.models import Intent
14 | from app.nlu.classifiers.sklearn_intent_classifer import \
15 | SklearnIntentClassifier
16 | from app.nlu.entity_extractor import EntityExtractor
17 | endpoint = Blueprint('api', __name__, url_prefix='/api')
18 | sentence_classifier = SklearnIntentClassifier()
19 | synonyms = None
20 | entity_extraction = None
21 |
22 | @endpoint.route('/v1', methods=['POST'])
23 | def api():
24 | """
25 | Endpoint to Converse with the Chatbot.
26 | Chat context is maintained by exchanging the payload between client and bot.
27 | :param json:
28 | :return json:
29 | """
30 | request_json = request.get_json(silent=True)
31 | print(request_json)
32 | result_json = copy.deepcopy(request_json)
33 |
34 | if request_json:
35 | context = {"context": request_json["context"]}
36 |
37 | # check if input method is event or raw text
38 | if request_json.get("input","").startswith("/"):
39 | intent_id = request_json.get("input").split("/")[1]
40 | confidence = 1
41 | else:
42 | intent_id, confidence, suggestions = predict(request_json.get("input"))
43 |
44 | app.logger.info("intent_id => %s" % intent_id)
45 | intent = Intent.objects.get(intentId=intent_id)
46 |
47 | # set intent as fallback intent
48 | if intent is None:
49 | intent = Intent.objects.get(intentId=app.config["DEFAULT_FALLBACK_INTENT_NAME"])
50 |
51 | parameters = []
52 | if intent.parameters:
53 | parameters = intent.parameters
54 | result_json["extractedParameters"] = request_json.get("extractedParameters") or {}
55 |
56 | if ((request_json.get("complete") is None) or (request_json.get("complete") is True)):
57 | result_json["intent"] = {
58 | "object_id": str(intent.id),
59 | "confidence": confidence,
60 | "id": intent.intentId
61 | }
62 |
63 | if parameters:
64 | # Extract NER entities
65 | result_json["extractedParameters"].update(entity_extraction.predict(
66 | intent_id, request_json.get("input")))
67 |
68 | missing_parameters = []
69 | result_json["missingParameters"] = []
70 | result_json["parameters"] = []
71 | for parameter in parameters:
72 | result_json["parameters"].append({
73 | "name": parameter.name,
74 | "type": parameter.type,
75 | "required": parameter.required
76 | })
77 |
78 | if parameter.required:
79 | if parameter.name not in result_json["extractedParameters"].keys():
80 | result_json["missingParameters"].append(
81 | parameter.name)
82 | missing_parameters.append(parameter)
83 |
84 | if missing_parameters:
85 | result_json["complete"] = False
86 | current_node = missing_parameters[0]
87 | result_json["currentNode"] = current_node["name"]
88 | result_json["speechResponse"] = split_sentence(current_node["prompt"])
89 | else:
90 | result_json["complete"] = True
91 | context["parameters"] = result_json["extractedParameters"]
92 | else:
93 | result_json["complete"] = True
94 |
95 | elif request_json.get("complete") is False:
96 | if "cancel" not in intent.name:
97 | intent_id = request_json["intent"]["id"]
98 | app.logger.info(intent_id)
99 | intent = Intent.objects.get(intentId=intent_id)
100 |
101 | extracted_parameter = entity_extraction.replace_synonyms({
102 | request_json.get("currentNode"): request_json.get("input")
103 | })
104 |
105 | # replace synonyms for entity values
106 | result_json["extractedParameters"].update(extracted_parameter)
107 |
108 | result_json["missingParameters"].remove(
109 | request_json.get("currentNode"))
110 |
111 | if len(result_json["missingParameters"]) == 0:
112 | result_json["complete"] = True
113 | context = {"parameters": result_json["extractedParameters"],
114 | "context": request_json["context"]}
115 | else:
116 | missing_parameter = result_json["missingParameters"][0]
117 | result_json["complete"] = False
118 | current_node = [
119 | node for node in intent.parameters if missing_parameter in node.name][0]
120 | result_json["currentNode"] = current_node.name
121 | result_json["speechResponse"] = split_sentence(current_node.prompt)
122 | else:
123 | result_json["currentNode"] = None
124 | result_json["missingParameters"] = []
125 | result_json["parameters"] = {}
126 | result_json["intent"] = {}
127 | result_json["complete"] = True
128 |
129 | if result_json["complete"]:
130 | if intent.apiTrigger:
131 | isJson = False
132 | parameters = result_json["extractedParameters"]
133 | headers = intent.apiDetails.get_headers()
134 | app.logger.info("headers %s" % headers)
135 | url_template = Template(
136 | intent.apiDetails.url, undefined=SilentUndefined)
137 | rendered_url = url_template.render(**context)
138 | if intent.apiDetails.isJson:
139 | isJson = True
140 | request_template = Template(
141 | intent.apiDetails.jsonData, undefined=SilentUndefined)
142 | parameters = json.loads(request_template.render(**context))
143 |
144 | try:
145 | result = call_api(rendered_url,
146 | intent.apiDetails.requestType, headers,
147 | parameters, isJson)
148 | except Exception as e:
149 | app.logger.warn("API call failed", e)
150 | result_json["speechResponse"] = ["Service is not available. Please try again later."]
151 | else:
152 | context["result"] = result
153 | template = Template(
154 | intent.speechResponse, undefined=SilentUndefined)
155 | result_json["speechResponse"] = split_sentence(template.render(**context))
156 | else:
157 | context["result"] = {}
158 | template = Template(intent.speechResponse,
159 | undefined=SilentUndefined)
160 | result_json["speechResponse"] = split_sentence(template.render(**context))
161 | app.logger.info(request_json.get("input"), extra=result_json)
162 | return build_response.build_json(result_json)
163 | else:
164 | return abort(400)
165 |
166 |
167 | def update_model():
168 | """
169 | Signal hook to be called after training is completed.
170 | Reloads ml models and synonyms.
171 | :param app:
172 | :param message:
173 | :param extra:
174 | :return:
175 | """
176 | global sentence_classifier
177 |
178 | sentence_classifier.load(app.config["MODELS_DIR"])
179 |
180 | synonyms = get_synonyms()
181 |
182 | global entity_extraction
183 |
184 | entity_extraction = EntityExtractor(synonyms)
185 | app.logger.info("Intent Model updated")
186 |
187 | def predict(sentence):
188 | """
189 | Predict Intent using Intent classifier
190 | :param sentence:
191 | :return:
192 | """
193 | bot = Bot.objects.get(name="default")
194 | predicted, intents = sentence_classifier.process(sentence)
195 | app.logger.info("predicted intent %s", predicted)
196 | app.logger.info("other intents %s", intents)
197 | if predicted["confidence"] < bot.config.get("confidence_threshold", .90):
198 | intent = Intent.objects.get(intentId=app.config["DEFAULT_FALLBACK_INTENT_NAME"])
199 | return intent.intentId, 1.0, []
200 | else:
201 | return predicted["intent"], predicted["confidence"], intents[1:]
202 |
--------------------------------------------------------------------------------
/app/endpoint/utils.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import requests
4 | from jinja2 import Undefined
5 |
6 | from flask import current_app as 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 |
--------------------------------------------------------------------------------
/app/entities/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Polymath1108/Chatbot-builder/7be874edba55a5e4100d29ad8526dde39699fefe/app/entities/__init__.py
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/app/intents/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Polymath1108/Chatbot-builder/7be874edba55a5e4100d29ad8526dde39699fefe/app/intents/__init__.py
--------------------------------------------------------------------------------
/app/intents/controllers.py:
--------------------------------------------------------------------------------
1 | import os
2 | from bson.json_util import dumps
3 | from bson.json_util import loads
4 | from bson.objectid import ObjectId
5 | from flask import Blueprint, request, Response
6 | from flask import abort
7 | from flask import current_app as app
8 | from app.commons import build_response
9 | from app.commons.utils import update_document
10 | from app.intents.models import ApiDetails
11 | from app.intents.models import Intent
12 | from app.intents.models import Parameter
13 | from app.nlu.tasks import train_models
14 |
15 | intents = Blueprint('intents_blueprint', __name__,
16 | url_prefix='/intents')
17 |
18 |
19 | @intents.route('/', methods=['POST'])
20 | def create_intent():
21 | """
22 | Create a story from the provided json
23 | :return:
24 | """
25 | content = request.get_json(silent=True)
26 |
27 | intent = Intent()
28 | intent.name = content.get("name")
29 | intent.intentId = content.get("intentId")
30 | intent.speechResponse = content.get("speechResponse")
31 | intent.trainingData = []
32 |
33 | if content.get("apiTrigger") is True:
34 | intent.apiTrigger = True
35 | api_details = ApiDetails()
36 | isJson = content.get("apiDetails").get("isJson")
37 | api_details.isJson = isJson
38 | if isJson:
39 | api_details.jsonData = content.get("apiDetails").get("jsonData")
40 |
41 | api_details.url = content.get("apiDetails").get("url")
42 | api_details.headers = content.get("apiDetails").get("headers")
43 | api_details.requestType = content.get("apiDetails").get("requestType")
44 | intent.apiDetails = api_details
45 | else:
46 | intent.apiTrigger = False
47 |
48 | if content.get("parameters"):
49 | for param in content.get("parameters"):
50 | parameter = Parameter()
51 | update_document(parameter, param)
52 | intent.parameters.append(parameter)
53 | try:
54 | story_id = intent.save()
55 | except Exception as e:
56 | return build_response.build_json({"error": str(e)})
57 |
58 | return build_response.build_json({
59 | "_id": str(story_id.id)
60 | })
61 |
62 |
63 | @intents.route('/')
64 | def read_intents():
65 | """
66 | find list of intents for the agent
67 | :return:
68 | """
69 | all_intents = Intent.objects
70 | return build_response.sent_json(all_intents.to_json())
71 |
72 |
73 | @intents.route('/')
74 | def read_intent(id):
75 | """
76 | Find details for the given intent id
77 | :param id:
78 | :return:
79 | """
80 | return Response(response=dumps(
81 | Intent.objects.get(
82 | id=ObjectId(id)).to_mongo().to_dict()),
83 | status=200,
84 | mimetype="application/json")
85 |
86 |
87 | @intents.route('/', methods=['PUT'])
88 | def update_intent(id):
89 | """
90 | Update a story from the provided json
91 | :return:
92 | """
93 | json_data = loads(request.get_data())
94 | intent = Intent.objects.get(id=ObjectId(id))
95 | intent = update_document(intent, json_data)
96 | intent.save()
97 | return 'success', 200
98 |
99 |
100 | @intents.route('/', methods=['DELETE'])
101 | def delete_intent(id):
102 | """
103 | Delete a intent
104 | :param id:
105 | :return:
106 | """
107 | Intent.objects.get(id=ObjectId(id)).delete()
108 |
109 | try:
110 | train_models()
111 | except BaseException:
112 | pass
113 |
114 | # remove NER model for the deleted story
115 | try:
116 | os.remove("{}/{}.model".format(app.config["MODELS_DIR"], id))
117 | except OSError:
118 | pass
119 | return build_response.sent_ok()
120 |
121 |
122 | @intents.route('/export', methods=['GET'])
123 | def export_intents():
124 | """
125 | Deserialize and export Mongoengines as jsonfile
126 | :return:
127 | """
128 | intents_json = Intent.objects.to_json()
129 | app.logger.info(intents_json)
130 | return Response(intents_json,
131 | mimetype='application/json',
132 | headers={'Content-Disposition':'attachment;filename=intents.json'})
133 |
134 |
135 | @intents.route('/import', methods=['POST'])
136 | def import_intents():
137 | """
138 | Convert json files to Intents objects and insert to MongoDB
139 | :return:
140 | """
141 | # check if the post request has the file part
142 | if 'file' not in request.files:
143 | abort(400, 'No file part')
144 | json_file = request.files['file']
145 | all_intents = import_json(json_file)
146 |
147 | return build_response.build_json({"num_intents_created": len(all_intents)})
148 |
149 |
150 | def import_json(json_file):
151 | json_data = json_file.read()
152 | # intents = Intent.objects.from_json(json_data)
153 | all_intents = loads(json_data)
154 |
155 | creates_intents = []
156 | for intent in all_intents:
157 | new_intent = Intent()
158 | new_intent = update_document(new_intent, intent)
159 | new_intent.save()
160 | creates_intents.append(new_intent)
161 | return creates_intents
162 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/nlu/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
--------------------------------------------------------------------------------
/app/nlu/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Polymath1108/Chatbot-builder/7be874edba55a5e4100d29ad8526dde39699fefe/app/nlu/__init__.py
--------------------------------------------------------------------------------
/app/nlu/classifiers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Polymath1108/Chatbot-builder/7be874edba55a5e4100d29ad8526dde39699fefe/app/nlu/classifiers/__init__.py
--------------------------------------------------------------------------------
/app/nlu/classifiers/sklearn_intent_classifer.py:
--------------------------------------------------------------------------------
1 | import os
2 | import cloudpickle
3 | import numpy as np
4 | from app import spacy_tokenizer
5 |
6 | class SklearnIntentClassifier:
7 |
8 | def __init__(self):
9 | self.model = None
10 |
11 | def get_spacy_embedding(self, sentence):
12 | """
13 | perform basic cleaning,tokenization and lemmatization
14 | :param sentence:
15 | :return list of clean tokens:
16 | """
17 | spacy_obj = spacy_tokenizer(sentence)
18 | return np.array(spacy_obj.vector)
19 |
20 | def train(self, X, y, outpath=None, verbose=True):
21 | """
22 | Train intent classifier for given training data
23 | :param X:
24 | :param y:
25 | :param outpath:
26 | :param verbose:
27 | :return:
28 | """
29 | from sklearn.model_selection import GridSearchCV
30 | from sklearn.svm import SVC
31 |
32 | X = np.stack(
33 | [
34 | self.get_spacy_embedding(example)
35 | for example in X
36 | ]
37 | )
38 |
39 | items, counts = np.unique(y, return_counts=True)
40 | cv_splits = max(2, min(5, np.min(counts) // 5))
41 |
42 | tuned_parameters = [
43 | {"C": [1, 2, 5, 10, 20, 100], "gamma": [0.1], "kernel": ["linear"]}
44 | ]
45 |
46 | classifier = GridSearchCV(
47 | SVC(C=1, probability=True, class_weight="balanced"),
48 | param_grid=tuned_parameters,
49 | n_jobs=-1,
50 | cv=cv_splits,
51 | scoring="f1_weighted",
52 | verbose=1,
53 | )
54 |
55 | classifier.fit(X, y)
56 |
57 | if outpath:
58 | path = os.path.join(outpath, "sklearn_intent_model.hd5")
59 | with open(path, 'wb') as f:
60 | cloudpickle.dump(classifier.best_estimator_, f)
61 |
62 | if verbose:
63 | print("Model written out to {}".format(outpath))
64 |
65 | return classifier.best_estimator_
66 |
67 | def load(self, PATH):
68 | """
69 | load trained model from given path
70 | :param PATH:
71 | :return:
72 | """
73 | try:
74 | PATH = os.path.join(PATH, "sklearn_intent_model.hd5")
75 | with open(PATH, 'rb') as f:
76 | self.model = cloudpickle.load(f)
77 | except IOError:
78 | return False
79 |
80 | def predict_proba(self, X):
81 | """Given a bow vector of an input text, predict most probable label.
82 | Returns only the most likely label.
83 |
84 | :param X: bow of input text
85 | :return: tuple of first, the most probable label
86 | and second, its probability"""
87 |
88 | pred_result = self.model.predict_proba([self.get_spacy_embedding(X)])
89 | # sort the probabilities retrieving the indices of the elements
90 | sorted_indices = np.fliplr(np.argsort(pred_result, axis=1))
91 | return sorted_indices, pred_result[:, sorted_indices]
92 |
93 | def process(self, x, INTENT_RANKING_LENGTH=5):
94 | """Returns the most likely intent and
95 | its probability for the input text."""
96 |
97 | intent = {"name": None, "confidence": 0.0}
98 | intent_ranking = []
99 |
100 | if self.model:
101 | intents, probabilities = self.predict_proba(x)
102 | intents = [self.model.classes_[intent]
103 | for intent in intents.flatten()]
104 | probabilities = probabilities.flatten()
105 |
106 | if len(intents) > 0 and len(probabilities) > 0:
107 | ranking = list(zip(list(intents), list(probabilities)))
108 | ranking = ranking[:INTENT_RANKING_LENGTH]
109 |
110 | intent = {"intent": intents[0], "confidence": probabilities[0]}
111 | intent_ranking = [{"intent": intent_name, "confidence": score}
112 | for intent_name, score in ranking]
113 |
114 | else:
115 | intent = {"name": None, "confidence": 0.0}
116 | intent_ranking = []
117 |
118 | return intent, intent_ranking
119 |
120 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/app/nlu/entity_extractor.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import pycrfsuite
4 | from flask import current_app as app
5 | from app import spacy_tokenizer
6 |
7 |
8 | class EntityExtractor:
9 | """
10 | Performs NER training, prediction, model import/export
11 | """
12 |
13 | def __init__(self, synonyms=[]):
14 | self.synonyms = synonyms
15 |
16 | def replace_synonyms(self, entities):
17 | """
18 | replace extracted entity values with
19 | root word by matching with synonyms dict.
20 | :param entities:
21 | :return:
22 | """
23 | for entity in entities.keys():
24 |
25 | entity_value = str(entities[entity])
26 |
27 | if entity_value.lower() in self.synonyms:
28 | entities[entity] = self.synonyms[entity_value.lower()]
29 |
30 | return entities
31 |
32 | def extract_features(self, sent, i):
33 | """
34 | Extract features for a given sentence
35 | :param sent:
36 | :param i:
37 | :return:
38 | """
39 | word = sent[i][0]
40 | postag = sent[i][1]
41 | features = [
42 | 'bias',
43 | 'word.lower=' + word.lower(),
44 | 'word[-3:]=' + word[-3:],
45 | 'word[-2:]=' + word[-2:],
46 | 'word.isupper=%s' % word.isupper(),
47 | 'word.istitle=%s' % word.istitle(),
48 | 'word.isdigit=%s' % word.isdigit(),
49 | 'postag=' + postag,
50 | 'postag[:2]=' + postag[:2],
51 | ]
52 | if i > 0:
53 | word1 = sent[i - 1][0]
54 | postag1 = sent[i - 1][1]
55 | features.extend([
56 | '-1:word.lower=' + word1.lower(),
57 | '-1:word.istitle=%s' % word1.istitle(),
58 | '-1:word.isupper=%s' % word1.isupper(),
59 | '-1:postag=' + postag1,
60 | '-1:postag[:2]=' + postag1[:2],
61 | ])
62 | else:
63 | features.append('BOS')
64 |
65 | if i < len(sent) - 1:
66 | word1 = sent[i + 1][0]
67 | postag1 = sent[i + 1][1]
68 | features.extend([
69 | '+1:word.lower=' + word1.lower(),
70 | '+1:word.istitle=%s' % word1.istitle(),
71 | '+1:word.isupper=%s' % word1.isupper(),
72 | '+1:postag=' + postag1,
73 | '+1:postag[:2]=' + postag1[:2],
74 | ])
75 | else:
76 | features.append('EOS')
77 |
78 | return features
79 |
80 | def sent_to_features(self, sent):
81 | """
82 | Extract features from training Data
83 | :param sent:
84 | :return:
85 | """
86 | return [self.extract_features(sent, i) for i in range(len(sent))]
87 |
88 | def sent_to_labels(self, sent):
89 | """
90 | Extract labels from training data
91 | :param sent:
92 | :return:
93 | """
94 | return [label for token, postag, label in sent]
95 |
96 | def sent_to_tokens(self, sent):
97 | """
98 | Extract tokens from training data
99 | :param sent:
100 | :return:
101 | """
102 | return [token for token, postag, label in sent]
103 |
104 | def train(self, train_sentences, model_name):
105 | """
106 | Train NER model for given model
107 | :param train_sentences:
108 | :param model_name:
109 | :return:
110 | """
111 | features = [self.sent_to_features(s) for s in train_sentences]
112 | labels = [self.sent_to_labels(s) for s in train_sentences]
113 |
114 | trainer = pycrfsuite.Trainer(verbose=False)
115 | for xseq, yseq in zip(features, labels):
116 | trainer.append(xseq, yseq)
117 |
118 | trainer.set_params({
119 | 'c1': 1.0, # coefficient for L1 penalty
120 | 'c2': 1e-3, # coefficient for L2 penalty
121 | 'max_iterations': 50, # stop earlier
122 |
123 | # include transitions that are possible, but not observed
124 | 'feature.possible_transitions': True
125 | })
126 | trainer.train('model_files/%s.model' % model_name)
127 | return True
128 |
129 | # Extract Labels from BIO tagged sentence
130 | def crf2json(self, tagged_sentence):
131 | """
132 | Extract label-value pair from NER prediction output
133 | :param tagged_sentence:
134 | :return:
135 | """
136 | labeled = {}
137 | labels = set()
138 | for s, tp in tagged_sentence:
139 | if tp != "O":
140 | label = tp[2:]
141 | if tp.startswith("B"):
142 | labeled[label] = s
143 | labels.add(label)
144 | elif tp.startswith("I") and (label in labels):
145 | labeled[label] += " %s" % s
146 | return labeled
147 |
148 | def extract_ner_labels(self, predicted_labels):
149 | """
150 | Extract name of labels from NER
151 | :param predicted_labels:
152 | :return:
153 | """
154 | labels = []
155 | for tp in predicted_labels:
156 | if tp != "O":
157 | labels.append(tp[2:])
158 | return labels
159 |
160 | def predict(self, model_name, sentence):
161 | """
162 | Predict NER labels for given model and query
163 | :param model_name:
164 | :param sentence:
165 | :return:
166 | """
167 | from app.nlu.tasks import pos_tagger
168 |
169 | doc = spacy_tokenizer(sentence)
170 | words = [token.text for token in doc]
171 | tagged_token = pos_tagger(sentence)
172 | tagger = pycrfsuite.Tagger()
173 | tagger.open("{}/{}.model".format(app.config["MODELS_DIR"], model_name))
174 | predicted_labels = tagger.tag(self.sent_to_features(tagged_token))
175 | extracted_entities = self.crf2json(
176 | zip(words, predicted_labels))
177 | return self.replace_synonyms(extracted_entities)
178 |
179 | @staticmethod
180 | def json2crf(training_data):
181 | """
182 | Takes json annotated data and converts to
183 | CRFSuite training data representation
184 | :param training_data:
185 | :return labeled_examples:
186 | """
187 | from app.nlu.tasks import sentence_tokenize, pos_tag_and_label
188 |
189 | labeled_examples = []
190 |
191 | for example in training_data:
192 | # POS tag and initialize bio label as 'O' for all the tokens
193 | tagged_example = pos_tag_and_label(example.get("text"))
194 |
195 | # find no of words before selection
196 | for enitity in example.get("entities"):
197 |
198 | try:
199 | begin_index = enitity.get("begin")
200 | end_index = enitity.get("end")
201 | # find no of words before the entity
202 | inverse_selection = example.get("text")[0:begin_index - 1]
203 | inverse_selection = sentence_tokenize(inverse_selection)
204 | inverse_selection = inverse_selection.split(" ")
205 | inverse_word_count = len(inverse_selection)
206 |
207 | # get the entity value from selection
208 | selection = example.get("text")[begin_index:end_index]
209 |
210 | tokens = sentence_tokenize(selection).split(" ")
211 |
212 | selection_word_count = len(tokens)
213 |
214 | # build BIO tagging
215 | for i in range(1, selection_word_count + 1):
216 | if i == 1:
217 | bio = "B-" + enitity.get("name")
218 | else:
219 | bio = "I-" + enitity.get("name")
220 | tagged_example[(inverse_word_count + i) - 1][2] = bio
221 | except:
222 | # catches and skips invalid offsets and annotation
223 | continue
224 |
225 | labeled_examples.append(tagged_example)
226 | return labeled_examples
227 |
--------------------------------------------------------------------------------
/app/nlu/tasks.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from flask import current_app as app
4 | from app.endpoint.controllers import update_model
5 | from app.intents.models import Intent
6 | from app import spacy_tokenizer
7 | from app.nlu.classifiers.sklearn_intent_classifer import \
8 | SklearnIntentClassifier
9 | from app.nlu.entity_extractor import EntityExtractor
10 |
11 |
12 | def train_models():
13 | """
14 | Initiate NER and Intent Classification training
15 | :return:
16 | """
17 | # generate intent classifier training data
18 | intents = Intent.objects
19 |
20 | if not intents:
21 | raise Exception("NO_DATA")
22 |
23 | # train intent classifier on all intents
24 | train_intent_classifier(intents)
25 |
26 | # train ner model for each Stories
27 | for intent in intents:
28 | train_all_ner(intent.intentId, intent.trainingData)
29 |
30 | update_model()
31 |
32 | def train_intent_classifier(intents):
33 | """
34 | Train intent classifier model
35 | :param intents:
36 | :return:
37 | """
38 | X = []
39 | y = []
40 | for intent in intents:
41 | training_data = intent.trainingData
42 | for example in training_data:
43 | if example.get("text").strip() == "":
44 | continue
45 | X.append(example.get("text"))
46 | y.append(intent.intentId)
47 |
48 | intent_classifier = SklearnIntentClassifier()
49 | intent_classifier.train(X, y,outpath=app.config["MODELS_DIR"])
50 |
51 | def train_all_ner(story_id, training_data):
52 | """
53 | Train NER model for single Story
54 | :param story_id:
55 | :param training_data:
56 | :return:
57 | """
58 | entityExtraction = EntityExtractor()
59 | # generate crf training data
60 | ner_training_data = entityExtraction.json2crf(training_data)
61 | # train and store ner model
62 | entityExtraction.train(ner_training_data, story_id)
63 |
64 |
65 | # Load and initialize Perceptron tagger
66 |
67 | def pos_tagger(sentence):
68 | """
69 | perform POS tagging on a given sentence
70 | :param sentence:
71 | :return:
72 | """
73 | doc = spacy_tokenizer(sentence)
74 | taged_sentance = []
75 | for token in doc:
76 | taged_sentance.append((token.text, token.tag_))
77 | return taged_sentance
78 |
79 |
80 | def pos_tag_and_label(sentence):
81 | """
82 | Perform POS tagging and BIO labeling on given sentence
83 | :param sentence:
84 | :return:
85 | """
86 | tagged_sentence = pos_tagger(sentence)
87 | tagged_sentence_json = []
88 | for token, postag in tagged_sentence:
89 | tagged_sentence_json.append([token, postag, "O"])
90 | return tagged_sentence_json
91 |
92 |
93 | def sentence_tokenize(sentences):
94 | """
95 | Sentence tokenizer
96 | :param sentences:
97 | :return:
98 | """
99 | doc = spacy_tokenizer(sentences)
100 | words = [token.text for token in doc]
101 | return " ".join(words)
102 |
--------------------------------------------------------------------------------
/app/train/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Polymath1108/Chatbot-builder/7be874edba55a5e4100d29ad8526dde39699fefe/app/train/__init__.py
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | SPACY_LANG_MODEL = "en_core_web_md"
14 |
15 | class Development(Config):
16 | DEBUG = True
17 | TEMPLATES_AUTO_RELOAD=True
18 |
19 | class Testing(Config):
20 | DEBUG = True
21 | TESTING = True
22 |
23 | class Production(Config):
24 | # MongoDB Database Details
25 | MONGODB_HOST = "mongodb://mongodb:27017/iky-ai"
26 |
27 | # Web Server details
28 | WEB_SERVER_PORT = 8001
29 |
30 | class Heroku(Production):
31 | MONGODB_HOST = os.environ.get('MONGO_URL')
32 |
33 | class Helm(Production):
34 | MONGODB_HOST = os.environ.get('MONGO_URL')
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | migrate:
4 | container_name: iky_migrate
5 | image: alfredfrancis/ai-chatbot-framework_backend:latest
6 | command: python manage.py migrate
7 | environment:
8 | APPLICATION_ENV: Production
9 | depends_on:
10 | - mongodb
11 |
12 | iky_backend:
13 | container_name: iky_backend
14 | image: alfredfrancis/ai-chatbot-framework_backend:latest
15 | hostname: iky_backend
16 | environment:
17 | APPLICATION_ENV: Production
18 | depends_on:
19 | - migrate
20 | - mongodb
21 |
22 | iky_gateway:
23 | container_name: iky_gateway
24 | image: alfredfrancis/ai-chatbot-framework_frontend:latest
25 | ports:
26 | - "8080:80"
27 | depends_on:
28 | - iky_backend
29 |
30 | mongodb:
31 | container_name: mongodb
32 | image: mongo:4.2.20
33 | hostname: mongodb
34 | ports:
35 | - "27017:27017"
36 | volumes:
37 | - mongodbdata:/data
38 |
39 | volumes:
40 | mongodbdata:
41 |
--------------------------------------------------------------------------------
/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 ?"}]
--------------------------------------------------------------------------------
/examples/nodejs/discordRequest.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | //"msg" here is equal to the "message" object in discord.js. It represents a message sent by the user.
3 | chatRequest: function (msg) {
4 | //These are just here for modularity and can be removed if they're already set in the file calling this function.
5 | const Discord = require('discord.js');
6 | const request = require('request');
7 |
8 | var payload = {
9 | "currentNode": "",
10 | "complete": null,
11 | "context":{},
12 | "parameters": [],
13 | "extractedParameters": {},
14 | "speechResponse": "",
15 | "intent": {},
16 | "input": '',
17 | "missingParameters": []
18 | };
19 | //Set the input to a clean string of the user's message.
20 | var userInput= msg.cleanContent;
21 | payload["input"] = userInput;
22 |
23 | var options = {
24 | uri: 'http://localhost:8001/api/v1',
25 | headers: {
26 | 'Content-Type': 'application/json'
27 | },
28 | json: payload
29 | };
30 |
31 | request.post(options, function(err, res, body) {
32 | //Log any errors, along with the payload that got sent and the response you got and then have the bot respond to the user.
33 | console.log('ERROR: \n', err);
34 | console.log('PAYLOAD: \n', options['json']);
35 | console.log('RESPONSE: \n', body);
36 | msg.channel.send(body.speechResponse);
37 | });
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 :)"}]
--------------------------------------------------------------------------------
/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/.angular-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "project": {
4 | "name": "frontend"
5 | },
6 | "apps": [
7 | {
8 | "root": "src",
9 | "outDir": "dist",
10 | "assets": [
11 | "assets",
12 | "favicon.ico"
13 | ],
14 | "index": "index.html",
15 | "main": "main.ts",
16 | "polyfills": "polyfills.ts",
17 | "test": "test.ts",
18 | "tsconfig": "tsconfig.app.json",
19 | "testTsconfig": "tsconfig.spec.json",
20 | "prefix": "app",
21 | "styles": [
22 | "styles.scss"
23 | ],
24 | "scripts": [],
25 |
26 | "environmentSource": "environments/environment.ts",
27 | "environments": {
28 | "dev": "environments/environment.ts",
29 | "test": "environments/environment.test.ts",
30 | "prod": "environments/environment.prod.ts",
31 | "docker": "environments/environment.docker.ts"
32 | }
33 | }
34 | ],
35 | "e2e": {
36 | "protractor": {
37 | "config": "./protractor.conf.js"
38 | }
39 | },
40 | "lint": [
41 | {
42 | "project": "src/tsconfig.app.json",
43 | "exclude": "**/node_modules/**"
44 | },
45 | {
46 | "project": "src/tsconfig.spec.json",
47 | "exclude": "**/node_modules/**"
48 | },
49 | {
50 | "project": "e2e/tsconfig.e2e.json",
51 | "exclude": "**/node_modules/**"
52 | }
53 | ],
54 | "test": {
55 | "karma": {
56 | "config": "./karma.conf.js"
57 | }
58 | },
59 | "defaults": {
60 | "styleExt": "scss",
61 | "component": {}
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/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/.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/.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/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/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 |
--------------------------------------------------------------------------------
/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/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/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/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/nginx/default.conf:
--------------------------------------------------------------------------------
1 | server {
2 |
3 | listen 80;
4 | sendfile on;
5 |
6 | root /usr/share/nginx/html;
7 | resolver 127.0.0.11 valid=30s;
8 |
9 | set $backend http://iky_backend;
10 |
11 | location / {
12 | try_files $uri $uri/ /index.html =404;
13 | }
14 |
15 | location /gateway/ {
16 | rewrite ^/gateway/?(.*)$ /$1 break;
17 | proxy_pass $backend;
18 | proxy_set_header Host $host;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/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 | "rxjs": "^5.5.6",
30 | "zone.js": "^0.8.19"
31 | },
32 | "devDependencies": {
33 | "@angular/cli": "~1.7.3",
34 | "@angular/compiler-cli": "^5.2.0",
35 | "@angular/language-service": "^5.2.0",
36 | "@types/jasmine": "~2.8.3",
37 | "@types/jasminewd2": "~2.0.2",
38 | "@types/node": "~6.0.60",
39 | "codelyzer": "^4.0.1",
40 | "jasmine-core": "~2.8.0",
41 | "jasmine-spec-reporter": "~4.2.1",
42 | "karma": "~2.0.0",
43 | "karma-chrome-launcher": "~2.2.0",
44 | "karma-coverage-istanbul-reporter": "^1.2.1",
45 | "karma-jasmine": "~1.1.0",
46 | "karma-jasmine-html-reporter": "^0.2.2",
47 | "protractor": "~5.1.2",
48 | "ts-node": "~4.1.0",
49 | "tslint": "~5.9.1",
50 | "typescript": "~2.5.3"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/chat/chat.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{message.content}}
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
28 |
29 |
30 |
31 | POST /api/v1
32 | {{prettyChatCurrent}}
33 |
34 |
--------------------------------------------------------------------------------
/frontend/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/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/entities/entities.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Add
7 |
8 |
9 |
10 |
11 |
12 | {{entity.name}}
13 |
14 |
15 |
16 | edit
17 |
18 |
19 | delete
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/frontend/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/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/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/entity/entity.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Save
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | add
21 |
22 |
23 |
24 |
25 |
26 |
27 | {{value.value}}
28 |
29 |
30 |
31 |
32 | {{synonym}}
33 | cancel
34 |
35 |
37 |
38 |
39 |
40 |
41 | delete
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/entity/entity.component.scss:
--------------------------------------------------------------------------------
1 | .values{
2 | margin-top:20px;
3 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/intent/intent.component.html:
--------------------------------------------------------------------------------
1 |
2 | {{message}}
3 |
4 |
116 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/intents/intents.component.html:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {{intent.name}}
18 |
19 |
20 |
21 |
22 | add
23 |
24 | edit
25 |
26 |
27 | delete
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/settings/settings.component.html:
--------------------------------------------------------------------------------
1 |
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 |
Import Intents
40 |
41 |
Export Intents
42 |
43 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/frontend/src/app/agent/train/train.component.html:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 | Add to training set
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
{{example.text}}
32 |
33 | delete
34 |
35 |
36 |
37 |
38 |
39 | Select text to add an entity
40 |
41 |
42 |
43 |
44 |
45 |
46 | {{ entity.name }}
47 |
48 |
49 |
50 |
51 |
52 |
53 | Add an entity for
54 | "{{this.selectionInfo.value}}"
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Entities
63 |
68 |
69 | {{entity.name}}
70 | {{entity.value}}
71 |
72 |
73 | close
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/frontend/src/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/dashboard/layout/layout.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/layout/layout.component.scss:
--------------------------------------------------------------------------------
1 | mat-sidenav{
2 | width: 350px;
3 | border-right: 1px solid #e7e7e7;
4 | }
5 |
--------------------------------------------------------------------------------
/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/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.html:
--------------------------------------------------------------------------------
1 |
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/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 | padding: 5px;
11 | border-bottom: 1px solid #efefef;
12 | }
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/intent.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 IntentService {
7 | public static intentTypes = {
8 | 'mobile': 'Mobile number',
9 | 'email': 'Email',
10 | 'free_text': 'Free Text',
11 | 'number': 'Number',
12 | 'list': 'List',
13 | };
14 |
15 | constructor(public http: HttpClient) {
16 | }
17 |
18 | getIntents() {
19 | return this.http.get(environment.ikyBackend + 'intents/').toPromise();
20 | }
21 |
22 | getIntent(id) {
23 | return this.http.get(environment.ikyBackend + `intents/${id}`).toPromise();
24 | }
25 |
26 | saveIntent(intent) {
27 | if (intent._id) {
28 | return this.update_intent(intent);
29 | } else {
30 | delete intent._id;
31 | return this.create_intent(intent);
32 | }
33 | }
34 |
35 | create_intent(intent) {
36 | return this.http.post(environment.ikyBackend + `intents/`, intent).toPromise();
37 | }
38 |
39 | update_intent(intent) {
40 | return this.http.put(environment.ikyBackend + `intents/${intent._id}`, intent).toPromise();
41 | }
42 |
43 | delete_intent(id) {
44 | return this.http.delete(environment.ikyBackend + `intents/${id}`, {}).toPromise();
45 | }
46 |
47 | importIntents(fileToUpload: File){
48 | const formData: FormData = new FormData();
49 | formData.append('file', fileToUpload, fileToUpload.name);
50 | return this.http
51 | .post(environment.ikyBackend +"intents/import", formData).toPromise();
52 | }
53 |
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Polymath1108/Chatbot-builder/7be874edba55a5e4100d29ad8526dde39699fefe/frontend/src/assets/.gitkeep
--------------------------------------------------------------------------------
/frontend/src/assets/images/iky-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Polymath1108/Chatbot-builder/7be874edba55a5e4100d29ad8526dde39699fefe/frontend/src/assets/images/iky-logo.png
--------------------------------------------------------------------------------
/frontend/src/assets/widget/iky_widget.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var config = {
3 | "base_url": window.iky_base_url,
4 | "chat_context": window.chat_context
5 | }
6 |
7 | // Localize jQuery variable
8 | var jQuery;
9 |
10 | /******** Load jQuery if not present *********/
11 | if (window.jQuery === undefined) {
12 | var script_tag = document.createElement('script');
13 | script_tag.setAttribute("type", "text/javascript");
14 | script_tag.setAttribute("src",
15 | "http://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js");
16 | if (script_tag.readyState) {
17 | script_tag.onreadystatechange = function () { // For old versions of IE
18 | if (this.readyState == 'complete' || this.readyState == 'loaded') {
19 | scriptLoadHandler();
20 | }
21 | };
22 | } else {
23 | script_tag.onload = scriptLoadHandler;
24 | }
25 | // Try to find the head, otherwise default to the documentElement
26 | (document.getElementsByTagName("head")[0] || document.documentElement).appendChild(script_tag);
27 | } else {
28 | // The jQuery version on the window is the one we want to use
29 | jQuery = window.jQuery;
30 | main(config);
31 | }
32 |
33 | /******** Called once jQuery has loaded ******/
34 | function scriptLoadHandler() {
35 | // Restore $ and window.jQuery to their previous values and store the
36 | // new jQuery in our local jQuery variable
37 | jQuery = window.jQuery.noConflict(true);
38 | // Call our main function
39 | main(config);
40 | }
41 |
42 | /******** Our main function ********/
43 | function main(config) {
44 | jQuery(document).ready(function ($) {
45 | console.log("received", config)
46 | /******* Load CSS *******/
47 | var css_link = $(" ", {
48 | rel: "stylesheet",
49 | type: "text/css",
50 | href: config["base_url"] +"assets/widget/style.css"
51 | });
52 | css_link.appendTo('head');
53 |
54 |
55 | content = `
56 |
57 |
58 |
61 |
65 |
66 |
67 |
68 |
69 |
72 |
73 | `
74 |
75 | $("body").append(content);
76 |
77 | $(".iky_container").hide();
78 |
79 |
80 | /******* chat begins *******/
81 |
82 | if (typeof payload == "undefined") {
83 | payload =
84 | {
85 | "currentNode": "",
86 | "complete": true,
87 | "parameters": [],
88 | "extractedParameters": {},
89 | "missingParameters": [],
90 | "intent": {},
91 | "context": config["chat_context"],
92 | "input": "init_conversation",
93 | "speechResponse": []
94 | }
95 |
96 |
97 | }
98 |
99 | $.ajaxSetup({
100 | headers: {
101 | 'Content-Type': 'application/json',
102 | 'Accept': 'application/json'
103 | }
104 | });
105 |
106 | var send_req = (userQuery)=> {
107 | showTyping();
108 | // send request to bot
109 | payload["input"] = userQuery;
110 |
111 | $.post(config["base_url"]+'gateway/api/v1', JSON.stringify(payload))
112 | .done(((response)=>{
113 | successRoutes(response);}))
114 | .fail((x,t,m)=>{
115 | errorRoutes(x,t,m);
116 | });
117 |
118 | // $.ajax({
119 | // // url: config["base_url"]+'gateway/api/v1',
120 | // url: 'http://localhost:8080/gateway/api/v1',
121 | // type: 'POST',
122 | // data: JSON.stringify(payload),
123 | // contentType: 'application/json; charset=utf-8',
124 | // datatype: "json",
125 | // success: successRoutes,
126 | // error: errorRoutes
127 |
128 | // });
129 | return true;
130 | };
131 |
132 | function showTyping(){
133 | $("input.iky_user_input_field").prop('disabled', true);
134 | html_data = ' ';
135 | $("ul.iky_Chat_container").append(html_data);
136 | scrollToBottom();
137 |
138 | }
139 |
140 | function hideTyping(){
141 | $('li#typing-indicator').remove();
142 | $("input.iky_user_input_field").prop('disabled', false);
143 | }
144 |
145 |
146 | successRoutes = function (response) {
147 | hideTyping();
148 | var responseObject;
149 | if (typeof response == 'object') {
150 | responseObject = response;
151 | }
152 | else {
153 | var parsedResponse = JSON.parse(response);
154 | responseObject = parsedResponse.responseData;
155 | }
156 | payload = responseObject;
157 | put_text(responseObject);
158 | };
159 |
160 | errorRoutes = function (x, t, m) {
161 | hideTyping();
162 | responseObject = payload;
163 | if (t === "timeout") {
164 | responseObject["speechResponse"] = ["Due to band-width constraints, I'm not able to serve you now","please try again later"]
165 | } else {
166 | responseObject["speechResponse"] = ["I'm not able to serve you at the moment"," please try again later"]
167 | }
168 | payload = responseObject;
169 | put_text(responseObject);
170 | };
171 |
172 | function scrollToBottom() {
173 | $(".iky_Chat_container").stop().animate({ scrollTop: $(".iky_Chat_container")[0].scrollHeight}, 1000);
174 | }
175 |
176 | var put_text = function (bot_say) {
177 | $.each(bot_say["speechResponse"],function (index, data) {
178 | html_data = ''+ data +' ';
179 | $("ul.iky_Chat_container").append(html_data);
180 | });
181 | scrollToBottom();
182 | };
183 |
184 |
185 |
186 | send_req("init_conversation");
187 |
188 |
189 | $('.iky_user_input_field').keydown(function (e) {
190 | if (e.keyCode == 13) {
191 | userQuery = $(".iky_user_input_field").val();
192 | $(".iky_user_input_field").val("");
193 | html_data = ' '+userQuery+' ';
194 | $("ul.iky_Chat_container").append(html_data);
195 | send_req(userQuery);
196 |
197 | }
198 | })
199 | $(".iky_action, .btn_close").click(function(){
200 | $(".iky_container").toggle();
201 | $("input.iky_user_input_field").focus();
202 | });
203 |
204 | });
205 |
206 |
207 | }
208 |
209 | })(); // We call our anonymous function immediately
--------------------------------------------------------------------------------
/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/src/environments/environment.docker.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | ikyBackend: "/gateway/"
4 | };
5 |
--------------------------------------------------------------------------------
/frontend/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | ikyBackend: "/"
4 | };
5 |
--------------------------------------------------------------------------------
/frontend/src/environments/environment.test.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | ikyBackend: "http://localhost:8080/"
4 | };
5 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Polymath1108/Chatbot-builder/7be874edba55a5e4100d29ad8526dde39699fefe/frontend/src/favicon.ico
--------------------------------------------------------------------------------
/frontend/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Frontend
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var module: NodeModule;
3 | interface NodeModule {
4 | id: string;
5 | }
6 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/helm/ai-chatbot-framework/.gitignore:
--------------------------------------------------------------------------------
1 | charts/*
2 | Chart.lock
--------------------------------------------------------------------------------
/helm/ai-chatbot-framework/.helmignore:
--------------------------------------------------------------------------------
1 | # Patterns to ignore when building packages.
2 | # This supports shell glob matching, relative path matching, and
3 | # negation (prefixed with !). Only one pattern per line.
4 | .DS_Store
5 | # Common VCS dirs
6 | .git/
7 | .gitignore
8 | .bzr/
9 | .bzrignore
10 | .hg/
11 | .hgignore
12 | .svn/
13 | # Common backup files
14 | *.swp
15 | *.bak
16 | *.tmp
17 | *.orig
18 | *~
19 | # Various IDEs
20 | .project
21 | .idea/
22 | *.tmproj
23 | .vscode/
24 |
--------------------------------------------------------------------------------
/helm/ai-chatbot-framework/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v2
2 | name: ai-chatbot-framework
3 | description: A python chatbot framework with Natural Language Understanding and Artificial Intelligence.
4 | type: application
5 | version: 0.1.0
6 | appVersion: "1.0.0"
7 | dependencies:
8 | - name: mongodb
9 | version: 10.3.3
10 | repository: "https://charts.bitnami.com/bitnami"
11 | alias: mongodb
12 | condition: mongodb.enabled
13 |
14 |
--------------------------------------------------------------------------------
/helm/ai-chatbot-framework/templates/_helpers.tpl:
--------------------------------------------------------------------------------
1 | {{/*
2 | Expand the name of the chart.
3 | */}}
4 | {{- define "ai-chatbot-framework.name" -}}
5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
6 | {{- end }}
7 |
8 | {{/*
9 | Create a default fully qualified app name.
10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
11 | If release name contains chart name it will be used as a full name.
12 | */}}
13 | {{- define "ai-chatbot-framework.fullname" -}}
14 | {{- if .Values.fullnameOverride }}
15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
16 | {{- else }}
17 | {{- $name := default .Chart.Name .Values.nameOverride }}
18 | {{- if contains $name .Release.Name }}
19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }}
20 | {{- else }}
21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
22 | {{- end }}
23 | {{- end }}
24 | {{- end }}
25 |
26 | {{/*
27 | Create chart name and version as used by the chart label.
28 | */}}
29 | {{- define "ai-chatbot-framework.chart" -}}
30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
31 | {{- end }}
32 |
33 | {{/*
34 | Common labels
35 | */}}
36 | {{- define "ai-chatbot-framework.labels" -}}
37 | helm.sh/chart: {{ include "ai-chatbot-framework.chart" . }}
38 | {{ include "ai-chatbot-framework.selectorLabels" . }}
39 | {{- if .Chart.AppVersion }}
40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
41 | {{- end }}
42 | app.kubernetes.io/managed-by: {{ .Release.Service }}
43 | {{- end }}
44 |
45 | {{/*
46 | Selector labels
47 | */}}
48 | {{- define "ai-chatbot-framework.selectorLabels" -}}
49 | app.kubernetes.io/name: {{ include "ai-chatbot-framework.name" . }}
50 | app.kubernetes.io/instance: {{ .Release.Name }}
51 | {{- end }}
52 |
53 | {{/*
54 | Create the name of the service account to use
55 | */}}
56 | {{- define "ai-chatbot-framework.serviceAccountName" -}}
57 | {{- if .Values.serviceAccount.create }}
58 | {{- default (include "ai-chatbot-framework.fullname" .) .Values.serviceAccount.name }}
59 | {{- else }}
60 | {{- default "default" .Values.serviceAccount.name }}
61 | {{- end }}
62 | {{- end }}
63 |
--------------------------------------------------------------------------------
/helm/ai-chatbot-framework/templates/deployment-backend.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: {{ include "ai-chatbot-framework.fullname" . }}-backend
5 | labels:
6 | app: "ai-chatbot-framework"
7 | name: "ai-chatbot-framework-backend"
8 | {{- include "ai-chatbot-framework.labels" . | nindent 4 }}
9 | spec:
10 | replicas: {{ .Values.replicaCount }}
11 | selector:
12 | matchLabels:
13 | app: "ai-chatbot-framework"
14 | name: "ai-chatbot-framework-backend"
15 | template:
16 | metadata:
17 | labels:
18 | app: "ai-chatbot-framework"
19 | name: "ai-chatbot-framework-backend"
20 | spec:
21 | initContainers:
22 | - name: {{ .Chart.Name }}-init
23 | image: "{{ .Values.image.backend.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
24 | imagePullPolicy: {{ .Values.image.pullPolicy }}
25 | command: ["python","manage.py","migrate"]
26 | env:
27 | - name: MONGO_URL
28 | value: {{.Values.mongodb.uri}}
29 | - name: APPLICATION_ENV
30 | value: 'Helm'
31 | containers:
32 | - name: {{ .Chart.Name }}
33 | image: "{{ .Values.image.backend.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
34 | imagePullPolicy: {{ .Values.image.pullPolicy }}
35 | command: ["gunicorn", "run:app" ,"--log-level=debug", "--timeout", "90","--bind", "0.0.0.0:80" ]
36 | env:
37 | - name: MONGO_URL
38 | value: {{.Values.mongodb.uri}}
39 | - name: APPLICATION_ENV
40 | value: 'Helm'
41 | ports:
42 | - name: http
43 | containerPort: 80
44 | protocol: TCP
45 | livenessProbe:
46 | initialDelaySeconds: 120
47 | httpGet:
48 | path: /ready
49 | port: http
50 | readinessProbe:
51 | initialDelaySeconds: 120
52 | httpGet:
53 | path: /ready
54 | port: http
55 | resources:
56 | {{- toYaml .Values.resources.backend | nindent 12 }}
57 |
--------------------------------------------------------------------------------
/helm/ai-chatbot-framework/templates/deployment-frontend.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: {{ include "ai-chatbot-framework.fullname" . }}-frontend
5 | labels:
6 | app: "ai-chatbot-framework"
7 | name: "ai-chatbot-framework-frontend"
8 | {{- include "ai-chatbot-framework.labels" . | nindent 4 }}
9 | spec:
10 | replicas: {{ .Values.replicaCount }}
11 | selector:
12 | matchLabels:
13 | app: "ai-chatbot-framework"
14 | name: "ai-chatbot-framework-frontend"
15 | template:
16 | metadata:
17 | {{- with .Values.podAnnotations }}
18 | {{- end }}
19 | labels:
20 | app: "ai-chatbot-framework"
21 | name: "ai-chatbot-framework-frontend"
22 | spec:
23 | containers:
24 | - name: {{ .Chart.Name }}
25 | image: "{{ .Values.image.frontend.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
26 | imagePullPolicy: {{ .Values.image.pullPolicy }}
27 | command: ["nginx", "-g", "daemon off;"]
28 | ports:
29 | - name: http
30 | containerPort: 80
31 | protocol: TCP
32 | resources:
33 | {{- toYaml .Values.resources.frontend | nindent 12 }}
34 |
--------------------------------------------------------------------------------
/helm/ai-chatbot-framework/templates/ingress.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: networking.k8s.io/v1
2 | kind: Ingress
3 | metadata:
4 | name: {{ include "ai-chatbot-framework.fullname" . }}
5 | annotations:
6 | nginx.ingress.kubernetes.io/rewrite-target: /$1
7 | labels:
8 | app: "ai-chatbot-framework"
9 | name: "ai-chatbot-framework-ingress"
10 | {{- include "ai-chatbot-framework.labels" . | nindent 4 }}
11 | spec:
12 | ingressClassName: nginx
13 | rules:
14 | - http:
15 | paths:
16 | - pathType: Prefix
17 | path: /(.*)
18 | backend:
19 | service:
20 | name: {{ include "ai-chatbot-framework.fullname" . }}-frontend
21 | port:
22 | number: 80
23 | - pathType: Prefix
24 | path: /gateway/(.*)
25 | backend:
26 | service:
27 | name: {{ include "ai-chatbot-framework.fullname" . }}-backend
28 | port:
29 | number: 80
--------------------------------------------------------------------------------
/helm/ai-chatbot-framework/templates/service.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: Service
4 | metadata:
5 | name: {{ include "ai-chatbot-framework.fullname" . }}-backend
6 | labels:
7 | app: "ai-chatbot-framework"
8 | name: "ai-chatbot-framework-backend"
9 | {{- include "ai-chatbot-framework.labels" . | nindent 4 }}
10 | spec:
11 | type: {{ .Values.service.type }}
12 | ports:
13 | - port: {{ .Values.service.port }}
14 | targetPort: 80
15 | protocol: TCP
16 | name: http-backend
17 | selector:
18 | name: "ai-chatbot-framework-backend"
19 |
20 | ---
21 | apiVersion: v1
22 | kind: Service
23 | metadata:
24 | name: {{ include "ai-chatbot-framework.fullname" . }}-frontend
25 | labels:
26 | app: "ai-chatbot-framework"
27 | name: "ai-chatbot-framework-frontend"
28 | {{- include "ai-chatbot-framework.labels" . | nindent 4 }}
29 | spec:
30 | type: {{ .Values.service.type }}
31 | ports:
32 | - port: {{ .Values.service.port }}
33 | targetPort: 80
34 | protocol: TCP
35 | name: http-frontend
36 | selector:
37 | name: "ai-chatbot-framework-frontend"
--------------------------------------------------------------------------------
/helm/ai-chatbot-framework/values.yaml:
--------------------------------------------------------------------------------
1 | replicaCount: 2
2 |
3 | image:
4 | backend:
5 | repository: "alfredfrancis/ai-chatbot-framework_backend"
6 | frontend:
7 | repository: "alfredfrancis/ai-chatbot-framework_frontend"
8 | pullPolicy: Always
9 | tag: "latest"
10 |
11 | nameOverride: ""
12 | fullnameOverride: ""
13 |
14 | mongodb:
15 | enabled: true
16 | uri: "mongodb://root:root@ai-chatbot-framework-mongodb:27017/ai_chatbot_framework"
17 | auth:
18 | username: root
19 | password: root
20 | database: ai_chatbot_framework
21 | rootPassword: root
22 | service:
23 | name: ai-chatbot-framework-mongodb
24 | port: 27017
25 |
26 | service:
27 | url: ai-chatbot-framework.local
28 | type: ClusterIP
29 | port: 80
30 |
31 | resources:
32 | frontend:
33 | requests:
34 | memory: "64Mi"
35 | cpu: "100m"
36 | limits:
37 | memory: "128Mi"
38 | cpu: "500m"
39 | backend:
40 | requests:
41 | memory: "1024Mi"
42 | cpu: "100m"
43 | limits:
44 | memory: "2048Mi"
45 | cpu: "500m"
--------------------------------------------------------------------------------
/logs/.keep:
--------------------------------------------------------------------------------
1 | *.*
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | from flask_script import Manager
2 | from app import create_app
3 |
4 | app = create_app()
5 |
6 | manager = Manager(app)
7 |
8 | @manager.command
9 | def migrate():
10 | from app.agents.models import Bot
11 |
12 | try:
13 | # create default bot
14 | bot = Bot()
15 | bot.name = "default"
16 | bot.save()
17 | print("Created default bot")
18 | except:
19 | print("Default agent exists.. skipping..")
20 |
21 |
22 | # import some default intents
23 | from app.intents.controllers import import_json
24 | json_file = open("examples/default_intents.json", "r+")
25 | stories = import_json(json_file)
26 | print("Imported {} Stories".format(len(stories)))
27 |
28 | try:
29 | print("Training models..")
30 | from app.nlu.tasks import train_models
31 | train_models()
32 | print("Training models finished..")
33 | except Exception as e:
34 | e = str(e)
35 | if e == "NO_DATA":
36 | e = "load Data first into mongodb. Reffer Readme."
37 | print("Could not train models..skipping.. (reason: {})".format(e))
38 |
39 |
40 | if __name__ == "__main__":
41 | manager.run()
42 |
--------------------------------------------------------------------------------
/model_files/.gitignore:
--------------------------------------------------------------------------------
1 | *.model
2 | *.pkl
3 | *.hd5
4 | *.index
5 | *.meta
6 | checkpoint
7 | intent_*
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | attrs==21.2.0
2 | beautifulsoup4==4.9.3
3 | blis==0.4.1
4 | bs4==0.0.1
5 | catalogue==2.0.6
6 | certifi==2021.5.30
7 | chardet==3.0.4
8 | click==7.1.2
9 | cloudpickle==1.6.0
10 | cymem==2.0.5
11 | spacy==3.2.0
12 | en-core-web-md @ https://github.com/explosion/spacy-models/releases/download/en_core_web_md-3.2.0/en_core_web_md-3.2.0-py3-none-any.whl
13 | Flask==1.1.2
14 | Flask-Cors==3.0.3
15 | flask-mongoengine==0.9.5
16 | Flask-Script==2.0.6
17 | Flask-WTF==0.15.1
18 | gevent==21.1.2
19 | greenlet==1.1.0
20 | gunicorn==20.1.0
21 | html2text==2017.10.4
22 | idna==2.6
23 | itsdangerous==2.0.1
24 | Jinja2==3.0.1
25 | joblib==1.0.1
26 | MarkupSafe==2.0.1
27 | mongoengine==0.23.1
28 | murmurhash==1.0.5
29 | numpy==1.21.1
30 | packaging==21.0
31 | pandas==1.3.1
32 | parsedatetime==2.6
33 | pathy==0.6.0
34 | plac==1.1.3
35 | pluggy==0.6.0
36 | preshed==3.0.5
37 | py==1.10.0
38 | pydantic==1.8.2
39 | pymongo==3.12.0
40 | pyparsing==2.4.7
41 | pytest==3.4.2
42 | python-crfsuite==0.9.7
43 | python-dateutil==2.8.2
44 | pytz==2021.1
45 | requests==2.18.4
46 | scikit-learn==0.24.2
47 | scipy==1.7.0
48 | simplejson==3.13.2
49 | six==1.16.0
50 | smart-open==5.1.0
51 | soupsieve==2.2.1
52 | srsly==2.4.1
53 | thinc==8.0.12
54 | threadpoolctl==2.2.0
55 | tqdm==4.61.2
56 | typer==0.3.2
57 | typing-extensions==3.10.0.0
58 | urllib3==1.22
59 | wasabi==0.8.2
60 | Werkzeug==2.0.1
61 | WTForms==2.3.3
62 | zope.event==4.5.0
63 | zope.interface==5.4.0
64 |
--------------------------------------------------------------------------------
/run.py:
--------------------------------------------------------------------------------
1 | from app import create_app
2 | app = create_app()
3 | if __name__ == '__main__':
4 | app.run(host='127.0.0.1', port=8080, debug=True, threaded=True)
5 |
--------------------------------------------------------------------------------
/swagger.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: AI Chatbot Framework
4 | description: |-
5 | This is the conversational API for AI Chatbot Framework
6 | version: 1.0.11
7 | externalDocs:
8 | description: Find out more about AI Chatbot Framework
9 | url: https://github.com/alfredfrancis/ai-chatbot-framework/
10 | servers:
11 | - url: http://localhost:8080/api
12 | paths:
13 | /v1:
14 | post:
15 | summary: Call the converse API to chat with the bot
16 | operationId: converse
17 | requestBody:
18 | content:
19 | application/json:
20 | schema:
21 | $ref: '#/components/schemas/Converse'
22 | required: true
23 | responses:
24 | '200':
25 | description: Successful operation
26 | content:
27 | application/json:
28 | schema:
29 | $ref: '#/components/schemas/Converse'
30 | '400':
31 | description: Invalid input
32 | components:
33 | schemas:
34 | Converse:
35 | type: object
36 | properties:
37 | currentNode:
38 | type: string
39 | example: ask_name
40 | complete:
41 | type: boolean
42 | example: false
43 | context:
44 | type: object
45 | parameters:
46 | type: array
47 | items:
48 | type: object
49 | properties:
50 | name:
51 | type: string
52 | example: full_name
53 | type:
54 | type: string
55 | example: name
56 | required:
57 | type: boolean
58 | example: true
59 | extractedParameters:
60 | type: object
61 | example: {"country":"india"}
62 | speechResponse:
63 | type: array
64 | items:
65 | example: "Please enter your name"
66 | type: string
67 | intent:
68 | type: object
69 | properties:
70 | object_id:
71 | example: "88ea1ef0-5782-11ed-9b6a-0242ac120002"
72 | type: string
73 | confidence:
74 | type: number
75 | example: 1
76 | id:
77 | type: string
78 | example: "ask_name"
79 | input:
80 | type: string
81 | example: "My name is Alfred"
82 | missingParameters:
83 | type: array
84 | items:
85 | type: string
86 | example: phone
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Polymath1108/Chatbot-builder/7be874edba55a5e4100d29ad8526dde39699fefe/tests/__init__.py
--------------------------------------------------------------------------------
/tests/test_nlu.py:
--------------------------------------------------------------------------------
1 | from app import create_app
2 | import unittest
3 |
4 | class TestCase(unittest.TestCase):
5 | def setUp(self):
6 | self.app = create_app('Testing')
7 | self.app_context = self.app.app_context()
8 | self.app_context.push()
9 | self.client = self.app.test_client()
10 |
11 | def tearDown(self):
12 | self.app_context.pop()
13 |
14 | def test_train_nlu(self):
15 | rv = self.client.post('/nlu/build_models', json={}, follow_redirects=True)
16 | assert rv.status_code == 200
17 |
18 | if __name__ == '__main__':
19 | unittest.main()
--------------------------------------------------------------------------------